| author | |
| committer | |
| log | 328280b566e13e11a1e1e9cb28f3670b7cc8c030 |
| tree | fa1601fa5f2ce01971aa646e6b15888e5c035f40 |
| parent | 1bdcdbd996a73a7270d6668fca8893b4fc701280 |
23 files changed, 10612 insertions(+), 11578 deletions(-)
lib/compiler/translate-c/MacroTranslator.zig created+1307| ... | ... | @@ -0,0 +1,1307 @@ |
| 1 | const std = @import("std"); | |
| 2 | const math = std.math; | |
| 3 | const mem = std.mem; | |
| 4 | const assert = std.debug.assert; | |
| 5 | ||
| 6 | const aro = @import("aro"); | |
| 7 | const CToken = aro.Tokenizer.Token; | |
| 8 | ||
| 9 | const ast = @import("ast.zig"); | |
| 10 | const builtins = @import("builtins.zig"); | |
| 11 | const ZigNode = ast.Node; | |
| 12 | const ZigTag = ZigNode.Tag; | |
| 13 | const Scope = @import("Scope.zig"); | |
| 14 | const Translator = @import("Translator.zig"); | |
| 15 | ||
| 16 | const Error = Translator.Error; | |
| 17 | pub const ParseError = Error || error{ParseError}; | |
| 18 | ||
| 19 | const MacroTranslator = @This(); | |
| 20 | ||
| 21 | t: *Translator, | |
| 22 | macro: aro.Preprocessor.Macro, | |
| 23 | name: []const u8, | |
| 24 | ||
| 25 | tokens: []const CToken, | |
| 26 | source: []const u8, | |
| 27 | i: usize = 0, | |
| 28 | /// If an object macro references a global var it needs to be converted into | |
| 29 | /// an inline function. | |
| 30 | refs_var_decl: bool = false, | |
| 31 | ||
| 32 | fn peek(mt: *MacroTranslator) CToken.Id { | |
| 33 | if (mt.i >= mt.tokens.len) return .eof; | |
| 34 | return mt.tokens[mt.i].id; | |
| 35 | } | |
| 36 | ||
| 37 | fn eat(mt: *MacroTranslator, expected_id: CToken.Id) bool { | |
| 38 | if (mt.peek() == expected_id) { | |
| 39 | mt.i += 1; | |
| 40 | return true; | |
| 41 | } | |
| 42 | return false; | |
| 43 | } | |
| 44 | ||
| 45 | fn expect(mt: *MacroTranslator, expected_id: CToken.Id) ParseError!void { | |
| 46 | const next_id = mt.peek(); | |
| 47 | if (next_id != expected_id and !(expected_id == .identifier and next_id == .extended_identifier)) { | |
| 48 | try mt.fail( | |
| 49 | "unable to translate C expr: expected '{s}' instead got '{s}'", | |
| 50 | .{ expected_id.symbol(), next_id.symbol() }, | |
| 51 | ); | |
| 52 | return error.ParseError; | |
| 53 | } | |
| 54 | mt.i += 1; | |
| 55 | } | |
| 56 | ||
| 57 | fn fail(mt: *MacroTranslator, comptime fmt: []const u8, args: anytype) !void { | |
| 58 | return mt.t.failDeclExtra(&mt.t.global_scope.base, mt.macro.loc, mt.name, fmt, args); | |
| 59 | } | |
| 60 | ||
| 61 | fn tokSlice(mt: *const MacroTranslator) []const u8 { | |
| 62 | const tok = mt.tokens[mt.i]; | |
| 63 | return mt.source[tok.start..tok.end]; | |
| 64 | } | |
| 65 | ||
| 66 | pub fn transFnMacro(mt: *MacroTranslator) ParseError!void { | |
| 67 | var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false); | |
| 68 | defer block_scope.deinit(); | |
| 69 | const scope = &block_scope.base; | |
| 70 | ||
| 71 | const fn_params = try mt.t.arena.alloc(ast.Payload.Param, mt.macro.params.len); | |
| 72 | for (fn_params, mt.macro.params) |*param, param_name| { | |
| 73 | const mangled_name = try block_scope.makeMangledName(param_name); | |
| 74 | param.* = .{ | |
| 75 | .is_noalias = false, | |
| 76 | .name = mangled_name, | |
| 77 | .type = ZigTag.@"anytype".init(), | |
| 78 | }; | |
| 79 | try block_scope.discardVariable(mangled_name); | |
| 80 | } | |
| 81 | ||
| 82 | const expr = try mt.parseCExpr(scope); | |
| 83 | const last = mt.peek(); | |
| 84 | if (last != .eof) | |
| 85 | return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()}); | |
| 86 | ||
| 87 | const typeof_arg = if (expr.castTag(.block)) |some| blk: { | |
| 88 | const stmts = some.data.stmts; | |
| 89 | const blk_last = stmts[stmts.len - 1]; | |
| 90 | const br = blk_last.castTag(.break_val).?; | |
| 91 | break :blk br.data.val; | |
| 92 | } else expr; | |
| 93 | ||
| 94 | const return_type = ret: { | |
| 95 | if (typeof_arg.castTag(.helper_call)) |some| { | |
| 96 | if (std.mem.eql(u8, some.data.name, "cast")) { | |
| 97 | break :ret some.data.args[0]; | |
| 98 | } | |
| 99 | } | |
| 100 | if (typeof_arg.castTag(.std_mem_zeroinit)) |some| break :ret some.data.lhs; | |
| 101 | if (typeof_arg.castTag(.std_mem_zeroes)) |some| break :ret some.data; | |
| 102 | break :ret try ZigTag.typeof.create(mt.t.arena, typeof_arg); | |
| 103 | }; | |
| 104 | ||
| 105 | const return_expr = try ZigTag.@"return".create(mt.t.arena, expr); | |
| 106 | try block_scope.statements.append(mt.t.gpa, return_expr); | |
| 107 | ||
| 108 | const fn_decl = try ZigTag.pub_inline_fn.create(mt.t.arena, .{ | |
| 109 | .name = mt.name, | |
| 110 | .params = fn_params, | |
| 111 | .return_type = return_type, | |
| 112 | .body = try block_scope.complete(), | |
| 113 | }); | |
| 114 | try mt.t.addTopLevelDecl(mt.name, fn_decl); | |
| 115 | } | |
| 116 | ||
| 117 | pub fn transMacro(mt: *MacroTranslator) ParseError!void { | |
| 118 | const scope = &mt.t.global_scope.base; | |
| 119 | ||
| 120 | // Check if the macro only uses other blank macros. | |
| 121 | while (true) { | |
| 122 | switch (mt.peek()) { | |
| 123 | .identifier, .extended_identifier => { | |
| 124 | if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) { | |
| 125 | mt.i += 1; | |
| 126 | continue; | |
| 127 | } | |
| 128 | }, | |
| 129 | .eof, .nl => { | |
| 130 | try mt.t.global_scope.blank_macros.put(mt.t.gpa, mt.name, {}); | |
| 131 | const init_node = try ZigTag.string_literal.create(mt.t.arena, "\"\""); | |
| 132 | const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node }); | |
| 133 | try mt.t.addTopLevelDecl(mt.name, var_decl); | |
| 134 | return; | |
| 135 | }, | |
| 136 | else => {}, | |
| 137 | } | |
| 138 | break; | |
| 139 | } | |
| 140 | ||
| 141 | const init_node = try mt.parseCExpr(scope); | |
| 142 | const last = mt.peek(); | |
| 143 | if (last != .eof) | |
| 144 | return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()}); | |
| 145 | ||
| 146 | const node = node: { | |
| 147 | const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node }); | |
| 148 | ||
| 149 | if (mt.t.getFnProto(var_decl)) |proto_node| { | |
| 150 | // If a macro aliases a global variable which is a function pointer, we conclude that | |
| 151 | // the macro is intended to represent a function that assumes the function pointer | |
| 152 | // variable is non-null and calls it. | |
| 153 | break :node try mt.createMacroFn(mt.name, var_decl, proto_node); | |
| 154 | } else if (mt.refs_var_decl) { | |
| 155 | const return_type = try ZigTag.typeof.create(mt.t.arena, init_node); | |
| 156 | const return_expr = try ZigTag.@"return".create(mt.t.arena, init_node); | |
| 157 | const block = try ZigTag.block_single.create(mt.t.arena, return_expr); | |
| 158 | ||
| 159 | const loc_str = try mt.t.locStr(mt.macro.loc); | |
| 160 | const value = try std.fmt.allocPrint(mt.t.arena, "\n// {s}: warning: macro '{s}' contains a runtime value, translated to function", .{ loc_str, mt.name }); | |
| 161 | try scope.appendNode(try ZigTag.warning.create(mt.t.arena, value)); | |
| 162 | ||
| 163 | break :node try ZigTag.pub_inline_fn.create(mt.t.arena, .{ | |
| 164 | .name = mt.name, | |
| 165 | .params = &.{}, | |
| 166 | .return_type = return_type, | |
| 167 | .body = block, | |
| 168 | }); | |
| 169 | } | |
| 170 | ||
| 171 | break :node var_decl; | |
| 172 | }; | |
| 173 | ||
| 174 | try mt.t.addTopLevelDecl(mt.name, node); | |
| 175 | } | |
| 176 | ||
| 177 | fn createMacroFn(mt: *MacroTranslator, name: []const u8, ref: ZigNode, proto_alias: *ast.Payload.Func) !ZigNode { | |
| 178 | var fn_params = std.ArrayList(ast.Payload.Param).init(mt.t.gpa); | |
| 179 | defer fn_params.deinit(); | |
| 180 | ||
| 181 | var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false); | |
| 182 | defer block_scope.deinit(); | |
| 183 | ||
| 184 | for (proto_alias.data.params) |param| { | |
| 185 | const param_name = try block_scope.makeMangledName(param.name orelse "arg"); | |
| 186 | ||
| 187 | try fn_params.append(.{ | |
| 188 | .name = param_name, | |
| 189 | .type = param.type, | |
| 190 | .is_noalias = param.is_noalias, | |
| 191 | }); | |
| 192 | } | |
| 193 | ||
| 194 | const init = if (ref.castTag(.var_decl)) |v| | |
| 195 | v.data.init.? | |
| 196 | else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v| | |
| 197 | v.data.init | |
| 198 | else | |
| 199 | unreachable; | |
| 200 | ||
| 201 | const unwrap_expr = try ZigTag.unwrap.create(mt.t.arena, init); | |
| 202 | const args = try mt.t.arena.alloc(ZigNode, fn_params.items.len); | |
| 203 | for (fn_params.items, 0..) |param, i| { | |
| 204 | args[i] = try ZigTag.identifier.create(mt.t.arena, param.name.?); | |
| 205 | } | |
| 206 | const call_expr = try ZigTag.call.create(mt.t.arena, .{ | |
| 207 | .lhs = unwrap_expr, | |
| 208 | .args = args, | |
| 209 | }); | |
| 210 | const return_expr = try ZigTag.@"return".create(mt.t.arena, call_expr); | |
| 211 | const block = try ZigTag.block_single.create(mt.t.arena, return_expr); | |
| 212 | ||
| 213 | return ZigTag.pub_inline_fn.create(mt.t.arena, .{ | |
| 214 | .name = name, | |
| 215 | .params = try mt.t.arena.dupe(ast.Payload.Param, fn_params.items), | |
| 216 | .return_type = proto_alias.data.return_type, | |
| 217 | .body = block, | |
| 218 | }); | |
| 219 | } | |
| 220 | ||
| 221 | fn parseCExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 222 | // TODO parseCAssignExpr here | |
| 223 | var block_scope = try Scope.Block.init(mt.t, scope, true); | |
| 224 | defer block_scope.deinit(); | |
| 225 | ||
| 226 | const node = try mt.parseCCondExpr(&block_scope.base); | |
| 227 | if (!mt.eat(.comma)) return node; | |
| 228 | ||
| 229 | var last = node; | |
| 230 | while (true) { | |
| 231 | // suppress result | |
| 232 | const ignore = try ZigTag.discard.create(mt.t.arena, .{ .should_skip = false, .value = last }); | |
| 233 | try block_scope.statements.append(mt.t.gpa, ignore); | |
| 234 | ||
| 235 | last = try mt.parseCCondExpr(&block_scope.base); | |
| 236 | if (!mt.eat(.comma)) break; | |
| 237 | } | |
| 238 | ||
| 239 | const break_node = try ZigTag.break_val.create(mt.t.arena, .{ | |
| 240 | .label = block_scope.label, | |
| 241 | .val = last, | |
| 242 | }); | |
| 243 | try block_scope.statements.append(mt.t.gpa, break_node); | |
| 244 | return try block_scope.complete(); | |
| 245 | } | |
| 246 | ||
| 247 | fn parseCNumLit(mt: *MacroTranslator) ParseError!ZigNode { | |
| 248 | const lit_bytes = mt.tokSlice(); | |
| 249 | mt.i += 1; | |
| 250 | ||
| 251 | var bytes = try std.ArrayListUnmanaged(u8).initCapacity(mt.t.arena, lit_bytes.len + 3); | |
| 252 | ||
| 253 | const prefix = aro.Tree.Token.NumberPrefix.fromString(lit_bytes); | |
| 254 | switch (prefix) { | |
| 255 | .binary => bytes.appendSliceAssumeCapacity("0b"), | |
| 256 | .octal => bytes.appendSliceAssumeCapacity("0o"), | |
| 257 | .hex => bytes.appendSliceAssumeCapacity("0x"), | |
| 258 | .decimal => {}, | |
| 259 | } | |
| 260 | ||
| 261 | const after_prefix = lit_bytes[prefix.stringLen()..]; | |
| 262 | const after_int = for (after_prefix, 0..) |c, i| switch (c) { | |
| 263 | '.' => { | |
| 264 | if (i == 0) { | |
| 265 | bytes.appendAssumeCapacity('0'); | |
| 266 | } | |
| 267 | break after_prefix[i..]; | |
| 268 | }, | |
| 269 | 'e', 'E' => { | |
| 270 | if (prefix != .hex) break after_prefix[i..]; | |
| 271 | bytes.appendAssumeCapacity(c); | |
| 272 | }, | |
| 273 | 'p', 'P' => break after_prefix[i..], | |
| 274 | '0'...'9', 'a'...'d', 'A'...'D', 'f', 'F' => { | |
| 275 | if (!prefix.digitAllowed(c)) break after_prefix[i..]; | |
| 276 | bytes.appendAssumeCapacity(c); | |
| 277 | }, | |
| 278 | '\'' => { | |
| 279 | bytes.appendAssumeCapacity('_'); | |
| 280 | }, | |
| 281 | else => break after_prefix[i..], | |
| 282 | } else ""; | |
| 283 | ||
| 284 | const after_frac = frac: { | |
| 285 | if (after_int.len == 0 or after_int[0] != '.') break :frac after_int; | |
| 286 | bytes.appendAssumeCapacity('.'); | |
| 287 | for (after_int[1..], 1..) |c, i| { | |
| 288 | if (c == '\'') { | |
| 289 | bytes.appendAssumeCapacity('_'); | |
| 290 | continue; | |
| 291 | } | |
| 292 | if (!prefix.digitAllowed(c)) break :frac after_int[i..]; | |
| 293 | bytes.appendAssumeCapacity(c); | |
| 294 | } | |
| 295 | break :frac ""; | |
| 296 | }; | |
| 297 | ||
| 298 | const suffix_str = exponent: { | |
| 299 | if (after_frac.len == 0) break :exponent after_frac; | |
| 300 | switch (after_frac[0]) { | |
| 301 | 'e', 'E' => {}, | |
| 302 | 'p', 'P' => if (prefix != .hex) break :exponent after_frac, | |
| 303 | else => break :exponent after_frac, | |
| 304 | } | |
| 305 | bytes.appendAssumeCapacity(after_frac[0]); | |
| 306 | for (after_frac[1..], 1..) |c, i| switch (c) { | |
| 307 | '+', '-', '0'...'9' => { | |
| 308 | bytes.appendAssumeCapacity(c); | |
| 309 | }, | |
| 310 | '\'' => { | |
| 311 | bytes.appendAssumeCapacity('_'); | |
| 312 | }, | |
| 313 | else => break :exponent after_frac[i..], | |
| 314 | }; | |
| 315 | break :exponent ""; | |
| 316 | }; | |
| 317 | ||
| 318 | const is_float = after_int.len != suffix_str.len; | |
| 319 | const suffix = aro.Tree.Token.NumberSuffix.fromString(suffix_str, if (is_float) .float else .int) orelse { | |
| 320 | try mt.fail("invalid number suffix: '{s}'", .{suffix_str}); | |
| 321 | return error.ParseError; | |
| 322 | }; | |
| 323 | if (suffix.isImaginary()) { | |
| 324 | try mt.fail("TODO: imaginary literals", .{}); | |
| 325 | return error.ParseError; | |
| 326 | } | |
| 327 | if (suffix.isBitInt()) { | |
| 328 | try mt.fail("TODO: _BitInt literals", .{}); | |
| 329 | return error.ParseError; | |
| 330 | } | |
| 331 | ||
| 332 | if (is_float) { | |
| 333 | const type_node = try ZigTag.type.create(mt.t.arena, switch (suffix) { | |
| 334 | .F16 => "f16", | |
| 335 | .F => "f32", | |
| 336 | .None => "f64", | |
| 337 | .L => "c_longdouble", | |
| 338 | .W => "f80", | |
| 339 | .Q, .F128 => "f128", | |
| 340 | else => unreachable, | |
| 341 | }); | |
| 342 | const rhs = try ZigTag.float_literal.create(mt.t.arena, bytes.items); | |
| 343 | return ZigTag.as.create(mt.t.arena, .{ .lhs = type_node, .rhs = rhs }); | |
| 344 | } else { | |
| 345 | const type_node = try ZigTag.type.create(mt.t.arena, switch (suffix) { | |
| 346 | .None => "c_int", | |
| 347 | .U => "c_uint", | |
| 348 | .L => "c_long", | |
| 349 | .UL => "c_ulong", | |
| 350 | .LL => "c_longlong", | |
| 351 | .ULL => "c_ulonglong", | |
| 352 | else => unreachable, | |
| 353 | }); | |
| 354 | const value = std.fmt.parseInt(i128, bytes.items, 0) catch math.maxInt(i128); | |
| 355 | ||
| 356 | // make the output less noisy by skipping promoteIntLiteral where | |
| 357 | // it's guaranteed to not be required because of C standard type constraints | |
| 358 | const guaranteed_to_fit = switch (suffix) { | |
| 359 | .None => math.cast(i16, value) != null, | |
| 360 | .U => math.cast(u16, value) != null, | |
| 361 | .L => math.cast(i32, value) != null, | |
| 362 | .UL => math.cast(u32, value) != null, | |
| 363 | .LL => math.cast(i64, value) != null, | |
| 364 | .ULL => math.cast(u64, value) != null, | |
| 365 | else => unreachable, | |
| 366 | }; | |
| 367 | ||
| 368 | const literal_node = try ZigTag.integer_literal.create(mt.t.arena, bytes.items); | |
| 369 | if (guaranteed_to_fit) { | |
| 370 | return ZigTag.as.create(mt.t.arena, .{ .lhs = type_node, .rhs = literal_node }); | |
| 371 | } else { | |
| 372 | return mt.t.createHelperCallNode(.promoteIntLiteral, &.{ type_node, literal_node, try ZigTag.enum_literal.create(mt.t.arena, @tagName(prefix)) }); | |
| 373 | } | |
| 374 | } | |
| 375 | } | |
| 376 | ||
| 377 | fn zigifyEscapeSequences(mt: *MacroTranslator, slice: []const u8) ![]const u8 { | |
| 378 | var source = slice; | |
| 379 | for (source, 0..) |c, i| { | |
| 380 | if (c == '\"' or c == '\'') { | |
| 381 | source = source[i..]; | |
| 382 | break; | |
| 383 | } | |
| 384 | } | |
| 385 | for (source) |c| { | |
| 386 | if (c == '\\' or c == '\t') { | |
| 387 | break; | |
| 388 | } | |
| 389 | } else return source; | |
| 390 | const bytes = try mt.t.arena.alloc(u8, source.len * 2); | |
| 391 | var state: enum { | |
| 392 | start, | |
| 393 | escape, | |
| 394 | hex, | |
| 395 | octal, | |
| 396 | } = .start; | |
| 397 | var i: usize = 0; | |
| 398 | var count: u8 = 0; | |
| 399 | var num: u8 = 0; | |
| 400 | for (source) |c| { | |
| 401 | switch (state) { | |
| 402 | .escape => { | |
| 403 | switch (c) { | |
| 404 | 'n', 'r', 't', '\\', '\'', '\"' => { | |
| 405 | bytes[i] = c; | |
| 406 | }, | |
| 407 | '0'...'7' => { | |
| 408 | count += 1; | |
| 409 | num += c - '0'; | |
| 410 | state = .octal; | |
| 411 | bytes[i] = 'x'; | |
| 412 | }, | |
| 413 | 'x' => { | |
| 414 | state = .hex; | |
| 415 | bytes[i] = 'x'; | |
| 416 | }, | |
| 417 | 'a' => { | |
| 418 | bytes[i] = 'x'; | |
| 419 | i += 1; | |
| 420 | bytes[i] = '0'; | |
| 421 | i += 1; | |
| 422 | bytes[i] = '7'; | |
| 423 | }, | |
| 424 | 'b' => { | |
| 425 | bytes[i] = 'x'; | |
| 426 | i += 1; | |
| 427 | bytes[i] = '0'; | |
| 428 | i += 1; | |
| 429 | bytes[i] = '8'; | |
| 430 | }, | |
| 431 | 'f' => { | |
| 432 | bytes[i] = 'x'; | |
| 433 | i += 1; | |
| 434 | bytes[i] = '0'; | |
| 435 | i += 1; | |
| 436 | bytes[i] = 'C'; | |
| 437 | }, | |
| 438 | 'v' => { | |
| 439 | bytes[i] = 'x'; | |
| 440 | i += 1; | |
| 441 | bytes[i] = '0'; | |
| 442 | i += 1; | |
| 443 | bytes[i] = 'B'; | |
| 444 | }, | |
| 445 | '?' => { | |
| 446 | i -= 1; | |
| 447 | bytes[i] = '?'; | |
| 448 | }, | |
| 449 | 'u', 'U' => { | |
| 450 | try mt.fail("macro tokenizing failed: TODO unicode escape sequences", .{}); | |
| 451 | return error.ParseError; | |
| 452 | }, | |
| 453 | else => { | |
| 454 | try mt.fail("macro tokenizing failed: unknown escape sequence", .{}); | |
| 455 | return error.ParseError; | |
| 456 | }, | |
| 457 | } | |
| 458 | i += 1; | |
| 459 | if (state == .escape) | |
| 460 | state = .start; | |
| 461 | }, | |
| 462 | .start => { | |
| 463 | if (c == '\t') { | |
| 464 | bytes[i] = '\\'; | |
| 465 | i += 1; | |
| 466 | bytes[i] = 't'; | |
| 467 | i += 1; | |
| 468 | continue; | |
| 469 | } | |
| 470 | if (c == '\\') { | |
| 471 | state = .escape; | |
| 472 | } | |
| 473 | bytes[i] = c; | |
| 474 | i += 1; | |
| 475 | }, | |
| 476 | .hex => { | |
| 477 | switch (c) { | |
| 478 | '0'...'9' => { | |
| 479 | num = std.math.mul(u8, num, 16) catch { | |
| 480 | try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); | |
| 481 | return error.ParseError; | |
| 482 | }; | |
| 483 | num += c - '0'; | |
| 484 | }, | |
| 485 | 'a'...'f' => { | |
| 486 | num = std.math.mul(u8, num, 16) catch { | |
| 487 | try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); | |
| 488 | return error.ParseError; | |
| 489 | }; | |
| 490 | num += c - 'a' + 10; | |
| 491 | }, | |
| 492 | 'A'...'F' => { | |
| 493 | num = std.math.mul(u8, num, 16) catch { | |
| 494 | try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); | |
| 495 | return error.ParseError; | |
| 496 | }; | |
| 497 | num += c - 'A' + 10; | |
| 498 | }, | |
| 499 | else => { | |
| 500 | i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); | |
| 501 | num = 0; | |
| 502 | if (c == '\\') | |
| 503 | state = .escape | |
| 504 | else | |
| 505 | state = .start; | |
| 506 | bytes[i] = c; | |
| 507 | i += 1; | |
| 508 | }, | |
| 509 | } | |
| 510 | }, | |
| 511 | .octal => { | |
| 512 | const accept_digit = switch (c) { | |
| 513 | // The maximum length of a octal literal is 3 digits | |
| 514 | '0'...'7' => count < 3, | |
| 515 | else => false, | |
| 516 | }; | |
| 517 | ||
| 518 | if (accept_digit) { | |
| 519 | count += 1; | |
| 520 | num = std.math.mul(u8, num, 8) catch { | |
| 521 | try mt.fail("macro tokenizing failed: octal literal overflowed", .{}); | |
| 522 | return error.ParseError; | |
| 523 | }; | |
| 524 | num += c - '0'; | |
| 525 | } else { | |
| 526 | i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); | |
| 527 | num = 0; | |
| 528 | count = 0; | |
| 529 | if (c == '\\') | |
| 530 | state = .escape | |
| 531 | else | |
| 532 | state = .start; | |
| 533 | bytes[i] = c; | |
| 534 | i += 1; | |
| 535 | } | |
| 536 | }, | |
| 537 | } | |
| 538 | } | |
| 539 | if (state == .hex or state == .octal) { | |
| 540 | i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); | |
| 541 | } | |
| 542 | ||
| 543 | return bytes[0..i]; | |
| 544 | } | |
| 545 | ||
| 546 | /// non-ASCII characters (mt > 127) are also treated as non-printable by fmtSliceEscapeLower. | |
| 547 | /// If a C string literal or char literal in a macro is not valid UTF-8, we need to escape | |
| 548 | /// non-ASCII characters so that the Zig source we output will itself be UTF-8. | |
| 549 | fn escapeUnprintables(mt: *MacroTranslator) ![]const u8 { | |
| 550 | const slice = mt.tokSlice(); | |
| 551 | mt.i += 1; | |
| 552 | ||
| 553 | const zigified = try mt.zigifyEscapeSequences(slice); | |
| 554 | if (std.unicode.utf8ValidateSlice(zigified)) return zigified; | |
| 555 | ||
| 556 | const formatter = std.ascii.hexEscape(zigified, .lower); | |
| 557 | const encoded_size = @as(usize, @intCast(std.fmt.count("{f}", .{formatter}))); | |
| 558 | const output = try mt.t.arena.alloc(u8, encoded_size); | |
| 559 | return std.fmt.bufPrint(output, "{f}", .{formatter}) catch |err| switch (err) { | |
| 560 | error.NoSpaceLeft => unreachable, | |
| 561 | else => |e| return e, | |
| 562 | }; | |
| 563 | } | |
| 564 | ||
| 565 | fn parseCPrimaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 566 | const tok = mt.peek(); | |
| 567 | switch (tok) { | |
| 568 | .char_literal, | |
| 569 | .char_literal_utf_8, | |
| 570 | .char_literal_utf_16, | |
| 571 | .char_literal_utf_32, | |
| 572 | .char_literal_wide, | |
| 573 | => { | |
| 574 | const slice = mt.tokSlice(); | |
| 575 | if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) { | |
| 576 | return ZigTag.char_literal.create(mt.t.arena, try mt.escapeUnprintables()); | |
| 577 | } else { | |
| 578 | mt.i += 1; | |
| 579 | ||
| 580 | const str = try std.fmt.allocPrint(mt.t.arena, "0x{x}", .{slice[1 .. slice.len - 1]}); | |
| 581 | return ZigTag.integer_literal.create(mt.t.arena, str); | |
| 582 | } | |
| 583 | }, | |
| 584 | .string_literal, | |
| 585 | .string_literal_utf_16, | |
| 586 | .string_literal_utf_8, | |
| 587 | .string_literal_utf_32, | |
| 588 | .string_literal_wide, | |
| 589 | => return ZigTag.string_literal.create(mt.t.arena, try mt.escapeUnprintables()), | |
| 590 | .pp_num => return mt.parseCNumLit(), | |
| 591 | .l_paren => { | |
| 592 | mt.i += 1; | |
| 593 | const inner_node = try mt.parseCExpr(scope); | |
| 594 | ||
| 595 | try mt.expect(.r_paren); | |
| 596 | return inner_node; | |
| 597 | }, | |
| 598 | .macro_param, .macro_param_no_expand => { | |
| 599 | const param = mt.macro.params[mt.tokens[mt.i].end]; | |
| 600 | mt.i += 1; | |
| 601 | ||
| 602 | const mangled_name = scope.getAlias(param) orelse param; | |
| 603 | return try ZigTag.identifier.create(mt.t.arena, mangled_name); | |
| 604 | }, | |
| 605 | .identifier, .extended_identifier => { | |
| 606 | const slice = mt.tokSlice(); | |
| 607 | mt.i += 1; | |
| 608 | ||
| 609 | const mangled_name = scope.getAlias(slice) orelse slice; | |
| 610 | if (Translator.builtin_typedef_map.get(mangled_name)) |ty| { | |
| 611 | return ZigTag.type.create(mt.t.arena, ty); | |
| 612 | } | |
| 613 | if (builtins.map.get(mangled_name)) |builtin| { | |
| 614 | const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin"); | |
| 615 | return ZigTag.field_access.create(mt.t.arena, .{ | |
| 616 | .lhs = builtin_identifier, | |
| 617 | .field_name = builtin.name, | |
| 618 | }); | |
| 619 | } | |
| 620 | ||
| 621 | const identifier = try ZigTag.identifier.create(mt.t.arena, mangled_name); | |
| 622 | scope.skipVariableDiscard(mangled_name); | |
| 623 | refs_var: { | |
| 624 | const ident_node = mt.t.global_scope.sym_table.get(slice) orelse break :refs_var; | |
| 625 | const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var; | |
| 626 | if (!var_decl_node.data.is_const) mt.refs_var_decl = true; | |
| 627 | } | |
| 628 | return identifier; | |
| 629 | }, | |
| 630 | else => {}, | |
| 631 | } | |
| 632 | ||
| 633 | // for handling type macros (EVIL) | |
| 634 | // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList? | |
| 635 | if (try mt.parseCTypeName(scope, true)) |type_name| { | |
| 636 | return type_name; | |
| 637 | } | |
| 638 | ||
| 639 | try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()}); | |
| 640 | return error.ParseError; | |
| 641 | } | |
| 642 | ||
| 643 | fn macroIntFromBool(mt: *MacroTranslator, node: ZigNode) !ZigNode { | |
| 644 | if (!node.isBoolRes()) return node; | |
| 645 | ||
| 646 | return ZigTag.int_from_bool.create(mt.t.arena, node); | |
| 647 | } | |
| 648 | ||
| 649 | fn macroIntToBool(mt: *MacroTranslator, node: ZigNode) !ZigNode { | |
| 650 | if (node.isBoolRes()) return node; | |
| 651 | ||
| 652 | if (node.tag() == .string_literal) { | |
| 653 | // @intFromPtr(node) != 0 | |
| 654 | const int_from_ptr = try ZigTag.int_from_ptr.create(mt.t.arena, node); | |
| 655 | return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); | |
| 656 | } | |
| 657 | // node != 0 | |
| 658 | return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); | |
| 659 | } | |
| 660 | ||
| 661 | fn parseCCondExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 662 | const node = try mt.parseCOrExpr(scope); | |
| 663 | if (!mt.eat(.question_mark)) return node; | |
| 664 | ||
| 665 | const then_body = try mt.parseCOrExpr(scope); | |
| 666 | try mt.expect(.colon); | |
| 667 | const else_body = try mt.parseCCondExpr(scope); | |
| 668 | return ZigTag.@"if".create(mt.t.arena, .{ .cond = node, .then = then_body, .@"else" = else_body }); | |
| 669 | } | |
| 670 | ||
| 671 | fn parseCOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 672 | var node = try mt.parseCAndExpr(scope); | |
| 673 | while (mt.eat(.pipe_pipe)) { | |
| 674 | const lhs = try mt.macroIntToBool(node); | |
| 675 | const rhs = try mt.macroIntToBool(try mt.parseCAndExpr(scope)); | |
| 676 | node = try ZigTag.@"or".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 677 | } | |
| 678 | return node; | |
| 679 | } | |
| 680 | ||
| 681 | fn parseCAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 682 | var node = try mt.parseCBitOrExpr(scope); | |
| 683 | while (mt.eat(.ampersand_ampersand)) { | |
| 684 | const lhs = try mt.macroIntToBool(node); | |
| 685 | const rhs = try mt.macroIntToBool(try mt.parseCBitOrExpr(scope)); | |
| 686 | node = try ZigTag.@"and".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 687 | } | |
| 688 | return node; | |
| 689 | } | |
| 690 | ||
| 691 | fn parseCBitOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 692 | var node = try mt.parseCBitXorExpr(scope); | |
| 693 | while (mt.eat(.pipe)) { | |
| 694 | const lhs = try mt.macroIntFromBool(node); | |
| 695 | const rhs = try mt.macroIntFromBool(try mt.parseCBitXorExpr(scope)); | |
| 696 | node = try ZigTag.bit_or.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 697 | } | |
| 698 | return node; | |
| 699 | } | |
| 700 | ||
| 701 | fn parseCBitXorExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 702 | var node = try mt.parseCBitAndExpr(scope); | |
| 703 | while (mt.eat(.caret)) { | |
| 704 | const lhs = try mt.macroIntFromBool(node); | |
| 705 | const rhs = try mt.macroIntFromBool(try mt.parseCBitAndExpr(scope)); | |
| 706 | node = try ZigTag.bit_xor.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 707 | } | |
| 708 | return node; | |
| 709 | } | |
| 710 | ||
| 711 | fn parseCBitAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 712 | var node = try mt.parseCEqExpr(scope); | |
| 713 | while (mt.eat(.ampersand)) { | |
| 714 | const lhs = try mt.macroIntFromBool(node); | |
| 715 | const rhs = try mt.macroIntFromBool(try mt.parseCEqExpr(scope)); | |
| 716 | node = try ZigTag.bit_and.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 717 | } | |
| 718 | return node; | |
| 719 | } | |
| 720 | ||
| 721 | fn parseCEqExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 722 | var node = try mt.parseCRelExpr(scope); | |
| 723 | while (true) { | |
| 724 | switch (mt.peek()) { | |
| 725 | .bang_equal => { | |
| 726 | mt.i += 1; | |
| 727 | const lhs = try mt.macroIntFromBool(node); | |
| 728 | const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope)); | |
| 729 | node = try ZigTag.not_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 730 | }, | |
| 731 | .equal_equal => { | |
| 732 | mt.i += 1; | |
| 733 | const lhs = try mt.macroIntFromBool(node); | |
| 734 | const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope)); | |
| 735 | node = try ZigTag.equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 736 | }, | |
| 737 | else => return node, | |
| 738 | } | |
| 739 | } | |
| 740 | } | |
| 741 | ||
| 742 | fn parseCRelExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 743 | var node = try mt.parseCShiftExpr(scope); | |
| 744 | while (true) { | |
| 745 | switch (mt.peek()) { | |
| 746 | .angle_bracket_right => { | |
| 747 | mt.i += 1; | |
| 748 | const lhs = try mt.macroIntFromBool(node); | |
| 749 | const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); | |
| 750 | node = try ZigTag.greater_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 751 | }, | |
| 752 | .angle_bracket_right_equal => { | |
| 753 | mt.i += 1; | |
| 754 | const lhs = try mt.macroIntFromBool(node); | |
| 755 | const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); | |
| 756 | node = try ZigTag.greater_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 757 | }, | |
| 758 | .angle_bracket_left => { | |
| 759 | mt.i += 1; | |
| 760 | const lhs = try mt.macroIntFromBool(node); | |
| 761 | const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); | |
| 762 | node = try ZigTag.less_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 763 | }, | |
| 764 | .angle_bracket_left_equal => { | |
| 765 | mt.i += 1; | |
| 766 | const lhs = try mt.macroIntFromBool(node); | |
| 767 | const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); | |
| 768 | node = try ZigTag.less_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 769 | }, | |
| 770 | else => return node, | |
| 771 | } | |
| 772 | } | |
| 773 | } | |
| 774 | ||
| 775 | fn parseCShiftExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 776 | var node = try mt.parseCAddSubExpr(scope); | |
| 777 | while (true) { | |
| 778 | switch (mt.peek()) { | |
| 779 | .angle_bracket_angle_bracket_left => { | |
| 780 | mt.i += 1; | |
| 781 | const lhs = try mt.macroIntFromBool(node); | |
| 782 | const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope)); | |
| 783 | node = try ZigTag.shl.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 784 | }, | |
| 785 | .angle_bracket_angle_bracket_right => { | |
| 786 | mt.i += 1; | |
| 787 | const lhs = try mt.macroIntFromBool(node); | |
| 788 | const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope)); | |
| 789 | node = try ZigTag.shr.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 790 | }, | |
| 791 | else => return node, | |
| 792 | } | |
| 793 | } | |
| 794 | } | |
| 795 | ||
| 796 | fn parseCAddSubExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 797 | var node = try mt.parseCMulExpr(scope); | |
| 798 | while (true) { | |
| 799 | switch (mt.peek()) { | |
| 800 | .plus => { | |
| 801 | mt.i += 1; | |
| 802 | const lhs = try mt.macroIntFromBool(node); | |
| 803 | const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope)); | |
| 804 | node = try ZigTag.add.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 805 | }, | |
| 806 | .minus => { | |
| 807 | mt.i += 1; | |
| 808 | const lhs = try mt.macroIntFromBool(node); | |
| 809 | const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope)); | |
| 810 | node = try ZigTag.sub.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 811 | }, | |
| 812 | else => return node, | |
| 813 | } | |
| 814 | } | |
| 815 | } | |
| 816 | ||
| 817 | fn parseCMulExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 818 | var node = try mt.parseCCastExpr(scope); | |
| 819 | while (true) { | |
| 820 | switch (mt.peek()) { | |
| 821 | .asterisk => { | |
| 822 | mt.i += 1; | |
| 823 | const lhs = try mt.macroIntFromBool(node); | |
| 824 | const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 825 | node = try ZigTag.mul.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 826 | }, | |
| 827 | .slash => { | |
| 828 | mt.i += 1; | |
| 829 | const lhs = try mt.macroIntFromBool(node); | |
| 830 | const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 831 | node = try mt.t.createHelperCallNode(.div, &.{ lhs, rhs }); | |
| 832 | }, | |
| 833 | .percent => { | |
| 834 | mt.i += 1; | |
| 835 | const lhs = try mt.macroIntFromBool(node); | |
| 836 | const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 837 | node = try mt.t.createHelperCallNode(.rem, &.{ lhs, rhs }); | |
| 838 | }, | |
| 839 | else => return node, | |
| 840 | } | |
| 841 | } | |
| 842 | } | |
| 843 | ||
| 844 | fn parseCCastExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 845 | if (mt.eat(.l_paren)) { | |
| 846 | if (try mt.parseCTypeName(scope, true)) |type_name| { | |
| 847 | while (true) { | |
| 848 | const next_tok = mt.peek(); | |
| 849 | if (next_tok == .r_paren) { | |
| 850 | mt.i += 1; | |
| 851 | break; | |
| 852 | } | |
| 853 | // Skip trailing blank defined before the RParen. | |
| 854 | if ((next_tok == .identifier or next_tok == .extended_identifier) and | |
| 855 | mt.t.global_scope.blank_macros.contains(mt.tokSlice())) | |
| 856 | { | |
| 857 | mt.i += 1; | |
| 858 | continue; | |
| 859 | } | |
| 860 | ||
| 861 | try mt.fail( | |
| 862 | "unable to translate C expr: expected ')' instead got '{s}'", | |
| 863 | .{next_tok.symbol()}, | |
| 864 | ); | |
| 865 | return error.ParseError; | |
| 866 | } | |
| 867 | if (mt.peek() == .l_brace) { | |
| 868 | // initializer list | |
| 869 | return mt.parseCPostfixExpr(scope, type_name); | |
| 870 | } | |
| 871 | const node_to_cast = try mt.parseCCastExpr(scope); | |
| 872 | return mt.t.createHelperCallNode(.cast, &.{ type_name, node_to_cast }); | |
| 873 | } | |
| 874 | mt.i -= 1; // l_paren | |
| 875 | } | |
| 876 | return mt.parseCUnaryExpr(scope); | |
| 877 | } | |
| 878 | ||
| 879 | // allow_fail is set when unsure if we are parsing a type-name | |
| 880 | fn parseCTypeName(mt: *MacroTranslator, scope: *Scope, allow_fail: bool) ParseError!?ZigNode { | |
| 881 | if (try mt.parseCSpecifierQualifierList(scope, allow_fail)) |node| { | |
| 882 | return try mt.parseCAbstractDeclarator(node); | |
| 883 | } | |
| 884 | return null; | |
| 885 | } | |
| 886 | ||
| 887 | fn parseCSpecifierQualifierList(mt: *MacroTranslator, scope: *Scope, allow_fail: bool) ParseError!?ZigNode { | |
| 888 | const tok = mt.peek(); | |
| 889 | switch (tok) { | |
| 890 | .macro_param, .macro_param_no_expand => { | |
| 891 | const param = mt.macro.params[mt.tokens[mt.i].end]; | |
| 892 | ||
| 893 | // Assume that this is only a cast if the next token is ')' | |
| 894 | // e.g. param)identifier | |
| 895 | if (allow_fail and (mt.macro.tokens.len < mt.i + 3 or | |
| 896 | mt.macro.tokens[mt.i + 1].id != .r_paren or | |
| 897 | mt.macro.tokens[mt.i + 2].id != .identifier)) | |
| 898 | return null; | |
| 899 | ||
| 900 | mt.i += 1; | |
| 901 | const mangled_name = scope.getAlias(param) orelse param; | |
| 902 | return try ZigTag.identifier.create(mt.t.arena, mangled_name); | |
| 903 | }, | |
| 904 | .identifier, .extended_identifier => { | |
| 905 | const slice = mt.tokSlice(); | |
| 906 | const mangled_name = scope.getAlias(slice) orelse slice; | |
| 907 | ||
| 908 | if (mt.t.global_scope.blank_macros.contains(slice)) { | |
| 909 | mt.i += 1; | |
| 910 | return try mt.parseCSpecifierQualifierList(scope, allow_fail); | |
| 911 | } | |
| 912 | ||
| 913 | if (!allow_fail or mt.t.typedefs.contains(mangled_name)) { | |
| 914 | mt.i += 1; | |
| 915 | if (Translator.builtin_typedef_map.get(mangled_name)) |ty| { | |
| 916 | return try ZigTag.type.create(mt.t.arena, ty); | |
| 917 | } | |
| 918 | if (builtins.map.get(mangled_name)) |builtin| { | |
| 919 | const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin"); | |
| 920 | return try ZigTag.field_access.create(mt.t.arena, .{ | |
| 921 | .lhs = builtin_identifier, | |
| 922 | .field_name = builtin.name, | |
| 923 | }); | |
| 924 | } | |
| 925 | ||
| 926 | return try ZigTag.identifier.create(mt.t.arena, mangled_name); | |
| 927 | } | |
| 928 | }, | |
| 929 | .keyword_void => { | |
| 930 | mt.i += 1; | |
| 931 | return try ZigTag.type.create(mt.t.arena, "anyopaque"); | |
| 932 | }, | |
| 933 | .keyword_bool => { | |
| 934 | mt.i += 1; | |
| 935 | return try ZigTag.type.create(mt.t.arena, "bool"); | |
| 936 | }, | |
| 937 | .keyword_char, | |
| 938 | .keyword_int, | |
| 939 | .keyword_short, | |
| 940 | .keyword_long, | |
| 941 | .keyword_float, | |
| 942 | .keyword_double, | |
| 943 | .keyword_signed, | |
| 944 | .keyword_unsigned, | |
| 945 | .keyword_complex, | |
| 946 | => return try mt.parseCNumericType(), | |
| 947 | .keyword_enum, .keyword_struct, .keyword_union => { | |
| 948 | const tag_name = mt.tokSlice(); | |
| 949 | mt.i += 1; | |
| 950 | ||
| 951 | // struct Foo will be declared as struct_Foo by transRecordDecl | |
| 952 | const identifier = mt.tokSlice(); | |
| 953 | try mt.expect(.identifier); | |
| 954 | ||
| 955 | const name = try std.fmt.allocPrint(mt.t.arena, "{s}_{s}", .{ tag_name, identifier }); | |
| 956 | return try ZigTag.identifier.create(mt.t.arena, name); | |
| 957 | }, | |
| 958 | else => {}, | |
| 959 | } | |
| 960 | ||
| 961 | if (allow_fail) return null; | |
| 962 | ||
| 963 | try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()}); | |
| 964 | return error.ParseError; | |
| 965 | } | |
| 966 | ||
| 967 | fn parseCNumericType(mt: *MacroTranslator) ParseError!ZigNode { | |
| 968 | const KwCounter = struct { | |
| 969 | double: u8 = 0, | |
| 970 | long: u8 = 0, | |
| 971 | int: u8 = 0, | |
| 972 | float: u8 = 0, | |
| 973 | short: u8 = 0, | |
| 974 | char: u8 = 0, | |
| 975 | unsigned: u8 = 0, | |
| 976 | signed: u8 = 0, | |
| 977 | complex: u8 = 0, | |
| 978 | ||
| 979 | fn eql(self: @This(), other: @This()) bool { | |
| 980 | return std.meta.eql(self, other); | |
| 981 | } | |
| 982 | }; | |
| 983 | ||
| 984 | // Yes, these can be in *any* order | |
| 985 | // This still doesn't cover cases where for example volatile is intermixed | |
| 986 | ||
| 987 | var kw = KwCounter{}; | |
| 988 | // prevent overflow | |
| 989 | var i: u8 = 0; | |
| 990 | while (i < math.maxInt(u8)) : (i += 1) { | |
| 991 | switch (mt.peek()) { | |
| 992 | .keyword_double => kw.double += 1, | |
| 993 | .keyword_long => kw.long += 1, | |
| 994 | .keyword_int => kw.int += 1, | |
| 995 | .keyword_float => kw.float += 1, | |
| 996 | .keyword_short => kw.short += 1, | |
| 997 | .keyword_char => kw.char += 1, | |
| 998 | .keyword_unsigned => kw.unsigned += 1, | |
| 999 | .keyword_signed => kw.signed += 1, | |
| 1000 | .keyword_complex => kw.complex += 1, | |
| 1001 | else => break, | |
| 1002 | } | |
| 1003 | mt.i += 1; | |
| 1004 | } | |
| 1005 | ||
| 1006 | if (kw.eql(.{ .int = 1 }) or kw.eql(.{ .signed = 1 }) or kw.eql(.{ .signed = 1, .int = 1 })) | |
| 1007 | return ZigTag.type.create(mt.t.arena, "c_int"); | |
| 1008 | ||
| 1009 | if (kw.eql(.{ .unsigned = 1 }) or kw.eql(.{ .unsigned = 1, .int = 1 })) | |
| 1010 | return ZigTag.type.create(mt.t.arena, "c_uint"); | |
| 1011 | ||
| 1012 | if (kw.eql(.{ .long = 1 }) or kw.eql(.{ .signed = 1, .long = 1 }) or kw.eql(.{ .long = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 1, .int = 1 })) | |
| 1013 | return ZigTag.type.create(mt.t.arena, "c_long"); | |
| 1014 | ||
| 1015 | if (kw.eql(.{ .unsigned = 1, .long = 1 }) or kw.eql(.{ .unsigned = 1, .long = 1, .int = 1 })) | |
| 1016 | return ZigTag.type.create(mt.t.arena, "c_ulong"); | |
| 1017 | ||
| 1018 | if (kw.eql(.{ .long = 2 }) or kw.eql(.{ .signed = 1, .long = 2 }) or kw.eql(.{ .long = 2, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 2, .int = 1 })) | |
| 1019 | return ZigTag.type.create(mt.t.arena, "c_longlong"); | |
| 1020 | ||
| 1021 | if (kw.eql(.{ .unsigned = 1, .long = 2 }) or kw.eql(.{ .unsigned = 1, .long = 2, .int = 1 })) | |
| 1022 | return ZigTag.type.create(mt.t.arena, "c_ulonglong"); | |
| 1023 | ||
| 1024 | if (kw.eql(.{ .signed = 1, .char = 1 })) | |
| 1025 | return ZigTag.type.create(mt.t.arena, "i8"); | |
| 1026 | ||
| 1027 | if (kw.eql(.{ .char = 1 }) or kw.eql(.{ .unsigned = 1, .char = 1 })) | |
| 1028 | return ZigTag.type.create(mt.t.arena, "u8"); | |
| 1029 | ||
| 1030 | if (kw.eql(.{ .short = 1 }) or kw.eql(.{ .signed = 1, .short = 1 }) or kw.eql(.{ .short = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .short = 1, .int = 1 })) | |
| 1031 | return ZigTag.type.create(mt.t.arena, "c_short"); | |
| 1032 | ||
| 1033 | if (kw.eql(.{ .unsigned = 1, .short = 1 }) or kw.eql(.{ .unsigned = 1, .short = 1, .int = 1 })) | |
| 1034 | return ZigTag.type.create(mt.t.arena, "c_ushort"); | |
| 1035 | ||
| 1036 | if (kw.eql(.{ .float = 1 })) | |
| 1037 | return ZigTag.type.create(mt.t.arena, "f32"); | |
| 1038 | ||
| 1039 | if (kw.eql(.{ .double = 1 })) | |
| 1040 | return ZigTag.type.create(mt.t.arena, "f64"); | |
| 1041 | ||
| 1042 | if (kw.eql(.{ .long = 1, .double = 1 })) { | |
| 1043 | try mt.fail("unable to translate: TODO long double", .{}); | |
| 1044 | return error.ParseError; | |
| 1045 | } | |
| 1046 | ||
| 1047 | if (kw.eql(.{ .float = 1, .complex = 1 })) { | |
| 1048 | try mt.fail("unable to translate: TODO _Complex", .{}); | |
| 1049 | return error.ParseError; | |
| 1050 | } | |
| 1051 | ||
| 1052 | if (kw.eql(.{ .double = 1, .complex = 1 })) { | |
| 1053 | try mt.fail("unable to translate: TODO _Complex", .{}); | |
| 1054 | return error.ParseError; | |
| 1055 | } | |
| 1056 | ||
| 1057 | if (kw.eql(.{ .long = 1, .double = 1, .complex = 1 })) { | |
| 1058 | try mt.fail("unable to translate: TODO _Complex", .{}); | |
| 1059 | return error.ParseError; | |
| 1060 | } | |
| 1061 | ||
| 1062 | try mt.fail("unable to translate: invalid numeric type", .{}); | |
| 1063 | return error.ParseError; | |
| 1064 | } | |
| 1065 | ||
| 1066 | fn parseCAbstractDeclarator(mt: *MacroTranslator, node: ZigNode) ParseError!ZigNode { | |
| 1067 | if (mt.eat(.asterisk)) { | |
| 1068 | if (node.castTag(.type)) |some| { | |
| 1069 | if (std.mem.eql(u8, some.data, "anyopaque")) { | |
| 1070 | const ptr = try ZigTag.single_pointer.create(mt.t.arena, .{ | |
| 1071 | .is_const = false, | |
| 1072 | .is_volatile = false, | |
| 1073 | .is_allowzero = false, | |
| 1074 | .elem_type = node, | |
| 1075 | }); | |
| 1076 | return ZigTag.optional_type.create(mt.t.arena, ptr); | |
| 1077 | } | |
| 1078 | } | |
| 1079 | return ZigTag.c_pointer.create(mt.t.arena, .{ | |
| 1080 | .is_const = false, | |
| 1081 | .is_volatile = false, | |
| 1082 | .is_allowzero = false, | |
| 1083 | .elem_type = node, | |
| 1084 | }); | |
| 1085 | } | |
| 1086 | return node; | |
| 1087 | } | |
| 1088 | ||
| 1089 | fn parseCPostfixExpr(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode { | |
| 1090 | var node = try mt.parseCPostfixExprInner(scope, type_name); | |
| 1091 | // In C the preprocessor would handle concatting strings while expanding macros. | |
| 1092 | // This should do approximately the same by concatting any strings and identifiers | |
| 1093 | // after a primary or postfix expression. | |
| 1094 | while (true) { | |
| 1095 | switch (mt.peek()) { | |
| 1096 | .string_literal, | |
| 1097 | .string_literal_utf_16, | |
| 1098 | .string_literal_utf_8, | |
| 1099 | .string_literal_utf_32, | |
| 1100 | .string_literal_wide, | |
| 1101 | => {}, | |
| 1102 | .identifier, .extended_identifier => { | |
| 1103 | if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) { | |
| 1104 | mt.i += 1; | |
| 1105 | continue; | |
| 1106 | } | |
| 1107 | }, | |
| 1108 | else => break, | |
| 1109 | } | |
| 1110 | const rhs = try mt.parseCPostfixExprInner(scope, type_name); | |
| 1111 | node = try ZigTag.array_cat.create(mt.t.arena, .{ .lhs = node, .rhs = rhs }); | |
| 1112 | } | |
| 1113 | return node; | |
| 1114 | } | |
| 1115 | ||
| 1116 | fn parseCPostfixExprInner(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode { | |
| 1117 | var node = type_name orelse try mt.parseCPrimaryExpr(scope); | |
| 1118 | while (true) { | |
| 1119 | switch (mt.peek()) { | |
| 1120 | .period => { | |
| 1121 | mt.i += 1; | |
| 1122 | const field_name = mt.tokSlice(); | |
| 1123 | try mt.expect(.identifier); | |
| 1124 | ||
| 1125 | node = try ZigTag.field_access.create(mt.t.arena, .{ .lhs = node, .field_name = field_name }); | |
| 1126 | }, | |
| 1127 | .arrow => { | |
| 1128 | mt.i += 1; | |
| 1129 | const field_name = mt.tokSlice(); | |
| 1130 | try mt.expect(.identifier); | |
| 1131 | ||
| 1132 | const deref = try ZigTag.deref.create(mt.t.arena, node); | |
| 1133 | node = try ZigTag.field_access.create(mt.t.arena, .{ .lhs = deref, .field_name = field_name }); | |
| 1134 | }, | |
| 1135 | .l_bracket => { | |
| 1136 | mt.i += 1; | |
| 1137 | ||
| 1138 | const index_val = try mt.macroIntFromBool(try mt.parseCExpr(scope)); | |
| 1139 | const index = try ZigTag.as.create(mt.t.arena, .{ | |
| 1140 | .lhs = try ZigTag.type.create(mt.t.arena, "usize"), | |
| 1141 | .rhs = try ZigTag.int_cast.create(mt.t.arena, index_val), | |
| 1142 | }); | |
| 1143 | node = try ZigTag.array_access.create(mt.t.arena, .{ .lhs = node, .rhs = index }); | |
| 1144 | try mt.expect(.r_bracket); | |
| 1145 | }, | |
| 1146 | .l_paren => { | |
| 1147 | mt.i += 1; | |
| 1148 | ||
| 1149 | if (mt.eat(.r_paren)) { | |
| 1150 | node = try ZigTag.call.create(mt.t.arena, .{ .lhs = node, .args = &.{} }); | |
| 1151 | } else { | |
| 1152 | var args = std.ArrayList(ZigNode).init(mt.t.gpa); | |
| 1153 | defer args.deinit(); | |
| 1154 | ||
| 1155 | while (true) { | |
| 1156 | const arg = try mt.parseCCondExpr(scope); | |
| 1157 | try args.append(arg); | |
| 1158 | ||
| 1159 | const next_id = mt.peek(); | |
| 1160 | switch (next_id) { | |
| 1161 | .comma => { | |
| 1162 | mt.i += 1; | |
| 1163 | }, | |
| 1164 | .r_paren => { | |
| 1165 | mt.i += 1; | |
| 1166 | break; | |
| 1167 | }, | |
| 1168 | else => { | |
| 1169 | try mt.fail("unable to translate C expr: expected ',' or ')' instead got '{s}'", .{next_id.symbol()}); | |
| 1170 | return error.ParseError; | |
| 1171 | }, | |
| 1172 | } | |
| 1173 | } | |
| 1174 | node = try ZigTag.call.create(mt.t.arena, .{ .lhs = node, .args = try mt.t.arena.dupe(ZigNode, args.items) }); | |
| 1175 | } | |
| 1176 | }, | |
| 1177 | .l_brace => { | |
| 1178 | mt.i += 1; | |
| 1179 | ||
| 1180 | // Check for designated field initializers | |
| 1181 | if (mt.peek() == .period) { | |
| 1182 | var init_vals = std.ArrayList(ast.Payload.ContainerInitDot.Initializer).init(mt.t.gpa); | |
| 1183 | defer init_vals.deinit(); | |
| 1184 | ||
| 1185 | while (true) { | |
| 1186 | try mt.expect(.period); | |
| 1187 | const name = mt.tokSlice(); | |
| 1188 | try mt.expect(.identifier); | |
| 1189 | try mt.expect(.equal); | |
| 1190 | ||
| 1191 | const val = try mt.parseCCondExpr(scope); | |
| 1192 | try init_vals.append(.{ .name = name, .value = val }); | |
| 1193 | ||
| 1194 | const next_id = mt.peek(); | |
| 1195 | switch (next_id) { | |
| 1196 | .comma => { | |
| 1197 | mt.i += 1; | |
| 1198 | }, | |
| 1199 | .r_brace => { | |
| 1200 | mt.i += 1; | |
| 1201 | break; | |
| 1202 | }, | |
| 1203 | else => { | |
| 1204 | try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()}); | |
| 1205 | return error.ParseError; | |
| 1206 | }, | |
| 1207 | } | |
| 1208 | } | |
| 1209 | const tuple_node = try ZigTag.container_init_dot.create(mt.t.arena, try mt.t.arena.dupe(ast.Payload.ContainerInitDot.Initializer, init_vals.items)); | |
| 1210 | node = try ZigTag.std_mem_zeroinit.create(mt.t.arena, .{ .lhs = node, .rhs = tuple_node }); | |
| 1211 | continue; | |
| 1212 | } | |
| 1213 | ||
| 1214 | var init_vals = std.ArrayList(ZigNode).init(mt.t.gpa); | |
| 1215 | defer init_vals.deinit(); | |
| 1216 | ||
| 1217 | while (true) { | |
| 1218 | const val = try mt.parseCCondExpr(scope); | |
| 1219 | try init_vals.append(val); | |
| 1220 | ||
| 1221 | const next_id = mt.peek(); | |
| 1222 | switch (next_id) { | |
| 1223 | .comma => { | |
| 1224 | mt.i += 1; | |
| 1225 | }, | |
| 1226 | .r_brace => { | |
| 1227 | mt.i += 1; | |
| 1228 | break; | |
| 1229 | }, | |
| 1230 | else => { | |
| 1231 | try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()}); | |
| 1232 | return error.ParseError; | |
| 1233 | }, | |
| 1234 | } | |
| 1235 | } | |
| 1236 | const tuple_node = try ZigTag.tuple.create(mt.t.arena, try mt.t.arena.dupe(ZigNode, init_vals.items)); | |
| 1237 | node = try ZigTag.std_mem_zeroinit.create(mt.t.arena, .{ .lhs = node, .rhs = tuple_node }); | |
| 1238 | }, | |
| 1239 | .plus_plus, .minus_minus => { | |
| 1240 | try mt.fail("TODO postfix inc/dec expr", .{}); | |
| 1241 | return error.ParseError; | |
| 1242 | }, | |
| 1243 | else => return node, | |
| 1244 | } | |
| 1245 | } | |
| 1246 | } | |
| 1247 | ||
| 1248 | fn parseCUnaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 1249 | switch (mt.peek()) { | |
| 1250 | .bang => { | |
| 1251 | mt.i += 1; | |
| 1252 | const operand = try mt.macroIntToBool(try mt.parseCCastExpr(scope)); | |
| 1253 | return ZigTag.not.create(mt.t.arena, operand); | |
| 1254 | }, | |
| 1255 | .minus => { | |
| 1256 | mt.i += 1; | |
| 1257 | const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 1258 | return ZigTag.negate.create(mt.t.arena, operand); | |
| 1259 | }, | |
| 1260 | .plus => { | |
| 1261 | mt.i += 1; | |
| 1262 | return try mt.parseCCastExpr(scope); | |
| 1263 | }, | |
| 1264 | .tilde => { | |
| 1265 | mt.i += 1; | |
| 1266 | const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 1267 | return ZigTag.bit_not.create(mt.t.arena, operand); | |
| 1268 | }, | |
| 1269 | .asterisk => { | |
| 1270 | mt.i += 1; | |
| 1271 | const operand = try mt.parseCCastExpr(scope); | |
| 1272 | return ZigTag.deref.create(mt.t.arena, operand); | |
| 1273 | }, | |
| 1274 | .ampersand => { | |
| 1275 | mt.i += 1; | |
| 1276 | const operand = try mt.parseCCastExpr(scope); | |
| 1277 | return ZigTag.address_of.create(mt.t.arena, operand); | |
| 1278 | }, | |
| 1279 | .keyword_sizeof => { | |
| 1280 | mt.i += 1; | |
| 1281 | const operand = if (mt.eat(.l_paren)) blk: { | |
| 1282 | const inner = (try mt.parseCTypeName(scope, false)).?; | |
| 1283 | try mt.expect(.r_paren); | |
| 1284 | break :blk inner; | |
| 1285 | } else try mt.parseCUnaryExpr(scope); | |
| 1286 | ||
| 1287 | return mt.t.createHelperCallNode(.sizeof, &.{operand}); | |
| 1288 | }, | |
| 1289 | .keyword_alignof => { | |
| 1290 | mt.i += 1; | |
| 1291 | // TODO this won't work if using <stdalign.h>'s | |
| 1292 | // #define alignof _Alignof | |
| 1293 | try mt.expect(.l_paren); | |
| 1294 | const operand = (try mt.parseCTypeName(scope, false)).?; | |
| 1295 | try mt.expect(.r_paren); | |
| 1296 | ||
| 1297 | return ZigTag.alignof.create(mt.t.arena, operand); | |
| 1298 | }, | |
| 1299 | .plus_plus, .minus_minus => { | |
| 1300 | try mt.fail("TODO unary inc/dec expr", .{}); | |
| 1301 | return error.ParseError; | |
| 1302 | }, | |
| 1303 | else => {}, | |
| 1304 | } | |
| 1305 | ||
| 1306 | return try mt.parseCPostfixExpr(scope, null); | |
| 1307 | } |
lib/compiler/translate-c/PatternList.zig created+288| ... | ... | @@ -0,0 +1,288 @@ |
| 1 | const std = @import("std"); | |
| 2 | const mem = std.mem; | |
| 3 | const assert = std.debug.assert; | |
| 4 | ||
| 5 | const aro = @import("aro"); | |
| 6 | const CToken = aro.Tokenizer.Token; | |
| 7 | ||
| 8 | const helpers = @import("helpers.zig"); | |
| 9 | const Translator = @import("Translator.zig"); | |
| 10 | const Error = Translator.Error; | |
| 11 | pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; | |
| 12 | ||
| 13 | const Impl = std.meta.DeclEnum(@import("helpers")); | |
| 14 | const Template = struct { []const u8, Impl }; | |
| 15 | ||
| 16 | /// Templates must be function-like macros | |
| 17 | /// first element is macro source, second element is the name of the function | |
| 18 | /// in __helpers which implements it | |
| 19 | const templates = [_]Template{ | |
| 20 | .{ "f_SUFFIX(X) (X ## f)", .F_SUFFIX }, | |
| 21 | .{ "F_SUFFIX(X) (X ## F)", .F_SUFFIX }, | |
| 22 | ||
| 23 | .{ "u_SUFFIX(X) (X ## u)", .U_SUFFIX }, | |
| 24 | .{ "U_SUFFIX(X) (X ## U)", .U_SUFFIX }, | |
| 25 | ||
| 26 | .{ "l_SUFFIX(X) (X ## l)", .L_SUFFIX }, | |
| 27 | .{ "L_SUFFIX(X) (X ## L)", .L_SUFFIX }, | |
| 28 | ||
| 29 | .{ "ul_SUFFIX(X) (X ## ul)", .UL_SUFFIX }, | |
| 30 | .{ "uL_SUFFIX(X) (X ## uL)", .UL_SUFFIX }, | |
| 31 | .{ "Ul_SUFFIX(X) (X ## Ul)", .UL_SUFFIX }, | |
| 32 | .{ "UL_SUFFIX(X) (X ## UL)", .UL_SUFFIX }, | |
| 33 | ||
| 34 | .{ "ll_SUFFIX(X) (X ## ll)", .LL_SUFFIX }, | |
| 35 | .{ "LL_SUFFIX(X) (X ## LL)", .LL_SUFFIX }, | |
| 36 | ||
| 37 | .{ "ull_SUFFIX(X) (X ## ull)", .ULL_SUFFIX }, | |
| 38 | .{ "uLL_SUFFIX(X) (X ## uLL)", .ULL_SUFFIX }, | |
| 39 | .{ "Ull_SUFFIX(X) (X ## Ull)", .ULL_SUFFIX }, | |
| 40 | .{ "ULL_SUFFIX(X) (X ## ULL)", .ULL_SUFFIX }, | |
| 41 | ||
| 42 | .{ "f_SUFFIX(X) X ## f", .F_SUFFIX }, | |
| 43 | .{ "F_SUFFIX(X) X ## F", .F_SUFFIX }, | |
| 44 | ||
| 45 | .{ "u_SUFFIX(X) X ## u", .U_SUFFIX }, | |
| 46 | .{ "U_SUFFIX(X) X ## U", .U_SUFFIX }, | |
| 47 | ||
| 48 | .{ "l_SUFFIX(X) X ## l", .L_SUFFIX }, | |
| 49 | .{ "L_SUFFIX(X) X ## L", .L_SUFFIX }, | |
| 50 | ||
| 51 | .{ "ul_SUFFIX(X) X ## ul", .UL_SUFFIX }, | |
| 52 | .{ "uL_SUFFIX(X) X ## uL", .UL_SUFFIX }, | |
| 53 | .{ "Ul_SUFFIX(X) X ## Ul", .UL_SUFFIX }, | |
| 54 | .{ "UL_SUFFIX(X) X ## UL", .UL_SUFFIX }, | |
| 55 | ||
| 56 | .{ "ll_SUFFIX(X) X ## ll", .LL_SUFFIX }, | |
| 57 | .{ "LL_SUFFIX(X) X ## LL", .LL_SUFFIX }, | |
| 58 | ||
| 59 | .{ "ull_SUFFIX(X) X ## ull", .ULL_SUFFIX }, | |
| 60 | .{ "uLL_SUFFIX(X) X ## uLL", .ULL_SUFFIX }, | |
| 61 | .{ "Ull_SUFFIX(X) X ## Ull", .ULL_SUFFIX }, | |
| 62 | .{ "ULL_SUFFIX(X) X ## ULL", .ULL_SUFFIX }, | |
| 63 | ||
| 64 | .{ "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL }, | |
| 65 | .{ "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL }, | |
| 66 | ||
| 67 | .{ | |
| 68 | \\wl_container_of(ptr, sample, member) \ | |
| 69 | \\(__typeof__(sample))((char *)(ptr) - \ | |
| 70 | \\ offsetof(__typeof__(*sample), member)) | |
| 71 | , | |
| 72 | .WL_CONTAINER_OF, | |
| 73 | }, | |
| 74 | ||
| 75 | .{ "IGNORE_ME(X) ((void)(X))", .DISCARD }, | |
| 76 | .{ "IGNORE_ME(X) (void)(X)", .DISCARD }, | |
| 77 | .{ "IGNORE_ME(X) ((const void)(X))", .DISCARD }, | |
| 78 | .{ "IGNORE_ME(X) (const void)(X)", .DISCARD }, | |
| 79 | .{ "IGNORE_ME(X) ((volatile void)(X))", .DISCARD }, | |
| 80 | .{ "IGNORE_ME(X) (volatile void)(X)", .DISCARD }, | |
| 81 | .{ "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD }, | |
| 82 | .{ "IGNORE_ME(X) (const volatile void)(X)", .DISCARD }, | |
| 83 | .{ "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD }, | |
| 84 | .{ "IGNORE_ME(X) (volatile const void)(X)", .DISCARD }, | |
| 85 | }; | |
| 86 | ||
| 87 | const Pattern = struct { | |
| 88 | slicer: MacroSlicer, | |
| 89 | impl: Impl, | |
| 90 | ||
| 91 | fn init(pl: *Pattern, allocator: mem.Allocator, template: Template) Error!void { | |
| 92 | const source = template[0]; | |
| 93 | const impl = template[1]; | |
| 94 | var tok_list = std.ArrayList(CToken).init(allocator); | |
| 95 | defer tok_list.deinit(); | |
| 96 | ||
| 97 | pl.* = .{ | |
| 98 | .slicer = try tokenizeMacro(source, &tok_list), | |
| 99 | .impl = impl, | |
| 100 | }; | |
| 101 | } | |
| 102 | ||
| 103 | fn deinit(pl: *Pattern, allocator: mem.Allocator) void { | |
| 104 | allocator.free(pl.slicer.tokens); | |
| 105 | pl.* = undefined; | |
| 106 | } | |
| 107 | ||
| 108 | /// This function assumes that `ms` has already been validated to contain a function-like | |
| 109 | /// macro, and that the parsed template macro in `pl` also contains a function-like | |
| 110 | /// macro. Please review this logic carefully if changing that assumption. Two | |
| 111 | /// function-like macros are considered equivalent if and only if they contain the same | |
| 112 | /// list of tokens, modulo parameter names. | |
| 113 | fn matches(pat: Pattern, ms: MacroSlicer) bool { | |
| 114 | if (ms.params != pat.slicer.params) return false; | |
| 115 | if (ms.tokens.len != pat.slicer.tokens.len) return false; | |
| 116 | ||
| 117 | for (ms.tokens, pat.slicer.tokens) |macro_tok, pat_tok| { | |
| 118 | if (macro_tok.id != pat_tok.id) return false; | |
| 119 | switch (macro_tok.id) { | |
| 120 | .macro_param, .macro_param_no_expand => { | |
| 121 | // `.end` is the parameter index. | |
| 122 | if (macro_tok.end != pat_tok.end) return false; | |
| 123 | }, | |
| 124 | .identifier, .extended_identifier, .string_literal, .char_literal, .pp_num => { | |
| 125 | const macro_bytes = ms.slice(macro_tok); | |
| 126 | const pattern_bytes = pat.slicer.slice(pat_tok); | |
| 127 | ||
| 128 | if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; | |
| 129 | }, | |
| 130 | else => { | |
| 131 | // other tags correspond to keywords and operators that do not contain a "payload" | |
| 132 | // that can vary | |
| 133 | }, | |
| 134 | } | |
| 135 | } | |
| 136 | return true; | |
| 137 | } | |
| 138 | }; | |
| 139 | ||
| 140 | const PatternList = @This(); | |
| 141 | ||
| 142 | patterns: []Pattern, | |
| 143 | ||
| 144 | pub const MacroSlicer = struct { | |
| 145 | source: []const u8, | |
| 146 | tokens: []const CToken, | |
| 147 | params: u32, | |
| 148 | ||
| 149 | fn slice(pl: MacroSlicer, token: CToken) []const u8 { | |
| 150 | return pl.source[token.start..token.end]; | |
| 151 | } | |
| 152 | }; | |
| 153 | ||
| 154 | pub fn init(allocator: mem.Allocator) Error!PatternList { | |
| 155 | const patterns = try allocator.alloc(Pattern, templates.len); | |
| 156 | for (patterns, templates) |*pattern, template| { | |
| 157 | try pattern.init(allocator, template); | |
| 158 | } | |
| 159 | return .{ .patterns = patterns }; | |
| 160 | } | |
| 161 | ||
| 162 | pub fn deinit(pl: *PatternList, allocator: mem.Allocator) void { | |
| 163 | for (pl.patterns) |*pattern| pattern.deinit(allocator); | |
| 164 | allocator.free(pl.patterns); | |
| 165 | pl.* = undefined; | |
| 166 | } | |
| 167 | ||
| 168 | pub fn match(pl: PatternList, ms: MacroSlicer) Error!?Impl { | |
| 169 | for (pl.patterns) |pattern| if (pattern.matches(ms)) return pattern.impl; | |
| 170 | return null; | |
| 171 | } | |
| 172 | ||
| 173 | fn tokenizeMacro(source: []const u8, tok_list: *std.ArrayList(CToken)) Error!MacroSlicer { | |
| 174 | var param_count: u32 = 0; | |
| 175 | var param_buf: [8][]const u8 = undefined; | |
| 176 | ||
| 177 | var tokenizer: aro.Tokenizer = .{ | |
| 178 | .buf = source, | |
| 179 | .source = .unused, | |
| 180 | .langopts = .{}, | |
| 181 | }; | |
| 182 | { | |
| 183 | const name_tok = tokenizer.nextNoWS(); | |
| 184 | assert(name_tok.id == .identifier); | |
| 185 | const l_paren = tokenizer.nextNoWS(); | |
| 186 | assert(l_paren.id == .l_paren); | |
| 187 | } | |
| 188 | ||
| 189 | while (true) { | |
| 190 | const param = tokenizer.nextNoWS(); | |
| 191 | if (param.id == .r_paren) break; | |
| 192 | assert(param.id == .identifier); | |
| 193 | const slice = source[param.start..param.end]; | |
| 194 | param_buf[param_count] = slice; | |
| 195 | param_count += 1; | |
| 196 | ||
| 197 | const comma = tokenizer.nextNoWS(); | |
| 198 | if (comma.id == .r_paren) break; | |
| 199 | assert(comma.id == .comma); | |
| 200 | } | |
| 201 | ||
| 202 | outer: while (true) { | |
| 203 | const tok = tokenizer.next(); | |
| 204 | switch (tok.id) { | |
| 205 | .whitespace, .comment => continue, | |
| 206 | .identifier => { | |
| 207 | const slice = source[tok.start..tok.end]; | |
| 208 | for (param_buf[0..param_count], 0..) |param, i| { | |
| 209 | if (std.mem.eql(u8, param, slice)) { | |
| 210 | try tok_list.append(.{ | |
| 211 | .id = .macro_param, | |
| 212 | .source = .unused, | |
| 213 | .end = @intCast(i), | |
| 214 | }); | |
| 215 | continue :outer; | |
| 216 | } | |
| 217 | } | |
| 218 | }, | |
| 219 | .hash_hash => { | |
| 220 | if (tok_list.items[tok_list.items.len - 1].id == .macro_param) { | |
| 221 | tok_list.items[tok_list.items.len - 1].id = .macro_param_no_expand; | |
| 222 | } | |
| 223 | }, | |
| 224 | .nl, .eof => break, | |
| 225 | else => {}, | |
| 226 | } | |
| 227 | try tok_list.append(tok); | |
| 228 | } | |
| 229 | ||
| 230 | return .{ | |
| 231 | .source = source, | |
| 232 | .tokens = try tok_list.toOwnedSlice(), | |
| 233 | .params = param_count, | |
| 234 | }; | |
| 235 | } | |
| 236 | ||
| 237 | test "Macro matching" { | |
| 238 | const testing = std.testing; | |
| 239 | const helper = struct { | |
| 240 | fn checkMacro( | |
| 241 | allocator: mem.Allocator, | |
| 242 | pattern_list: PatternList, | |
| 243 | source: []const u8, | |
| 244 | comptime expected_match: ?Impl, | |
| 245 | ) !void { | |
| 246 | var tok_list = std.ArrayList(CToken).init(allocator); | |
| 247 | defer tok_list.deinit(); | |
| 248 | const ms = try tokenizeMacro(source, &tok_list); | |
| 249 | defer allocator.free(ms.tokens); | |
| 250 | ||
| 251 | const matched = try pattern_list.match(ms); | |
| 252 | if (expected_match) |expected| { | |
| 253 | try testing.expectEqual(expected, matched); | |
| 254 | } else { | |
| 255 | try testing.expectEqual(@as(@TypeOf(matched), null), matched); | |
| 256 | } | |
| 257 | } | |
| 258 | }; | |
| 259 | const allocator = std.testing.allocator; | |
| 260 | var pattern_list = try PatternList.init(allocator); | |
| 261 | defer pattern_list.deinit(allocator); | |
| 262 | ||
| 263 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", .F_SUFFIX); | |
| 264 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", .U_SUFFIX); | |
| 265 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", .L_SUFFIX); | |
| 266 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", .LL_SUFFIX); | |
| 267 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", .UL_SUFFIX); | |
| 268 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", .ULL_SUFFIX); | |
| 269 | try helper.checkMacro(allocator, pattern_list, | |
| 270 | \\container_of(a, b, c) \ | |
| 271 | \\(__typeof__(b))((char *)(a) - \ | |
| 272 | \\ offsetof(__typeof__(*b), c)) | |
| 273 | , .WL_CONTAINER_OF); | |
| 274 | ||
| 275 | try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null); | |
| 276 | try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL); | |
| 277 | try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL); | |
| 278 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", .DISCARD); | |
| 279 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", .DISCARD); | |
| 280 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", .DISCARD); | |
| 281 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", .DISCARD); | |
| 282 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", .DISCARD); | |
| 283 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", .DISCARD); | |
| 284 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", .DISCARD); | |
| 285 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD); | |
| 286 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", .DISCARD); | |
| 287 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD); | |
| 288 | } |
lib/compiler/translate-c/Scope.zig created+399| ... | ... | @@ -0,0 +1,399 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | const aro = @import("aro"); | |
| 4 | ||
| 5 | const ast = @import("ast.zig"); | |
| 6 | const Translator = @import("Translator.zig"); | |
| 7 | ||
| 8 | const Scope = @This(); | |
| 9 | ||
| 10 | pub const SymbolTable = std.StringArrayHashMapUnmanaged(ast.Node); | |
| 11 | pub const AliasList = std.ArrayListUnmanaged(struct { | |
| 12 | alias: []const u8, | |
| 13 | name: []const u8, | |
| 14 | }); | |
| 15 | ||
| 16 | /// Associates a container (structure or union) with its relevant member functions. | |
| 17 | pub const ContainerMemberFns = struct { | |
| 18 | container_decl_ptr: *ast.Node, | |
| 19 | member_fns: std.ArrayListUnmanaged(*ast.Payload.Func) = .empty, | |
| 20 | }; | |
| 21 | pub const ContainerMemberFnsHashMap = std.AutoArrayHashMapUnmanaged(aro.QualType, ContainerMemberFns); | |
| 22 | ||
| 23 | id: Id, | |
| 24 | parent: ?*Scope, | |
| 25 | ||
| 26 | pub const Id = enum { | |
| 27 | block, | |
| 28 | root, | |
| 29 | condition, | |
| 30 | loop, | |
| 31 | do_loop, | |
| 32 | }; | |
| 33 | ||
| 34 | /// Used for the scope of condition expressions, for example `if (cond)`. | |
| 35 | /// The block is lazily initialized because it is only needed for rare | |
| 36 | /// cases of comma operators being used. | |
| 37 | pub const Condition = struct { | |
| 38 | base: Scope, | |
| 39 | block: ?Block = null, | |
| 40 | ||
| 41 | fn getBlockScope(cond: *Condition, t: *Translator) !*Block { | |
| 42 | if (cond.block) |*b| return b; | |
| 43 | cond.block = try Block.init(t, &cond.base, true); | |
| 44 | return &cond.block.?; | |
| 45 | } | |
| 46 | ||
| 47 | pub fn deinit(cond: *Condition) void { | |
| 48 | if (cond.block) |*b| b.deinit(); | |
| 49 | } | |
| 50 | }; | |
| 51 | ||
| 52 | /// Represents an in-progress Node.Block. This struct is stack-allocated. | |
| 53 | /// When it is deinitialized, it produces an Node.Block which is allocated | |
| 54 | /// into the main arena. | |
| 55 | pub const Block = struct { | |
| 56 | base: Scope, | |
| 57 | translator: *Translator, | |
| 58 | statements: std.ArrayListUnmanaged(ast.Node), | |
| 59 | variables: AliasList, | |
| 60 | mangle_count: u32 = 0, | |
| 61 | label: ?[]const u8 = null, | |
| 62 | ||
| 63 | /// By default all variables are discarded, since we do not know in advance if they | |
| 64 | /// will be used. This maps the variable's name to the Discard payload, so that if | |
| 65 | /// the variable is subsequently referenced we can indicate that the discard should | |
| 66 | /// be skipped during the intermediate AST -> Zig AST render step. | |
| 67 | variable_discards: std.StringArrayHashMapUnmanaged(*ast.Payload.Discard), | |
| 68 | ||
| 69 | /// When the block corresponds to a function, keep track of the return type | |
| 70 | /// so that the return expression can be cast, if necessary | |
| 71 | return_type: ?aro.QualType = null, | |
| 72 | ||
| 73 | /// C static local variables are wrapped in a block-local struct. The struct | |
| 74 | /// is named `mangle(static_local_ + name)` and the Zig variable within the | |
| 75 | /// struct keeps the name of the C variable. | |
| 76 | pub const static_local_prefix = "static_local"; | |
| 77 | ||
| 78 | /// C extern local variables are wrapped in a block-local struct. The struct | |
| 79 | /// is named `mangle(extern_local + name)` and the Zig variable within the | |
| 80 | /// struct keeps the name of the C variable. | |
| 81 | pub const extern_local_prefix = "extern_local"; | |
| 82 | ||
| 83 | pub fn init(t: *Translator, parent: *Scope, labeled: bool) !Block { | |
| 84 | var blk: Block = .{ | |
| 85 | .base = .{ | |
| 86 | .id = .block, | |
| 87 | .parent = parent, | |
| 88 | }, | |
| 89 | .translator = t, | |
| 90 | .statements = .empty, | |
| 91 | .variables = .empty, | |
| 92 | .variable_discards = .empty, | |
| 93 | }; | |
| 94 | if (labeled) { | |
| 95 | blk.label = try blk.makeMangledName("blk"); | |
| 96 | } | |
| 97 | return blk; | |
| 98 | } | |
| 99 | ||
| 100 | pub fn deinit(block: *Block) void { | |
| 101 | block.statements.deinit(block.translator.gpa); | |
| 102 | block.variables.deinit(block.translator.gpa); | |
| 103 | block.variable_discards.deinit(block.translator.gpa); | |
| 104 | block.* = undefined; | |
| 105 | } | |
| 106 | ||
| 107 | pub fn complete(block: *Block) !ast.Node { | |
| 108 | const arena = block.translator.arena; | |
| 109 | if (block.base.parent.?.id == .do_loop) { | |
| 110 | // We reserve 1 extra statement if the parent is a do_loop. This is in case of | |
| 111 | // do while, we want to put `if (cond) break;` at the end. | |
| 112 | const alloc_len = block.statements.items.len + @intFromBool(block.base.parent.?.id == .do_loop); | |
| 113 | var stmts = try arena.alloc(ast.Node, alloc_len); | |
| 114 | stmts.len = block.statements.items.len; | |
| 115 | @memcpy(stmts[0..block.statements.items.len], block.statements.items); | |
| 116 | return ast.Node.Tag.block.create(arena, .{ | |
| 117 | .label = block.label, | |
| 118 | .stmts = stmts, | |
| 119 | }); | |
| 120 | } | |
| 121 | if (block.statements.items.len == 0) return ast.Node.Tag.empty_block.init(); | |
| 122 | return ast.Node.Tag.block.create(arena, .{ | |
| 123 | .label = block.label, | |
| 124 | .stmts = try arena.dupe(ast.Node, block.statements.items), | |
| 125 | }); | |
| 126 | } | |
| 127 | ||
| 128 | /// Given the desired name, return a name that does not shadow anything from outer scopes. | |
| 129 | /// Inserts the returned name into the scope. | |
| 130 | /// The name will not be visible to callers of getAlias. | |
| 131 | pub fn reserveMangledName(block: *Block, name: []const u8) ![]const u8 { | |
| 132 | return block.createMangledName(name, true, null); | |
| 133 | } | |
| 134 | ||
| 135 | /// Same as reserveMangledName, but enables the alias immediately. | |
| 136 | pub fn makeMangledName(block: *Block, name: []const u8) ![]const u8 { | |
| 137 | return block.createMangledName(name, false, null); | |
| 138 | } | |
| 139 | ||
| 140 | pub fn createMangledName(block: *Block, name: []const u8, reservation: bool, prefix_opt: ?[]const u8) ![]const u8 { | |
| 141 | const arena = block.translator.arena; | |
| 142 | const name_copy = try arena.dupe(u8, name); | |
| 143 | const alias_base = if (prefix_opt) |prefix| | |
| 144 | try std.fmt.allocPrint(arena, "{s}_{s}", .{ prefix, name }) | |
| 145 | else | |
| 146 | name; | |
| 147 | var proposed_name = alias_base; | |
| 148 | while (block.contains(proposed_name)) { | |
| 149 | block.mangle_count += 1; | |
| 150 | proposed_name = try std.fmt.allocPrint(arena, "{s}_{d}", .{ alias_base, block.mangle_count }); | |
| 151 | } | |
| 152 | const new_mangle = try block.variables.addOne(block.translator.gpa); | |
| 153 | if (reservation) { | |
| 154 | new_mangle.* = .{ .name = name_copy, .alias = name_copy }; | |
| 155 | } else { | |
| 156 | new_mangle.* = .{ .name = name_copy, .alias = proposed_name }; | |
| 157 | } | |
| 158 | return proposed_name; | |
| 159 | } | |
| 160 | ||
| 161 | fn getAlias(block: *Block, name: []const u8) ?[]const u8 { | |
| 162 | for (block.variables.items) |p| { | |
| 163 | if (std.mem.eql(u8, p.name, name)) | |
| 164 | return p.alias; | |
| 165 | } | |
| 166 | return block.base.parent.?.getAlias(name); | |
| 167 | } | |
| 168 | ||
| 169 | fn localContains(block: *Block, name: []const u8) bool { | |
| 170 | for (block.variables.items) |p| { | |
| 171 | if (std.mem.eql(u8, p.alias, name)) | |
| 172 | return true; | |
| 173 | } | |
| 174 | return false; | |
| 175 | } | |
| 176 | ||
| 177 | fn contains(block: *Block, name: []const u8) bool { | |
| 178 | if (block.localContains(name)) | |
| 179 | return true; | |
| 180 | return block.base.parent.?.contains(name); | |
| 181 | } | |
| 182 | ||
| 183 | pub fn discardVariable(block: *Block, name: []const u8) Translator.Error!void { | |
| 184 | const gpa = block.translator.gpa; | |
| 185 | const arena = block.translator.arena; | |
| 186 | const name_node = try ast.Node.Tag.identifier.create(arena, name); | |
| 187 | const discard = try ast.Node.Tag.discard.create(arena, .{ .should_skip = false, .value = name_node }); | |
| 188 | try block.statements.append(gpa, discard); | |
| 189 | try block.variable_discards.putNoClobber(gpa, name, discard.castTag(.discard).?); | |
| 190 | } | |
| 191 | }; | |
| 192 | ||
| 193 | pub const Root = struct { | |
| 194 | base: Scope, | |
| 195 | translator: *Translator, | |
| 196 | sym_table: SymbolTable, | |
| 197 | blank_macros: std.StringArrayHashMapUnmanaged(void), | |
| 198 | nodes: std.ArrayListUnmanaged(ast.Node), | |
| 199 | container_member_fns_map: ContainerMemberFnsHashMap, | |
| 200 | ||
| 201 | pub fn init(t: *Translator) Root { | |
| 202 | return .{ | |
| 203 | .base = .{ | |
| 204 | .id = .root, | |
| 205 | .parent = null, | |
| 206 | }, | |
| 207 | .translator = t, | |
| 208 | .sym_table = .empty, | |
| 209 | .blank_macros = .empty, | |
| 210 | .nodes = .empty, | |
| 211 | .container_member_fns_map = .empty, | |
| 212 | }; | |
| 213 | } | |
| 214 | ||
| 215 | pub fn deinit(root: *Root) void { | |
| 216 | root.sym_table.deinit(root.translator.gpa); | |
| 217 | root.blank_macros.deinit(root.translator.gpa); | |
| 218 | root.nodes.deinit(root.translator.gpa); | |
| 219 | for (root.container_member_fns_map.values()) |*members| { | |
| 220 | members.member_fns.deinit(root.translator.gpa); | |
| 221 | } | |
| 222 | root.container_member_fns_map.deinit(root.translator.gpa); | |
| 223 | } | |
| 224 | ||
| 225 | /// Check if the global scope contains this name, without looking into the "future", e.g. | |
| 226 | /// ignore the preprocessed decl and macro names. | |
| 227 | pub fn containsNow(root: *Root, name: []const u8) bool { | |
| 228 | return root.sym_table.contains(name); | |
| 229 | } | |
| 230 | ||
| 231 | /// Check if the global scope contains the name, includes all decls that haven't been translated yet. | |
| 232 | pub fn contains(root: *Root, name: []const u8) bool { | |
| 233 | return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name); | |
| 234 | } | |
| 235 | ||
| 236 | pub fn addMemberFunction(root: *Root, func_ty: aro.Type.Func, func: *ast.Payload.Func) !void { | |
| 237 | std.debug.assert(func.data.name != null); | |
| 238 | if (func_ty.params.len == 0) return; | |
| 239 | ||
| 240 | const param1_base = func_ty.params[0].qt.base(root.translator.comp); | |
| 241 | const container_qt = if (param1_base.type == .pointer) | |
| 242 | param1_base.type.pointer.child.base(root.translator.comp).qt | |
| 243 | else | |
| 244 | param1_base.qt; | |
| 245 | ||
| 246 | if (root.container_member_fns_map.getPtr(container_qt)) |members| { | |
| 247 | try members.member_fns.append(root.translator.gpa, func); | |
| 248 | } | |
| 249 | } | |
| 250 | ||
| 251 | pub fn processContainerMemberFns(root: *Root) !void { | |
| 252 | const gpa = root.translator.gpa; | |
| 253 | const arena = root.translator.arena; | |
| 254 | ||
| 255 | var member_names: std.StringArrayHashMapUnmanaged(u32) = .empty; | |
| 256 | defer member_names.deinit(gpa); | |
| 257 | for (root.container_member_fns_map.values()) |members| { | |
| 258 | member_names.clearRetainingCapacity(); | |
| 259 | const decls_ptr = switch (members.container_decl_ptr.tag()) { | |
| 260 | .@"struct", .@"union" => blk_record: { | |
| 261 | const payload: *ast.Payload.Container = @alignCast(@fieldParentPtr("base", members.container_decl_ptr.ptr_otherwise)); | |
| 262 | // Avoid duplication with field names | |
| 263 | for (payload.data.fields) |field| { | |
| 264 | try member_names.put(gpa, field.name, 0); | |
| 265 | } | |
| 266 | break :blk_record &payload.data.decls; | |
| 267 | }, | |
| 268 | .opaque_literal => blk_opaque: { | |
| 269 | const container_decl = try ast.Node.Tag.@"opaque".create(arena, .{ | |
| 270 | .layout = .none, | |
| 271 | .fields = &.{}, | |
| 272 | .decls = &.{}, | |
| 273 | }); | |
| 274 | members.container_decl_ptr.* = container_decl; | |
| 275 | break :blk_opaque &container_decl.castTag(.@"opaque").?.data.decls; | |
| 276 | }, | |
| 277 | else => return, | |
| 278 | }; | |
| 279 | ||
| 280 | const old_decls = decls_ptr.*; | |
| 281 | const new_decls = try arena.alloc(ast.Node, old_decls.len + members.member_fns.items.len); | |
| 282 | @memcpy(new_decls[0..old_decls.len], old_decls); | |
| 283 | // Assume the allocator of payload.data.decls is arena, | |
| 284 | // so don't add arena.free(old_variables). | |
| 285 | const func_ref_vars = new_decls[old_decls.len..]; | |
| 286 | var count: u32 = 0; | |
| 287 | for (members.member_fns.items) |func| { | |
| 288 | const func_name = func.data.name.?; | |
| 289 | ||
| 290 | const last_index = std.mem.lastIndexOf(u8, func_name, "_"); | |
| 291 | const last_name = if (last_index) |index| func_name[index + 1 ..] else continue; | |
| 292 | var same_count: u32 = 0; | |
| 293 | const gop = try member_names.getOrPutValue(gpa, last_name, same_count); | |
| 294 | if (gop.found_existing) { | |
| 295 | gop.value_ptr.* += 1; | |
| 296 | same_count = gop.value_ptr.*; | |
| 297 | } | |
| 298 | const var_name = if (same_count == 0) | |
| 299 | last_name | |
| 300 | else | |
| 301 | try std.fmt.allocPrint(arena, "{s}{d}", .{ last_name, same_count }); | |
| 302 | ||
| 303 | func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{ | |
| 304 | .name = var_name, | |
| 305 | .init = try ast.Node.Tag.identifier.create(arena, func_name), | |
| 306 | }); | |
| 307 | count += 1; | |
| 308 | } | |
| 309 | decls_ptr.* = new_decls[0 .. old_decls.len + count]; | |
| 310 | } | |
| 311 | } | |
| 312 | }; | |
| 313 | ||
| 314 | pub fn findBlockScope(inner: *Scope, t: *Translator) !*Block { | |
| 315 | var scope = inner; | |
| 316 | while (true) { | |
| 317 | switch (scope.id) { | |
| 318 | .root => unreachable, | |
| 319 | .block => return @fieldParentPtr("base", scope), | |
| 320 | .condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(t), | |
| 321 | else => scope = scope.parent.?, | |
| 322 | } | |
| 323 | } | |
| 324 | } | |
| 325 | ||
| 326 | pub fn findBlockReturnType(inner: *Scope) aro.QualType { | |
| 327 | var scope = inner; | |
| 328 | while (true) { | |
| 329 | switch (scope.id) { | |
| 330 | .root => unreachable, | |
| 331 | .block => { | |
| 332 | const block: *Block = @fieldParentPtr("base", scope); | |
| 333 | if (block.return_type) |qt| return qt; | |
| 334 | scope = scope.parent.?; | |
| 335 | }, | |
| 336 | else => scope = scope.parent.?, | |
| 337 | } | |
| 338 | } | |
| 339 | } | |
| 340 | ||
| 341 | pub fn getAlias(scope: *Scope, name: []const u8) ?[]const u8 { | |
| 342 | return switch (scope.id) { | |
| 343 | .root => null, | |
| 344 | .block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name), | |
| 345 | .loop, .do_loop, .condition => scope.parent.?.getAlias(name), | |
| 346 | }; | |
| 347 | } | |
| 348 | ||
| 349 | fn contains(scope: *Scope, name: []const u8) bool { | |
| 350 | return switch (scope.id) { | |
| 351 | .root => @as(*Root, @fieldParentPtr("base", scope)).contains(name), | |
| 352 | .block => @as(*Block, @fieldParentPtr("base", scope)).contains(name), | |
| 353 | .loop, .do_loop, .condition => scope.parent.?.contains(name), | |
| 354 | }; | |
| 355 | } | |
| 356 | ||
| 357 | /// Appends a node to the first block scope if inside a function, or to the root tree if not. | |
| 358 | pub fn appendNode(inner: *Scope, node: ast.Node) !void { | |
| 359 | var scope = inner; | |
| 360 | while (true) { | |
| 361 | switch (scope.id) { | |
| 362 | .root => { | |
| 363 | const root: *Root = @fieldParentPtr("base", scope); | |
| 364 | return root.nodes.append(root.translator.gpa, node); | |
| 365 | }, | |
| 366 | .block => { | |
| 367 | const block: *Block = @fieldParentPtr("base", scope); | |
| 368 | return block.statements.append(block.translator.gpa, node); | |
| 369 | }, | |
| 370 | else => scope = scope.parent.?, | |
| 371 | } | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void { | |
| 376 | if (true) { | |
| 377 | // TODO: due to 'local variable is never mutated' errors, we can | |
| 378 | // only skip discards if a variable is used as an lvalue, which | |
| 379 | // we don't currently have detection for in translate-c. | |
| 380 | // Once #17584 is completed, perhaps we can do away with this | |
| 381 | // logic entirely, and instead rely on render to fixup code. | |
| 382 | return; | |
| 383 | } | |
| 384 | var scope = inner; | |
| 385 | while (true) { | |
| 386 | switch (scope.id) { | |
| 387 | .root => return, | |
| 388 | .block => { | |
| 389 | const block: *Block = @fieldParentPtr("base", scope); | |
| 390 | if (block.variable_discards.get(name)) |discard| { | |
| 391 | discard.data.should_skip = true; | |
| 392 | return; | |
| 393 | } | |
| 394 | }, | |
| 395 | else => {}, | |
| 396 | } | |
| 397 | scope = scope.parent.?; | |
| 398 | } | |
| 399 | } |
lib/compiler/translate-c/Translator.zig created+4183| ... | ... | @@ -0,0 +1,4183 @@ |
| 1 | const std = @import("std"); | |
| 2 | const mem = std.mem; | |
| 3 | const assert = std.debug.assert; | |
| 4 | const CallingConvention = std.builtin.CallingConvention; | |
| 5 | ||
| 6 | const aro = @import("aro"); | |
| 7 | const CToken = aro.Tokenizer.Token; | |
| 8 | const Tree = aro.Tree; | |
| 9 | const Node = Tree.Node; | |
| 10 | const TokenIndex = Tree.TokenIndex; | |
| 11 | const QualType = aro.QualType; | |
| 12 | ||
| 13 | const ast = @import("ast.zig"); | |
| 14 | const ZigNode = ast.Node; | |
| 15 | const ZigTag = ZigNode.Tag; | |
| 16 | const builtins = @import("builtins.zig"); | |
| 17 | const helpers = @import("helpers.zig"); | |
| 18 | const MacroTranslator = @import("MacroTranslator.zig"); | |
| 19 | const PatternList = @import("PatternList.zig"); | |
| 20 | const Scope = @import("Scope.zig"); | |
| 21 | ||
| 22 | pub const Error = std.mem.Allocator.Error; | |
| 23 | pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; | |
| 24 | pub const TypeError = Error || error{UnsupportedType}; | |
| 25 | pub const TransError = TypeError || error{UnsupportedTranslation}; | |
| 26 | ||
| 27 | const Translator = @This(); | |
| 28 | ||
| 29 | /// The C AST to be translated. | |
| 30 | tree: *const Tree, | |
| 31 | /// The compilation corresponding to the AST. | |
| 32 | comp: *aro.Compilation, | |
| 33 | /// The Preprocessor that produced the source for `tree`. | |
| 34 | pp: *const aro.Preprocessor, | |
| 35 | ||
| 36 | gpa: mem.Allocator, | |
| 37 | arena: mem.Allocator, | |
| 38 | ||
| 39 | alias_list: Scope.AliasList, | |
| 40 | global_scope: *Scope.Root, | |
| 41 | /// Running number used for creating new unique identifiers. | |
| 42 | mangle_count: u32 = 0, | |
| 43 | ||
| 44 | /// Table of declarations for enum, struct, union and typedef types. | |
| 45 | type_decls: std.AutoArrayHashMapUnmanaged(Node.Index, []const u8) = .empty, | |
| 46 | /// Table of record decls that have been demoted to opaques. | |
| 47 | opaque_demotes: std.AutoHashMapUnmanaged(QualType, void) = .empty, | |
| 48 | /// Table of unnamed enums and records that are child types of typedefs. | |
| 49 | unnamed_typedefs: std.AutoHashMapUnmanaged(QualType, []const u8) = .empty, | |
| 50 | /// Table of anonymous record to generated field names. | |
| 51 | anonymous_record_field_names: std.AutoHashMapUnmanaged(struct { | |
| 52 | parent: QualType, | |
| 53 | field: QualType, | |
| 54 | }, []const u8) = .empty, | |
| 55 | ||
| 56 | /// This one is different than the root scope's name table. This contains | |
| 57 | /// a list of names that we found by visiting all the top level decls without | |
| 58 | /// translating them. The other maps are updated as we translate; this one is updated | |
| 59 | /// up front in a pre-processing step. | |
| 60 | global_names: std.StringArrayHashMapUnmanaged(void) = .empty, | |
| 61 | ||
| 62 | /// This is similar to `global_names`, but contains names which we would | |
| 63 | /// *like* to use, but do not strictly *have* to if they are unavailable. | |
| 64 | /// These are relevant to types, which ideally we would name like | |
| 65 | /// 'struct_foo' with an alias 'foo', but if either of those names is taken, | |
| 66 | /// may be mangled. | |
| 67 | /// This is distinct from `global_names` so we can detect at a type | |
| 68 | /// declaration whether or not the name is available. | |
| 69 | weak_global_names: std.StringArrayHashMapUnmanaged(void) = .empty, | |
| 70 | ||
| 71 | /// Set of identifiers known to refer to typedef declarations. | |
| 72 | /// Used when parsing macros. | |
| 73 | typedefs: std.StringArrayHashMapUnmanaged(void) = .empty, | |
| 74 | ||
| 75 | /// The lhs lval of a compound assignment expression. | |
| 76 | compound_assign_dummy: ?ZigNode = null, | |
| 77 | ||
| 78 | pub fn getMangle(t: *Translator) u32 { | |
| 79 | t.mangle_count += 1; | |
| 80 | return t.mangle_count; | |
| 81 | } | |
| 82 | ||
| 83 | /// Convert an `aro.Source.Location` to a 'file:line:column' string. | |
| 84 | pub fn locStr(t: *Translator, loc: aro.Source.Location) ![]const u8 { | |
| 85 | const source = t.comp.getSource(loc.id); | |
| 86 | const line_col = source.lineCol(loc); | |
| 87 | const filename = source.path; | |
| 88 | ||
| 89 | const line = source.physicalLine(loc); | |
| 90 | const col = line_col.col; | |
| 91 | ||
| 92 | return std.fmt.allocPrint(t.arena, "{s}:{d}:{d}", .{ filename, line, col }); | |
| 93 | } | |
| 94 | ||
| 95 | fn maybeSuppressResult(t: *Translator, used: ResultUsed, result: ZigNode) TransError!ZigNode { | |
| 96 | if (used == .used) return result; | |
| 97 | return ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = result }); | |
| 98 | } | |
| 99 | ||
| 100 | pub fn addTopLevelDecl(t: *Translator, name: []const u8, decl_node: ZigNode) !void { | |
| 101 | const gop = try t.global_scope.sym_table.getOrPut(t.gpa, name); | |
| 102 | if (!gop.found_existing) { | |
| 103 | gop.value_ptr.* = decl_node; | |
| 104 | try t.global_scope.nodes.append(t.gpa, decl_node); | |
| 105 | } | |
| 106 | } | |
| 107 | ||
| 108 | fn fail( | |
| 109 | t: *Translator, | |
| 110 | err: anytype, | |
| 111 | source_loc: TokenIndex, | |
| 112 | comptime format: []const u8, | |
| 113 | args: anytype, | |
| 114 | ) (@TypeOf(err) || error{OutOfMemory}) { | |
| 115 | try t.warn(&t.global_scope.base, source_loc, format, args); | |
| 116 | return err; | |
| 117 | } | |
| 118 | ||
| 119 | pub fn failDecl( | |
| 120 | t: *Translator, | |
| 121 | scope: *Scope, | |
| 122 | tok_idx: TokenIndex, | |
| 123 | name: []const u8, | |
| 124 | comptime format: []const u8, | |
| 125 | args: anytype, | |
| 126 | ) Error!void { | |
| 127 | const loc = t.tree.tokens.items(.loc)[tok_idx]; | |
| 128 | return t.failDeclExtra(scope, loc, name, format, args); | |
| 129 | } | |
| 130 | ||
| 131 | pub fn failDeclExtra( | |
| 132 | t: *Translator, | |
| 133 | scope: *Scope, | |
| 134 | loc: aro.Source.Location, | |
| 135 | name: []const u8, | |
| 136 | comptime format: []const u8, | |
| 137 | args: anytype, | |
| 138 | ) Error!void { | |
| 139 | // location | |
| 140 | // pub const name = @compileError(msg); | |
| 141 | const fail_msg = try std.fmt.allocPrint(t.arena, format, args); | |
| 142 | const fail_decl = try ZigTag.fail_decl.create(t.arena, .{ .actual = name, .mangled = fail_msg }); | |
| 143 | ||
| 144 | const str = try t.locStr(loc); | |
| 145 | const location_comment = try std.fmt.allocPrint(t.arena, "// {s}", .{str}); | |
| 146 | const loc_node = try ZigTag.warning.create(t.arena, location_comment); | |
| 147 | ||
| 148 | if (scope.id == .root) { | |
| 149 | try t.addTopLevelDecl(name, fail_decl); | |
| 150 | try scope.appendNode(loc_node); | |
| 151 | } else { | |
| 152 | try scope.appendNode(fail_decl); | |
| 153 | try scope.appendNode(loc_node); | |
| 154 | ||
| 155 | const bs = try scope.findBlockScope(t); | |
| 156 | try bs.discardVariable(name); | |
| 157 | } | |
| 158 | } | |
| 159 | ||
| 160 | fn warn(t: *Translator, scope: *Scope, tok_idx: TokenIndex, comptime format: []const u8, args: anytype) !void { | |
| 161 | const loc = t.tree.tokens.items(.loc)[tok_idx]; | |
| 162 | const str = try t.locStr(loc); | |
| 163 | const value = try std.fmt.allocPrint(t.arena, "// {s}: warning: " ++ format, .{str} ++ args); | |
| 164 | try scope.appendNode(try ZigTag.warning.create(t.arena, value)); | |
| 165 | } | |
| 166 | ||
| 167 | pub const Options = struct { | |
| 168 | gpa: mem.Allocator, | |
| 169 | comp: *aro.Compilation, | |
| 170 | pp: *const aro.Preprocessor, | |
| 171 | tree: *const aro.Tree, | |
| 172 | module_libs: bool, | |
| 173 | }; | |
| 174 | ||
| 175 | pub fn translate(options: Options) ![]u8 { | |
| 176 | const gpa = options.gpa; | |
| 177 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); | |
| 178 | defer arena_allocator.deinit(); | |
| 179 | const arena = arena_allocator.allocator(); | |
| 180 | ||
| 181 | var translator: Translator = .{ | |
| 182 | .gpa = gpa, | |
| 183 | .arena = arena, | |
| 184 | .alias_list = .empty, | |
| 185 | .global_scope = try arena.create(Scope.Root), | |
| 186 | .comp = options.comp, | |
| 187 | .pp = options.pp, | |
| 188 | .tree = options.tree, | |
| 189 | }; | |
| 190 | translator.global_scope.* = Scope.Root.init(&translator); | |
| 191 | defer { | |
| 192 | translator.type_decls.deinit(gpa); | |
| 193 | translator.alias_list.deinit(gpa); | |
| 194 | translator.global_names.deinit(gpa); | |
| 195 | translator.weak_global_names.deinit(gpa); | |
| 196 | translator.opaque_demotes.deinit(gpa); | |
| 197 | translator.unnamed_typedefs.deinit(gpa); | |
| 198 | translator.anonymous_record_field_names.deinit(gpa); | |
| 199 | translator.typedefs.deinit(gpa); | |
| 200 | translator.global_scope.deinit(); | |
| 201 | } | |
| 202 | ||
| 203 | try translator.prepopulateGlobalNameTable(); | |
| 204 | try translator.transTopLevelDecls(); | |
| 205 | ||
| 206 | // Insert empty line before macros. | |
| 207 | try translator.global_scope.nodes.append(gpa, try ZigTag.warning.create(arena, "\n")); | |
| 208 | ||
| 209 | try translator.transMacros(); | |
| 210 | ||
| 211 | for (translator.alias_list.items) |alias| { | |
| 212 | if (!translator.global_scope.sym_table.contains(alias.alias)) { | |
| 213 | const node = try ZigTag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name }); | |
| 214 | try translator.addTopLevelDecl(alias.alias, node); | |
| 215 | } | |
| 216 | } | |
| 217 | ||
| 218 | try translator.global_scope.processContainerMemberFns(); | |
| 219 | ||
| 220 | var buf: std.ArrayList(u8) = .init(gpa); | |
| 221 | defer buf.deinit(); | |
| 222 | ||
| 223 | if (options.module_libs) { | |
| 224 | try buf.appendSlice( | |
| 225 | \\pub const __builtin = @import("c_builtins"); | |
| 226 | \\pub const __helpers = @import("helpers"); | |
| 227 | \\ | |
| 228 | \\ | |
| 229 | ); | |
| 230 | } else { | |
| 231 | try buf.appendSlice( | |
| 232 | \\pub const __builtin = @import("c_builtins.zig"); | |
| 233 | \\pub const __helpers = @import("helpers.zig"); | |
| 234 | \\ | |
| 235 | \\ | |
| 236 | ); | |
| 237 | } | |
| 238 | ||
| 239 | var zig_ast = try ast.render(gpa, translator.global_scope.nodes.items); | |
| 240 | defer { | |
| 241 | gpa.free(zig_ast.source); | |
| 242 | zig_ast.deinit(gpa); | |
| 243 | } | |
| 244 | try zig_ast.renderToArrayList(&buf, .{}); | |
| 245 | return buf.toOwnedSlice(); | |
| 246 | } | |
| 247 | ||
| 248 | fn prepopulateGlobalNameTable(t: *Translator) !void { | |
| 249 | for (t.tree.root_decls.items) |decl| { | |
| 250 | switch (decl.get(t.tree)) { | |
| 251 | .typedef => |typedef_decl| { | |
| 252 | const decl_name = t.tree.tokSlice(typedef_decl.name_tok); | |
| 253 | try t.global_names.put(t.gpa, decl_name, {}); | |
| 254 | ||
| 255 | // Check for typedefs with unnamed enum/record child types. | |
| 256 | const base = typedef_decl.qt.base(t.comp); | |
| 257 | switch (base.type) { | |
| 258 | .@"enum" => |enum_ty| { | |
| 259 | if (enum_ty.name.lookup(t.comp)[0] != '(') continue; | |
| 260 | }, | |
| 261 | .@"struct", .@"union" => |record_ty| { | |
| 262 | if (record_ty.name.lookup(t.comp)[0] != '(') continue; | |
| 263 | }, | |
| 264 | else => continue, | |
| 265 | } | |
| 266 | ||
| 267 | const gop = try t.unnamed_typedefs.getOrPut(t.gpa, base.qt); | |
| 268 | if (gop.found_existing) { | |
| 269 | // One typedef can declare multiple names. | |
| 270 | // TODO Don't put this one in `decl_table` so it's processed later. | |
| 271 | continue; | |
| 272 | } | |
| 273 | gop.value_ptr.* = decl_name; | |
| 274 | }, | |
| 275 | ||
| 276 | .struct_decl, | |
| 277 | .union_decl, | |
| 278 | .struct_forward_decl, | |
| 279 | .union_forward_decl, | |
| 280 | .enum_decl, | |
| 281 | .enum_forward_decl, | |
| 282 | => { | |
| 283 | const decl_qt = decl.qt(t.tree); | |
| 284 | const prefix, const name = switch (decl_qt.base(t.comp).type) { | |
| 285 | .@"struct" => |struct_ty| .{ "struct", struct_ty.name.lookup(t.comp) }, | |
| 286 | .@"union" => |union_ty| .{ "union", union_ty.name.lookup(t.comp) }, | |
| 287 | .@"enum" => |enum_ty| .{ "enum", enum_ty.name.lookup(t.comp) }, | |
| 288 | else => unreachable, | |
| 289 | }; | |
| 290 | const prefixed_name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ prefix, name }); | |
| 291 | // `name` and `prefixed_name` are the preferred names for this type. | |
| 292 | // However, we can name it anything else if necessary, so these are "weak names". | |
| 293 | try t.weak_global_names.ensureUnusedCapacity(t.gpa, 2); | |
| 294 | t.weak_global_names.putAssumeCapacity(name, {}); | |
| 295 | t.weak_global_names.putAssumeCapacity(prefixed_name, {}); | |
| 296 | }, | |
| 297 | ||
| 298 | .function, .variable => { | |
| 299 | const decl_name = t.tree.tokSlice(decl.tok(t.tree)); | |
| 300 | try t.global_names.put(t.gpa, decl_name, {}); | |
| 301 | }, | |
| 302 | .static_assert => {}, | |
| 303 | .empty_decl => {}, | |
| 304 | .global_asm => {}, | |
| 305 | else => unreachable, | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { | |
| 310 | if (macro.is_builtin) continue; | |
| 311 | if (!t.isSelfDefinedMacro(name, macro)) { | |
| 312 | try t.global_names.put(t.gpa, name, {}); | |
| 313 | } | |
| 314 | } | |
| 315 | } | |
| 316 | ||
| 317 | /// Determines whether macro is of the form: `#define FOO FOO` (Possibly with trailing tokens) | |
| 318 | /// Macros of this form will not be translated. | |
| 319 | fn isSelfDefinedMacro(t: *Translator, name: []const u8, macro: aro.Preprocessor.Macro) bool { | |
| 320 | if (macro.is_func) return false; | |
| 321 | ||
| 322 | if (macro.tokens.len < 1) return false; | |
| 323 | const first_tok = macro.tokens[0]; | |
| 324 | ||
| 325 | const source = t.comp.getSource(macro.loc.id); | |
| 326 | const slice = source.buf[first_tok.start..first_tok.end]; | |
| 327 | ||
| 328 | return std.mem.eql(u8, name, slice); | |
| 329 | } | |
| 330 | ||
| 331 | // ======================= | |
| 332 | // Declaration translation | |
| 333 | // ======================= | |
| 334 | ||
| 335 | fn transTopLevelDecls(t: *Translator) !void { | |
| 336 | for (t.tree.root_decls.items) |decl| { | |
| 337 | try t.transDecl(&t.global_scope.base, decl); | |
| 338 | } | |
| 339 | } | |
| 340 | ||
| 341 | fn transDecl(t: *Translator, scope: *Scope, decl: Node.Index) !void { | |
| 342 | switch (decl.get(t.tree)) { | |
| 343 | .typedef => |typedef_decl| { | |
| 344 | // Implicit typedefs are translated only if referenced. | |
| 345 | if (typedef_decl.implicit) return; | |
| 346 | try t.transTypeDef(scope, decl); | |
| 347 | }, | |
| 348 | ||
| 349 | .struct_decl, .union_decl => |record_decl| { | |
| 350 | try t.transRecordDecl(scope, record_decl.container_qt); | |
| 351 | }, | |
| 352 | ||
| 353 | .enum_decl => |enum_decl| { | |
| 354 | try t.transEnumDecl(scope, enum_decl.container_qt); | |
| 355 | }, | |
| 356 | ||
| 357 | .enum_field, | |
| 358 | .record_field, | |
| 359 | .struct_forward_decl, | |
| 360 | .union_forward_decl, | |
| 361 | .enum_forward_decl, | |
| 362 | => return, | |
| 363 | ||
| 364 | .function => |function| { | |
| 365 | if (function.definition) |definition| { | |
| 366 | return t.transFnDecl(scope, definition.get(t.tree).function); | |
| 367 | } | |
| 368 | try t.transFnDecl(scope, function); | |
| 369 | }, | |
| 370 | ||
| 371 | .variable => |variable| { | |
| 372 | if (variable.definition != null) return; | |
| 373 | try t.transVarDecl(scope, variable); | |
| 374 | }, | |
| 375 | .static_assert => |static_assert| { | |
| 376 | try t.transStaticAssert(&t.global_scope.base, static_assert); | |
| 377 | }, | |
| 378 | .global_asm => |global_asm| { | |
| 379 | try t.transGlobalAsm(&t.global_scope.base, global_asm); | |
| 380 | }, | |
| 381 | .empty_decl => {}, | |
| 382 | else => unreachable, | |
| 383 | } | |
| 384 | } | |
| 385 | ||
| 386 | pub const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{ | |
| 387 | .{ "uint8_t", "u8" }, | |
| 388 | .{ "int8_t", "i8" }, | |
| 389 | .{ "uint16_t", "u16" }, | |
| 390 | .{ "int16_t", "i16" }, | |
| 391 | .{ "uint32_t", "u32" }, | |
| 392 | .{ "int32_t", "i32" }, | |
| 393 | .{ "uint64_t", "u64" }, | |
| 394 | .{ "int64_t", "i64" }, | |
| 395 | .{ "intptr_t", "isize" }, | |
| 396 | .{ "uintptr_t", "usize" }, | |
| 397 | .{ "ssize_t", "isize" }, | |
| 398 | .{ "size_t", "usize" }, | |
| 399 | }); | |
| 400 | ||
| 401 | fn transTypeDef(t: *Translator, scope: *Scope, typedef_node: Node.Index) Error!void { | |
| 402 | const typedef_decl = typedef_node.get(t.tree).typedef; | |
| 403 | if (t.type_decls.get(typedef_node)) |_| | |
| 404 | return; // Avoid processing this decl twice | |
| 405 | ||
| 406 | const toplevel = scope.id == .root; | |
| 407 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; | |
| 408 | ||
| 409 | var name: []const u8 = t.tree.tokSlice(typedef_decl.name_tok); | |
| 410 | try t.typedefs.put(t.gpa, name, {}); | |
| 411 | ||
| 412 | if (builtin_typedef_map.get(name)) |builtin| { | |
| 413 | return t.type_decls.putNoClobber(t.gpa, typedef_node, builtin); | |
| 414 | } | |
| 415 | if (!toplevel) name = try bs.makeMangledName(name); | |
| 416 | try t.type_decls.putNoClobber(t.gpa, typedef_node, name); | |
| 417 | ||
| 418 | const typedef_loc = typedef_decl.name_tok; | |
| 419 | const init_node = t.transType(scope, typedef_decl.qt, typedef_loc) catch |err| switch (err) { | |
| 420 | error.UnsupportedType => { | |
| 421 | return t.failDecl(scope, typedef_loc, name, "unable to resolve typedef child type", .{}); | |
| 422 | }, | |
| 423 | error.OutOfMemory => |e| return e, | |
| 424 | }; | |
| 425 | ||
| 426 | const payload = try t.arena.create(ast.Payload.SimpleVarDecl); | |
| 427 | payload.* = .{ | |
| 428 | .base = .{ .tag = if (toplevel) .pub_var_simple else .var_simple }, | |
| 429 | .data = .{ | |
| 430 | .name = name, | |
| 431 | .init = init_node, | |
| 432 | }, | |
| 433 | }; | |
| 434 | const node = ZigNode.initPayload(&payload.base); | |
| 435 | ||
| 436 | if (toplevel) { | |
| 437 | try t.addTopLevelDecl(name, node); | |
| 438 | } else { | |
| 439 | try scope.appendNode(node); | |
| 440 | try bs.discardVariable(name); | |
| 441 | } | |
| 442 | } | |
| 443 | ||
| 444 | fn mangleWeakGlobalName(t: *Translator, want_name: []const u8) Error![]const u8 { | |
| 445 | var cur_name = want_name; | |
| 446 | ||
| 447 | if (!t.weak_global_names.contains(want_name)) { | |
| 448 | // This type wasn't noticed by the name detection pass, so nothing has been treating this as | |
| 449 | // a weak global name. We must mangle it to avoid conflicts with locals. | |
| 450 | cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); | |
| 451 | } | |
| 452 | ||
| 453 | while (t.global_names.contains(cur_name)) { | |
| 454 | cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); | |
| 455 | } | |
| 456 | return cur_name; | |
| 457 | } | |
| 458 | ||
| 459 | fn transRecordDecl(t: *Translator, scope: *Scope, record_qt: QualType) Error!void { | |
| 460 | const base = record_qt.base(t.comp); | |
| 461 | const record_ty = switch (base.type) { | |
| 462 | .@"struct", .@"union" => |record_ty| record_ty, | |
| 463 | else => unreachable, | |
| 464 | }; | |
| 465 | ||
| 466 | if (t.type_decls.get(record_ty.decl_node)) |_| | |
| 467 | return; // Avoid processing this decl twice | |
| 468 | ||
| 469 | const toplevel = scope.id == .root; | |
| 470 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; | |
| 471 | ||
| 472 | const container_kind: ZigTag = if (base.type == .@"union") .@"union" else .@"struct"; | |
| 473 | const container_kind_name = @tagName(container_kind); | |
| 474 | ||
| 475 | var bare_name = record_ty.name.lookup(t.comp); | |
| 476 | var is_unnamed = false; | |
| 477 | var name = bare_name; | |
| 478 | ||
| 479 | if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { | |
| 480 | bare_name = typedef_name; | |
| 481 | name = typedef_name; | |
| 482 | } else { | |
| 483 | if (record_ty.isAnonymous(t.comp)) { | |
| 484 | bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); | |
| 485 | is_unnamed = true; | |
| 486 | } | |
| 487 | name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ container_kind_name, bare_name }); | |
| 488 | if (toplevel and !is_unnamed) { | |
| 489 | name = try t.mangleWeakGlobalName(name); | |
| 490 | } | |
| 491 | } | |
| 492 | if (!toplevel) name = try bs.makeMangledName(name); | |
| 493 | try t.type_decls.putNoClobber(t.gpa, record_ty.decl_node, name); | |
| 494 | ||
| 495 | const is_pub = toplevel and !is_unnamed; | |
| 496 | const init_node = init: { | |
| 497 | if (record_ty.layout == null) { | |
| 498 | try t.opaque_demotes.put(t.gpa, base.qt, {}); | |
| 499 | break :init ZigTag.opaque_literal.init(); | |
| 500 | } | |
| 501 | ||
| 502 | var fields = try std.ArrayList(ast.Payload.Container.Field).initCapacity(t.gpa, record_ty.fields.len); | |
| 503 | defer fields.deinit(); | |
| 504 | ||
| 505 | var functions = std.ArrayList(ZigNode).init(t.gpa); | |
| 506 | defer functions.deinit(); | |
| 507 | ||
| 508 | var unnamed_field_count: u32 = 0; | |
| 509 | ||
| 510 | // If a record doesn't have any attributes that would affect the alignment and | |
| 511 | // layout, then we can just use a simple `extern` type. If it does have attributes, | |
| 512 | // then we need to inspect the layout and assign an `align` value for each field. | |
| 513 | const has_alignment_attributes = aligned: { | |
| 514 | if (record_qt.hasAttribute(t.comp, .@"packed")) break :aligned true; | |
| 515 | if (record_qt.hasAttribute(t.comp, .aligned)) break :aligned true; | |
| 516 | for (record_ty.fields) |field| { | |
| 517 | const field_attrs = field.attributes(t.comp); | |
| 518 | for (field_attrs) |field_attr| { | |
| 519 | switch (field_attr.tag) { | |
| 520 | .@"packed", .aligned => break :aligned true, | |
| 521 | else => {}, | |
| 522 | } | |
| 523 | } | |
| 524 | } | |
| 525 | break :aligned false; | |
| 526 | }; | |
| 527 | const head_field_alignment: ?c_uint = if (has_alignment_attributes) t.headFieldAlignment(record_ty) else null; | |
| 528 | ||
| 529 | for (record_ty.fields, 0..) |field, field_index| { | |
| 530 | const field_loc = field.name_tok; | |
| 531 | ||
| 532 | // Demote record to opaque if it contains a bitfield | |
| 533 | if (field.bit_width != .null) { | |
| 534 | try t.opaque_demotes.put(t.gpa, base.qt, {}); | |
| 535 | try t.warn(scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name}); | |
| 536 | break :init ZigTag.opaque_literal.init(); | |
| 537 | } | |
| 538 | ||
| 539 | var field_name = field.name.lookup(t.comp); | |
| 540 | if (field.name_tok == 0) { | |
| 541 | field_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{unnamed_field_count}); | |
| 542 | unnamed_field_count += 1; | |
| 543 | try t.anonymous_record_field_names.put(t.gpa, .{ | |
| 544 | .parent = base.qt, | |
| 545 | .field = field.qt, | |
| 546 | }, field_name); | |
| 547 | } | |
| 548 | ||
| 549 | const field_alignment = if (has_alignment_attributes) | |
| 550 | t.alignmentForField(record_ty, head_field_alignment, field_index) | |
| 551 | else | |
| 552 | null; | |
| 553 | ||
| 554 | const field_type = field_type: { | |
| 555 | // Check if this is a flexible array member. | |
| 556 | flexible: { | |
| 557 | if (field_index != record_ty.fields.len - 1 and container_kind != .@"union") break :flexible; | |
| 558 | const array_ty = field.qt.get(t.comp, .array) orelse break :flexible; | |
| 559 | if (array_ty.len != .incomplete and (array_ty.len != .fixed or array_ty.len.fixed != 0)) break :flexible; | |
| 560 | ||
| 561 | const elem_type = t.transType(scope, array_ty.elem, field_loc) catch |err| switch (err) { | |
| 562 | error.UnsupportedType => break :flexible, | |
| 563 | else => |e| return e, | |
| 564 | }; | |
| 565 | const zero_array = try ZigTag.array_type.create(t.arena, .{ .len = 0, .elem_type = elem_type }); | |
| 566 | ||
| 567 | const member_name = field_name; | |
| 568 | field_name = try std.fmt.allocPrint(t.arena, "_{s}", .{field_name}); | |
| 569 | ||
| 570 | const member = try t.createFlexibleMemberFn(member_name, field_name); | |
| 571 | try functions.append(member); | |
| 572 | ||
| 573 | break :field_type zero_array; | |
| 574 | } | |
| 575 | ||
| 576 | break :field_type t.transType(scope, field.qt, field_loc) catch |err| switch (err) { | |
| 577 | error.UnsupportedType => { | |
| 578 | try t.opaque_demotes.put(t.gpa, base.qt, {}); | |
| 579 | try t.warn(scope, field.name_tok, "{s} demoted to opaque type - unable to translate type of field {s}", .{ | |
| 580 | container_kind_name, | |
| 581 | field_name, | |
| 582 | }); | |
| 583 | break :init ZigTag.opaque_literal.init(); | |
| 584 | }, | |
| 585 | else => |e| return e, | |
| 586 | }; | |
| 587 | }; | |
| 588 | ||
| 589 | // C99 introduced designated initializers for structs. Omitted fields are implicitly | |
| 590 | // initialized to zero. Some C APIs are designed with this in mind. Defaulting to zero | |
| 591 | // values for translated struct fields permits Zig code to comfortably use such an API. | |
| 592 | const default_value = if (container_kind == .@"struct") | |
| 593 | try t.createZeroValueNode(field.qt, field_type, .no_as) | |
| 594 | else | |
| 595 | null; | |
| 596 | ||
| 597 | fields.appendAssumeCapacity(.{ | |
| 598 | .name = field_name, | |
| 599 | .type = field_type, | |
| 600 | .alignment = field_alignment, | |
| 601 | .default_value = default_value, | |
| 602 | }); | |
| 603 | } | |
| 604 | ||
| 605 | // A record is empty if it has no fields or only flexible array fields. | |
| 606 | if (record_ty.fields.len == functions.items.len and | |
| 607 | t.comp.target.os.tag == .windows and t.comp.target.abi == .msvc) | |
| 608 | { | |
| 609 | // In MSVC empty records have the same size as their alignment. | |
| 610 | const padding_bits = record_ty.layout.?.size_bits; | |
| 611 | const alignment_bits = record_ty.layout.?.field_alignment_bits; | |
| 612 | ||
| 613 | try fields.append(.{ | |
| 614 | .name = "_padding", | |
| 615 | .type = try ZigTag.type.create(t.arena, try std.fmt.allocPrint(t.arena, "u{d}", .{padding_bits})), | |
| 616 | .alignment = @divExact(alignment_bits, 8), | |
| 617 | .default_value = if (container_kind == .@"struct") | |
| 618 | ZigTag.zero_literal.init() | |
| 619 | else | |
| 620 | null, | |
| 621 | }); | |
| 622 | } | |
| 623 | ||
| 624 | const container_payload = try t.arena.create(ast.Payload.Container); | |
| 625 | container_payload.* = .{ | |
| 626 | .base = .{ .tag = container_kind }, | |
| 627 | .data = .{ | |
| 628 | .layout = .@"extern", | |
| 629 | .fields = try t.arena.dupe(ast.Payload.Container.Field, fields.items), | |
| 630 | .decls = try t.arena.dupe(ZigNode, functions.items), | |
| 631 | }, | |
| 632 | }; | |
| 633 | break :init ZigNode.initPayload(&container_payload.base); | |
| 634 | }; | |
| 635 | ||
| 636 | const payload = try t.arena.create(ast.Payload.SimpleVarDecl); | |
| 637 | payload.* = .{ | |
| 638 | .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, | |
| 639 | .data = .{ | |
| 640 | .name = name, | |
| 641 | .init = init_node, | |
| 642 | }, | |
| 643 | }; | |
| 644 | const node = ZigNode.initPayload(&payload.base); | |
| 645 | if (toplevel) { | |
| 646 | try t.addTopLevelDecl(name, node); | |
| 647 | // Only add the alias if the name is available *and* it was caught by | |
| 648 | // name detection. Don't bother performing a weak mangle, since a | |
| 649 | // mangled name is of no real use here. | |
| 650 | if (!is_unnamed and !t.global_names.contains(bare_name) and t.weak_global_names.contains(bare_name)) | |
| 651 | try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); | |
| 652 | try t.global_scope.container_member_fns_map.put(t.gpa, record_qt, .{ | |
| 653 | .container_decl_ptr = &payload.data.init, | |
| 654 | }); | |
| 655 | } else { | |
| 656 | try scope.appendNode(node); | |
| 657 | try bs.discardVariable(name); | |
| 658 | } | |
| 659 | } | |
| 660 | ||
| 661 | fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!void { | |
| 662 | const func_ty = function.qt.get(t.comp, .func).?; | |
| 663 | ||
| 664 | const is_pub = scope.id == .root; | |
| 665 | ||
| 666 | const fn_name = t.tree.tokSlice(function.name_tok); | |
| 667 | if (scope.getAlias(fn_name) != null or t.global_scope.containsNow(fn_name)) | |
| 668 | return; // Avoid processing this decl twice | |
| 669 | ||
| 670 | const fn_decl_loc = function.name_tok; | |
| 671 | const has_body = function.body != null and func_ty.kind != .variadic; | |
| 672 | if (function.body != null and func_ty.kind == .variadic) { | |
| 673 | try t.warn(scope, function.name_tok, "TODO unable to translate variadic function, demoted to extern", .{}); | |
| 674 | } | |
| 675 | ||
| 676 | const is_always_inline = has_body and function.qt.getAttribute(t.comp, .always_inline) != null; | |
| 677 | const proto_ctx: FnProtoContext = .{ | |
| 678 | .fn_name = fn_name, | |
| 679 | .is_always_inline = is_always_inline, | |
| 680 | .is_extern = !has_body, | |
| 681 | .is_export = !function.static and has_body and !is_always_inline and !function.@"inline", | |
| 682 | .is_pub = is_pub, | |
| 683 | .has_body = has_body, | |
| 684 | .cc = if (function.qt.getAttribute(t.comp, .calling_convention)) |some| switch (some.cc) { | |
| 685 | .c => .c, | |
| 686 | .stdcall => .x86_stdcall, | |
| 687 | .thiscall => .x86_thiscall, | |
| 688 | .fastcall => .x86_fastcall, | |
| 689 | .regcall => .x86_regcall, | |
| 690 | .riscv_vector => .riscv_vector, | |
| 691 | .aarch64_sve_pcs => .aarch64_sve_pcs, | |
| 692 | .aarch64_vector_pcs => .aarch64_vfabi, | |
| 693 | .arm_aapcs => .arm_aapcs, | |
| 694 | .arm_aapcs_vfp => .arm_aapcs_vfp, | |
| 695 | .vectorcall => switch (t.comp.target.cpu.arch) { | |
| 696 | .x86 => .x86_vectorcall, | |
| 697 | .aarch64, .aarch64_be => .aarch64_vfabi, | |
| 698 | else => .c, | |
| 699 | }, | |
| 700 | .x86_64_sysv => .x86_64_sysv, | |
| 701 | .x86_64_win => .x86_64_win, | |
| 702 | } else .c, | |
| 703 | }; | |
| 704 | ||
| 705 | const proto_node = t.transFnType(&t.global_scope.base, function.qt, func_ty, fn_decl_loc, proto_ctx) catch |err| switch (err) { | |
| 706 | error.UnsupportedType => { | |
| 707 | return t.failDecl(scope, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{}); | |
| 708 | }, | |
| 709 | error.OutOfMemory => |e| return e, | |
| 710 | }; | |
| 711 | ||
| 712 | const proto_payload = proto_node.castTag(.func).?; | |
| 713 | if (!has_body) { | |
| 714 | if (scope.id != .root) { | |
| 715 | const bs: *Scope.Block = try scope.findBlockScope(t); | |
| 716 | const mangled_name = try bs.createMangledName(fn_name, false, Scope.Block.extern_local_prefix); | |
| 717 | const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = mangled_name, .init = proto_node }); | |
| 718 | try scope.appendNode(wrapped); | |
| 719 | try bs.discardVariable(mangled_name); | |
| 720 | return; | |
| 721 | } | |
| 722 | try t.global_scope.addMemberFunction(func_ty, proto_payload); | |
| 723 | return t.addTopLevelDecl(fn_name, proto_node); | |
| 724 | } | |
| 725 | ||
| 726 | // actual function definition with body | |
| 727 | const body_stmt = function.body.?.get(t.tree).compound_stmt; | |
| 728 | var block_scope = try Scope.Block.init(t, &t.global_scope.base, false); | |
| 729 | block_scope.return_type = func_ty.return_type; | |
| 730 | defer block_scope.deinit(); | |
| 731 | ||
| 732 | var param_id: c_uint = 0; | |
| 733 | for (proto_payload.data.params, func_ty.params) |*param, param_info| { | |
| 734 | const param_name = param.name orelse { | |
| 735 | proto_payload.data.is_extern = true; | |
| 736 | proto_payload.data.is_export = false; | |
| 737 | proto_payload.data.is_inline = false; | |
| 738 | try t.warn(&t.global_scope.base, fn_decl_loc, "function {s} parameter has no name, demoted to extern", .{fn_name}); | |
| 739 | return t.addTopLevelDecl(fn_name, proto_node); | |
| 740 | }; | |
| 741 | ||
| 742 | const is_const = param_info.qt.@"const"; | |
| 743 | ||
| 744 | const mangled_param_name = try block_scope.makeMangledName(param_name); | |
| 745 | param.name = mangled_param_name; | |
| 746 | ||
| 747 | if (!is_const) { | |
| 748 | const bare_arg_name = try std.fmt.allocPrint(t.arena, "arg_{s}", .{mangled_param_name}); | |
| 749 | const arg_name = try block_scope.makeMangledName(bare_arg_name); | |
| 750 | param.name = arg_name; | |
| 751 | ||
| 752 | const redecl_node = try ZigTag.arg_redecl.create(t.arena, .{ .actual = mangled_param_name, .mangled = arg_name }); | |
| 753 | try block_scope.statements.append(t.gpa, redecl_node); | |
| 754 | } | |
| 755 | try block_scope.discardVariable(mangled_param_name); | |
| 756 | ||
| 757 | param_id += 1; | |
| 758 | } | |
| 759 | ||
| 760 | t.transCompoundStmtInline(body_stmt, &block_scope) catch |err| switch (err) { | |
| 761 | error.OutOfMemory => |e| return e, | |
| 762 | error.UnsupportedTranslation, | |
| 763 | error.UnsupportedType, | |
| 764 | => { | |
| 765 | proto_payload.data.is_extern = true; | |
| 766 | proto_payload.data.is_export = false; | |
| 767 | proto_payload.data.is_inline = false; | |
| 768 | try t.warn(&t.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{}); | |
| 769 | return t.addTopLevelDecl(fn_name, proto_node); | |
| 770 | }, | |
| 771 | }; | |
| 772 | ||
| 773 | try t.global_scope.addMemberFunction(func_ty, proto_payload); | |
| 774 | proto_payload.data.body = try block_scope.complete(); | |
| 775 | return t.addTopLevelDecl(fn_name, proto_node); | |
| 776 | } | |
| 777 | ||
| 778 | fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!void { | |
| 779 | const base_name = t.tree.tokSlice(variable.name_tok); | |
| 780 | const toplevel = scope.id == .root; | |
| 781 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; | |
| 782 | const name, const use_base_name = blk: { | |
| 783 | if (toplevel) break :blk .{ base_name, false }; | |
| 784 | ||
| 785 | // Local extern and static variables are wrapped in a struct. | |
| 786 | const prefix: ?[]const u8 = switch (variable.storage_class) { | |
| 787 | .@"extern" => Scope.Block.extern_local_prefix, | |
| 788 | .static => Scope.Block.static_local_prefix, | |
| 789 | else => null, | |
| 790 | }; | |
| 791 | break :blk .{ try bs.createMangledName(base_name, false, prefix), prefix != null }; | |
| 792 | }; | |
| 793 | ||
| 794 | if (t.typeWasDemotedToOpaque(variable.qt)) { | |
| 795 | if (variable.storage_class != .@"extern" and scope.id == .root) { | |
| 796 | return t.failDecl(scope, variable.name_tok, name, "non-extern variable has opaque type", .{}); | |
| 797 | } else { | |
| 798 | return t.failDecl(scope, variable.name_tok, name, "local variable has opaque type", .{}); | |
| 799 | } | |
| 800 | } | |
| 801 | ||
| 802 | const type_node = (if (variable.initializer) |init| | |
| 803 | t.transTypeInit(scope, variable.qt, init, variable.name_tok) | |
| 804 | else | |
| 805 | t.transType(scope, variable.qt, variable.name_tok)) catch |err| switch (err) { | |
| 806 | error.UnsupportedType => { | |
| 807 | return t.failDecl(scope, variable.name_tok, name, "unable to translate variable declaration type", .{}); | |
| 808 | }, | |
| 809 | else => |e| return e, | |
| 810 | }; | |
| 811 | ||
| 812 | const array_ty = variable.qt.get(t.comp, .array); | |
| 813 | var is_const = variable.qt.@"const" or (array_ty != null and array_ty.?.elem.@"const"); | |
| 814 | var is_extern = variable.storage_class == .@"extern"; | |
| 815 | ||
| 816 | const init_node = init: { | |
| 817 | if (variable.initializer) |init| { | |
| 818 | const maybe_literal = init.get(t.tree); | |
| 819 | const init_node = (if (maybe_literal == .string_literal_expr) | |
| 820 | t.transStringLiteralInitializer(init, maybe_literal.string_literal_expr, type_node) | |
| 821 | else | |
| 822 | t.transExprCoercing(scope, init, .used)) catch |err| switch (err) { | |
| 823 | error.UnsupportedTranslation, error.UnsupportedType => { | |
| 824 | return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{}); | |
| 825 | }, | |
| 826 | else => |e| return e, | |
| 827 | }; | |
| 828 | ||
| 829 | if (!variable.qt.is(t.comp, .bool) and init_node.isBoolRes()) { | |
| 830 | break :init try ZigTag.int_from_bool.create(t.arena, init_node); | |
| 831 | } else { | |
| 832 | break :init init_node; | |
| 833 | } | |
| 834 | } | |
| 835 | if (variable.storage_class == .@"extern") { | |
| 836 | if (array_ty != null and array_ty.?.len == .incomplete) { | |
| 837 | // Oh no, an extern array of unknown size! These are really fun because there's no | |
| 838 | // direct equivalent in Zig. To translate correctly, we'll have to create a C-pointer | |
| 839 | // to the data initialized via @extern. | |
| 840 | ||
| 841 | // Since this is really a pointer to the underlying data, we tweak a few properties. | |
| 842 | is_extern = false; | |
| 843 | is_const = true; | |
| 844 | ||
| 845 | const name_str = try std.fmt.allocPrint(t.arena, "\"{s}\"", .{base_name}); | |
| 846 | break :init try ZigTag.builtin_extern.create(t.arena, .{ | |
| 847 | .type = type_node, | |
| 848 | .name = try ZigTag.string_literal.create(t.arena, name_str), | |
| 849 | }); | |
| 850 | } | |
| 851 | break :init null; | |
| 852 | } | |
| 853 | if (toplevel or variable.storage_class == .static or variable.thread_local) { | |
| 854 | // The C language specification states that variables with static or threadlocal | |
| 855 | // storage without an initializer are initialized to a zero value. | |
| 856 | break :init try t.createZeroValueNode(variable.qt, type_node, .no_as); | |
| 857 | } | |
| 858 | break :init ZigTag.undefined_literal.init(); | |
| 859 | }; | |
| 860 | ||
| 861 | const linksection_string = blk: { | |
| 862 | if (variable.qt.getAttribute(t.comp, .section)) |section| { | |
| 863 | break :blk t.comp.interner.get(section.name.ref()).bytes; | |
| 864 | } | |
| 865 | break :blk null; | |
| 866 | }; | |
| 867 | ||
| 868 | const alignment: ?c_uint = variable.qt.requestedAlignment(t.comp) orelse null; | |
| 869 | var node = try ZigTag.var_decl.create(t.arena, .{ | |
| 870 | .is_pub = toplevel, | |
| 871 | .is_const = is_const, | |
| 872 | .is_extern = is_extern, | |
| 873 | .is_export = toplevel and variable.storage_class == .auto, | |
| 874 | .is_threadlocal = variable.thread_local, | |
| 875 | .linksection_string = linksection_string, | |
| 876 | .alignment = alignment, | |
| 877 | .name = if (use_base_name) base_name else name, | |
| 878 | .type = type_node, | |
| 879 | .init = init_node, | |
| 880 | }); | |
| 881 | ||
| 882 | if (toplevel) { | |
| 883 | try t.addTopLevelDecl(name, node); | |
| 884 | } else { | |
| 885 | if (use_base_name) { | |
| 886 | node = try ZigTag.wrapped_local.create(t.arena, .{ .name = name, .init = node }); | |
| 887 | } | |
| 888 | try scope.appendNode(node); | |
| 889 | try bs.discardVariable(name); | |
| 890 | ||
| 891 | if (variable.qt.getAttribute(t.comp, .cleanup)) |cleanup_attr| { | |
| 892 | const cleanup_fn_name = t.tree.tokSlice(cleanup_attr.function.tok); | |
| 893 | const mangled_fn_name = scope.getAlias(cleanup_fn_name) orelse cleanup_fn_name; | |
| 894 | const fn_id = try ZigTag.identifier.create(t.arena, mangled_fn_name); | |
| 895 | ||
| 896 | const varname = try ZigTag.identifier.create(t.arena, name); | |
| 897 | const args = try t.arena.alloc(ZigNode, 1); | |
| 898 | args[0] = try ZigTag.address_of.create(t.arena, varname); | |
| 899 | ||
| 900 | const cleanup_call = try ZigTag.call.create(t.arena, .{ .lhs = fn_id, .args = args }); | |
| 901 | const discard = try ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = cleanup_call }); | |
| 902 | const deferred_cleanup = try ZigTag.@"defer".create(t.arena, discard); | |
| 903 | ||
| 904 | try bs.statements.append(t.gpa, deferred_cleanup); | |
| 905 | } | |
| 906 | } | |
| 907 | } | |
| 908 | ||
| 909 | fn transEnumDecl(t: *Translator, scope: *Scope, enum_qt: QualType) Error!void { | |
| 910 | const base = enum_qt.base(t.comp); | |
| 911 | const enum_ty = base.type.@"enum"; | |
| 912 | ||
| 913 | if (t.type_decls.get(enum_ty.decl_node)) |_| | |
| 914 | return; // Avoid processing this decl twice | |
| 915 | ||
| 916 | const toplevel = scope.id == .root; | |
| 917 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; | |
| 918 | ||
| 919 | var bare_name = enum_ty.name.lookup(t.comp); | |
| 920 | var is_unnamed = false; | |
| 921 | var name = bare_name; | |
| 922 | if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { | |
| 923 | bare_name = typedef_name; | |
| 924 | name = typedef_name; | |
| 925 | } else { | |
| 926 | if (enum_ty.isAnonymous(t.comp)) { | |
| 927 | bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); | |
| 928 | is_unnamed = true; | |
| 929 | } | |
| 930 | name = try std.fmt.allocPrint(t.arena, "enum_{s}", .{bare_name}); | |
| 931 | } | |
| 932 | if (!toplevel) name = try bs.makeMangledName(name); | |
| 933 | try t.type_decls.putNoClobber(t.gpa, enum_ty.decl_node, name); | |
| 934 | ||
| 935 | const enum_type_node = if (!base.qt.hasIncompleteSize(t.comp)) blk: { | |
| 936 | const enum_decl = enum_ty.decl_node.get(t.tree).enum_decl; | |
| 937 | for (enum_ty.fields, enum_decl.fields) |field, field_node| { | |
| 938 | var enum_val_name = field.name.lookup(t.comp); | |
| 939 | if (!toplevel) { | |
| 940 | enum_val_name = try bs.makeMangledName(enum_val_name); | |
| 941 | } | |
| 942 | ||
| 943 | const enum_const_type_node: ?ZigNode = t.transType(scope, field.qt, field.name_tok) catch |err| switch (err) { | |
| 944 | error.UnsupportedType => null, | |
| 945 | else => |e| return e, | |
| 946 | }; | |
| 947 | ||
| 948 | const val = t.tree.value_map.get(field_node).?; | |
| 949 | const enum_const_def = try ZigTag.enum_constant.create(t.arena, .{ | |
| 950 | .name = enum_val_name, | |
| 951 | .is_public = toplevel, | |
| 952 | .type = enum_const_type_node, | |
| 953 | .value = try t.createIntNode(val), | |
| 954 | }); | |
| 955 | if (toplevel) | |
| 956 | try t.addTopLevelDecl(enum_val_name, enum_const_def) | |
| 957 | else { | |
| 958 | try scope.appendNode(enum_const_def); | |
| 959 | try bs.discardVariable(enum_val_name); | |
| 960 | } | |
| 961 | } | |
| 962 | ||
| 963 | break :blk t.transType(scope, enum_ty.tag.?, enum_decl.name_or_kind_tok) catch |err| switch (err) { | |
| 964 | error.UnsupportedType => { | |
| 965 | return t.failDecl(scope, enum_decl.name_or_kind_tok, name, "unable to translate enum integer type", .{}); | |
| 966 | }, | |
| 967 | else => |e| return e, | |
| 968 | }; | |
| 969 | } else blk: { | |
| 970 | try t.opaque_demotes.put(t.gpa, base.qt, {}); | |
| 971 | break :blk ZigTag.opaque_literal.init(); | |
| 972 | }; | |
| 973 | ||
| 974 | const is_pub = toplevel and !is_unnamed; | |
| 975 | const payload = try t.arena.create(ast.Payload.SimpleVarDecl); | |
| 976 | payload.* = .{ | |
| 977 | .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, | |
| 978 | .data = .{ | |
| 979 | .init = enum_type_node, | |
| 980 | .name = name, | |
| 981 | }, | |
| 982 | }; | |
| 983 | const node = ZigNode.initPayload(&payload.base); | |
| 984 | if (toplevel) { | |
| 985 | try t.addTopLevelDecl(name, node); | |
| 986 | if (!is_unnamed) | |
| 987 | try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); | |
| 988 | } else { | |
| 989 | try scope.appendNode(node); | |
| 990 | try bs.discardVariable(name); | |
| 991 | } | |
| 992 | } | |
| 993 | ||
| 994 | fn transStaticAssert(t: *Translator, scope: *Scope, static_assert: Node.StaticAssert) Error!void { | |
| 995 | const condition = t.transExpr(scope, static_assert.cond, .used) catch |err| switch (err) { | |
| 996 | error.UnsupportedTranslation, error.UnsupportedType => { | |
| 997 | return try t.warn(&t.global_scope.base, static_assert.cond.tok(t.tree), "unable to translate _Static_assert condition", .{}); | |
| 998 | }, | |
| 999 | error.OutOfMemory => |e| return e, | |
| 1000 | }; | |
| 1001 | ||
| 1002 | // generate @compileError message that matches C compiler output | |
| 1003 | const diagnostic = if (static_assert.message) |message| str: { | |
| 1004 | // Aro guarantees this to be a string literal. | |
| 1005 | const str_val = t.tree.value_map.get(message).?; | |
| 1006 | const str_qt = message.qt(t.tree); | |
| 1007 | ||
| 1008 | const bytes = t.comp.interner.get(str_val.ref()).bytes; | |
| 1009 | var allocating: std.Io.Writer.Allocating = .init(t.gpa); | |
| 1010 | defer allocating.deinit(); | |
| 1011 | ||
| 1012 | allocating.writer.writeAll("\"static assertion failed \\") catch return error.OutOfMemory; | |
| 1013 | ||
| 1014 | aro.Value.printString(bytes, str_qt, t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 1015 | allocating.writer.end -= 1; // printString adds a terminating " so we need to remove it | |
| 1016 | allocating.writer.writeAll("\\\"\"") catch return error.OutOfMemory; | |
| 1017 | ||
| 1018 | break :str try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); | |
| 1019 | } else try ZigTag.string_literal.create(t.arena, "\"static assertion failed\""); | |
| 1020 | ||
| 1021 | const assert_node = try ZigTag.static_assert.create(t.arena, .{ .lhs = condition, .rhs = diagnostic }); | |
| 1022 | try scope.appendNode(assert_node); | |
| 1023 | } | |
| 1024 | ||
| 1025 | fn transGlobalAsm(t: *Translator, scope: *Scope, global_asm: Node.SimpleAsm) Error!void { | |
| 1026 | const asm_string = t.tree.value_map.get(global_asm.asm_str).?; | |
| 1027 | const bytes = t.comp.interner.get(asm_string.ref()).bytes; | |
| 1028 | ||
| 1029 | var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); | |
| 1030 | defer allocating.deinit(); | |
| 1031 | aro.Value.printString(bytes, global_asm.asm_str.qt(t.tree), t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 1032 | ||
| 1033 | const str_node = try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); | |
| 1034 | ||
| 1035 | const asm_node = try ZigTag.asm_simple.create(t.arena, str_node); | |
| 1036 | const block = try ZigTag.block_single.create(t.arena, asm_node); | |
| 1037 | const comptime_node = try ZigTag.@"comptime".create(t.arena, block); | |
| 1038 | ||
| 1039 | try scope.appendNode(comptime_node); | |
| 1040 | } | |
| 1041 | ||
| 1042 | // ================ | |
| 1043 | // Type translation | |
| 1044 | // ================ | |
| 1045 | ||
| 1046 | fn getTypeStr(t: *Translator, qt: QualType) ![]const u8 { | |
| 1047 | var allocating: std.Io.Writer.Allocating = .init(t.gpa); | |
| 1048 | defer allocating.deinit(); | |
| 1049 | qt.print(t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 1050 | return t.arena.dupe(u8, allocating.getWritten()); | |
| 1051 | } | |
| 1052 | ||
| 1053 | fn transType(t: *Translator, scope: *Scope, qt: QualType, source_loc: TokenIndex) TypeError!ZigNode { | |
| 1054 | loop: switch (qt.type(t.comp)) { | |
| 1055 | .atomic => { | |
| 1056 | const type_name = try t.getTypeStr(qt); | |
| 1057 | return t.fail(error.UnsupportedType, source_loc, "TODO support atomic type: '{s}'", .{type_name}); | |
| 1058 | }, | |
| 1059 | .void => return ZigTag.type.create(t.arena, "anyopaque"), | |
| 1060 | .bool => return ZigTag.type.create(t.arena, "bool"), | |
| 1061 | .int => |int_ty| switch (int_ty) { | |
| 1062 | //.char => return ZigTag.type.create(t.arena, "c_char"), // TODO: this is the preferred translation | |
| 1063 | .char => return ZigTag.type.create(t.arena, "u8"), | |
| 1064 | .schar => return ZigTag.type.create(t.arena, "i8"), | |
| 1065 | .uchar => return ZigTag.type.create(t.arena, "u8"), | |
| 1066 | .short => return ZigTag.type.create(t.arena, "c_short"), | |
| 1067 | .ushort => return ZigTag.type.create(t.arena, "c_ushort"), | |
| 1068 | .int => return ZigTag.type.create(t.arena, "c_int"), | |
| 1069 | .uint => return ZigTag.type.create(t.arena, "c_uint"), | |
| 1070 | .long => return ZigTag.type.create(t.arena, "c_long"), | |
| 1071 | .ulong => return ZigTag.type.create(t.arena, "c_ulong"), | |
| 1072 | .long_long => return ZigTag.type.create(t.arena, "c_longlong"), | |
| 1073 | .ulong_long => return ZigTag.type.create(t.arena, "c_ulonglong"), | |
| 1074 | .int128 => return ZigTag.type.create(t.arena, "i128"), | |
| 1075 | .uint128 => return ZigTag.type.create(t.arena, "u128"), | |
| 1076 | }, | |
| 1077 | .float => |float_ty| switch (float_ty) { | |
| 1078 | .fp16, .float16 => return ZigTag.type.create(t.arena, "f16"), | |
| 1079 | .float => return ZigTag.type.create(t.arena, "f32"), | |
| 1080 | .double => return ZigTag.type.create(t.arena, "f64"), | |
| 1081 | .long_double => return ZigTag.type.create(t.arena, "c_longdouble"), | |
| 1082 | .float128 => return ZigTag.type.create(t.arena, "f128"), | |
| 1083 | }, | |
| 1084 | .pointer => |pointer_ty| { | |
| 1085 | const child_qt = pointer_ty.child; | |
| 1086 | ||
| 1087 | const is_fn_proto = child_qt.is(t.comp, .func); | |
| 1088 | const is_const = is_fn_proto or child_qt.@"const"; | |
| 1089 | const is_volatile = child_qt.@"volatile"; | |
| 1090 | const elem_type = try t.transType(scope, child_qt, source_loc); | |
| 1091 | const ptr_info: @FieldType(ast.Payload.Pointer, "data") = .{ | |
| 1092 | .is_const = is_const, | |
| 1093 | .is_volatile = is_volatile, | |
| 1094 | .elem_type = elem_type, | |
| 1095 | .is_allowzero = false, | |
| 1096 | }; | |
| 1097 | if (is_fn_proto or | |
| 1098 | t.typeIsOpaque(child_qt) or | |
| 1099 | t.typeWasDemotedToOpaque(child_qt)) | |
| 1100 | { | |
| 1101 | const ptr = try ZigTag.single_pointer.create(t.arena, ptr_info); | |
| 1102 | return ZigTag.optional_type.create(t.arena, ptr); | |
| 1103 | } | |
| 1104 | ||
| 1105 | return ZigTag.c_pointer.create(t.arena, ptr_info); | |
| 1106 | }, | |
| 1107 | .array => |array_ty| { | |
| 1108 | const elem_qt = array_ty.elem; | |
| 1109 | switch (array_ty.len) { | |
| 1110 | .incomplete, .unspecified_variable => { | |
| 1111 | const elem_type = try t.transType(scope, elem_qt, source_loc); | |
| 1112 | return ZigTag.c_pointer.create(t.arena, .{ | |
| 1113 | .is_const = elem_qt.@"const", | |
| 1114 | .is_volatile = elem_qt.@"volatile", | |
| 1115 | .is_allowzero = false, | |
| 1116 | .elem_type = elem_type, | |
| 1117 | }); | |
| 1118 | }, | |
| 1119 | .fixed, .static => |len| { | |
| 1120 | const elem_type = try t.transType(scope, elem_qt, source_loc); | |
| 1121 | return ZigTag.array_type.create(t.arena, .{ .len = len, .elem_type = elem_type }); | |
| 1122 | }, | |
| 1123 | .variable => return t.fail(error.UnsupportedType, source_loc, "VLA unsupported '{s}'", .{try t.getTypeStr(qt)}), | |
| 1124 | } | |
| 1125 | }, | |
| 1126 | .func => |func_ty| return t.transFnType(scope, qt, func_ty, source_loc, .{}), | |
| 1127 | .@"struct", .@"union" => |record_ty| { | |
| 1128 | var trans_scope = scope; | |
| 1129 | if (!record_ty.isAnonymous(t.comp)) { | |
| 1130 | if (t.weak_global_names.contains(record_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; | |
| 1131 | } | |
| 1132 | try t.transRecordDecl(trans_scope, qt); | |
| 1133 | const name = t.type_decls.get(record_ty.decl_node).?; | |
| 1134 | return ZigTag.identifier.create(t.arena, name); | |
| 1135 | }, | |
| 1136 | .@"enum" => |enum_ty| { | |
| 1137 | var trans_scope = scope; | |
| 1138 | const is_anonymous = enum_ty.isAnonymous(t.comp); | |
| 1139 | if (!is_anonymous) { | |
| 1140 | if (t.weak_global_names.contains(enum_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; | |
| 1141 | } | |
| 1142 | try t.transEnumDecl(trans_scope, qt); | |
| 1143 | const name = t.type_decls.get(enum_ty.decl_node).?; | |
| 1144 | return ZigTag.identifier.create(t.arena, name); | |
| 1145 | }, | |
| 1146 | .typedef => |typedef_ty| { | |
| 1147 | var trans_scope = scope; | |
| 1148 | const typedef_name = typedef_ty.name.lookup(t.comp); | |
| 1149 | if (builtin_typedef_map.get(typedef_name)) |builtin| return ZigTag.type.create(t.arena, builtin); | |
| 1150 | if (t.global_names.contains(typedef_name)) trans_scope = &t.global_scope.base; | |
| 1151 | ||
| 1152 | try t.transTypeDef(trans_scope, typedef_ty.decl_node); | |
| 1153 | const name = t.type_decls.get(typedef_ty.decl_node).?; | |
| 1154 | return ZigTag.identifier.create(t.arena, name); | |
| 1155 | }, | |
| 1156 | .attributed => |attributed_ty| continue :loop attributed_ty.base.type(t.comp), | |
| 1157 | .typeof => |typeof_ty| continue :loop typeof_ty.base.type(t.comp), | |
| 1158 | .vector => |vector_ty| { | |
| 1159 | const len = try t.createNumberNode(vector_ty.len, .int); | |
| 1160 | const elem_type = try t.transType(scope, vector_ty.elem, source_loc); | |
| 1161 | return ZigTag.vector.create(t.arena, .{ .lhs = len, .rhs = elem_type }); | |
| 1162 | }, | |
| 1163 | else => return t.fail(error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{try t.getTypeStr(qt)}), | |
| 1164 | } | |
| 1165 | } | |
| 1166 | ||
| 1167 | /// Look ahead through the fields of the record to determine what the alignment of the record | |
| 1168 | /// would be without any align/packed/etc. attributes. This helps us determine whether or not | |
| 1169 | /// the fields with 0 offset need an `align` qualifier. Strictly speaking, we could just | |
| 1170 | /// pedantically assign those fields the same alignment as the parent's pointer alignment, | |
| 1171 | /// but this helps the generated code to be a little less verbose. | |
| 1172 | fn headFieldAlignment(t: *Translator, record_decl: aro.Type.Record) ?c_uint { | |
| 1173 | const bits_per_byte = 8; | |
| 1174 | const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; | |
| 1175 | const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; | |
| 1176 | var max_field_alignment_bits: u64 = 0; | |
| 1177 | for (record_decl.fields) |field| { | |
| 1178 | if (field.qt.getRecord(t.comp)) |field_record_decl| { | |
| 1179 | const child_record_alignment = field_record_decl.layout.?.field_alignment_bits; | |
| 1180 | if (child_record_alignment > max_field_alignment_bits) | |
| 1181 | max_field_alignment_bits = child_record_alignment; | |
| 1182 | } else { | |
| 1183 | const field_size = field.layout.size_bits; | |
| 1184 | if (field_size > max_field_alignment_bits) | |
| 1185 | max_field_alignment_bits = field_size; | |
| 1186 | } | |
| 1187 | } | |
| 1188 | if (max_field_alignment_bits != parent_ptr_alignment_bits) { | |
| 1189 | return parent_ptr_alignment; | |
| 1190 | } else { | |
| 1191 | return null; | |
| 1192 | } | |
| 1193 | } | |
| 1194 | ||
| 1195 | /// This function inspects the generated layout of a record to determine the alignment for a | |
| 1196 | /// particular field. This approach is necessary because unlike Zig, a C compiler is not | |
| 1197 | /// required to fulfill the requested alignment, which means we'd risk generating different code | |
| 1198 | /// if we only look at the user-requested alignment. | |
| 1199 | /// | |
| 1200 | /// Returns a ?c_uint to match Clang's behavior of using c_uint. The return type can be changed | |
| 1201 | /// after the Clang frontend for translate-c is removed. A null value indicates that a field is | |
| 1202 | /// 'naturally aligned'. | |
| 1203 | fn alignmentForField( | |
| 1204 | t: *Translator, | |
| 1205 | record_decl: aro.Type.Record, | |
| 1206 | head_field_alignment: ?c_uint, | |
| 1207 | field_index: usize, | |
| 1208 | ) ?c_uint { | |
| 1209 | const fields = record_decl.fields; | |
| 1210 | assert(fields.len != 0); | |
| 1211 | const field = fields[field_index]; | |
| 1212 | ||
| 1213 | const bits_per_byte = 8; | |
| 1214 | const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; | |
| 1215 | const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; | |
| 1216 | ||
| 1217 | // bitfields aren't supported yet. Until support is added, records with bitfields | |
| 1218 | // should be demoted to opaque, and this function shouldn't be called for them. | |
| 1219 | if (field.bit_width != .null) { | |
| 1220 | @panic("TODO: add bitfield support for records"); | |
| 1221 | } | |
| 1222 | ||
| 1223 | const field_offset_bits: u64 = field.layout.offset_bits; | |
| 1224 | const field_size_bits: u64 = field.layout.size_bits; | |
| 1225 | ||
| 1226 | // Fields with zero width always have an alignment of 1 | |
| 1227 | if (field_size_bits == 0) { | |
| 1228 | return 1; | |
| 1229 | } | |
| 1230 | ||
| 1231 | // Fields with 0 offset inherit the parent's pointer alignment. | |
| 1232 | if (field_offset_bits == 0) { | |
| 1233 | return head_field_alignment; | |
| 1234 | } | |
| 1235 | ||
| 1236 | // Records have a natural alignment when used as a field, and their size is | |
| 1237 | // a multiple of this alignment value. For all other types, the natural alignment | |
| 1238 | // is their size. | |
| 1239 | const field_natural_alignment_bits: u64 = if (field.qt.getRecord(t.comp)) |record| | |
| 1240 | record.layout.?.field_alignment_bits | |
| 1241 | else | |
| 1242 | field_size_bits; | |
| 1243 | const rem_bits = field_offset_bits % field_natural_alignment_bits; | |
| 1244 | ||
| 1245 | // If there's a remainder, then the alignment is smaller than the field's | |
| 1246 | // natural alignment | |
| 1247 | if (rem_bits > 0) { | |
| 1248 | const rem_alignment = rem_bits / bits_per_byte; | |
| 1249 | if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) { | |
| 1250 | const actual_alignment = @min(rem_alignment, parent_ptr_alignment); | |
| 1251 | return @as(c_uint, @truncate(actual_alignment)); | |
| 1252 | } else { | |
| 1253 | return 1; | |
| 1254 | } | |
| 1255 | } | |
| 1256 | ||
| 1257 | // A field may have an offset which positions it to be naturally aligned, but the | |
| 1258 | // parent's pointer alignment determines if this is actually true, so we take the minimum | |
| 1259 | // value. | |
| 1260 | // For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural | |
| 1261 | // alignment, but if the parent pointer alignment is 2, then the actual alignment of the | |
| 1262 | // float is 2. | |
| 1263 | const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte; | |
| 1264 | const offset_alignment = field_offset_bits / bits_per_byte; | |
| 1265 | const possible_alignment = @min(parent_ptr_alignment, offset_alignment); | |
| 1266 | if (possible_alignment == field_natural_alignment) { | |
| 1267 | return null; | |
| 1268 | } else if (possible_alignment < field_natural_alignment) { | |
| 1269 | if (std.math.isPowerOfTwo(possible_alignment)) { | |
| 1270 | return possible_alignment; | |
| 1271 | } else { | |
| 1272 | return 1; | |
| 1273 | } | |
| 1274 | } else { // possible_alignment > field_natural_alignment | |
| 1275 | // Here, the field is positioned be at a higher alignment than it's natural alignment. This means we | |
| 1276 | // need to determine whether it's a specified alignment. We can determine that from the padding preceding | |
| 1277 | // the field. | |
| 1278 | const padding_from_prev_field: u64 = blk: { | |
| 1279 | if (field_offset_bits != 0) { | |
| 1280 | const previous_field = fields[field_index - 1]; | |
| 1281 | break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits; | |
| 1282 | } else { | |
| 1283 | break :blk 0; | |
| 1284 | } | |
| 1285 | }; | |
| 1286 | if (padding_from_prev_field < field_natural_alignment_bits) { | |
| 1287 | return null; | |
| 1288 | } else { | |
| 1289 | return possible_alignment; | |
| 1290 | } | |
| 1291 | } | |
| 1292 | } | |
| 1293 | ||
| 1294 | const FnProtoContext = struct { | |
| 1295 | is_pub: bool = false, | |
| 1296 | is_export: bool = false, | |
| 1297 | is_extern: bool = false, | |
| 1298 | is_always_inline: bool = false, | |
| 1299 | fn_name: ?[]const u8 = null, | |
| 1300 | has_body: bool = false, | |
| 1301 | cc: ast.Payload.Func.CallingConvention = .c, | |
| 1302 | }; | |
| 1303 | ||
| 1304 | fn transFnType( | |
| 1305 | t: *Translator, | |
| 1306 | scope: *Scope, | |
| 1307 | func_qt: QualType, | |
| 1308 | func_ty: aro.Type.Func, | |
| 1309 | source_loc: TokenIndex, | |
| 1310 | ctx: FnProtoContext, | |
| 1311 | ) !ZigNode { | |
| 1312 | const param_count: usize = func_ty.params.len; | |
| 1313 | const fn_params = try t.arena.alloc(ast.Payload.Param, param_count); | |
| 1314 | ||
| 1315 | for (func_ty.params, fn_params) |param_info, *param_node| { | |
| 1316 | const param_qt = param_info.qt; | |
| 1317 | const is_noalias = param_qt.restrict; | |
| 1318 | ||
| 1319 | const param_name: ?[]const u8 = if (param_info.name == .empty) | |
| 1320 | null | |
| 1321 | else | |
| 1322 | param_info.name.lookup(t.comp); | |
| 1323 | ||
| 1324 | const type_node = try t.transType(scope, param_qt, param_info.name_tok); | |
| 1325 | param_node.* = .{ | |
| 1326 | .is_noalias = is_noalias, | |
| 1327 | .name = param_name, | |
| 1328 | .type = type_node, | |
| 1329 | }; | |
| 1330 | } | |
| 1331 | ||
| 1332 | const linksection_string = blk: { | |
| 1333 | if (func_qt.getAttribute(t.comp, .section)) |section| { | |
| 1334 | break :blk t.comp.interner.get(section.name.ref()).bytes; | |
| 1335 | } | |
| 1336 | break :blk null; | |
| 1337 | }; | |
| 1338 | ||
| 1339 | const alignment: ?c_uint = func_qt.requestedAlignment(t.comp) orelse null; | |
| 1340 | ||
| 1341 | const explicit_callconv = if ((ctx.is_always_inline or ctx.is_export or ctx.is_extern) and ctx.cc == .c) null else ctx.cc; | |
| 1342 | ||
| 1343 | const return_type_node = blk: { | |
| 1344 | if (func_qt.getAttribute(t.comp, .noreturn) != null) { | |
| 1345 | break :blk ZigTag.noreturn_type.init(); | |
| 1346 | } else { | |
| 1347 | const return_qt = func_ty.return_type; | |
| 1348 | if (return_qt.is(t.comp, .void)) { | |
| 1349 | // convert primitive anyopaque to actual void (only for return type) | |
| 1350 | break :blk ZigTag.void_type.init(); | |
| 1351 | } else { | |
| 1352 | break :blk t.transType(scope, return_qt, source_loc) catch |err| switch (err) { | |
| 1353 | error.UnsupportedType => { | |
| 1354 | try t.warn(scope, source_loc, "unsupported function proto return type", .{}); | |
| 1355 | return err; | |
| 1356 | }, | |
| 1357 | error.OutOfMemory => |e| return e, | |
| 1358 | }; | |
| 1359 | } | |
| 1360 | } | |
| 1361 | }; | |
| 1362 | ||
| 1363 | const payload = try t.arena.create(ast.Payload.Func); | |
| 1364 | payload.* = .{ | |
| 1365 | .base = .{ .tag = .func }, | |
| 1366 | .data = .{ | |
| 1367 | .is_pub = ctx.is_pub, | |
| 1368 | .is_extern = ctx.is_extern, | |
| 1369 | .is_export = ctx.is_export, | |
| 1370 | .is_inline = ctx.is_always_inline, | |
| 1371 | .is_var_args = switch (func_ty.kind) { | |
| 1372 | .normal => false, | |
| 1373 | .variadic => true, | |
| 1374 | .old_style => !ctx.is_export and !ctx.is_always_inline and !ctx.has_body, | |
| 1375 | }, | |
| 1376 | .name = ctx.fn_name, | |
| 1377 | .linksection_string = linksection_string, | |
| 1378 | .explicit_callconv = explicit_callconv, | |
| 1379 | .params = fn_params, | |
| 1380 | .return_type = return_type_node, | |
| 1381 | .body = null, | |
| 1382 | .alignment = alignment, | |
| 1383 | }, | |
| 1384 | }; | |
| 1385 | return ZigNode.initPayload(&payload.base); | |
| 1386 | } | |
| 1387 | ||
| 1388 | /// Produces a Zig AST node by translating a Type, respecting the width, but modifying the signed-ness. | |
| 1389 | /// Asserts the type is an integer. | |
| 1390 | fn transTypeIntWidthOf(t: *Translator, qt: QualType, is_signed: bool) TypeError!ZigNode { | |
| 1391 | return ZigTag.type.create(t.arena, loop: switch (qt.base(t.comp).type) { | |
| 1392 | .int => |int_ty| switch (int_ty) { | |
| 1393 | .char, .schar, .uchar => if (is_signed) "i8" else "u8", | |
| 1394 | .short, .ushort => if (is_signed) "c_short" else "c_ushort", | |
| 1395 | .int, .uint => if (is_signed) "c_int" else "c_uint", | |
| 1396 | .long, .ulong => if (is_signed) "c_long" else "c_ulong", | |
| 1397 | .long_long, .ulong_long => if (is_signed) "c_longlong" else "c_ulonglong", | |
| 1398 | .int128, .uint128 => if (is_signed) "i128" else "u128", | |
| 1399 | }, | |
| 1400 | .bit_int => |bit_int_ty| try std.fmt.allocPrint(t.arena, "{s}{d}", .{ | |
| 1401 | if (is_signed) "i" else "u", | |
| 1402 | bit_int_ty.bits, | |
| 1403 | }), | |
| 1404 | .@"enum" => |enum_ty| blk: { | |
| 1405 | const tag_ty = enum_ty.tag orelse | |
| 1406 | break :blk if (is_signed) "c_int" else "c_uint"; | |
| 1407 | ||
| 1408 | continue :loop tag_ty.base(t.comp).type; | |
| 1409 | }, | |
| 1410 | else => unreachable, // only call this function when it has already been determined the type is int | |
| 1411 | }); | |
| 1412 | } | |
| 1413 | ||
| 1414 | fn transTypeInit( | |
| 1415 | t: *Translator, | |
| 1416 | scope: *Scope, | |
| 1417 | qt: QualType, | |
| 1418 | init: Node.Index, | |
| 1419 | source_loc: TokenIndex, | |
| 1420 | ) TypeError!ZigNode { | |
| 1421 | switch (init.get(t.tree)) { | |
| 1422 | .string_literal_expr => |literal| { | |
| 1423 | const elem_ty = try t.transType(scope, qt.childType(t.comp), source_loc); | |
| 1424 | ||
| 1425 | const string_lit_size = literal.qt.arrayLen(t.comp).?; | |
| 1426 | const array_size = qt.arrayLen(t.comp).?; | |
| 1427 | ||
| 1428 | if (array_size == string_lit_size) { | |
| 1429 | return ZigTag.null_sentinel_array_type.create(t.arena, .{ .len = array_size - 1, .elem_type = elem_ty }); | |
| 1430 | } else { | |
| 1431 | return ZigTag.array_type.create(t.arena, .{ .len = array_size, .elem_type = elem_ty }); | |
| 1432 | } | |
| 1433 | }, | |
| 1434 | else => {}, | |
| 1435 | } | |
| 1436 | return t.transType(scope, qt, source_loc); | |
| 1437 | } | |
| 1438 | ||
| 1439 | // ============ | |
| 1440 | // Type helpers | |
| 1441 | // ============ | |
| 1442 | ||
| 1443 | fn typeIsOpaque(t: *Translator, qt: QualType) bool { | |
| 1444 | return switch (qt.base(t.comp).type) { | |
| 1445 | .void => true, | |
| 1446 | .@"struct", .@"union" => |record_ty| { | |
| 1447 | if (record_ty.layout == null) return true; | |
| 1448 | for (record_ty.fields) |field| { | |
| 1449 | if (field.bit_width != .null) return true; | |
| 1450 | } | |
| 1451 | return false; | |
| 1452 | }, | |
| 1453 | else => false, | |
| 1454 | }; | |
| 1455 | } | |
| 1456 | ||
| 1457 | fn typeWasDemotedToOpaque(t: *Translator, qt: QualType) bool { | |
| 1458 | const base = qt.base(t.comp); | |
| 1459 | switch (base.type) { | |
| 1460 | .@"struct", .@"union" => |record_ty| { | |
| 1461 | if (t.opaque_demotes.contains(base.qt)) return true; | |
| 1462 | for (record_ty.fields) |field| { | |
| 1463 | if (t.typeWasDemotedToOpaque(field.qt)) return true; | |
| 1464 | } | |
| 1465 | return false; | |
| 1466 | }, | |
| 1467 | .@"enum" => return t.opaque_demotes.contains(base.qt), | |
| 1468 | else => return false, | |
| 1469 | } | |
| 1470 | } | |
| 1471 | ||
| 1472 | fn typeHasWrappingOverflow(t: *Translator, qt: QualType) bool { | |
| 1473 | if (t.signedness(qt) == .unsigned) { | |
| 1474 | // unsigned integer overflow wraps around. | |
| 1475 | return true; | |
| 1476 | } else { | |
| 1477 | // float, signed integer, and pointer overflow is undefined behavior. | |
| 1478 | return false; | |
| 1479 | } | |
| 1480 | } | |
| 1481 | ||
| 1482 | /// Signedness of type when translated to Zig. | |
| 1483 | /// Different from `QualType.signedness()` for `char` and enums. | |
| 1484 | /// Returns null for non-int types. | |
| 1485 | fn signedness(t: *Translator, qt: QualType) ?std.builtin.Signedness { | |
| 1486 | return loop: switch (qt.base(t.comp).type) { | |
| 1487 | .bool => .unsigned, | |
| 1488 | .bit_int => |bit_int| bit_int.signedness, | |
| 1489 | .int => |int_ty| switch (int_ty) { | |
| 1490 | .char => .unsigned, // Always translated as u8 | |
| 1491 | .schar, .short, .int, .long, .long_long, .int128 => .signed, | |
| 1492 | .uchar, .ushort, .uint, .ulong, .ulong_long, .uint128 => .unsigned, | |
| 1493 | }, | |
| 1494 | .@"enum" => |enum_ty| { | |
| 1495 | const tag_qt = enum_ty.tag orelse return .signed; | |
| 1496 | continue :loop tag_qt.base(t.comp).type; | |
| 1497 | }, | |
| 1498 | else => return null, | |
| 1499 | }; | |
| 1500 | } | |
| 1501 | ||
| 1502 | // ===================== | |
| 1503 | // Statement translation | |
| 1504 | // ===================== | |
| 1505 | ||
| 1506 | fn transStmt(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { | |
| 1507 | switch (stmt.get(t.tree)) { | |
| 1508 | .compound_stmt => |compound| { | |
| 1509 | return t.transCompoundStmt(scope, compound); | |
| 1510 | }, | |
| 1511 | .static_assert => |static_assert| { | |
| 1512 | try t.transStaticAssert(scope, static_assert); | |
| 1513 | return ZigTag.declaration.init(); | |
| 1514 | }, | |
| 1515 | .return_stmt => |return_stmt| return t.transReturnStmt(scope, return_stmt), | |
| 1516 | .null_stmt => return ZigTag.empty_block.init(), | |
| 1517 | .if_stmt => |if_stmt| return t.transIfStmt(scope, if_stmt), | |
| 1518 | .while_stmt => |while_stmt| return t.transWhileStmt(scope, while_stmt), | |
| 1519 | .do_while_stmt => |do_while_stmt| return t.transDoWhileStmt(scope, do_while_stmt), | |
| 1520 | .for_stmt => |for_stmt| return t.transForStmt(scope, for_stmt), | |
| 1521 | .continue_stmt => return ZigTag.@"continue".init(), | |
| 1522 | .break_stmt => return ZigTag.@"break".init(), | |
| 1523 | .typedef => |typedef_decl| { | |
| 1524 | assert(!typedef_decl.implicit); | |
| 1525 | try t.transTypeDef(scope, stmt); | |
| 1526 | return ZigTag.declaration.init(); | |
| 1527 | }, | |
| 1528 | .struct_decl, .union_decl => |record_decl| { | |
| 1529 | try t.transRecordDecl(scope, record_decl.container_qt); | |
| 1530 | return ZigTag.declaration.init(); | |
| 1531 | }, | |
| 1532 | .enum_decl => |enum_decl| { | |
| 1533 | try t.transEnumDecl(scope, enum_decl.container_qt); | |
| 1534 | return ZigTag.declaration.init(); | |
| 1535 | }, | |
| 1536 | .function => |function| { | |
| 1537 | try t.transFnDecl(scope, function); | |
| 1538 | return ZigTag.declaration.init(); | |
| 1539 | }, | |
| 1540 | .variable => |variable| { | |
| 1541 | try t.transVarDecl(scope, variable); | |
| 1542 | return ZigTag.declaration.init(); | |
| 1543 | }, | |
| 1544 | .switch_stmt => |switch_stmt| return t.transSwitch(scope, switch_stmt), | |
| 1545 | .case_stmt, .default_stmt => { | |
| 1546 | return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO complex switch", .{}); | |
| 1547 | }, | |
| 1548 | .goto_stmt, .computed_goto_stmt, .labeled_stmt => { | |
| 1549 | return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO goto", .{}); | |
| 1550 | }, | |
| 1551 | else => return t.transExprCoercing(scope, stmt, .unused), | |
| 1552 | } | |
| 1553 | } | |
| 1554 | ||
| 1555 | fn transCompoundStmtInline(t: *Translator, compound: Node.CompoundStmt, block: *Scope.Block) TransError!void { | |
| 1556 | for (compound.body) |stmt| { | |
| 1557 | const result = try t.transStmt(&block.base, stmt); | |
| 1558 | switch (result.tag()) { | |
| 1559 | .declaration, .empty_block => {}, | |
| 1560 | else => try block.statements.append(t.gpa, result), | |
| 1561 | } | |
| 1562 | } | |
| 1563 | } | |
| 1564 | ||
| 1565 | fn transCompoundStmt(t: *Translator, scope: *Scope, compound: Node.CompoundStmt) TransError!ZigNode { | |
| 1566 | var block_scope = try Scope.Block.init(t, scope, false); | |
| 1567 | defer block_scope.deinit(); | |
| 1568 | try t.transCompoundStmtInline(compound, &block_scope); | |
| 1569 | return try block_scope.complete(); | |
| 1570 | } | |
| 1571 | ||
| 1572 | fn transReturnStmt(t: *Translator, scope: *Scope, return_stmt: Node.ReturnStmt) TransError!ZigNode { | |
| 1573 | switch (return_stmt.operand) { | |
| 1574 | .none => return ZigTag.return_void.init(), | |
| 1575 | .expr => |operand| { | |
| 1576 | var rhs = try t.transExprCoercing(scope, operand, .used); | |
| 1577 | const return_qt = scope.findBlockReturnType(); | |
| 1578 | if (rhs.isBoolRes() and !return_qt.is(t.comp, .bool)) { | |
| 1579 | rhs = try ZigTag.int_from_bool.create(t.arena, rhs); | |
| 1580 | } | |
| 1581 | return ZigTag.@"return".create(t.arena, rhs); | |
| 1582 | }, | |
| 1583 | .implicit => |zero| { | |
| 1584 | if (zero) return ZigTag.@"return".create(t.arena, ZigTag.zero_literal.init()); | |
| 1585 | ||
| 1586 | const return_qt = scope.findBlockReturnType(); | |
| 1587 | if (return_qt.is(t.comp, .void)) return ZigTag.empty_block.init(); | |
| 1588 | ||
| 1589 | return ZigTag.@"return".create(t.arena, ZigTag.undefined_literal.init()); | |
| 1590 | }, | |
| 1591 | } | |
| 1592 | } | |
| 1593 | ||
| 1594 | /// If a statement can possibly translate to a Zig assignment (either directly because it's | |
| 1595 | /// an assignment in C or indirectly via result assignment to `_`) AND it's the sole statement | |
| 1596 | /// in the body of an if statement or loop, then we need to put the statement into its own block. | |
| 1597 | /// The `else` case here corresponds to statements that could result in an assignment. If a statement | |
| 1598 | /// class never needs a block, add its enum to the top prong. | |
| 1599 | fn maybeBlockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { | |
| 1600 | switch (stmt.get(t.tree)) { | |
| 1601 | .break_stmt, | |
| 1602 | .continue_stmt, | |
| 1603 | .compound_stmt, | |
| 1604 | .decl_ref_expr, | |
| 1605 | .enumeration_ref, | |
| 1606 | .do_while_stmt, | |
| 1607 | .for_stmt, | |
| 1608 | .if_stmt, | |
| 1609 | .return_stmt, | |
| 1610 | .null_stmt, | |
| 1611 | .while_stmt, | |
| 1612 | => return t.transStmt(scope, stmt), | |
| 1613 | else => return t.blockify(scope, stmt), | |
| 1614 | } | |
| 1615 | } | |
| 1616 | ||
| 1617 | /// Translate statement and place it in its own block. | |
| 1618 | fn blockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { | |
| 1619 | var block_scope = try Scope.Block.init(t, scope, false); | |
| 1620 | defer block_scope.deinit(); | |
| 1621 | const result = try t.transStmt(&block_scope.base, stmt); | |
| 1622 | try block_scope.statements.append(t.gpa, result); | |
| 1623 | return block_scope.complete(); | |
| 1624 | } | |
| 1625 | ||
| 1626 | fn transIfStmt(t: *Translator, scope: *Scope, if_stmt: Node.IfStmt) TransError!ZigNode { | |
| 1627 | var cond_scope: Scope.Condition = .{ | |
| 1628 | .base = .{ | |
| 1629 | .parent = scope, | |
| 1630 | .id = .condition, | |
| 1631 | }, | |
| 1632 | }; | |
| 1633 | defer cond_scope.deinit(); | |
| 1634 | const cond = try t.transBoolExpr(&cond_scope.base, if_stmt.cond); | |
| 1635 | ||
| 1636 | // block needed to keep else statement from attaching to inner while | |
| 1637 | const must_blockify = (if_stmt.else_body != null) and switch (if_stmt.then_body.get(t.tree)) { | |
| 1638 | .while_stmt, .do_while_stmt, .for_stmt => true, | |
| 1639 | else => false, | |
| 1640 | }; | |
| 1641 | ||
| 1642 | const then_node = if (must_blockify) | |
| 1643 | try t.blockify(scope, if_stmt.then_body) | |
| 1644 | else | |
| 1645 | try t.maybeBlockify(scope, if_stmt.then_body); | |
| 1646 | ||
| 1647 | const else_node = if (if_stmt.else_body) |stmt| | |
| 1648 | try t.maybeBlockify(scope, stmt) | |
| 1649 | else | |
| 1650 | null; | |
| 1651 | return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_node, .@"else" = else_node }); | |
| 1652 | } | |
| 1653 | ||
| 1654 | fn transWhileStmt(t: *Translator, scope: *Scope, while_stmt: Node.WhileStmt) TransError!ZigNode { | |
| 1655 | var cond_scope: Scope.Condition = .{ | |
| 1656 | .base = .{ | |
| 1657 | .parent = scope, | |
| 1658 | .id = .condition, | |
| 1659 | }, | |
| 1660 | }; | |
| 1661 | defer cond_scope.deinit(); | |
| 1662 | const cond = try t.transBoolExpr(&cond_scope.base, while_stmt.cond); | |
| 1663 | ||
| 1664 | var loop_scope: Scope = .{ | |
| 1665 | .parent = scope, | |
| 1666 | .id = .loop, | |
| 1667 | }; | |
| 1668 | const body = try t.maybeBlockify(&loop_scope, while_stmt.body); | |
| 1669 | return ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = null }); | |
| 1670 | } | |
| 1671 | ||
| 1672 | fn transDoWhileStmt(t: *Translator, scope: *Scope, do_stmt: Node.DoWhileStmt) TransError!ZigNode { | |
| 1673 | var loop_scope: Scope = .{ | |
| 1674 | .parent = scope, | |
| 1675 | .id = .do_loop, | |
| 1676 | }; | |
| 1677 | ||
| 1678 | // if (!cond) break; | |
| 1679 | var cond_scope: Scope.Condition = .{ | |
| 1680 | .base = .{ | |
| 1681 | .parent = scope, | |
| 1682 | .id = .condition, | |
| 1683 | }, | |
| 1684 | }; | |
| 1685 | defer cond_scope.deinit(); | |
| 1686 | const cond = try t.transBoolExpr(&cond_scope.base, do_stmt.cond); | |
| 1687 | const if_not_break = switch (cond.tag()) { | |
| 1688 | .true_literal => { | |
| 1689 | const body_node = try t.maybeBlockify(scope, do_stmt.body); | |
| 1690 | return ZigTag.while_true.create(t.arena, body_node); | |
| 1691 | }, | |
| 1692 | else => try ZigTag.if_not_break.create(t.arena, cond), | |
| 1693 | }; | |
| 1694 | ||
| 1695 | var body_node = try t.transStmt(&loop_scope, do_stmt.body); | |
| 1696 | if (body_node.isNoreturn(true)) { | |
| 1697 | // The body node ends in a noreturn statement. Simply put it in a while (true) | |
| 1698 | // in case it contains breaks or continues. | |
| 1699 | } else if (do_stmt.body.get(t.tree) == .compound_stmt) { | |
| 1700 | // there's already a block in C, so we'll append our condition to it. | |
| 1701 | // c: do { | |
| 1702 | // c: a; | |
| 1703 | // c: b; | |
| 1704 | // c: } while(c); | |
| 1705 | // zig: while (true) { | |
| 1706 | // zig: a; | |
| 1707 | // zig: b; | |
| 1708 | // zig: if (!cond) break; | |
| 1709 | // zig: } | |
| 1710 | const block = body_node.castTag(.block).?; | |
| 1711 | block.data.stmts.len += 1; // This is safe since we reserve one extra space in Scope.Block.complete. | |
| 1712 | block.data.stmts[block.data.stmts.len - 1] = if_not_break; | |
| 1713 | } else { | |
| 1714 | // the C statement is without a block, so we need to create a block to contain it. | |
| 1715 | // c: do | |
| 1716 | // c: a; | |
| 1717 | // c: while(c); | |
| 1718 | // zig: while (true) { | |
| 1719 | // zig: a; | |
| 1720 | // zig: if (!cond) break; | |
| 1721 | // zig: } | |
| 1722 | const statements = try t.arena.alloc(ZigNode, 2); | |
| 1723 | statements[0] = body_node; | |
| 1724 | statements[1] = if_not_break; | |
| 1725 | body_node = try ZigTag.block.create(t.arena, .{ .label = null, .stmts = statements }); | |
| 1726 | } | |
| 1727 | return ZigTag.while_true.create(t.arena, body_node); | |
| 1728 | } | |
| 1729 | ||
| 1730 | fn transForStmt(t: *Translator, scope: *Scope, for_stmt: Node.ForStmt) TransError!ZigNode { | |
| 1731 | var loop_scope: Scope = .{ | |
| 1732 | .parent = scope, | |
| 1733 | .id = .loop, | |
| 1734 | }; | |
| 1735 | ||
| 1736 | var block_scope: ?Scope.Block = null; | |
| 1737 | defer if (block_scope) |*bs| bs.deinit(); | |
| 1738 | ||
| 1739 | switch (for_stmt.init) { | |
| 1740 | .decls => |decls| { | |
| 1741 | block_scope = try Scope.Block.init(t, scope, false); | |
| 1742 | loop_scope.parent = &block_scope.?.base; | |
| 1743 | for (decls) |decl| { | |
| 1744 | try t.transDecl(&block_scope.?.base, decl); | |
| 1745 | } | |
| 1746 | }, | |
| 1747 | .expr => |maybe_init| if (maybe_init) |init| { | |
| 1748 | block_scope = try Scope.Block.init(t, scope, false); | |
| 1749 | loop_scope.parent = &block_scope.?.base; | |
| 1750 | const init_node = try t.transStmt(&block_scope.?.base, init); | |
| 1751 | try loop_scope.appendNode(init_node); | |
| 1752 | }, | |
| 1753 | } | |
| 1754 | var cond_scope: Scope.Condition = .{ | |
| 1755 | .base = .{ | |
| 1756 | .parent = &loop_scope, | |
| 1757 | .id = .condition, | |
| 1758 | }, | |
| 1759 | }; | |
| 1760 | defer cond_scope.deinit(); | |
| 1761 | ||
| 1762 | const cond = if (for_stmt.cond) |cond| | |
| 1763 | try t.transBoolExpr(&cond_scope.base, cond) | |
| 1764 | else | |
| 1765 | ZigTag.true_literal.init(); | |
| 1766 | ||
| 1767 | const cont_expr = if (for_stmt.incr) |incr| | |
| 1768 | try t.transExpr(&cond_scope.base, incr, .unused) | |
| 1769 | else | |
| 1770 | null; | |
| 1771 | ||
| 1772 | const body = try t.maybeBlockify(&loop_scope, for_stmt.body); | |
| 1773 | const while_node = try ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr }); | |
| 1774 | if (block_scope) |*bs| { | |
| 1775 | try bs.statements.append(t.gpa, while_node); | |
| 1776 | return try bs.complete(); | |
| 1777 | } else { | |
| 1778 | return while_node; | |
| 1779 | } | |
| 1780 | } | |
| 1781 | ||
| 1782 | fn transSwitch(t: *Translator, scope: *Scope, switch_stmt: Node.SwitchStmt) TransError!ZigNode { | |
| 1783 | var loop_scope: Scope = .{ | |
| 1784 | .parent = scope, | |
| 1785 | .id = .loop, | |
| 1786 | }; | |
| 1787 | ||
| 1788 | var block_scope = try Scope.Block.init(t, &loop_scope, false); | |
| 1789 | defer block_scope.deinit(); | |
| 1790 | ||
| 1791 | const base_scope = &block_scope.base; | |
| 1792 | ||
| 1793 | var cond_scope: Scope.Condition = .{ | |
| 1794 | .base = .{ | |
| 1795 | .parent = base_scope, | |
| 1796 | .id = .condition, | |
| 1797 | }, | |
| 1798 | }; | |
| 1799 | defer cond_scope.deinit(); | |
| 1800 | const switch_expr = try t.transExpr(&cond_scope.base, switch_stmt.cond, .used); | |
| 1801 | ||
| 1802 | var cases = std.ArrayList(ZigNode).init(t.gpa); | |
| 1803 | defer cases.deinit(); | |
| 1804 | var has_default = false; | |
| 1805 | ||
| 1806 | const body_node = switch_stmt.body.get(t.tree); | |
| 1807 | if (body_node != .compound_stmt) { | |
| 1808 | return t.fail(error.UnsupportedTranslation, switch_stmt.switch_tok, "TODO complex switch", .{}); | |
| 1809 | } | |
| 1810 | const body = body_node.compound_stmt.body; | |
| 1811 | // Iterate over switch body and collect all cases. | |
| 1812 | // Fallthrough is handled by duplicating statements. | |
| 1813 | for (body, 0..) |stmt, i| { | |
| 1814 | switch (stmt.get(t.tree)) { | |
| 1815 | .case_stmt => { | |
| 1816 | var items = std.ArrayList(ZigNode).init(t.gpa); | |
| 1817 | defer items.deinit(); | |
| 1818 | const sub = try t.transCaseStmt(base_scope, stmt, &items); | |
| 1819 | const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); | |
| 1820 | ||
| 1821 | if (items.items.len == 0) { | |
| 1822 | has_default = true; | |
| 1823 | const switch_else = try ZigTag.switch_else.create(t.arena, res); | |
| 1824 | try cases.append(switch_else); | |
| 1825 | } else { | |
| 1826 | const switch_prong = try ZigTag.switch_prong.create(t.arena, .{ | |
| 1827 | .cases = try t.arena.dupe(ZigNode, items.items), | |
| 1828 | .cond = res, | |
| 1829 | }); | |
| 1830 | try cases.append(switch_prong); | |
| 1831 | } | |
| 1832 | }, | |
| 1833 | .default_stmt => |default_stmt| { | |
| 1834 | has_default = true; | |
| 1835 | ||
| 1836 | var sub = default_stmt.body; | |
| 1837 | while (true) switch (sub.get(t.tree)) { | |
| 1838 | .case_stmt => |sub_case| sub = sub_case.body, | |
| 1839 | .default_stmt => |sub_default| sub = sub_default.body, | |
| 1840 | else => break, | |
| 1841 | }; | |
| 1842 | ||
| 1843 | const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); | |
| 1844 | ||
| 1845 | const switch_else = try ZigTag.switch_else.create(t.arena, res); | |
| 1846 | try cases.append(switch_else); | |
| 1847 | }, | |
| 1848 | else => {}, // collected in transSwitchProngStmt | |
| 1849 | } | |
| 1850 | } | |
| 1851 | ||
| 1852 | if (!has_default) { | |
| 1853 | const else_prong = try ZigTag.switch_else.create(t.arena, ZigTag.empty_block.init()); | |
| 1854 | try cases.append(else_prong); | |
| 1855 | } | |
| 1856 | ||
| 1857 | const switch_node = try ZigTag.@"switch".create(t.arena, .{ | |
| 1858 | .cond = switch_expr, | |
| 1859 | .cases = try t.arena.dupe(ZigNode, cases.items), | |
| 1860 | }); | |
| 1861 | try block_scope.statements.append(t.gpa, switch_node); | |
| 1862 | try block_scope.statements.append(t.gpa, ZigTag.@"break".init()); | |
| 1863 | const while_body = try block_scope.complete(); | |
| 1864 | ||
| 1865 | return ZigTag.while_true.create(t.arena, while_body); | |
| 1866 | } | |
| 1867 | ||
| 1868 | /// Collects all items for this case, returns the first statement after the labels. | |
| 1869 | /// If items ends up empty, the prong should be translated as an else. | |
| 1870 | fn transCaseStmt( | |
| 1871 | t: *Translator, | |
| 1872 | scope: *Scope, | |
| 1873 | stmt: Node.Index, | |
| 1874 | items: *std.ArrayList(ZigNode), | |
| 1875 | ) TransError!Node.Index { | |
| 1876 | var sub = stmt; | |
| 1877 | var seen_default = false; | |
| 1878 | while (true) { | |
| 1879 | switch (sub.get(t.tree)) { | |
| 1880 | .default_stmt => |default_stmt| { | |
| 1881 | seen_default = true; | |
| 1882 | items.items.len = 0; | |
| 1883 | sub = default_stmt.body; | |
| 1884 | }, | |
| 1885 | .case_stmt => |case_stmt| { | |
| 1886 | if (seen_default) { | |
| 1887 | items.items.len = 0; | |
| 1888 | sub = case_stmt.body; | |
| 1889 | continue; | |
| 1890 | } | |
| 1891 | ||
| 1892 | const expr = if (case_stmt.end) |end| blk: { | |
| 1893 | const start_node = try t.transExpr(scope, case_stmt.start, .used); | |
| 1894 | const end_node = try t.transExpr(scope, end, .used); | |
| 1895 | ||
| 1896 | break :blk try ZigTag.ellipsis3.create(t.arena, .{ .lhs = start_node, .rhs = end_node }); | |
| 1897 | } else try t.transExpr(scope, case_stmt.start, .used); | |
| 1898 | ||
| 1899 | try items.append(expr); | |
| 1900 | sub = case_stmt.body; | |
| 1901 | }, | |
| 1902 | else => return sub, | |
| 1903 | } | |
| 1904 | } | |
| 1905 | } | |
| 1906 | ||
| 1907 | /// Collects all statements seen by this case into a block. | |
| 1908 | /// Avoids creating a block if the first statement is a break or return. | |
| 1909 | fn transSwitchProngStmt( | |
| 1910 | t: *Translator, | |
| 1911 | scope: *Scope, | |
| 1912 | stmt: Node.Index, | |
| 1913 | body: []const Node.Index, | |
| 1914 | ) TransError!ZigNode { | |
| 1915 | switch (stmt.get(t.tree)) { | |
| 1916 | .break_stmt => return ZigTag.@"break".init(), | |
| 1917 | .return_stmt => return t.transStmt(scope, stmt), | |
| 1918 | .case_stmt, .default_stmt => unreachable, | |
| 1919 | else => { | |
| 1920 | var block_scope = try Scope.Block.init(t, scope, false); | |
| 1921 | defer block_scope.deinit(); | |
| 1922 | ||
| 1923 | // we do not need to translate `stmt` since it is the first stmt of `body` | |
| 1924 | try t.transSwitchProngStmtInline(&block_scope, body); | |
| 1925 | return try block_scope.complete(); | |
| 1926 | }, | |
| 1927 | } | |
| 1928 | } | |
| 1929 | ||
| 1930 | /// Collects all statements seen by this case into a block. | |
| 1931 | fn transSwitchProngStmtInline( | |
| 1932 | t: *Translator, | |
| 1933 | block: *Scope.Block, | |
| 1934 | body: []const Node.Index, | |
| 1935 | ) TransError!void { | |
| 1936 | for (body) |stmt| { | |
| 1937 | switch (stmt.get(t.tree)) { | |
| 1938 | .return_stmt => { | |
| 1939 | const result = try t.transStmt(&block.base, stmt); | |
| 1940 | try block.statements.append(t.gpa, result); | |
| 1941 | return; | |
| 1942 | }, | |
| 1943 | .break_stmt => { | |
| 1944 | try block.statements.append(t.gpa, ZigTag.@"break".init()); | |
| 1945 | return; | |
| 1946 | }, | |
| 1947 | .case_stmt => |case_stmt| { | |
| 1948 | var sub = case_stmt.body; | |
| 1949 | while (true) switch (sub.get(t.tree)) { | |
| 1950 | .case_stmt => |sub_case| sub = sub_case.body, | |
| 1951 | .default_stmt => |sub_default| sub = sub_default.body, | |
| 1952 | else => break, | |
| 1953 | }; | |
| 1954 | const result = try t.transStmt(&block.base, sub); | |
| 1955 | assert(result.tag() != .declaration); | |
| 1956 | try block.statements.append(t.gpa, result); | |
| 1957 | if (result.isNoreturn(true)) return; | |
| 1958 | }, | |
| 1959 | .default_stmt => |default_stmt| { | |
| 1960 | var sub = default_stmt.body; | |
| 1961 | while (true) switch (sub.get(t.tree)) { | |
| 1962 | .case_stmt => |sub_case| sub = sub_case.body, | |
| 1963 | .default_stmt => |sub_default| sub = sub_default.body, | |
| 1964 | else => break, | |
| 1965 | }; | |
| 1966 | const result = try t.transStmt(&block.base, sub); | |
| 1967 | assert(result.tag() != .declaration); | |
| 1968 | try block.statements.append(t.gpa, result); | |
| 1969 | if (result.isNoreturn(true)) return; | |
| 1970 | }, | |
| 1971 | .compound_stmt => |compound_stmt| { | |
| 1972 | const result = try t.transCompoundStmt(&block.base, compound_stmt); | |
| 1973 | try block.statements.append(t.gpa, result); | |
| 1974 | if (result.isNoreturn(true)) return; | |
| 1975 | }, | |
| 1976 | else => { | |
| 1977 | const result = try t.transStmt(&block.base, stmt); | |
| 1978 | switch (result.tag()) { | |
| 1979 | .declaration, .empty_block => {}, | |
| 1980 | else => try block.statements.append(t.gpa, result), | |
| 1981 | } | |
| 1982 | }, | |
| 1983 | } | |
| 1984 | } | |
| 1985 | } | |
| 1986 | ||
| 1987 | // ====================== | |
| 1988 | // Expression translation | |
| 1989 | // ====================== | |
| 1990 | ||
| 1991 | const ResultUsed = enum { used, unused }; | |
| 1992 | ||
| 1993 | fn transExpr(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { | |
| 1994 | const qt = expr.qt(t.tree); | |
| 1995 | return t.maybeSuppressResult(used, switch (expr.get(t.tree)) { | |
| 1996 | .paren_expr => |paren_expr| { | |
| 1997 | return t.transExpr(scope, paren_expr.operand, used); | |
| 1998 | }, | |
| 1999 | .cast => |cast| return t.transCastExpr(scope, cast, cast.qt, used, .with_as), | |
| 2000 | .decl_ref_expr => |decl_ref| try t.transDeclRefExpr(scope, decl_ref), | |
| 2001 | .enumeration_ref => |enum_ref| try t.transDeclRefExpr(scope, enum_ref), | |
| 2002 | .addr_of_expr => |addr_of_expr| try ZigTag.address_of.create(t.arena, try t.transExpr(scope, addr_of_expr.operand, .used)), | |
| 2003 | .deref_expr => |deref_expr| res: { | |
| 2004 | if (t.typeWasDemotedToOpaque(qt)) | |
| 2005 | return t.fail(error.UnsupportedTranslation, deref_expr.op_tok, "cannot dereference opaque type", .{}); | |
| 2006 | ||
| 2007 | // Dereferencing a function pointer is a no-op. | |
| 2008 | if (qt.is(t.comp, .func)) return t.transExpr(scope, deref_expr.operand, used); | |
| 2009 | ||
| 2010 | break :res try ZigTag.deref.create(t.arena, try t.transExpr(scope, deref_expr.operand, .used)); | |
| 2011 | }, | |
| 2012 | .bool_not_expr => |bool_not_expr| try ZigTag.not.create(t.arena, try t.transBoolExpr(scope, bool_not_expr.operand)), | |
| 2013 | .bit_not_expr => |bit_not_expr| try ZigTag.bit_not.create(t.arena, try t.transExpr(scope, bit_not_expr.operand, .used)), | |
| 2014 | .plus_expr => |plus_expr| return t.transExpr(scope, plus_expr.operand, used), | |
| 2015 | .negate_expr => |negate_expr| res: { | |
| 2016 | const operand_qt = negate_expr.operand.qt(t.tree); | |
| 2017 | if (!t.typeHasWrappingOverflow(operand_qt)) { | |
| 2018 | const sub_expr_node = try t.transExpr(scope, negate_expr.operand, .used); | |
| 2019 | const to_negate = if (sub_expr_node.isBoolRes()) blk: { | |
| 2020 | const ty_node = try ZigTag.type.create(t.arena, "c_int"); | |
| 2021 | const int_node = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); | |
| 2022 | break :blk try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_node }); | |
| 2023 | } else sub_expr_node; | |
| 2024 | ||
| 2025 | break :res try ZigTag.negate.create(t.arena, to_negate); | |
| 2026 | } else if (t.signedness(operand_qt) == .unsigned) { | |
| 2027 | // use -% x for unsigned integers | |
| 2028 | break :res try ZigTag.negate_wrap.create(t.arena, try t.transExpr(scope, negate_expr.operand, .used)); | |
| 2029 | } else return t.fail(error.UnsupportedTranslation, negate_expr.op_tok, "C negation with non float non integer", .{}); | |
| 2030 | }, | |
| 2031 | .div_expr => |div_expr| res: { | |
| 2032 | if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { | |
| 2033 | // signed integer division uses @divTrunc | |
| 2034 | const lhs = try t.transExpr(scope, div_expr.lhs, .used); | |
| 2035 | const rhs = try t.transExpr(scope, div_expr.rhs, .used); | |
| 2036 | break :res try ZigTag.div_trunc.create(t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 2037 | } | |
| 2038 | // unsigned/float division uses the operator | |
| 2039 | break :res try t.transBinExpr(scope, div_expr, .div); | |
| 2040 | }, | |
| 2041 | .mod_expr => |mod_expr| res: { | |
| 2042 | if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { | |
| 2043 | // signed integer remainder uses __helpers.signedRemainder | |
| 2044 | const lhs = try t.transExpr(scope, mod_expr.lhs, .used); | |
| 2045 | const rhs = try t.transExpr(scope, mod_expr.rhs, .used); | |
| 2046 | break :res try t.createHelperCallNode(.signedRemainder, &.{ lhs, rhs }); | |
| 2047 | } | |
| 2048 | // unsigned/float division uses the operator | |
| 2049 | break :res try t.transBinExpr(scope, mod_expr, .mod); | |
| 2050 | }, | |
| 2051 | .add_expr => |add_expr| res: { | |
| 2052 | // `ptr + idx` and `idx + ptr` -> ptr + @as(usize, @bitCast(@as(isize, @intCast(idx)))) | |
| 2053 | const lhs_qt = add_expr.lhs.qt(t.tree); | |
| 2054 | const rhs_qt = add_expr.rhs.qt(t.tree); | |
| 2055 | if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or | |
| 2056 | t.signedness(rhs_qt) == .signed)) | |
| 2057 | { | |
| 2058 | break :res try t.transPointerArithmeticSignedOp(scope, add_expr, .add); | |
| 2059 | } | |
| 2060 | ||
| 2061 | if (t.signedness(qt) == .unsigned) { | |
| 2062 | break :res try t.transBinExpr(scope, add_expr, .add_wrap); | |
| 2063 | } else { | |
| 2064 | break :res try t.transBinExpr(scope, add_expr, .add); | |
| 2065 | } | |
| 2066 | }, | |
| 2067 | .sub_expr => |sub_expr| res: { | |
| 2068 | // `ptr - idx` -> ptr - @as(usize, @bitCast(@as(isize, @intCast(idx)))) | |
| 2069 | const lhs_qt = sub_expr.lhs.qt(t.tree); | |
| 2070 | const rhs_qt = sub_expr.rhs.qt(t.tree); | |
| 2071 | if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or | |
| 2072 | t.signedness(rhs_qt) == .signed)) | |
| 2073 | { | |
| 2074 | break :res try t.transPointerArithmeticSignedOp(scope, sub_expr, .sub); | |
| 2075 | } | |
| 2076 | ||
| 2077 | if (sub_expr.lhs.qt(t.tree).isPointer(t.comp) and sub_expr.rhs.qt(t.tree).isPointer(t.comp)) { | |
| 2078 | break :res try t.transPtrDiffExpr(scope, sub_expr); | |
| 2079 | } else if (t.signedness(qt) == .unsigned) { | |
| 2080 | break :res try t.transBinExpr(scope, sub_expr, .sub_wrap); | |
| 2081 | } else { | |
| 2082 | break :res try t.transBinExpr(scope, sub_expr, .sub); | |
| 2083 | } | |
| 2084 | }, | |
| 2085 | .mul_expr => |mul_expr| if (t.signedness(qt) == .unsigned) | |
| 2086 | try t.transBinExpr(scope, mul_expr, .mul_wrap) | |
| 2087 | else | |
| 2088 | try t.transBinExpr(scope, mul_expr, .mul), | |
| 2089 | ||
| 2090 | .less_than_expr => |lt| try t.transBinExpr(scope, lt, .less_than), | |
| 2091 | .greater_than_expr => |gt| try t.transBinExpr(scope, gt, .greater_than), | |
| 2092 | .less_than_equal_expr => |lte| try t.transBinExpr(scope, lte, .less_than_equal), | |
| 2093 | .greater_than_equal_expr => |gte| try t.transBinExpr(scope, gte, .greater_than_equal), | |
| 2094 | .equal_expr => |equal_expr| try t.transBinExpr(scope, equal_expr, .equal), | |
| 2095 | .not_equal_expr => |not_equal_expr| try t.transBinExpr(scope, not_equal_expr, .not_equal), | |
| 2096 | ||
| 2097 | .bool_and_expr => |bool_and_expr| try t.transBoolBinExpr(scope, bool_and_expr, .@"and"), | |
| 2098 | .bool_or_expr => |bool_or_expr| try t.transBoolBinExpr(scope, bool_or_expr, .@"or"), | |
| 2099 | ||
| 2100 | .bit_and_expr => |bit_and_expr| try t.transBinExpr(scope, bit_and_expr, .bit_and), | |
| 2101 | .bit_or_expr => |bit_or_expr| try t.transBinExpr(scope, bit_or_expr, .bit_or), | |
| 2102 | .bit_xor_expr => |bit_xor_expr| try t.transBinExpr(scope, bit_xor_expr, .bit_xor), | |
| 2103 | ||
| 2104 | .shl_expr => |shl_expr| try t.transShiftExpr(scope, shl_expr, .shl), | |
| 2105 | .shr_expr => |shr_expr| try t.transShiftExpr(scope, shr_expr, .shr), | |
| 2106 | ||
| 2107 | .member_access_expr => |member_access| try t.transMemberAccess(scope, .normal, member_access, null), | |
| 2108 | .member_access_ptr_expr => |member_access| try t.transMemberAccess(scope, .ptr, member_access, null), | |
| 2109 | .array_access_expr => |array_access| try t.transArrayAccess(scope, array_access, null), | |
| 2110 | ||
| 2111 | .builtin_ref => unreachable, | |
| 2112 | .builtin_call_expr => |call| return t.transBuiltinCall(scope, call, used), | |
| 2113 | .call_expr => |call| return t.transCall(scope, call, used), | |
| 2114 | ||
| 2115 | .builtin_types_compatible_p => |compatible| blk: { | |
| 2116 | const lhs = try t.transType(scope, compatible.lhs, compatible.builtin_tok); | |
| 2117 | const rhs = try t.transType(scope, compatible.rhs, compatible.builtin_tok); | |
| 2118 | ||
| 2119 | break :blk try ZigTag.equal.create(t.arena, .{ | |
| 2120 | .lhs = lhs, | |
| 2121 | .rhs = rhs, | |
| 2122 | }); | |
| 2123 | }, | |
| 2124 | .builtin_choose_expr => |choose| return t.transCondExpr(scope, choose, used), | |
| 2125 | .cond_expr => |cond_expr| return t.transCondExpr(scope, cond_expr, used), | |
| 2126 | .binary_cond_expr => |conditional| return t.transBinaryCondExpr(scope, conditional, used), | |
| 2127 | .cond_dummy_expr => unreachable, | |
| 2128 | ||
| 2129 | .assign_expr => |assign| return t.transAssignExpr(scope, assign, used), | |
| 2130 | .add_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2131 | .sub_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2132 | .mul_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2133 | .div_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2134 | .mod_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2135 | .shl_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2136 | .shr_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2137 | .bit_and_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2138 | .bit_xor_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2139 | .bit_or_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2140 | .compound_assign_dummy_expr => { | |
| 2141 | assert(used == .used); | |
| 2142 | return t.compound_assign_dummy.?; | |
| 2143 | }, | |
| 2144 | ||
| 2145 | .comma_expr => |comma_expr| return t.transCommaExpr(scope, comma_expr, used), | |
| 2146 | .pre_inc_expr => |un| return t.transIncDecExpr(scope, un, .pre, .inc, used), | |
| 2147 | .pre_dec_expr => |un| return t.transIncDecExpr(scope, un, .pre, .dec, used), | |
| 2148 | .post_inc_expr => |un| return t.transIncDecExpr(scope, un, .post, .inc, used), | |
| 2149 | .post_dec_expr => |un| return t.transIncDecExpr(scope, un, .post, .dec, used), | |
| 2150 | ||
| 2151 | .int_literal => return t.transIntLiteral(scope, expr, used, .with_as), | |
| 2152 | .char_literal => return t.transCharLiteral(scope, expr, used, .with_as), | |
| 2153 | .float_literal => return t.transFloatLiteral(scope, expr, used, .with_as), | |
| 2154 | .string_literal_expr => |literal| try t.transStringLiteral(scope, expr, literal), | |
| 2155 | .bool_literal => res: { | |
| 2156 | const val = t.tree.value_map.get(expr).?; | |
| 2157 | break :res if (val.toBool(t.comp)) | |
| 2158 | ZigTag.true_literal.init() | |
| 2159 | else | |
| 2160 | ZigTag.false_literal.init(); | |
| 2161 | }, | |
| 2162 | .nullptr_literal => ZigTag.null_literal.init(), | |
| 2163 | .imaginary_literal => |literal| { | |
| 2164 | return t.fail(error.UnsupportedTranslation, literal.op_tok, "TODO complex numbers", .{}); | |
| 2165 | }, | |
| 2166 | .compound_literal_expr => |literal| return t.transCompoundLiteral(scope, literal, used), | |
| 2167 | ||
| 2168 | .default_init_expr => |default_init| return t.transDefaultInit(scope, default_init, used, .with_as), | |
| 2169 | .array_init_expr => |array_init| return t.transArrayInit(scope, array_init, used), | |
| 2170 | .union_init_expr => |union_init| return t.transUnionInit(scope, union_init, used), | |
| 2171 | .struct_init_expr => |struct_init| return t.transStructInit(scope, struct_init, used), | |
| 2172 | .array_filler_expr => unreachable, | |
| 2173 | ||
| 2174 | .sizeof_expr => |sizeof| try t.transTypeInfo(scope, .sizeof, sizeof), | |
| 2175 | .alignof_expr => |alignof| try t.transTypeInfo(scope, .alignof, alignof), | |
| 2176 | ||
| 2177 | .imag_expr, .real_expr => |un| { | |
| 2178 | return t.fail(error.UnsupportedTranslation, un.op_tok, "TODO complex numbers", .{}); | |
| 2179 | }, | |
| 2180 | .addr_of_label => |addr_of_label| { | |
| 2181 | return t.fail(error.UnsupportedTranslation, addr_of_label.label_tok, "TODO computed goto", .{}); | |
| 2182 | }, | |
| 2183 | ||
| 2184 | .generic_expr => |generic| return t.transExpr(scope, generic.chosen, used), | |
| 2185 | .generic_association_expr => |generic| return t.transExpr(scope, generic.expr, used), | |
| 2186 | .generic_default_expr => |generic| return t.transExpr(scope, generic.expr, used), | |
| 2187 | ||
| 2188 | .stmt_expr => |stmt_expr| return t.transStmtExpr(scope, stmt_expr, used), | |
| 2189 | ||
| 2190 | .builtin_convertvector => |convertvector| try t.transConvertvectorExpr(scope, convertvector), | |
| 2191 | .builtin_shufflevector => |shufflevector| try t.transShufflevectorExpr(scope, shufflevector), | |
| 2192 | ||
| 2193 | .compound_stmt, | |
| 2194 | .static_assert, | |
| 2195 | .return_stmt, | |
| 2196 | .null_stmt, | |
| 2197 | .if_stmt, | |
| 2198 | .while_stmt, | |
| 2199 | .do_while_stmt, | |
| 2200 | .for_stmt, | |
| 2201 | .continue_stmt, | |
| 2202 | .break_stmt, | |
| 2203 | .labeled_stmt, | |
| 2204 | .switch_stmt, | |
| 2205 | .case_stmt, | |
| 2206 | .default_stmt, | |
| 2207 | .goto_stmt, | |
| 2208 | .computed_goto_stmt, | |
| 2209 | .gnu_asm_simple, | |
| 2210 | .global_asm, | |
| 2211 | .typedef, | |
| 2212 | .struct_decl, | |
| 2213 | .union_decl, | |
| 2214 | .enum_decl, | |
| 2215 | .function, | |
| 2216 | .param, | |
| 2217 | .variable, | |
| 2218 | .enum_field, | |
| 2219 | .record_field, | |
| 2220 | .struct_forward_decl, | |
| 2221 | .union_forward_decl, | |
| 2222 | .enum_forward_decl, | |
| 2223 | .empty_decl, | |
| 2224 | => unreachable, // not an expression | |
| 2225 | }); | |
| 2226 | } | |
| 2227 | ||
| 2228 | /// Same as `transExpr` but with the knowledge that the operand will be type coerced, and therefore | |
| 2229 | /// an `@as` would be redundant. This is used to prevent redundant `@as` in integer literals. | |
| 2230 | fn transExprCoercing(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { | |
| 2231 | switch (expr.get(t.tree)) { | |
| 2232 | .int_literal => return t.transIntLiteral(scope, expr, used, .no_as), | |
| 2233 | .char_literal => return t.transCharLiteral(scope, expr, used, .no_as), | |
| 2234 | .float_literal => return t.transFloatLiteral(scope, expr, used, .no_as), | |
| 2235 | .cast => |cast| switch (cast.kind) { | |
| 2236 | .no_op => { | |
| 2237 | const operand = cast.operand.get(t.tree); | |
| 2238 | if (operand == .cast) { | |
| 2239 | return t.transCastExpr(scope, operand.cast, cast.qt, used, .no_as); | |
| 2240 | } | |
| 2241 | return t.transExprCoercing(scope, cast.operand, used); | |
| 2242 | }, | |
| 2243 | .lval_to_rval => return t.transExprCoercing(scope, cast.operand, used), | |
| 2244 | else => return t.transCastExpr(scope, cast, cast.qt, used, .no_as), | |
| 2245 | }, | |
| 2246 | .default_init_expr => |default_init| return try t.transDefaultInit(scope, default_init, used, .no_as), | |
| 2247 | .compound_literal_expr => |literal| { | |
| 2248 | if (!literal.thread_local and literal.storage_class != .static) { | |
| 2249 | return t.transExprCoercing(scope, literal.initializer, used); | |
| 2250 | } | |
| 2251 | }, | |
| 2252 | else => {}, | |
| 2253 | } | |
| 2254 | ||
| 2255 | return t.transExpr(scope, expr, used); | |
| 2256 | } | |
| 2257 | ||
| 2258 | fn transBoolExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { | |
| 2259 | switch (expr.get(t.tree)) { | |
| 2260 | .int_literal => { | |
| 2261 | const int_val = t.tree.value_map.get(expr).?; | |
| 2262 | return if (int_val.isZero(t.comp)) | |
| 2263 | ZigTag.false_literal.init() | |
| 2264 | else | |
| 2265 | ZigTag.true_literal.init(); | |
| 2266 | }, | |
| 2267 | .cast => |cast| switch (cast.kind) { | |
| 2268 | .bool_to_int => return t.transExpr(scope, cast.operand, .used), | |
| 2269 | .array_to_pointer => { | |
| 2270 | const operand = cast.operand.get(t.tree); | |
| 2271 | if (operand == .string_literal_expr) { | |
| 2272 | // @intFromPtr("foo") != 0, always true | |
| 2273 | const str = try t.transStringLiteral(scope, cast.operand, operand.string_literal_expr); | |
| 2274 | const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, str); | |
| 2275 | return ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); | |
| 2276 | } | |
| 2277 | }, | |
| 2278 | else => {}, | |
| 2279 | }, | |
| 2280 | else => {}, | |
| 2281 | } | |
| 2282 | ||
| 2283 | const maybe_bool_res = try t.transExpr(scope, expr, .used); | |
| 2284 | if (maybe_bool_res.isBoolRes()) { | |
| 2285 | return maybe_bool_res; | |
| 2286 | } | |
| 2287 | ||
| 2288 | return t.finishBoolExpr(expr.qt(t.tree), maybe_bool_res); | |
| 2289 | } | |
| 2290 | ||
| 2291 | fn finishBoolExpr(t: *Translator, qt: QualType, node: ZigNode) TransError!ZigNode { | |
| 2292 | const sk = qt.scalarKind(t.comp); | |
| 2293 | if (sk == .bool) return node; | |
| 2294 | if (sk == .nullptr_t) { | |
| 2295 | // node == null, always true | |
| 2296 | return ZigTag.equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); | |
| 2297 | } | |
| 2298 | if (sk.isPointer()) { | |
| 2299 | // node != null | |
| 2300 | return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); | |
| 2301 | } | |
| 2302 | if (sk != .none) { | |
| 2303 | // node != 0 | |
| 2304 | return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); | |
| 2305 | } | |
| 2306 | unreachable; // Unexpected bool expression type | |
| 2307 | } | |
| 2308 | ||
| 2309 | fn transCastExpr( | |
| 2310 | t: *Translator, | |
| 2311 | scope: *Scope, | |
| 2312 | cast: Node.Cast, | |
| 2313 | dest_qt: QualType, | |
| 2314 | used: ResultUsed, | |
| 2315 | suppress_as: SuppressCast, | |
| 2316 | ) TransError!ZigNode { | |
| 2317 | const operand = switch (cast.kind) { | |
| 2318 | .no_op => { | |
| 2319 | const operand = cast.operand.get(t.tree); | |
| 2320 | if (operand == .cast) { | |
| 2321 | return t.transCastExpr(scope, operand.cast, cast.qt, used, suppress_as); | |
| 2322 | } | |
| 2323 | return t.transExpr(scope, cast.operand, used); | |
| 2324 | }, | |
| 2325 | .lval_to_rval, .function_to_pointer => { | |
| 2326 | return t.transExpr(scope, cast.operand, used); | |
| 2327 | }, | |
| 2328 | .int_cast => int_cast: { | |
| 2329 | const src_qt = cast.operand.qt(t.tree); | |
| 2330 | ||
| 2331 | if (cast.implicit) { | |
| 2332 | if (t.tree.value_map.get(cast.operand)) |val| { | |
| 2333 | const max_int = try aro.Value.maxInt(dest_qt, t.comp); | |
| 2334 | const min_int = try aro.Value.minInt(dest_qt, t.comp); | |
| 2335 | ||
| 2336 | if (val.compare(.lte, max_int, t.comp) and val.compare(.gte, min_int, t.comp)) { | |
| 2337 | break :int_cast try t.transExprCoercing(scope, cast.operand, .used); | |
| 2338 | } | |
| 2339 | } | |
| 2340 | } | |
| 2341 | const operand = try t.transExpr(scope, cast.operand, .used); | |
| 2342 | break :int_cast try t.transIntCast(operand, src_qt, dest_qt); | |
| 2343 | }, | |
| 2344 | .to_void => { | |
| 2345 | assert(used == .unused); | |
| 2346 | return try t.transExpr(scope, cast.operand, .unused); | |
| 2347 | }, | |
| 2348 | .null_to_pointer => ZigTag.null_literal.init(), | |
| 2349 | .array_to_pointer => array_to_pointer: { | |
| 2350 | const child_qt = dest_qt.childType(t.comp); | |
| 2351 | ||
| 2352 | loop: switch (cast.operand.get(t.tree)) { | |
| 2353 | .string_literal_expr => |literal| { | |
| 2354 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2355 | ||
| 2356 | const ref = if (literal.kind == .utf8 or literal.kind == .ascii) | |
| 2357 | sub_expr_node | |
| 2358 | else | |
| 2359 | try ZigTag.address_of.create(t.arena, sub_expr_node); | |
| 2360 | ||
| 2361 | const casted = if (child_qt.@"const") | |
| 2362 | ref | |
| 2363 | else | |
| 2364 | try ZigTag.const_cast.create(t.arena, sub_expr_node); | |
| 2365 | ||
| 2366 | return t.maybeSuppressResult(used, casted); | |
| 2367 | }, | |
| 2368 | .paren_expr => |paren_expr| { | |
| 2369 | continue :loop paren_expr.operand.get(t.tree); | |
| 2370 | }, | |
| 2371 | .generic_expr => |generic| { | |
| 2372 | continue :loop generic.chosen.get(t.tree); | |
| 2373 | }, | |
| 2374 | .generic_association_expr => |generic| { | |
| 2375 | continue :loop generic.expr.get(t.tree); | |
| 2376 | }, | |
| 2377 | .generic_default_expr => |generic| { | |
| 2378 | continue :loop generic.expr.get(t.tree); | |
| 2379 | }, | |
| 2380 | else => {}, | |
| 2381 | } | |
| 2382 | ||
| 2383 | if (cast.operand.qt(t.tree).arrayLen(t.comp) == null) { | |
| 2384 | return try t.transExpr(scope, cast.operand, used); | |
| 2385 | } | |
| 2386 | ||
| 2387 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2388 | const ref = try ZigTag.address_of.create(t.arena, sub_expr_node); | |
| 2389 | const align_cast = try ZigTag.align_cast.create(t.arena, ref); | |
| 2390 | break :array_to_pointer try ZigTag.ptr_cast.create(t.arena, align_cast); | |
| 2391 | }, | |
| 2392 | .int_to_pointer => int_to_pointer: { | |
| 2393 | var sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2394 | const operand_qt = cast.operand.qt(t.tree); | |
| 2395 | if (t.signedness(operand_qt) == .signed or operand_qt.bitSizeof(t.comp) > t.comp.target.ptrBitWidth()) { | |
| 2396 | sub_expr_node = try ZigTag.as.create(t.arena, .{ | |
| 2397 | .lhs = try ZigTag.type.create(t.arena, "usize"), | |
| 2398 | .rhs = try ZigTag.int_cast.create(t.arena, sub_expr_node), | |
| 2399 | }); | |
| 2400 | } | |
| 2401 | break :int_to_pointer try ZigTag.ptr_from_int.create(t.arena, sub_expr_node); | |
| 2402 | }, | |
| 2403 | .int_to_bool => { | |
| 2404 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2405 | if (sub_expr_node.isBoolRes()) return sub_expr_node; | |
| 2406 | if (cast.operand.qt(t.tree).is(t.comp, .bool)) return sub_expr_node; | |
| 2407 | const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); | |
| 2408 | return t.maybeSuppressResult(used, cmp_node); | |
| 2409 | }, | |
| 2410 | .float_to_bool => { | |
| 2411 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2412 | const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); | |
| 2413 | return t.maybeSuppressResult(used, cmp_node); | |
| 2414 | }, | |
| 2415 | .pointer_to_bool => { | |
| 2416 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2417 | ||
| 2418 | // Special case function pointers as @intFromPtr(expr) != 0 | |
| 2419 | if (cast.operand.qt(t.tree).get(t.comp, .pointer)) |ptr_ty| if (ptr_ty.child.is(t.comp, .func)) { | |
| 2420 | const ptr_node = if (sub_expr_node.tag() == .identifier) | |
| 2421 | try ZigTag.address_of.create(t.arena, sub_expr_node) | |
| 2422 | else | |
| 2423 | sub_expr_node; | |
| 2424 | const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, ptr_node); | |
| 2425 | const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); | |
| 2426 | return t.maybeSuppressResult(used, cmp_node); | |
| 2427 | }; | |
| 2428 | ||
| 2429 | const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.null_literal.init() }); | |
| 2430 | return t.maybeSuppressResult(used, cmp_node); | |
| 2431 | }, | |
| 2432 | .bool_to_int => bool_to_int: { | |
| 2433 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2434 | break :bool_to_int try ZigTag.int_from_bool.create(t.arena, sub_expr_node); | |
| 2435 | }, | |
| 2436 | .bool_to_float => bool_to_float: { | |
| 2437 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2438 | const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); | |
| 2439 | break :bool_to_float try ZigTag.float_from_int.create(t.arena, int_from_bool); | |
| 2440 | }, | |
| 2441 | .bool_to_pointer => bool_to_pointer: { | |
| 2442 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2443 | const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); | |
| 2444 | break :bool_to_pointer try ZigTag.ptr_from_int.create(t.arena, int_from_bool); | |
| 2445 | }, | |
| 2446 | .float_cast => float_cast: { | |
| 2447 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2448 | break :float_cast try ZigTag.float_cast.create(t.arena, sub_expr_node); | |
| 2449 | }, | |
| 2450 | .int_to_float => int_to_float: { | |
| 2451 | const sub_expr_node = try t.transExpr(scope, cast.operand, used); | |
| 2452 | const int_node = if (sub_expr_node.isBoolRes()) | |
| 2453 | try ZigTag.int_from_bool.create(t.arena, sub_expr_node) | |
| 2454 | else | |
| 2455 | sub_expr_node; | |
| 2456 | break :int_to_float try ZigTag.float_from_int.create(t.arena, int_node); | |
| 2457 | }, | |
| 2458 | .float_to_int => float_to_int: { | |
| 2459 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2460 | break :float_to_int try ZigTag.int_from_float.create(t.arena, sub_expr_node); | |
| 2461 | }, | |
| 2462 | .pointer_to_int => pointer_to_int: { | |
| 2463 | const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); | |
| 2464 | const ptr_node = try ZigTag.int_from_ptr.create(t.arena, sub_expr_node); | |
| 2465 | break :pointer_to_int try ZigTag.int_cast.create(t.arena, ptr_node); | |
| 2466 | }, | |
| 2467 | .bitcast => bitcast: { | |
| 2468 | const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); | |
| 2469 | const operand_qt = cast.operand.qt(t.tree); | |
| 2470 | if (dest_qt.isPointer(t.comp) and operand_qt.isPointer(t.comp)) { | |
| 2471 | var casted = try ZigTag.align_cast.create(t.arena, sub_expr_node); | |
| 2472 | casted = try ZigTag.ptr_cast.create(t.arena, casted); | |
| 2473 | ||
| 2474 | const src_elem = operand_qt.childType(t.comp); | |
| 2475 | const dest_elem = dest_qt.childType(t.comp); | |
| 2476 | if ((src_elem.@"const" or src_elem.is(t.comp, .func)) and !dest_elem.@"const") { | |
| 2477 | casted = try ZigTag.const_cast.create(t.arena, casted); | |
| 2478 | } | |
| 2479 | if (src_elem.@"volatile" and !dest_elem.@"volatile") { | |
| 2480 | casted = try ZigTag.volatile_cast.create(t.arena, casted); | |
| 2481 | } | |
| 2482 | break :bitcast casted; | |
| 2483 | } | |
| 2484 | ||
| 2485 | break :bitcast try ZigTag.bit_cast.create(t.arena, sub_expr_node); | |
| 2486 | }, | |
| 2487 | .union_cast => union_cast: { | |
| 2488 | const union_type = try t.transType(scope, dest_qt, cast.l_paren); | |
| 2489 | ||
| 2490 | const operand_qt = cast.operand.qt(t.tree); | |
| 2491 | const union_base = dest_qt.base(t.comp); | |
| 2492 | const field = for (union_base.type.@"union".fields) |field| { | |
| 2493 | if (field.qt.eql(operand_qt, t.comp)) break field; | |
| 2494 | } else unreachable; | |
| 2495 | const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ | |
| 2496 | .parent = union_base.qt, | |
| 2497 | .field = field.qt, | |
| 2498 | }).? else field.name.lookup(t.comp); | |
| 2499 | ||
| 2500 | const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); | |
| 2501 | field_init.* = .{ | |
| 2502 | .name = field_name, | |
| 2503 | .value = try t.transExpr(scope, cast.operand, .used), | |
| 2504 | }; | |
| 2505 | break :union_cast try ZigTag.container_init.create(t.arena, .{ | |
| 2506 | .lhs = union_type, | |
| 2507 | .inits = field_init[0..1], | |
| 2508 | }); | |
| 2509 | }, | |
| 2510 | else => return t.fail(error.UnsupportedTranslation, cast.l_paren, "TODO translate {s} cast", .{@tagName(cast.kind)}), | |
| 2511 | }; | |
| 2512 | if (suppress_as == .no_as) return t.maybeSuppressResult(used, operand); | |
| 2513 | if (used == .unused) return t.maybeSuppressResult(used, operand); | |
| 2514 | const as = try ZigTag.as.create(t.arena, .{ | |
| 2515 | .lhs = try t.transType(scope, dest_qt, cast.l_paren), | |
| 2516 | .rhs = operand, | |
| 2517 | }); | |
| 2518 | return as; | |
| 2519 | } | |
| 2520 | ||
| 2521 | fn transIntCast(t: *Translator, operand: ZigNode, src_qt: QualType, dest_qt: QualType) !ZigNode { | |
| 2522 | const src_dest_order = src_qt.intRankOrder(dest_qt, t.comp); | |
| 2523 | const different_sign = t.signedness(src_qt) != t.signedness(dest_qt); | |
| 2524 | const needs_bitcast = different_sign and !(t.signedness(src_qt) == .unsigned and src_dest_order == .lt); | |
| 2525 | ||
| 2526 | var casted = operand; | |
| 2527 | if (casted.isBoolRes()) { | |
| 2528 | casted = try ZigTag.int_from_bool.create(t.arena, casted); | |
| 2529 | } else if (src_dest_order == .gt) { | |
| 2530 | // No C type is smaller than the 1 bit from @intFromBool | |
| 2531 | casted = try ZigTag.truncate.create(t.arena, casted); | |
| 2532 | } | |
| 2533 | if (needs_bitcast) { | |
| 2534 | if (src_dest_order != .eq) { | |
| 2535 | casted = try ZigTag.as.create(t.arena, .{ | |
| 2536 | .lhs = try t.transTypeIntWidthOf(dest_qt, t.signedness(src_qt) == .signed), | |
| 2537 | .rhs = casted, | |
| 2538 | }); | |
| 2539 | } | |
| 2540 | return ZigTag.bit_cast.create(t.arena, casted); | |
| 2541 | } | |
| 2542 | return casted; | |
| 2543 | } | |
| 2544 | ||
| 2545 | /// Same as `transExpr` but adds a `&` if the expression is an identifier referencing a function type. | |
| 2546 | fn transPointerCastExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { | |
| 2547 | const sub_expr_node = try t.transExpr(scope, expr, .used); | |
| 2548 | switch (expr.get(t.tree)) { | |
| 2549 | .cast => |cast| if (cast.kind == .function_to_pointer and sub_expr_node.tag() == .identifier) { | |
| 2550 | return ZigTag.address_of.create(t.arena, sub_expr_node); | |
| 2551 | }, | |
| 2552 | else => {}, | |
| 2553 | } | |
| 2554 | return sub_expr_node; | |
| 2555 | } | |
| 2556 | ||
| 2557 | fn transDeclRefExpr(t: *Translator, scope: *Scope, decl_ref: Node.DeclRef) TransError!ZigNode { | |
| 2558 | const name = t.tree.tokSlice(decl_ref.name_tok); | |
| 2559 | const maybe_alias = scope.getAlias(name); | |
| 2560 | const mangled_name = maybe_alias orelse name; | |
| 2561 | ||
| 2562 | switch (decl_ref.decl.get(t.tree)) { | |
| 2563 | .function => |function| if (function.definition == null and function.body == null) { | |
| 2564 | // Try translating the decl again in case of out of scope declaration. | |
| 2565 | try t.transFnDecl(scope, function); | |
| 2566 | }, | |
| 2567 | else => {}, | |
| 2568 | } | |
| 2569 | ||
| 2570 | const decl = decl_ref.decl.get(t.tree); | |
| 2571 | const ref_expr = blk: { | |
| 2572 | const identifier = try ZigTag.identifier.create(t.arena, mangled_name); | |
| 2573 | if (decl_ref.qt.is(t.comp, .func) and maybe_alias != null) { | |
| 2574 | break :blk try ZigTag.field_access.create(t.arena, .{ | |
| 2575 | .lhs = identifier, | |
| 2576 | .field_name = name, | |
| 2577 | }); | |
| 2578 | } | |
| 2579 | if (decl == .variable and maybe_alias != null) { | |
| 2580 | switch (decl.variable.storage_class) { | |
| 2581 | .@"extern", .static => { | |
| 2582 | break :blk try ZigTag.field_access.create(t.arena, .{ | |
| 2583 | .lhs = identifier, | |
| 2584 | .field_name = name, | |
| 2585 | }); | |
| 2586 | }, | |
| 2587 | else => {}, | |
| 2588 | } | |
| 2589 | } | |
| 2590 | break :blk identifier; | |
| 2591 | }; | |
| 2592 | ||
| 2593 | scope.skipVariableDiscard(mangled_name); | |
| 2594 | return ref_expr; | |
| 2595 | } | |
| 2596 | ||
| 2597 | fn transBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { | |
| 2598 | const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); | |
| 2599 | const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); | |
| 2600 | ||
| 2601 | const lhs = if (lhs_uncasted.isBoolRes()) | |
| 2602 | try ZigTag.int_from_bool.create(t.arena, lhs_uncasted) | |
| 2603 | else | |
| 2604 | lhs_uncasted; | |
| 2605 | ||
| 2606 | const rhs = if (rhs_uncasted.isBoolRes()) | |
| 2607 | try ZigTag.int_from_bool.create(t.arena, rhs_uncasted) | |
| 2608 | else | |
| 2609 | rhs_uncasted; | |
| 2610 | ||
| 2611 | return t.createBinOpNode(op_id, lhs, rhs); | |
| 2612 | } | |
| 2613 | ||
| 2614 | fn transBoolBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op: ZigTag) !ZigNode { | |
| 2615 | std.debug.assert(op == .@"and" or op == .@"or"); | |
| 2616 | ||
| 2617 | const lhs = try t.transBoolExpr(scope, bin.lhs); | |
| 2618 | const rhs = try t.transBoolExpr(scope, bin.rhs); | |
| 2619 | ||
| 2620 | return t.createBinOpNode(op, lhs, rhs); | |
| 2621 | } | |
| 2622 | ||
| 2623 | fn transShiftExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) !ZigNode { | |
| 2624 | std.debug.assert(op_id == .shl or op_id == .shr); | |
| 2625 | ||
| 2626 | // lhs >> @intCast(rh) | |
| 2627 | const lhs = try t.transExpr(scope, bin.lhs, .used); | |
| 2628 | ||
| 2629 | const rhs = try t.transExprCoercing(scope, bin.rhs, .used); | |
| 2630 | const rhs_casted = try ZigTag.int_cast.create(t.arena, rhs); | |
| 2631 | ||
| 2632 | return t.createBinOpNode(op_id, lhs, rhs_casted); | |
| 2633 | } | |
| 2634 | ||
| 2635 | fn transCondExpr( | |
| 2636 | t: *Translator, | |
| 2637 | scope: *Scope, | |
| 2638 | conditional: Node.Conditional, | |
| 2639 | used: ResultUsed, | |
| 2640 | ) TransError!ZigNode { | |
| 2641 | var cond_scope: Scope.Condition = .{ | |
| 2642 | .base = .{ | |
| 2643 | .parent = scope, | |
| 2644 | .id = .condition, | |
| 2645 | }, | |
| 2646 | }; | |
| 2647 | defer cond_scope.deinit(); | |
| 2648 | ||
| 2649 | const res_is_bool = conditional.qt.is(t.comp, .bool); | |
| 2650 | const cond = try t.transBoolExpr(&cond_scope.base, conditional.cond); | |
| 2651 | ||
| 2652 | var then_body = try t.transExpr(scope, conditional.then_expr, used); | |
| 2653 | if (!res_is_bool and then_body.isBoolRes()) { | |
| 2654 | then_body = try ZigTag.int_from_bool.create(t.arena, then_body); | |
| 2655 | } | |
| 2656 | ||
| 2657 | var else_body = try t.transExpr(scope, conditional.else_expr, used); | |
| 2658 | if (!res_is_bool and else_body.isBoolRes()) { | |
| 2659 | else_body = try ZigTag.int_from_bool.create(t.arena, else_body); | |
| 2660 | } | |
| 2661 | ||
| 2662 | // The `ResultUsed` is forwarded to both branches so no need to suppress the result here. | |
| 2663 | return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body }); | |
| 2664 | } | |
| 2665 | ||
| 2666 | fn transBinaryCondExpr( | |
| 2667 | t: *Translator, | |
| 2668 | scope: *Scope, | |
| 2669 | conditional: Node.Conditional, | |
| 2670 | used: ResultUsed, | |
| 2671 | ) TransError!ZigNode { | |
| 2672 | // GNU extension of the ternary operator where the middle expression is | |
| 2673 | // omitted, the condition itself is returned if it evaluates to true. | |
| 2674 | ||
| 2675 | if (used == .unused) { | |
| 2676 | // Result unused so this can be translated as | |
| 2677 | // if (condition) else_expr; | |
| 2678 | var cond_scope: Scope.Condition = .{ | |
| 2679 | .base = .{ | |
| 2680 | .parent = scope, | |
| 2681 | .id = .condition, | |
| 2682 | }, | |
| 2683 | }; | |
| 2684 | defer cond_scope.deinit(); | |
| 2685 | ||
| 2686 | return ZigTag.@"if".create(t.arena, .{ | |
| 2687 | .cond = try t.transBoolExpr(&cond_scope.base, conditional.cond), | |
| 2688 | .then = try t.transExpr(scope, conditional.else_expr, .unused), | |
| 2689 | .@"else" = null, | |
| 2690 | }); | |
| 2691 | } | |
| 2692 | ||
| 2693 | const res_is_bool = conditional.qt.is(t.comp, .bool); | |
| 2694 | // c: (condition)?:(else_expr) | |
| 2695 | // zig: (blk: { | |
| 2696 | // const _cond_temp = (condition); | |
| 2697 | // break :blk if (_cond_temp) _cond_temp else (else_expr); | |
| 2698 | // }) | |
| 2699 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 2700 | defer block_scope.deinit(); | |
| 2701 | ||
| 2702 | const cond_temp = try block_scope.reserveMangledName("cond_temp"); | |
| 2703 | const init_node = try t.transExpr(&block_scope.base, conditional.cond, .used); | |
| 2704 | const temp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = cond_temp, .init = init_node }); | |
| 2705 | try block_scope.statements.append(t.gpa, temp_decl); | |
| 2706 | ||
| 2707 | var cond_scope: Scope.Condition = .{ | |
| 2708 | .base = .{ | |
| 2709 | .parent = &block_scope.base, | |
| 2710 | .id = .condition, | |
| 2711 | }, | |
| 2712 | }; | |
| 2713 | defer cond_scope.deinit(); | |
| 2714 | ||
| 2715 | const cond_ident = try ZigTag.identifier.create(t.arena, cond_temp); | |
| 2716 | const cond_node = try t.finishBoolExpr(conditional.cond.qt(t.tree), cond_ident); | |
| 2717 | var then_body = cond_ident; | |
| 2718 | if (!res_is_bool and init_node.isBoolRes()) { | |
| 2719 | then_body = try ZigTag.int_from_bool.create(t.arena, then_body); | |
| 2720 | } | |
| 2721 | ||
| 2722 | var else_body = try t.transExpr(&block_scope.base, conditional.else_expr, .used); | |
| 2723 | if (!res_is_bool and else_body.isBoolRes()) { | |
| 2724 | else_body = try ZigTag.int_from_bool.create(t.arena, else_body); | |
| 2725 | } | |
| 2726 | const if_node = try ZigTag.@"if".create(t.arena, .{ | |
| 2727 | .cond = cond_node, | |
| 2728 | .then = then_body, | |
| 2729 | .@"else" = else_body, | |
| 2730 | }); | |
| 2731 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2732 | .label = block_scope.label, | |
| 2733 | .val = if_node, | |
| 2734 | }); | |
| 2735 | try block_scope.statements.append(t.gpa, break_node); | |
| 2736 | return block_scope.complete(); | |
| 2737 | } | |
| 2738 | ||
| 2739 | fn transCommaExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) TransError!ZigNode { | |
| 2740 | if (used == .unused) { | |
| 2741 | const lhs = try t.transExprCoercing(scope, bin.lhs, .unused); | |
| 2742 | try scope.appendNode(lhs); | |
| 2743 | const rhs = try t.transExprCoercing(scope, bin.rhs, .unused); | |
| 2744 | return rhs; | |
| 2745 | } | |
| 2746 | ||
| 2747 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 2748 | defer block_scope.deinit(); | |
| 2749 | ||
| 2750 | const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .unused); | |
| 2751 | try block_scope.statements.append(t.gpa, lhs); | |
| 2752 | ||
| 2753 | const rhs = try t.transExprCoercing(&block_scope.base, bin.rhs, .used); | |
| 2754 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2755 | .label = block_scope.label, | |
| 2756 | .val = rhs, | |
| 2757 | }); | |
| 2758 | try block_scope.statements.append(t.gpa, break_node); | |
| 2759 | ||
| 2760 | return try block_scope.complete(); | |
| 2761 | } | |
| 2762 | ||
| 2763 | fn transAssignExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) !ZigNode { | |
| 2764 | if (used == .unused) { | |
| 2765 | const lhs = try t.transExpr(scope, bin.lhs, .used); | |
| 2766 | var rhs = try t.transExprCoercing(scope, bin.rhs, .used); | |
| 2767 | ||
| 2768 | const lhs_qt = bin.lhs.qt(t.tree); | |
| 2769 | if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { | |
| 2770 | rhs = try ZigTag.int_from_bool.create(t.arena, rhs); | |
| 2771 | } | |
| 2772 | ||
| 2773 | return t.createBinOpNode(.assign, lhs, rhs); | |
| 2774 | } | |
| 2775 | ||
| 2776 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 2777 | defer block_scope.deinit(); | |
| 2778 | ||
| 2779 | const tmp = try block_scope.reserveMangledName("tmp"); | |
| 2780 | ||
| 2781 | var rhs = try t.transExpr(&block_scope.base, bin.rhs, .used); | |
| 2782 | const lhs_qt = bin.lhs.qt(t.tree); | |
| 2783 | if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { | |
| 2784 | rhs = try ZigTag.int_from_bool.create(t.arena, rhs); | |
| 2785 | } | |
| 2786 | ||
| 2787 | const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = rhs }); | |
| 2788 | try block_scope.statements.append(t.gpa, tmp_decl); | |
| 2789 | ||
| 2790 | const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .used); | |
| 2791 | const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); | |
| 2792 | ||
| 2793 | const assign = try t.createBinOpNode(.assign, lhs, tmp_ident); | |
| 2794 | try block_scope.statements.append(t.gpa, assign); | |
| 2795 | ||
| 2796 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2797 | .label = block_scope.label, | |
| 2798 | .val = tmp_ident, | |
| 2799 | }); | |
| 2800 | try block_scope.statements.append(t.gpa, break_node); | |
| 2801 | ||
| 2802 | return try block_scope.complete(); | |
| 2803 | } | |
| 2804 | ||
| 2805 | fn transCompoundAssign( | |
| 2806 | t: *Translator, | |
| 2807 | scope: *Scope, | |
| 2808 | assign: Node.Binary, | |
| 2809 | used: ResultUsed, | |
| 2810 | ) !ZigNode { | |
| 2811 | // If the result is unused we can try using the equivalent Zig operator | |
| 2812 | // without a block | |
| 2813 | if (used == .unused) { | |
| 2814 | if (try t.transCompoundAssignSimple(scope, null, assign)) |some| { | |
| 2815 | return some; | |
| 2816 | } | |
| 2817 | } | |
| 2818 | ||
| 2819 | // Otherwise we need to wrap the the compound assignment in a block. | |
| 2820 | var block_scope = try Scope.Block.init(t, scope, used == .used); | |
| 2821 | defer block_scope.deinit(); | |
| 2822 | const ref = try block_scope.reserveMangledName("ref"); | |
| 2823 | ||
| 2824 | const lhs_expr = try t.transExpr(&block_scope.base, assign.lhs, .used); | |
| 2825 | const addr_of = try ZigTag.address_of.create(t.arena, lhs_expr); | |
| 2826 | const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = addr_of }); | |
| 2827 | try block_scope.statements.append(t.gpa, ref_decl); | |
| 2828 | ||
| 2829 | const lhs_node = try ZigTag.identifier.create(t.arena, ref); | |
| 2830 | const ref_node = try ZigTag.deref.create(t.arena, lhs_node); | |
| 2831 | ||
| 2832 | // Use the equivalent Zig operator if possible. | |
| 2833 | if (try t.transCompoundAssignSimple(scope, ref_node, assign)) |some| { | |
| 2834 | try block_scope.statements.append(t.gpa, some); | |
| 2835 | } else { | |
| 2836 | const old_dummy = t.compound_assign_dummy; | |
| 2837 | defer t.compound_assign_dummy = old_dummy; | |
| 2838 | t.compound_assign_dummy = ref_node; | |
| 2839 | ||
| 2840 | // Otherwise do the operation and assignment separately. | |
| 2841 | const rhs_node = try t.transExprCoercing(&block_scope.base, assign.rhs, .used); | |
| 2842 | const assign_node = try t.createBinOpNode(.assign, ref_node, rhs_node); | |
| 2843 | try block_scope.statements.append(t.gpa, assign_node); | |
| 2844 | } | |
| 2845 | ||
| 2846 | if (used == .used) { | |
| 2847 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2848 | .label = block_scope.label, | |
| 2849 | .val = ref_node, | |
| 2850 | }); | |
| 2851 | try block_scope.statements.append(t.gpa, break_node); | |
| 2852 | } | |
| 2853 | return block_scope.complete(); | |
| 2854 | } | |
| 2855 | ||
| 2856 | /// Translates compound assignment using the equivalent Zig operator if possible. | |
| 2857 | fn transCompoundAssignSimple(t: *Translator, scope: *Scope, lhs_dummy_opt: ?ZigNode, assign: Node.Binary) TransError!?ZigNode { | |
| 2858 | const assign_rhs = assign.rhs.get(t.tree); | |
| 2859 | if (assign_rhs == .cast) return null; | |
| 2860 | ||
| 2861 | const is_signed = t.signedness(assign.qt) == .signed; | |
| 2862 | switch (assign_rhs) { | |
| 2863 | .div_expr, .mod_expr => if (is_signed) return null, | |
| 2864 | else => {}, | |
| 2865 | } | |
| 2866 | const lhs_ptr = assign.qt.isPointer(t.comp); | |
| 2867 | ||
| 2868 | const bin, const op: ZigTag, const cast: enum { none, shift, usize } = switch (assign_rhs) { | |
| 2869 | .add_expr => |bin| .{ | |
| 2870 | bin, | |
| 2871 | if (t.typeHasWrappingOverflow(bin.qt)) .add_wrap_assign else .add_assign, | |
| 2872 | if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, | |
| 2873 | }, | |
| 2874 | .sub_expr => |bin| .{ | |
| 2875 | bin, | |
| 2876 | if (t.typeHasWrappingOverflow(bin.qt)) .sub_wrap_assign else .sub_assign, | |
| 2877 | if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, | |
| 2878 | }, | |
| 2879 | .mul_expr => |bin| .{ | |
| 2880 | bin, | |
| 2881 | if (t.typeHasWrappingOverflow(bin.qt)) .mul_wrap_assign else .mul_assign, | |
| 2882 | .none, | |
| 2883 | }, | |
| 2884 | .mod_expr => |bin| .{ bin, .mod_assign, .none }, | |
| 2885 | .div_expr => |bin| .{ bin, .div_assign, .none }, | |
| 2886 | .shl_expr => |bin| .{ bin, .shl_assign, .shift }, | |
| 2887 | .shr_expr => |bin| .{ bin, .shr_assign, .shift }, | |
| 2888 | .bit_and_expr => |bin| .{ bin, .bit_and_assign, .none }, | |
| 2889 | .bit_xor_expr => |bin| .{ bin, .bit_xor_assign, .none }, | |
| 2890 | .bit_or_expr => |bin| .{ bin, .bit_or_assign, .none }, | |
| 2891 | else => unreachable, | |
| 2892 | }; | |
| 2893 | ||
| 2894 | const lhs_node = blk: { | |
| 2895 | const old_dummy = t.compound_assign_dummy; | |
| 2896 | defer t.compound_assign_dummy = old_dummy; | |
| 2897 | t.compound_assign_dummy = lhs_dummy_opt orelse try t.transExpr(scope, assign.lhs, .used); | |
| 2898 | ||
| 2899 | break :blk try t.transExpr(scope, bin.lhs, .used); | |
| 2900 | }; | |
| 2901 | ||
| 2902 | const rhs_node = try t.transExprCoercing(scope, bin.rhs, .used); | |
| 2903 | const casted_rhs = switch (cast) { | |
| 2904 | .none => rhs_node, | |
| 2905 | .shift => try ZigTag.int_cast.create(t.arena, rhs_node), | |
| 2906 | .usize => try t.usizeCastForWrappingPtrArithmetic(rhs_node), | |
| 2907 | }; | |
| 2908 | return try t.createBinOpNode(op, lhs_node, casted_rhs); | |
| 2909 | } | |
| 2910 | ||
| 2911 | fn transIncDecExpr( | |
| 2912 | t: *Translator, | |
| 2913 | scope: *Scope, | |
| 2914 | un: Node.Unary, | |
| 2915 | position: enum { pre, post }, | |
| 2916 | kind: enum { inc, dec }, | |
| 2917 | used: ResultUsed, | |
| 2918 | ) !ZigNode { | |
| 2919 | const is_wrapping = t.typeHasWrappingOverflow(un.qt); | |
| 2920 | const op: ZigTag = switch (kind) { | |
| 2921 | .inc => if (is_wrapping) .add_wrap_assign else .add_assign, | |
| 2922 | .dec => if (is_wrapping) .sub_wrap_assign else .sub_assign, | |
| 2923 | }; | |
| 2924 | ||
| 2925 | const one_literal = ZigTag.one_literal.init(); | |
| 2926 | if (used == .unused) { | |
| 2927 | const operand = try t.transExpr(scope, un.operand, .used); | |
| 2928 | return try t.createBinOpNode(op, operand, one_literal); | |
| 2929 | } | |
| 2930 | ||
| 2931 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 2932 | defer block_scope.deinit(); | |
| 2933 | ||
| 2934 | const ref = try block_scope.reserveMangledName("ref"); | |
| 2935 | const operand = try t.transExprCoercing(&block_scope.base, un.operand, .used); | |
| 2936 | const operand_ref = try ZigTag.address_of.create(t.arena, operand); | |
| 2937 | const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = operand_ref }); | |
| 2938 | try block_scope.statements.append(t.gpa, ref_decl); | |
| 2939 | ||
| 2940 | const ref_ident = try ZigTag.identifier.create(t.arena, ref); | |
| 2941 | const ref_deref = try ZigTag.deref.create(t.arena, ref_ident); | |
| 2942 | const effect = try t.createBinOpNode(op, ref_deref, one_literal); | |
| 2943 | ||
| 2944 | switch (position) { | |
| 2945 | .pre => { | |
| 2946 | try block_scope.statements.append(t.gpa, effect); | |
| 2947 | ||
| 2948 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2949 | .label = block_scope.label, | |
| 2950 | .val = ref_deref, | |
| 2951 | }); | |
| 2952 | try block_scope.statements.append(t.gpa, break_node); | |
| 2953 | }, | |
| 2954 | .post => { | |
| 2955 | const tmp = try block_scope.reserveMangledName("tmp"); | |
| 2956 | const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = ref_deref }); | |
| 2957 | try block_scope.statements.append(t.gpa, tmp_decl); | |
| 2958 | ||
| 2959 | try block_scope.statements.append(t.gpa, effect); | |
| 2960 | ||
| 2961 | const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); | |
| 2962 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2963 | .label = block_scope.label, | |
| 2964 | .val = tmp_ident, | |
| 2965 | }); | |
| 2966 | try block_scope.statements.append(t.gpa, break_node); | |
| 2967 | }, | |
| 2968 | } | |
| 2969 | ||
| 2970 | return try block_scope.complete(); | |
| 2971 | } | |
| 2972 | ||
| 2973 | fn transPtrDiffExpr(t: *Translator, scope: *Scope, bin: Node.Binary) TransError!ZigNode { | |
| 2974 | const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); | |
| 2975 | const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); | |
| 2976 | ||
| 2977 | const lhs = try ZigTag.int_from_ptr.create(t.arena, lhs_uncasted); | |
| 2978 | const rhs = try ZigTag.int_from_ptr.create(t.arena, rhs_uncasted); | |
| 2979 | ||
| 2980 | const sub_res = try t.createBinOpNode(.sub_wrap, lhs, rhs); | |
| 2981 | ||
| 2982 | // @divExact(@as(<platform-ptrdiff_t>, @bitCast(@intFromPtr(lhs)) -% @intFromPtr(rhs)), @sizeOf(<lhs target type>)) | |
| 2983 | const ptrdiff_type = try t.transTypeIntWidthOf(bin.qt, true); | |
| 2984 | ||
| 2985 | const bitcast = try ZigTag.as.create(t.arena, .{ | |
| 2986 | .lhs = ptrdiff_type, | |
| 2987 | .rhs = try ZigTag.bit_cast.create(t.arena, sub_res), | |
| 2988 | }); | |
| 2989 | ||
| 2990 | // C standard requires that pointer subtraction operands are of the same type, | |
| 2991 | // otherwise it is undefined behavior. So we can assume the left and right | |
| 2992 | // sides are the same Type and arbitrarily choose left. | |
| 2993 | const lhs_ty = try t.transType(scope, bin.lhs.qt(t.tree), bin.lhs.tok(t.tree)); | |
| 2994 | const c_pointer = t.getContainer(lhs_ty).?; | |
| 2995 | ||
| 2996 | if (c_pointer.castTag(.c_pointer)) |c_pointer_payload| { | |
| 2997 | const sizeof = try ZigTag.sizeof.create(t.arena, c_pointer_payload.data.elem_type); | |
| 2998 | return ZigTag.div_exact.create(t.arena, .{ | |
| 2999 | .lhs = bitcast, | |
| 3000 | .rhs = sizeof, | |
| 3001 | }); | |
| 3002 | } else { | |
| 3003 | // This is an opaque/incomplete type. This subtraction exhibits Undefined Behavior by the C99 spec. | |
| 3004 | // However, allowing subtraction on `void *` and function pointers is a commonly used extension. | |
| 3005 | // So, just return the value in byte units, mirroring the behavior of this language extension as implemented by GCC and Clang. | |
| 3006 | return bitcast; | |
| 3007 | } | |
| 3008 | } | |
| 3009 | ||
| 3010 | /// Translate an arithmetic expression with a pointer operand and a signed-integer operand. | |
| 3011 | /// Zig requires a usize argument for pointer arithmetic, so we intCast to isize and then | |
| 3012 | /// bitcast to usize; pointer wraparound makes the math work. | |
| 3013 | /// Zig pointer addition is not commutative (unlike C); the pointer operand needs to be on the left. | |
| 3014 | /// The + operator in C is not a sequence point so it should be safe to switch the order if necessary. | |
| 3015 | fn transPointerArithmeticSignedOp(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { | |
| 3016 | std.debug.assert(op_id == .add or op_id == .sub); | |
| 3017 | ||
| 3018 | const lhs_qt = bin.lhs.qt(t.tree); | |
| 3019 | const swap_operands = op_id == .add and t.signedness(lhs_qt) == .signed; | |
| 3020 | ||
| 3021 | const swizzled_lhs = if (swap_operands) bin.rhs else bin.lhs; | |
| 3022 | const swizzled_rhs = if (swap_operands) bin.lhs else bin.rhs; | |
| 3023 | ||
| 3024 | const lhs_node = try t.transExpr(scope, swizzled_lhs, .used); | |
| 3025 | const rhs_node = try t.transExpr(scope, swizzled_rhs, .used); | |
| 3026 | ||
| 3027 | const bitcast_node = try t.usizeCastForWrappingPtrArithmetic(rhs_node); | |
| 3028 | ||
| 3029 | return t.createBinOpNode(op_id, lhs_node, bitcast_node); | |
| 3030 | } | |
| 3031 | ||
| 3032 | fn transMemberAccess( | |
| 3033 | t: *Translator, | |
| 3034 | scope: *Scope, | |
| 3035 | kind: enum { normal, ptr }, | |
| 3036 | member_access: Node.MemberAccess, | |
| 3037 | opt_base: ?ZigNode, | |
| 3038 | ) TransError!ZigNode { | |
| 3039 | const base_info = switch (kind) { | |
| 3040 | .normal => member_access.base.qt(t.tree), | |
| 3041 | .ptr => member_access.base.qt(t.tree).childType(t.comp), | |
| 3042 | }; | |
| 3043 | const record = base_info.getRecord(t.comp).?; | |
| 3044 | const field = record.fields[member_access.member_index]; | |
| 3045 | const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ | |
| 3046 | .parent = base_info.base(t.comp).qt, | |
| 3047 | .field = field.qt, | |
| 3048 | }).? else field.name.lookup(t.comp); | |
| 3049 | const base_node = opt_base orelse try t.transExpr(scope, member_access.base, .used); | |
| 3050 | const lhs = switch (kind) { | |
| 3051 | .normal => base_node, | |
| 3052 | .ptr => try ZigTag.deref.create(t.arena, base_node), | |
| 3053 | }; | |
| 3054 | const field_access = try ZigTag.field_access.create(t.arena, .{ | |
| 3055 | .lhs = lhs, | |
| 3056 | .field_name = field_name, | |
| 3057 | }); | |
| 3058 | ||
| 3059 | // Flexible array members are translated as member functions. | |
| 3060 | if (member_access.member_index == record.fields.len - 1 or base_info.base(t.comp).type == .@"union") { | |
| 3061 | if (field.qt.get(t.comp, .array)) |array_ty| { | |
| 3062 | if (array_ty.len == .incomplete or (array_ty.len == .fixed and array_ty.len.fixed == 0)) { | |
| 3063 | return ZigTag.call.create(t.arena, .{ .lhs = field_access, .args = &.{} }); | |
| 3064 | } | |
| 3065 | } | |
| 3066 | } | |
| 3067 | ||
| 3068 | return field_access; | |
| 3069 | } | |
| 3070 | ||
| 3071 | fn transArrayAccess(t: *Translator, scope: *Scope, array_access: Node.ArrayAccess, opt_base: ?ZigNode) TransError!ZigNode { | |
| 3072 | // Unwrap the base statement if it's an array decayed to a bare pointer type | |
| 3073 | // so that we index the array itself | |
| 3074 | const base = base: { | |
| 3075 | const base = array_access.base.get(t.tree); | |
| 3076 | if (base != .cast) break :base array_access.base; | |
| 3077 | if (base.cast.kind != .array_to_pointer) break :base array_access.base; | |
| 3078 | break :base base.cast.operand; | |
| 3079 | }; | |
| 3080 | ||
| 3081 | const base_node = opt_base orelse try t.transExpr(scope, base, .used); | |
| 3082 | const index = index: { | |
| 3083 | const index = try t.transExpr(scope, array_access.index, .used); | |
| 3084 | const index_qt = array_access.index.qt(t.tree); | |
| 3085 | const maybe_bigger_than_usize = switch (index_qt.base(t.comp).type) { | |
| 3086 | .bool => { | |
| 3087 | break :index try ZigTag.int_from_bool.create(t.arena, index); | |
| 3088 | }, | |
| 3089 | .int => |int| switch (int) { | |
| 3090 | .long_long, .ulong_long, .int128, .uint128 => true, | |
| 3091 | else => false, | |
| 3092 | }, | |
| 3093 | .bit_int => |bit_int| bit_int.bits > t.comp.target.ptrBitWidth(), | |
| 3094 | else => unreachable, | |
| 3095 | }; | |
| 3096 | ||
| 3097 | const is_nonnegative_int_literal = if (t.tree.value_map.get(array_access.index)) |val| | |
| 3098 | val.compare(.gte, .zero, t.comp) | |
| 3099 | else | |
| 3100 | false; | |
| 3101 | const is_signed = t.signedness(index_qt) == .signed; | |
| 3102 | ||
| 3103 | if (is_signed and !is_nonnegative_int_literal) { | |
| 3104 | // First cast to `isize` to get proper sign extension and | |
| 3105 | // then @bitCast to `usize` to satisfy the compiler. | |
| 3106 | const index_isize = try ZigTag.as.create(t.arena, .{ | |
| 3107 | .lhs = try ZigTag.type.create(t.arena, "isize"), | |
| 3108 | .rhs = try ZigTag.int_cast.create(t.arena, index), | |
| 3109 | }); | |
| 3110 | break :index try ZigTag.bit_cast.create(t.arena, index_isize); | |
| 3111 | } | |
| 3112 | ||
| 3113 | if (maybe_bigger_than_usize) { | |
| 3114 | break :index try ZigTag.int_cast.create(t.arena, index); | |
| 3115 | } | |
| 3116 | break :index index; | |
| 3117 | }; | |
| 3118 | ||
| 3119 | return ZigTag.array_access.create(t.arena, .{ | |
| 3120 | .lhs = base_node, | |
| 3121 | .rhs = index, | |
| 3122 | }); | |
| 3123 | } | |
| 3124 | ||
| 3125 | fn transOffsetof(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { | |
| 3126 | // Translate __builtin_offsetof(T, designator) as | |
| 3127 | // @intFromPtr(&(@as(*allowzero T, @ptrFromInt(0)).designator)) | |
| 3128 | const member = try t.transMemberDesignator(scope, arg); | |
| 3129 | const address = try ZigTag.address_of.create(t.arena, member); | |
| 3130 | return ZigTag.int_from_ptr.create(t.arena, address); | |
| 3131 | } | |
| 3132 | ||
| 3133 | fn transMemberDesignator(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { | |
| 3134 | switch (arg.get(t.tree)) { | |
| 3135 | .default_init_expr => |default| { | |
| 3136 | const elem_node = try t.transType(scope, default.qt, default.last_tok); | |
| 3137 | const ptr_ty = try ZigTag.single_pointer.create(t.arena, .{ | |
| 3138 | .elem_type = elem_node, | |
| 3139 | .is_allowzero = true, | |
| 3140 | .is_const = false, | |
| 3141 | .is_volatile = false, | |
| 3142 | }); | |
| 3143 | const zero = try ZigTag.ptr_from_int.create(t.arena, ZigTag.zero_literal.init()); | |
| 3144 | return ZigTag.as.create(t.arena, .{ .lhs = ptr_ty, .rhs = zero }); | |
| 3145 | }, | |
| 3146 | .array_access_expr => |access| { | |
| 3147 | const base = try t.transMemberDesignator(scope, access.base); | |
| 3148 | return t.transArrayAccess(scope, access, base); | |
| 3149 | }, | |
| 3150 | .member_access_expr => |access| { | |
| 3151 | const base = try t.transMemberDesignator(scope, access.base); | |
| 3152 | return t.transMemberAccess(scope, .normal, access, base); | |
| 3153 | }, | |
| 3154 | .cast => |cast| { | |
| 3155 | assert(cast.kind == .array_to_pointer); | |
| 3156 | return t.transMemberDesignator(scope, cast.operand); | |
| 3157 | }, | |
| 3158 | else => unreachable, | |
| 3159 | } | |
| 3160 | } | |
| 3161 | ||
| 3162 | fn transBuiltinCall( | |
| 3163 | t: *Translator, | |
| 3164 | scope: *Scope, | |
| 3165 | call: Node.BuiltinCall, | |
| 3166 | used: ResultUsed, | |
| 3167 | ) TransError!ZigNode { | |
| 3168 | const builtin_name = t.tree.tokSlice(call.builtin_tok); | |
| 3169 | if (std.mem.eql(u8, builtin_name, "__builtin_offsetof")) { | |
| 3170 | const res = try t.transOffsetof(scope, call.args[0]); | |
| 3171 | return t.maybeSuppressResult(used, res); | |
| 3172 | } | |
| 3173 | ||
| 3174 | const builtin = builtins.map.get(builtin_name) orelse | |
| 3175 | return t.fail(error.UnsupportedTranslation, call.builtin_tok, "TODO implement function '{s}' in std.zig.c_builtins", .{builtin_name}); | |
| 3176 | ||
| 3177 | if (builtin.tag) |tag| switch (tag) { | |
| 3178 | .byte_swap, .ceil, .cos, .sin, .exp, .exp2, .exp10, .abs, .log, .log2, .log10, .round, .sqrt, .trunc, .floor => { | |
| 3179 | assert(call.args.len == 1); | |
| 3180 | const arg = try t.transExprCoercing(scope, call.args[0], .used); | |
| 3181 | const arg_ty = try t.transType(scope, call.args[0].qt(t.tree), call.args[0].tok(t.tree)); | |
| 3182 | const coerced = try ZigTag.as.create(t.arena, .{ .lhs = arg_ty, .rhs = arg }); | |
| 3183 | ||
| 3184 | const ptr = try t.arena.create(ast.Payload.UnOp); | |
| 3185 | ptr.* = .{ .base = .{ .tag = tag }, .data = coerced }; | |
| 3186 | return t.maybeSuppressResult(used, ZigNode.initPayload(&ptr.base)); | |
| 3187 | }, | |
| 3188 | .@"unreachable" => return ZigTag.@"unreachable".init(), | |
| 3189 | else => unreachable, | |
| 3190 | }; | |
| 3191 | ||
| 3192 | const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); | |
| 3193 | for (call.args, arg_nodes) |c_arg, *zig_arg| { | |
| 3194 | zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); | |
| 3195 | } | |
| 3196 | ||
| 3197 | const builtin_identifier = try ZigTag.identifier.create(t.arena, "__builtin"); | |
| 3198 | const field_access = try ZigTag.field_access.create(t.arena, .{ | |
| 3199 | .lhs = builtin_identifier, | |
| 3200 | .field_name = builtin.name, | |
| 3201 | }); | |
| 3202 | ||
| 3203 | const res = try ZigTag.call.create(t.arena, .{ | |
| 3204 | .lhs = field_access, | |
| 3205 | .args = arg_nodes, | |
| 3206 | }); | |
| 3207 | if (call.qt.is(t.comp, .void)) return res; | |
| 3208 | return t.maybeSuppressResult(used, res); | |
| 3209 | } | |
| 3210 | ||
| 3211 | fn transCall( | |
| 3212 | t: *Translator, | |
| 3213 | scope: *Scope, | |
| 3214 | call: Node.Call, | |
| 3215 | used: ResultUsed, | |
| 3216 | ) TransError!ZigNode { | |
| 3217 | const raw_fn_expr = try t.transExpr(scope, call.callee, .used); | |
| 3218 | const fn_expr = blk: { | |
| 3219 | loop: switch (call.callee.get(t.tree)) { | |
| 3220 | .paren_expr => |paren_expr| { | |
| 3221 | continue :loop paren_expr.operand.get(t.tree); | |
| 3222 | }, | |
| 3223 | .decl_ref_expr => |decl_ref| { | |
| 3224 | if (decl_ref.qt.is(t.comp, .func)) break :blk raw_fn_expr; | |
| 3225 | }, | |
| 3226 | .cast => |cast| { | |
| 3227 | if (cast.kind == .function_to_pointer) { | |
| 3228 | continue :loop cast.operand.get(t.tree); | |
| 3229 | } | |
| 3230 | }, | |
| 3231 | .deref_expr, .addr_of_expr => |un| { | |
| 3232 | continue :loop un.operand.get(t.tree); | |
| 3233 | }, | |
| 3234 | .generic_expr => |generic| { | |
| 3235 | continue :loop generic.chosen.get(t.tree); | |
| 3236 | }, | |
| 3237 | .generic_association_expr => |generic| { | |
| 3238 | continue :loop generic.expr.get(t.tree); | |
| 3239 | }, | |
| 3240 | .generic_default_expr => |generic| { | |
| 3241 | continue :loop generic.expr.get(t.tree); | |
| 3242 | }, | |
| 3243 | else => {}, | |
| 3244 | } | |
| 3245 | break :blk try ZigTag.unwrap.create(t.arena, raw_fn_expr); | |
| 3246 | }; | |
| 3247 | ||
| 3248 | const callee_qt = call.callee.qt(t.tree); | |
| 3249 | const maybe_ptr_ty = callee_qt.get(t.comp, .pointer); | |
| 3250 | const func_qt = if (maybe_ptr_ty) |ptr| ptr.child else callee_qt; | |
| 3251 | const func_ty = func_qt.get(t.comp, .func).?; | |
| 3252 | ||
| 3253 | const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); | |
| 3254 | for (call.args, arg_nodes, 0..) |c_arg, *zig_arg, i| { | |
| 3255 | if (i < func_ty.params.len) { | |
| 3256 | zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); | |
| 3257 | ||
| 3258 | if (zig_arg.isBoolRes() and !func_ty.params[i].qt.is(t.comp, .bool)) { | |
| 3259 | // In C the result type of a boolean expression is int. If this result is passed as | |
| 3260 | // an argument to a function whose parameter is also int, there is no cast. Therefore | |
| 3261 | // in Zig we'll need to cast it from bool to u1 (which will safely coerce to c_int). | |
| 3262 | zig_arg.* = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); | |
| 3263 | } | |
| 3264 | } else { | |
| 3265 | zig_arg.* = try t.transExpr(scope, c_arg, .used); | |
| 3266 | ||
| 3267 | if (zig_arg.isBoolRes()) { | |
| 3268 | // Same as above but now we don't have a result type. | |
| 3269 | const u1_node = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); | |
| 3270 | const c_int_node = try ZigTag.type.create(t.arena, "c_int"); | |
| 3271 | zig_arg.* = try ZigTag.as.create(t.arena, .{ .lhs = c_int_node, .rhs = u1_node }); | |
| 3272 | } | |
| 3273 | } | |
| 3274 | } | |
| 3275 | ||
| 3276 | const res = try ZigTag.call.create(t.arena, .{ | |
| 3277 | .lhs = fn_expr, | |
| 3278 | .args = arg_nodes, | |
| 3279 | }); | |
| 3280 | if (call.qt.is(t.comp, .void)) return res; | |
| 3281 | return t.maybeSuppressResult(used, res); | |
| 3282 | } | |
| 3283 | ||
| 3284 | const SuppressCast = enum { with_as, no_as }; | |
| 3285 | ||
| 3286 | fn transIntLiteral( | |
| 3287 | t: *Translator, | |
| 3288 | scope: *Scope, | |
| 3289 | literal_index: Node.Index, | |
| 3290 | used: ResultUsed, | |
| 3291 | suppress_as: SuppressCast, | |
| 3292 | ) TransError!ZigNode { | |
| 3293 | const val = t.tree.value_map.get(literal_index).?; | |
| 3294 | const int_lit_node = try t.createIntNode(val); | |
| 3295 | if (suppress_as == .no_as) { | |
| 3296 | return t.maybeSuppressResult(used, int_lit_node); | |
| 3297 | } | |
| 3298 | ||
| 3299 | // Integer literals in C have types, and this can matter for several reasons. | |
| 3300 | // For example, this is valid C: | |
| 3301 | // unsigned char y = 256; | |
| 3302 | // How this gets evaluated is the 256 is an integer, which gets truncated to signed char, then bit-casted | |
| 3303 | // to unsigned char, resulting in 0. In order for this to work, we have to emit this zig code: | |
| 3304 | // var y = @as(u8, @bitCast(@as(i8, @truncate(@as(c_int, 256))))); | |
| 3305 | ||
| 3306 | // @as(T, x) | |
| 3307 | const ty_node = try t.transType(scope, literal_index.qt(t.tree), literal_index.tok(t.tree)); | |
| 3308 | const as = try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_lit_node }); | |
| 3309 | return t.maybeSuppressResult(used, as); | |
| 3310 | } | |
| 3311 | ||
| 3312 | fn transCharLiteral( | |
| 3313 | t: *Translator, | |
| 3314 | scope: *Scope, | |
| 3315 | literal_index: Node.Index, | |
| 3316 | used: ResultUsed, | |
| 3317 | suppress_as: SuppressCast, | |
| 3318 | ) TransError!ZigNode { | |
| 3319 | const val = t.tree.value_map.get(literal_index).?; | |
| 3320 | const char_literal = literal_index.get(t.tree).char_literal; | |
| 3321 | const narrow = char_literal.kind == .ascii or char_literal.kind == .utf8; | |
| 3322 | ||
| 3323 | // C has a somewhat obscure feature called multi-character character constant | |
| 3324 | // e.g. 'abcd' | |
| 3325 | const int_value = val.toInt(u32, t.comp).?; | |
| 3326 | const int_lit_node = if (char_literal.kind == .ascii and int_value > 255) | |
| 3327 | try t.createNumberNode(int_value, .int) | |
| 3328 | else | |
| 3329 | try t.createCharLiteralNode(narrow, int_value); | |
| 3330 | ||
| 3331 | if (suppress_as == .no_as) { | |
| 3332 | return t.maybeSuppressResult(used, int_lit_node); | |
| 3333 | } | |
| 3334 | ||
| 3335 | // See comment in `transIntLiteral` for why this code is here. | |
| 3336 | // @as(T, x) | |
| 3337 | const as_node = try ZigTag.as.create(t.arena, .{ | |
| 3338 | .lhs = try t.transType(scope, char_literal.qt, char_literal.literal_tok), | |
| 3339 | .rhs = int_lit_node, | |
| 3340 | }); | |
| 3341 | return t.maybeSuppressResult(used, as_node); | |
| 3342 | } | |
| 3343 | ||
| 3344 | fn transFloatLiteral( | |
| 3345 | t: *Translator, | |
| 3346 | scope: *Scope, | |
| 3347 | literal_index: Node.Index, | |
| 3348 | used: ResultUsed, | |
| 3349 | suppress_as: SuppressCast, | |
| 3350 | ) TransError!ZigNode { | |
| 3351 | const val = t.tree.value_map.get(literal_index).?; | |
| 3352 | const float_literal = literal_index.get(t.tree).float_literal; | |
| 3353 | ||
| 3354 | var allocating: std.Io.Writer.Allocating = .init(t.gpa); | |
| 3355 | defer allocating.deinit(); | |
| 3356 | _ = val.print(float_literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 3357 | ||
| 3358 | const float_lit_node = try ZigTag.float_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); | |
| 3359 | if (suppress_as == .no_as) { | |
| 3360 | return t.maybeSuppressResult(used, float_lit_node); | |
| 3361 | } | |
| 3362 | ||
| 3363 | const as_node = try ZigTag.as.create(t.arena, .{ | |
| 3364 | .lhs = try t.transType(scope, float_literal.qt, float_literal.literal_tok), | |
| 3365 | .rhs = float_lit_node, | |
| 3366 | }); | |
| 3367 | return t.maybeSuppressResult(used, as_node); | |
| 3368 | } | |
| 3369 | ||
| 3370 | fn transStringLiteral( | |
| 3371 | t: *Translator, | |
| 3372 | scope: *Scope, | |
| 3373 | expr: Node.Index, | |
| 3374 | literal: Node.CharLiteral, | |
| 3375 | ) TransError!ZigNode { | |
| 3376 | switch (literal.kind) { | |
| 3377 | .ascii, .utf8 => return t.transNarrowStringLiteral(expr, literal), | |
| 3378 | .utf16, .utf32, .wide => { | |
| 3379 | const name = try std.fmt.allocPrint(t.arena, "{s}_string_{d}", .{ @tagName(literal.kind), t.getMangle() }); | |
| 3380 | ||
| 3381 | const array_type = try t.transTypeInit(scope, literal.qt, expr, literal.literal_tok); | |
| 3382 | const lit_array = try t.transStringLiteralInitializer(expr, literal, array_type); | |
| 3383 | const decl = try ZigTag.var_simple.create(t.arena, .{ .name = name, .init = lit_array }); | |
| 3384 | try scope.appendNode(decl); | |
| 3385 | return ZigTag.identifier.create(t.arena, name); | |
| 3386 | }, | |
| 3387 | } | |
| 3388 | } | |
| 3389 | ||
| 3390 | fn transNarrowStringLiteral( | |
| 3391 | t: *Translator, | |
| 3392 | expr: Node.Index, | |
| 3393 | literal: Node.CharLiteral, | |
| 3394 | ) TransError!ZigNode { | |
| 3395 | const val = t.tree.value_map.get(expr).?; | |
| 3396 | ||
| 3397 | const bytes = t.comp.interner.get(val.ref()).bytes; | |
| 3398 | var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); | |
| 3399 | defer allocating.deinit(); | |
| 3400 | ||
| 3401 | aro.Value.printString(bytes, literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 3402 | ||
| 3403 | return ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); | |
| 3404 | } | |
| 3405 | ||
| 3406 | /// Translate a string literal that is initializing an array. In general narrow string | |
| 3407 | /// literals become `"<string>".*` or `"<string>"[0..<size>].*` if they need truncation. | |
| 3408 | /// Wide string literals become an array of integers. zero-fillers pad out the array to | |
| 3409 | /// the appropriate length, if necessary. | |
| 3410 | fn transStringLiteralInitializer( | |
| 3411 | t: *Translator, | |
| 3412 | expr: Node.Index, | |
| 3413 | literal: Node.CharLiteral, | |
| 3414 | array_type: ZigNode, | |
| 3415 | ) TransError!ZigNode { | |
| 3416 | assert(array_type.tag() == .array_type or array_type.tag() == .null_sentinel_array_type); | |
| 3417 | ||
| 3418 | const is_narrow = literal.kind == .ascii or literal.kind == .utf8; | |
| 3419 | ||
| 3420 | // The length of the string literal excluding the sentinel. | |
| 3421 | const str_length = literal.qt.arrayLen(t.comp).? - 1; | |
| 3422 | ||
| 3423 | const payload = (array_type.castTag(.array_type) orelse array_type.castTag(.null_sentinel_array_type).?).data; | |
| 3424 | const array_size = payload.len; | |
| 3425 | const elem_type = payload.elem_type; | |
| 3426 | ||
| 3427 | if (array_size == 0) return ZigTag.empty_array.create(t.arena, array_type); | |
| 3428 | ||
| 3429 | const num_inits = @min(str_length, array_size); | |
| 3430 | if (num_inits == 0) { | |
| 3431 | return ZigTag.array_filler.create(t.arena, .{ | |
| 3432 | .type = elem_type, | |
| 3433 | .filler = ZigTag.zero_literal.init(), | |
| 3434 | .count = array_size, | |
| 3435 | }); | |
| 3436 | } | |
| 3437 | ||
| 3438 | const init_node = if (is_narrow) blk: { | |
| 3439 | // "string literal".* or string literal"[0..num_inits].* | |
| 3440 | var str = try t.transNarrowStringLiteral(expr, literal); | |
| 3441 | if (str_length != array_size) str = try ZigTag.string_slice.create(t.arena, .{ .string = str, .end = num_inits }); | |
| 3442 | break :blk try ZigTag.deref.create(t.arena, str); | |
| 3443 | } else blk: { | |
| 3444 | const size = literal.qt.childType(t.comp).sizeof(t.comp); | |
| 3445 | ||
| 3446 | const val = t.tree.value_map.get(expr).?; | |
| 3447 | const bytes = t.comp.interner.get(val.ref()).bytes; | |
| 3448 | ||
| 3449 | const init_list = try t.arena.alloc(ZigNode, @intCast(num_inits)); | |
| 3450 | for (init_list, 0..) |*item, i| { | |
| 3451 | const codepoint = switch (size) { | |
| 3452 | 2 => @as(*const u16, @alignCast(@ptrCast(bytes.ptr + i * 2))).*, | |
| 3453 | 4 => @as(*const u32, @alignCast(@ptrCast(bytes.ptr + i * 4))).*, | |
| 3454 | else => unreachable, | |
| 3455 | }; | |
| 3456 | item.* = try t.createCharLiteralNode(false, codepoint); | |
| 3457 | } | |
| 3458 | const init_args: ast.Payload.Array.ArrayTypeInfo = .{ .len = num_inits, .elem_type = elem_type }; | |
| 3459 | const init_array_type = if (array_type.tag() == .array_type) | |
| 3460 | try ZigTag.array_type.create(t.arena, init_args) | |
| 3461 | else | |
| 3462 | try ZigTag.null_sentinel_array_type.create(t.arena, init_args); | |
| 3463 | break :blk try ZigTag.array_init.create(t.arena, .{ | |
| 3464 | .cond = init_array_type, | |
| 3465 | .cases = init_list, | |
| 3466 | }); | |
| 3467 | }; | |
| 3468 | ||
| 3469 | if (num_inits == array_size) return init_node; | |
| 3470 | assert(array_size > str_length); // If array_size <= str_length, `num_inits == array_size` and we've already returned. | |
| 3471 | ||
| 3472 | const filler_node = try ZigTag.array_filler.create(t.arena, .{ | |
| 3473 | .type = elem_type, | |
| 3474 | .filler = ZigTag.zero_literal.init(), | |
| 3475 | .count = array_size - str_length, | |
| 3476 | }); | |
| 3477 | return ZigTag.array_cat.create(t.arena, .{ .lhs = init_node, .rhs = filler_node }); | |
| 3478 | } | |
| 3479 | ||
| 3480 | fn transCompoundLiteral( | |
| 3481 | t: *Translator, | |
| 3482 | scope: *Scope, | |
| 3483 | literal: Node.CompoundLiteral, | |
| 3484 | used: ResultUsed, | |
| 3485 | ) TransError!ZigNode { | |
| 3486 | if (used == .unused) { | |
| 3487 | return t.transExpr(scope, literal.initializer, .unused); | |
| 3488 | } | |
| 3489 | ||
| 3490 | // TODO taking a reference to a compound literal should result in a mutable | |
| 3491 | // pointer (unless the literal is const). | |
| 3492 | ||
| 3493 | const initializer = try t.transExprCoercing(scope, literal.initializer, .used); | |
| 3494 | const ty = try t.transType(scope, literal.qt, literal.l_paren_tok); | |
| 3495 | if (!literal.thread_local and literal.storage_class != .static) { | |
| 3496 | // In the simple case a compound literal can be translated | |
| 3497 | // simply as `@as(type, initializer)`. | |
| 3498 | return ZigTag.as.create(t.arena, .{ .lhs = ty, .rhs = initializer }); | |
| 3499 | } | |
| 3500 | ||
| 3501 | // Otherwise static or thread local compound literals are translated as | |
| 3502 | // a reference to a variable wrapped in a struct. | |
| 3503 | ||
| 3504 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 3505 | defer block_scope.deinit(); | |
| 3506 | ||
| 3507 | const tmp = try block_scope.reserveMangledName("tmp"); | |
| 3508 | const wrapped_name = "compound_literal"; | |
| 3509 | ||
| 3510 | // const tmp = struct { var compound_literal = initializer }; | |
| 3511 | const temp_decl = try ZigTag.var_decl.create(t.arena, .{ | |
| 3512 | .is_pub = false, | |
| 3513 | .is_const = literal.qt.@"const", | |
| 3514 | .is_extern = false, | |
| 3515 | .is_export = false, | |
| 3516 | .is_threadlocal = literal.thread_local, | |
| 3517 | .linksection_string = null, | |
| 3518 | .alignment = null, | |
| 3519 | .name = wrapped_name, | |
| 3520 | .type = ty, | |
| 3521 | .init = initializer, | |
| 3522 | }); | |
| 3523 | const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = tmp, .init = temp_decl }); | |
| 3524 | try block_scope.statements.append(t.gpa, wrapped); | |
| 3525 | ||
| 3526 | // break :blk tmp.compound_literal | |
| 3527 | const static_tmp_ident = try ZigTag.identifier.create(t.arena, tmp); | |
| 3528 | const field_access = try ZigTag.field_access.create(t.arena, .{ | |
| 3529 | .lhs = static_tmp_ident, | |
| 3530 | .field_name = wrapped_name, | |
| 3531 | }); | |
| 3532 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 3533 | .label = block_scope.label, | |
| 3534 | .val = field_access, | |
| 3535 | }); | |
| 3536 | try block_scope.statements.append(t.gpa, break_node); | |
| 3537 | ||
| 3538 | return block_scope.complete(); | |
| 3539 | } | |
| 3540 | ||
| 3541 | fn transDefaultInit( | |
| 3542 | t: *Translator, | |
| 3543 | scope: *Scope, | |
| 3544 | default_init: Node.DefaultInit, | |
| 3545 | used: ResultUsed, | |
| 3546 | suppress_as: SuppressCast, | |
| 3547 | ) TransError!ZigNode { | |
| 3548 | assert(used == .used); | |
| 3549 | const type_node = try t.transType(scope, default_init.qt, default_init.last_tok); | |
| 3550 | return try t.createZeroValueNode(default_init.qt, type_node, suppress_as); | |
| 3551 | } | |
| 3552 | ||
| 3553 | fn transArrayInit( | |
| 3554 | t: *Translator, | |
| 3555 | scope: *Scope, | |
| 3556 | array_init: Node.ContainerInit, | |
| 3557 | used: ResultUsed, | |
| 3558 | ) TransError!ZigNode { | |
| 3559 | assert(used == .used); | |
| 3560 | const array_item_qt = array_init.container_qt.childType(t.comp); | |
| 3561 | const array_item_type = try t.transType(scope, array_item_qt, array_init.l_brace_tok); | |
| 3562 | var maybe_lhs: ?ZigNode = null; | |
| 3563 | var val_list: std.ArrayListUnmanaged(ZigNode) = .empty; | |
| 3564 | defer val_list.deinit(t.gpa); | |
| 3565 | var i: usize = 0; | |
| 3566 | while (i < array_init.items.len) { | |
| 3567 | const rhs = switch (array_init.items[i].get(t.tree)) { | |
| 3568 | .array_filler_expr => |array_filler| blk: { | |
| 3569 | const node = try ZigTag.array_filler.create(t.arena, .{ | |
| 3570 | .type = array_item_type, | |
| 3571 | .filler = try t.createZeroValueNode(array_item_qt, array_item_type, .no_as), | |
| 3572 | .count = @intCast(array_filler.count), | |
| 3573 | }); | |
| 3574 | i += 1; | |
| 3575 | break :blk node; | |
| 3576 | }, | |
| 3577 | else => blk: { | |
| 3578 | defer val_list.clearRetainingCapacity(); | |
| 3579 | while (i < array_init.items.len) : (i += 1) { | |
| 3580 | if (array_init.items[i].get(t.tree) == .array_filler_expr) break; | |
| 3581 | const expr = try t.transExprCoercing(scope, array_init.items[i], .used); | |
| 3582 | try val_list.append(t.gpa, expr); | |
| 3583 | } | |
| 3584 | const array_type = try ZigTag.array_type.create(t.arena, .{ | |
| 3585 | .elem_type = array_item_type, | |
| 3586 | .len = val_list.items.len, | |
| 3587 | }); | |
| 3588 | const array_init_node = try ZigTag.array_init.create(t.arena, .{ | |
| 3589 | .cond = array_type, | |
| 3590 | .cases = try t.arena.dupe(ZigNode, val_list.items), | |
| 3591 | }); | |
| 3592 | break :blk array_init_node; | |
| 3593 | }, | |
| 3594 | }; | |
| 3595 | maybe_lhs = if (maybe_lhs) |lhs| blk: { | |
| 3596 | const cat = try ZigTag.array_cat.create(t.arena, .{ | |
| 3597 | .lhs = lhs, | |
| 3598 | .rhs = rhs, | |
| 3599 | }); | |
| 3600 | break :blk cat; | |
| 3601 | } else rhs; | |
| 3602 | } | |
| 3603 | return maybe_lhs orelse try ZigTag.container_init_dot.create(t.arena, &.{}); | |
| 3604 | } | |
| 3605 | ||
| 3606 | fn transUnionInit( | |
| 3607 | t: *Translator, | |
| 3608 | scope: *Scope, | |
| 3609 | union_init: Node.UnionInit, | |
| 3610 | used: ResultUsed, | |
| 3611 | ) TransError!ZigNode { | |
| 3612 | assert(used == .used); | |
| 3613 | const init_expr = union_init.initializer orelse | |
| 3614 | return ZigTag.undefined_literal.init(); | |
| 3615 | ||
| 3616 | if (init_expr.get(t.tree) == .default_init_expr) { | |
| 3617 | return try t.transExpr(scope, init_expr, used); | |
| 3618 | } | |
| 3619 | ||
| 3620 | const union_type = try t.transType(scope, union_init.union_qt, union_init.l_brace_tok); | |
| 3621 | ||
| 3622 | const union_base = union_init.union_qt.base(t.comp); | |
| 3623 | const field = union_base.type.@"union".fields[union_init.field_index]; | |
| 3624 | const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ | |
| 3625 | .parent = union_base.qt, | |
| 3626 | .field = field.qt, | |
| 3627 | }).? else field.name.lookup(t.comp); | |
| 3628 | ||
| 3629 | const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); | |
| 3630 | field_init.* = .{ | |
| 3631 | .name = field_name, | |
| 3632 | .value = try t.transExprCoercing(scope, init_expr, .used), | |
| 3633 | }; | |
| 3634 | const container_init = try ZigTag.container_init.create(t.arena, .{ | |
| 3635 | .lhs = union_type, | |
| 3636 | .inits = field_init[0..1], | |
| 3637 | }); | |
| 3638 | return container_init; | |
| 3639 | } | |
| 3640 | ||
| 3641 | fn transStructInit( | |
| 3642 | t: *Translator, | |
| 3643 | scope: *Scope, | |
| 3644 | struct_init: Node.ContainerInit, | |
| 3645 | used: ResultUsed, | |
| 3646 | ) TransError!ZigNode { | |
| 3647 | assert(used == .used); | |
| 3648 | const struct_type = try t.transType(scope, struct_init.container_qt, struct_init.l_brace_tok); | |
| 3649 | const field_inits = try t.arena.alloc(ast.Payload.ContainerInit.Initializer, struct_init.items.len); | |
| 3650 | ||
| 3651 | const struct_base = struct_init.container_qt.base(t.comp); | |
| 3652 | for ( | |
| 3653 | field_inits, | |
| 3654 | struct_init.items, | |
| 3655 | struct_base.type.@"struct".fields, | |
| 3656 | ) |*init, field_expr, field| { | |
| 3657 | const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ | |
| 3658 | .parent = struct_base.qt, | |
| 3659 | .field = field.qt, | |
| 3660 | }).? else field.name.lookup(t.comp); | |
| 3661 | init.* = .{ | |
| 3662 | .name = field_name, | |
| 3663 | .value = try t.transExprCoercing(scope, field_expr, .used), | |
| 3664 | }; | |
| 3665 | } | |
| 3666 | ||
| 3667 | const container_init = try ZigTag.container_init.create(t.arena, .{ | |
| 3668 | .lhs = struct_type, | |
| 3669 | .inits = field_inits, | |
| 3670 | }); | |
| 3671 | return container_init; | |
| 3672 | } | |
| 3673 | ||
| 3674 | fn transTypeInfo( | |
| 3675 | t: *Translator, | |
| 3676 | scope: *Scope, | |
| 3677 | op: ZigTag, | |
| 3678 | typeinfo: Node.TypeInfo, | |
| 3679 | ) TransError!ZigNode { | |
| 3680 | const operand = operand: { | |
| 3681 | if (typeinfo.expr) |expr| { | |
| 3682 | const operand = try t.transExpr(scope, expr, .used); | |
| 3683 | break :operand try ZigTag.typeof.create(t.arena, operand); | |
| 3684 | } | |
| 3685 | break :operand try t.transType(scope, typeinfo.operand_qt, typeinfo.op_tok); | |
| 3686 | }; | |
| 3687 | ||
| 3688 | const payload = try t.arena.create(ast.Payload.UnOp); | |
| 3689 | payload.* = .{ | |
| 3690 | .base = .{ .tag = op }, | |
| 3691 | .data = operand, | |
| 3692 | }; | |
| 3693 | return ZigNode.initPayload(&payload.base); | |
| 3694 | } | |
| 3695 | ||
| 3696 | fn transStmtExpr( | |
| 3697 | t: *Translator, | |
| 3698 | scope: *Scope, | |
| 3699 | stmt_expr: Node.Unary, | |
| 3700 | used: ResultUsed, | |
| 3701 | ) TransError!ZigNode { | |
| 3702 | const compound_stmt = stmt_expr.operand.get(t.tree).compound_stmt; | |
| 3703 | if (used == .unused) { | |
| 3704 | return t.transCompoundStmt(scope, compound_stmt); | |
| 3705 | } | |
| 3706 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 3707 | defer block_scope.deinit(); | |
| 3708 | ||
| 3709 | for (compound_stmt.body[0 .. compound_stmt.body.len - 1]) |stmt| { | |
| 3710 | const result = try t.transStmt(&block_scope.base, stmt); | |
| 3711 | switch (result.tag()) { | |
| 3712 | .declaration, .empty_block => {}, | |
| 3713 | else => try block_scope.statements.append(t.gpa, result), | |
| 3714 | } | |
| 3715 | } | |
| 3716 | ||
| 3717 | const last_result = try t.transExpr(&block_scope.base, compound_stmt.body[compound_stmt.body.len - 1], .used); | |
| 3718 | switch (last_result.tag()) { | |
| 3719 | .declaration, .empty_block => {}, | |
| 3720 | else => { | |
| 3721 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 3722 | .label = block_scope.label, | |
| 3723 | .val = last_result, | |
| 3724 | }); | |
| 3725 | try block_scope.statements.append(t.gpa, break_node); | |
| 3726 | }, | |
| 3727 | } | |
| 3728 | return block_scope.complete(); | |
| 3729 | } | |
| 3730 | ||
| 3731 | fn transConvertvectorExpr( | |
| 3732 | t: *Translator, | |
| 3733 | scope: *Scope, | |
| 3734 | convertvector: Node.Convertvector, | |
| 3735 | ) TransError!ZigNode { | |
| 3736 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 3737 | defer block_scope.deinit(); | |
| 3738 | ||
| 3739 | const src_expr_node = try t.transExpr(&block_scope.base, convertvector.operand, .used); | |
| 3740 | const tmp = try block_scope.reserveMangledName("tmp"); | |
| 3741 | const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = src_expr_node }); | |
| 3742 | try block_scope.statements.append(t.gpa, tmp_decl); | |
| 3743 | const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); | |
| 3744 | ||
| 3745 | const dest_type_node = try t.transType(&block_scope.base, convertvector.dest_qt, convertvector.builtin_tok); | |
| 3746 | const dest_vec_ty = convertvector.dest_qt.get(t.comp, .vector).?; | |
| 3747 | const src_vec_ty = convertvector.operand.qt(t.tree).get(t.comp, .vector).?; | |
| 3748 | ||
| 3749 | const src_elem_sk = src_vec_ty.elem.scalarKind(t.comp); | |
| 3750 | const dest_elem_sk = convertvector.dest_qt.childType(t.comp).scalarKind(t.comp); | |
| 3751 | ||
| 3752 | const items = try t.arena.alloc(ZigNode, dest_vec_ty.len); | |
| 3753 | for (items, 0..dest_vec_ty.len) |*item, i| { | |
| 3754 | const value = try ZigTag.array_access.create(t.arena, .{ | |
| 3755 | .lhs = tmp_ident, | |
| 3756 | .rhs = try t.createNumberNode(i, .int), | |
| 3757 | }); | |
| 3758 | ||
| 3759 | if (src_elem_sk == .float and dest_elem_sk == .float) { | |
| 3760 | item.* = try ZigTag.float_cast.create(t.arena, value); | |
| 3761 | } else if (src_elem_sk == .float) { | |
| 3762 | item.* = try ZigTag.int_from_float.create(t.arena, value); | |
| 3763 | } else if (dest_elem_sk == .float) { | |
| 3764 | item.* = try ZigTag.float_from_int.create(t.arena, value); | |
| 3765 | } else { | |
| 3766 | item.* = try t.transIntCast(value, src_vec_ty.elem, dest_vec_ty.elem); | |
| 3767 | } | |
| 3768 | } | |
| 3769 | ||
| 3770 | const vec_init = try ZigTag.array_init.create(t.arena, .{ | |
| 3771 | .cond = dest_type_node, | |
| 3772 | .cases = items, | |
| 3773 | }); | |
| 3774 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 3775 | .label = block_scope.label, | |
| 3776 | .val = vec_init, | |
| 3777 | }); | |
| 3778 | try block_scope.statements.append(t.gpa, break_node); | |
| 3779 | ||
| 3780 | return block_scope.complete(); | |
| 3781 | } | |
| 3782 | ||
| 3783 | fn transShufflevectorExpr( | |
| 3784 | t: *Translator, | |
| 3785 | scope: *Scope, | |
| 3786 | shufflevector: Node.Shufflevector, | |
| 3787 | ) TransError!ZigNode { | |
| 3788 | if (shufflevector.indexes.len == 0) { | |
| 3789 | return t.fail(error.UnsupportedTranslation, shufflevector.builtin_tok, "@shuffle needs at least 1 index", .{}); | |
| 3790 | } | |
| 3791 | ||
| 3792 | const a = try t.transExpr(scope, shufflevector.lhs, .used); | |
| 3793 | const b = try t.transExpr(scope, shufflevector.rhs, .used); | |
| 3794 | ||
| 3795 | // First two arguments to __builtin_shufflevector must be the same type | |
| 3796 | const vector_child_type = try t.vectorTypeInfo(a, "child"); | |
| 3797 | const vector_len = try t.vectorTypeInfo(a, "len"); | |
| 3798 | const shuffle_mask = blk: { | |
| 3799 | const mask_len = shufflevector.indexes.len; | |
| 3800 | ||
| 3801 | const mask_type = try ZigTag.vector.create(t.arena, .{ | |
| 3802 | .lhs = try t.createNumberNode(mask_len, .int), | |
| 3803 | .rhs = try ZigTag.type.create(t.arena, "i32"), | |
| 3804 | }); | |
| 3805 | ||
| 3806 | const init_list = try t.arena.alloc(ZigNode, mask_len); | |
| 3807 | for (init_list, shufflevector.indexes) |*init, index| { | |
| 3808 | const index_expr = try t.transExprCoercing(scope, index, .used); | |
| 3809 | const converted_index = try t.createHelperCallNode(.shuffleVectorIndex, &.{ index_expr, vector_len }); | |
| 3810 | init.* = converted_index; | |
| 3811 | } | |
| 3812 | ||
| 3813 | break :blk try ZigTag.array_init.create(t.arena, .{ | |
| 3814 | .cond = mask_type, | |
| 3815 | .cases = init_list, | |
| 3816 | }); | |
| 3817 | }; | |
| 3818 | ||
| 3819 | return ZigTag.shuffle.create(t.arena, .{ | |
| 3820 | .element_type = vector_child_type, | |
| 3821 | .a = a, | |
| 3822 | .b = b, | |
| 3823 | .mask_vector = shuffle_mask, | |
| 3824 | }); | |
| 3825 | } | |
| 3826 | ||
| 3827 | // ===================== | |
| 3828 | // Node creation helpers | |
| 3829 | // ===================== | |
| 3830 | ||
| 3831 | fn createZeroValueNode( | |
| 3832 | t: *Translator, | |
| 3833 | qt: QualType, | |
| 3834 | type_node: ZigNode, | |
| 3835 | suppress_as: SuppressCast, | |
| 3836 | ) !ZigNode { | |
| 3837 | switch (qt.base(t.comp).type) { | |
| 3838 | .bool => return ZigTag.false_literal.init(), | |
| 3839 | .int, .bit_int, .float => { | |
| 3840 | const zero_literal = ZigTag.zero_literal.init(); | |
| 3841 | return switch (suppress_as) { | |
| 3842 | .with_as => try t.createBinOpNode(.as, type_node, zero_literal), | |
| 3843 | .no_as => zero_literal, | |
| 3844 | }; | |
| 3845 | }, | |
| 3846 | .pointer => { | |
| 3847 | const null_literal = ZigTag.null_literal.init(); | |
| 3848 | return switch (suppress_as) { | |
| 3849 | .with_as => try t.createBinOpNode(.as, type_node, null_literal), | |
| 3850 | .no_as => null_literal, | |
| 3851 | }; | |
| 3852 | }, | |
| 3853 | else => {}, | |
| 3854 | } | |
| 3855 | return try ZigTag.std_mem_zeroes.create(t.arena, type_node); | |
| 3856 | } | |
| 3857 | ||
| 3858 | fn createIntNode(t: *Translator, int: aro.Value) !ZigNode { | |
| 3859 | var space: aro.Interner.Tag.Int.BigIntSpace = undefined; | |
| 3860 | var big = t.comp.interner.get(int.ref()).toBigInt(&space); | |
| 3861 | const is_negative = !big.positive; | |
| 3862 | big.positive = true; | |
| 3863 | ||
| 3864 | const str = big.toStringAlloc(t.arena, 10, .lower) catch |err| switch (err) { | |
| 3865 | error.OutOfMemory => return error.OutOfMemory, | |
| 3866 | }; | |
| 3867 | const res = try ZigTag.integer_literal.create(t.arena, str); | |
| 3868 | if (is_negative) return ZigTag.negate.create(t.arena, res); | |
| 3869 | return res; | |
| 3870 | } | |
| 3871 | ||
| 3872 | fn createNumberNode(t: *Translator, num: anytype, num_kind: enum { int, float }) !ZigNode { | |
| 3873 | const fmt_s = switch (@typeInfo(@TypeOf(num))) { | |
| 3874 | .int, .comptime_int => "{d}", | |
| 3875 | else => "{s}", | |
| 3876 | }; | |
| 3877 | const str = try std.fmt.allocPrint(t.arena, fmt_s, .{num}); | |
| 3878 | if (num_kind == .float) | |
| 3879 | return ZigTag.float_literal.create(t.arena, str) | |
| 3880 | else | |
| 3881 | return ZigTag.integer_literal.create(t.arena, str); | |
| 3882 | } | |
| 3883 | ||
| 3884 | fn createCharLiteralNode(t: *Translator, narrow: bool, val: u32) TransError!ZigNode { | |
| 3885 | return ZigTag.char_literal.create(t.arena, if (narrow) | |
| 3886 | try std.fmt.allocPrint(t.arena, "'{f}'", .{std.zig.fmtChar(&.{@as(u8, @intCast(val))})}) | |
| 3887 | else | |
| 3888 | try std.fmt.allocPrint(t.arena, "'\\u{{{x}}}'", .{val})); | |
| 3889 | } | |
| 3890 | ||
| 3891 | fn createBinOpNode( | |
| 3892 | t: *Translator, | |
| 3893 | op: ZigTag, | |
| 3894 | lhs: ZigNode, | |
| 3895 | rhs: ZigNode, | |
| 3896 | ) !ZigNode { | |
| 3897 | const payload = try t.arena.create(ast.Payload.BinOp); | |
| 3898 | payload.* = .{ | |
| 3899 | .base = .{ .tag = op }, | |
| 3900 | .data = .{ | |
| 3901 | .lhs = lhs, | |
| 3902 | .rhs = rhs, | |
| 3903 | }, | |
| 3904 | }; | |
| 3905 | return ZigNode.initPayload(&payload.base); | |
| 3906 | } | |
| 3907 | ||
| 3908 | pub fn createHelperCallNode(t: *Translator, name: std.meta.DeclEnum(@import("helpers")), args_opt: ?[]const ZigNode) !ZigNode { | |
| 3909 | if (args_opt) |args| { | |
| 3910 | return ZigTag.helper_call.create(t.arena, .{ | |
| 3911 | .name = @tagName(name), | |
| 3912 | .args = try t.arena.dupe(ZigNode, args), | |
| 3913 | }); | |
| 3914 | } else { | |
| 3915 | return ZigTag.helper_ref.create(t.arena, @tagName(name)); | |
| 3916 | } | |
| 3917 | } | |
| 3918 | ||
| 3919 | /// Cast a signed integer node to a usize, for use in pointer arithmetic. Negative numbers | |
| 3920 | /// will become very large positive numbers but that is ok since we only use this in | |
| 3921 | /// pointer arithmetic expressions, where wraparound will ensure we get the correct value. | |
| 3922 | /// node -> @as(usize, @bitCast(@as(isize, @intCast(node)))) | |
| 3923 | fn usizeCastForWrappingPtrArithmetic(t: *Translator, node: ZigNode) TransError!ZigNode { | |
| 3924 | const intcast_node = try ZigTag.as.create(t.arena, .{ | |
| 3925 | .lhs = try ZigTag.type.create(t.arena, "isize"), | |
| 3926 | .rhs = try ZigTag.int_cast.create(t.arena, node), | |
| 3927 | }); | |
| 3928 | ||
| 3929 | return ZigTag.as.create(t.arena, .{ | |
| 3930 | .lhs = try ZigTag.type.create(t.arena, "usize"), | |
| 3931 | .rhs = try ZigTag.bit_cast.create(t.arena, intcast_node), | |
| 3932 | }); | |
| 3933 | } | |
| 3934 | ||
| 3935 | /// @typeInfo(@TypeOf(vec_node)).vector.<field> | |
| 3936 | fn vectorTypeInfo(t: *Translator, vec_node: ZigNode, field: []const u8) TransError!ZigNode { | |
| 3937 | const typeof_call = try ZigTag.typeof.create(t.arena, vec_node); | |
| 3938 | const typeinfo_call = try ZigTag.typeinfo.create(t.arena, typeof_call); | |
| 3939 | const vector_type_info = try ZigTag.field_access.create(t.arena, .{ .lhs = typeinfo_call, .field_name = "vector" }); | |
| 3940 | return ZigTag.field_access.create(t.arena, .{ .lhs = vector_type_info, .field_name = field }); | |
| 3941 | } | |
| 3942 | ||
| 3943 | /// Build a getter function for a flexible array field in a C record | |
| 3944 | /// e.g. `T items[]` or `T items[0]`. The generated function returns a [*c] pointer | |
| 3945 | /// to the flexible array with the correct const and volatile qualifiers | |
| 3946 | fn createFlexibleMemberFn( | |
| 3947 | t: *Translator, | |
| 3948 | member_name: []const u8, | |
| 3949 | field_name: []const u8, | |
| 3950 | ) Error!ZigNode { | |
| 3951 | const self_param_name = "self"; | |
| 3952 | const self_param = try ZigTag.identifier.create(t.arena, self_param_name); | |
| 3953 | const self_type = try ZigTag.typeof.create(t.arena, self_param); | |
| 3954 | ||
| 3955 | const fn_params = try t.arena.alloc(ast.Payload.Param, 1); | |
| 3956 | fn_params[0] = .{ | |
| 3957 | .name = self_param_name, | |
| 3958 | .type = ZigTag.@"anytype".init(), | |
| 3959 | .is_noalias = false, | |
| 3960 | }; | |
| 3961 | ||
| 3962 | // @typeInfo(@TypeOf(self.*.<field_name>)).pointer.child | |
| 3963 | const dereffed = try ZigTag.deref.create(t.arena, self_param); | |
| 3964 | const field_access = try ZigTag.field_access.create(t.arena, .{ .lhs = dereffed, .field_name = field_name }); | |
| 3965 | const type_of = try ZigTag.typeof.create(t.arena, field_access); | |
| 3966 | const type_info = try ZigTag.typeinfo.create(t.arena, type_of); | |
| 3967 | const array_info = try ZigTag.field_access.create(t.arena, .{ .lhs = type_info, .field_name = "array" }); | |
| 3968 | const child_info = try ZigTag.field_access.create(t.arena, .{ .lhs = array_info, .field_name = "child" }); | |
| 3969 | ||
| 3970 | const return_type = try t.createHelperCallNode(.FlexibleArrayType, &.{ self_type, child_info }); | |
| 3971 | ||
| 3972 | // return @ptrCast(&self.*.<field_name>); | |
| 3973 | const address_of = try ZigTag.address_of.create(t.arena, field_access); | |
| 3974 | const casted = try ZigTag.ptr_cast.create(t.arena, address_of); | |
| 3975 | const return_stmt = try ZigTag.@"return".create(t.arena, casted); | |
| 3976 | const body = try ZigTag.block_single.create(t.arena, return_stmt); | |
| 3977 | ||
| 3978 | return ZigTag.func.create(t.arena, .{ | |
| 3979 | .is_pub = true, | |
| 3980 | .is_extern = false, | |
| 3981 | .is_export = false, | |
| 3982 | .is_inline = false, | |
| 3983 | .is_var_args = false, | |
| 3984 | .name = member_name, | |
| 3985 | .linksection_string = null, | |
| 3986 | .explicit_callconv = null, | |
| 3987 | .params = fn_params, | |
| 3988 | .return_type = return_type, | |
| 3989 | .body = body, | |
| 3990 | .alignment = null, | |
| 3991 | }); | |
| 3992 | } | |
| 3993 | ||
| 3994 | // ================= | |
| 3995 | // Macro translation | |
| 3996 | // ================= | |
| 3997 | ||
| 3998 | fn transMacros(t: *Translator) !void { | |
| 3999 | var tok_list = std.ArrayList(CToken).init(t.gpa); | |
| 4000 | defer tok_list.deinit(); | |
| 4001 | ||
| 4002 | var pattern_list = try PatternList.init(t.gpa); | |
| 4003 | defer pattern_list.deinit(t.gpa); | |
| 4004 | ||
| 4005 | for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { | |
| 4006 | if (macro.is_builtin) continue; | |
| 4007 | if (t.global_scope.containsNow(name)) { | |
| 4008 | continue; | |
| 4009 | } | |
| 4010 | ||
| 4011 | tok_list.items.len = 0; | |
| 4012 | try tok_list.ensureUnusedCapacity(macro.tokens.len); | |
| 4013 | for (macro.tokens) |tok| { | |
| 4014 | switch (tok.id) { | |
| 4015 | .invalid => continue, | |
| 4016 | .whitespace => continue, | |
| 4017 | .comment => continue, | |
| 4018 | .macro_ws => continue, | |
| 4019 | else => {}, | |
| 4020 | } | |
| 4021 | tok_list.appendAssumeCapacity(tok); | |
| 4022 | } | |
| 4023 | ||
| 4024 | if (macro.is_func) { | |
| 4025 | const ms: PatternList.MacroSlicer = .{ | |
| 4026 | .tokens = tok_list.items, | |
| 4027 | .source = t.comp.getSource(macro.loc.id).buf, | |
| 4028 | .params = @intCast(macro.params.len), | |
| 4029 | }; | |
| 4030 | if (try pattern_list.match(ms)) |impl| { | |
| 4031 | const decl = try ZigTag.pub_var_simple.create(t.arena, .{ | |
| 4032 | .name = name, | |
| 4033 | .init = try t.createHelperCallNode(impl, null), | |
| 4034 | }); | |
| 4035 | try t.addTopLevelDecl(name, decl); | |
| 4036 | continue; | |
| 4037 | } | |
| 4038 | } | |
| 4039 | ||
| 4040 | if (t.checkTranslatableMacro(tok_list.items, macro.params)) |err| { | |
| 4041 | switch (err) { | |
| 4042 | .undefined_identifier => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: undefined identifier `{s}`", .{ident}), | |
| 4043 | .invalid_arg_usage => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: untranslatable usage of arg `{s}`", .{ident}), | |
| 4044 | } | |
| 4045 | continue; | |
| 4046 | } | |
| 4047 | ||
| 4048 | var macro_translator: MacroTranslator = .{ | |
| 4049 | .t = t, | |
| 4050 | .tokens = tok_list.items, | |
| 4051 | .source = t.comp.getSource(macro.loc.id).buf, | |
| 4052 | .name = name, | |
| 4053 | .macro = macro, | |
| 4054 | }; | |
| 4055 | ||
| 4056 | const res = if (macro.is_func) | |
| 4057 | macro_translator.transFnMacro() | |
| 4058 | else | |
| 4059 | macro_translator.transMacro(); | |
| 4060 | res catch |err| switch (err) { | |
| 4061 | error.ParseError => continue, | |
| 4062 | error.OutOfMemory => |e| return e, | |
| 4063 | }; | |
| 4064 | } | |
| 4065 | } | |
| 4066 | ||
| 4067 | const MacroTranslateError = union(enum) { | |
| 4068 | undefined_identifier: []const u8, | |
| 4069 | invalid_arg_usage: []const u8, | |
| 4070 | }; | |
| 4071 | ||
| 4072 | fn checkTranslatableMacro(t: *Translator, tokens: []const CToken, params: []const []const u8) ?MacroTranslateError { | |
| 4073 | var last_is_type_kw = false; | |
| 4074 | var i: usize = 0; | |
| 4075 | while (i < tokens.len) : (i += 1) { | |
| 4076 | const token = tokens[i]; | |
| 4077 | switch (token.id) { | |
| 4078 | .period, .arrow => i += 1, // skip next token since field identifiers can be unknown | |
| 4079 | .keyword_struct, .keyword_union, .keyword_enum => if (!last_is_type_kw) { | |
| 4080 | last_is_type_kw = true; | |
| 4081 | continue; | |
| 4082 | }, | |
| 4083 | .macro_param, .macro_param_no_expand => { | |
| 4084 | if (last_is_type_kw) { | |
| 4085 | return .{ .invalid_arg_usage = params[token.end] }; | |
| 4086 | } | |
| 4087 | }, | |
| 4088 | .identifier, .extended_identifier => { | |
| 4089 | const identifier = t.pp.tokSlice(token); | |
| 4090 | if (!t.global_scope.contains(identifier) and !builtins.map.has(identifier)) { | |
| 4091 | return .{ .undefined_identifier = identifier }; | |
| 4092 | } | |
| 4093 | }, | |
| 4094 | else => {}, | |
| 4095 | } | |
| 4096 | last_is_type_kw = false; | |
| 4097 | } | |
| 4098 | return null; | |
| 4099 | } | |
| 4100 | ||
| 4101 | fn getContainer(t: *Translator, node: ZigNode) ?ZigNode { | |
| 4102 | switch (node.tag()) { | |
| 4103 | .@"union", | |
| 4104 | .@"struct", | |
| 4105 | .address_of, | |
| 4106 | .bit_not, | |
| 4107 | .not, | |
| 4108 | .optional_type, | |
| 4109 | .negate, | |
| 4110 | .negate_wrap, | |
| 4111 | .array_type, | |
| 4112 | .c_pointer, | |
| 4113 | .single_pointer, | |
| 4114 | => return node, | |
| 4115 | ||
| 4116 | .identifier => { | |
| 4117 | const ident = node.castTag(.identifier).?; | |
| 4118 | if (t.global_scope.sym_table.get(ident.data)) |value| { | |
| 4119 | if (value.castTag(.var_decl)) |var_decl| | |
| 4120 | return t.getContainer(var_decl.data.init.?); | |
| 4121 | if (value.castTag(.var_simple) orelse value.castTag(.pub_var_simple)) |var_decl| | |
| 4122 | return t.getContainer(var_decl.data.init); | |
| 4123 | } | |
| 4124 | }, | |
| 4125 | ||
| 4126 | .field_access => { | |
| 4127 | const field_access = node.castTag(.field_access).?; | |
| 4128 | ||
| 4129 | if (t.getContainerTypeOf(field_access.data.lhs)) |ty_node| { | |
| 4130 | if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| { | |
| 4131 | for (container.data.fields) |field| { | |
| 4132 | if (mem.eql(u8, field.name, field_access.data.field_name)) { | |
| 4133 | return t.getContainer(field.type); | |
| 4134 | } | |
| 4135 | } | |
| 4136 | } | |
| 4137 | } | |
| 4138 | }, | |
| 4139 | ||
| 4140 | else => {}, | |
| 4141 | } | |
| 4142 | return null; | |
| 4143 | } | |
| 4144 | ||
| 4145 | fn getContainerTypeOf(t: *Translator, ref: ZigNode) ?ZigNode { | |
| 4146 | if (ref.castTag(.identifier)) |ident| { | |
| 4147 | if (t.global_scope.sym_table.get(ident.data)) |value| { | |
| 4148 | if (value.castTag(.var_decl)) |var_decl| { | |
| 4149 | return t.getContainer(var_decl.data.type); | |
| 4150 | } | |
| 4151 | } | |
| 4152 | } else if (ref.castTag(.field_access)) |field_access| { | |
| 4153 | if (t.getContainerTypeOf(field_access.data.lhs)) |ty_node| { | |
| 4154 | if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| { | |
| 4155 | for (container.data.fields) |field| { | |
| 4156 | if (mem.eql(u8, field.name, field_access.data.field_name)) { | |
| 4157 | return t.getContainer(field.type); | |
| 4158 | } | |
| 4159 | } | |
| 4160 | } else return ty_node; | |
| 4161 | } | |
| 4162 | } | |
| 4163 | return null; | |
| 4164 | } | |
| 4165 | ||
| 4166 | pub fn getFnProto(t: *Translator, ref: ZigNode) ?*ast.Payload.Func { | |
| 4167 | const init = if (ref.castTag(.var_decl)) |v| | |
| 4168 | v.data.init orelse return null | |
| 4169 | else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v| | |
| 4170 | v.data.init | |
| 4171 | else | |
| 4172 | return null; | |
| 4173 | if (t.getContainerTypeOf(init)) |ty_node| { | |
| 4174 | if (ty_node.castTag(.optional_type)) |prefix| { | |
| 4175 | if (prefix.data.castTag(.single_pointer)) |sp| { | |
| 4176 | if (sp.data.elem_type.castTag(.func)) |fn_proto| { | |
| 4177 | return fn_proto; | |
| 4178 | } | |
| 4179 | } | |
| 4180 | } | |
| 4181 | } | |
| 4182 | return null; | |
| 4183 | } |
lib/compiler/translate-c/ast.zig created+3063| ... | ... | @@ -0,0 +1,3063 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | ||
| 4 | pub const Node = extern union { | |
| 5 | /// If the tag value is less than Tag.no_payload_count, then no pointer | |
| 6 | /// dereference is needed. | |
| 7 | tag_if_small_enough: usize, | |
| 8 | ptr_otherwise: *Payload, | |
| 9 | ||
| 10 | pub const Tag = enum { | |
| 11 | /// Declarations add themselves to the correct scopes and should not be emitted as this tag. | |
| 12 | declaration, | |
| 13 | null_literal, | |
| 14 | undefined_literal, | |
| 15 | /// opaque {} | |
| 16 | opaque_literal, | |
| 17 | true_literal, | |
| 18 | false_literal, | |
| 19 | empty_block, | |
| 20 | return_void, | |
| 21 | zero_literal, | |
| 22 | one_literal, | |
| 23 | @"unreachable", | |
| 24 | void_type, | |
| 25 | noreturn_type, | |
| 26 | @"anytype", | |
| 27 | @"continue", | |
| 28 | @"break", | |
| 29 | // After this, the tag requires a payload. | |
| 30 | ||
| 31 | integer_literal, | |
| 32 | float_literal, | |
| 33 | string_literal, | |
| 34 | char_literal, | |
| 35 | enum_literal, | |
| 36 | /// "string"[0..end] | |
| 37 | string_slice, | |
| 38 | identifier, | |
| 39 | @"if", | |
| 40 | /// if (!operand) break; | |
| 41 | if_not_break, | |
| 42 | @"while", | |
| 43 | /// while (true) operand | |
| 44 | while_true, | |
| 45 | @"switch", | |
| 46 | /// else => operand, | |
| 47 | switch_else, | |
| 48 | /// items => body, | |
| 49 | switch_prong, | |
| 50 | break_val, | |
| 51 | @"return", | |
| 52 | field_access, | |
| 53 | array_access, | |
| 54 | call, | |
| 55 | var_decl, | |
| 56 | /// const name = struct { init } | |
| 57 | wrapped_local, | |
| 58 | /// var name = init.* | |
| 59 | mut_str, | |
| 60 | func, | |
| 61 | warning, | |
| 62 | @"struct", | |
| 63 | @"union", | |
| 64 | @"opaque", | |
| 65 | @"comptime", | |
| 66 | @"defer", | |
| 67 | array_init, | |
| 68 | tuple, | |
| 69 | container_init, | |
| 70 | container_init_dot, | |
| 71 | /// _ = operand; | |
| 72 | discard, | |
| 73 | ||
| 74 | // a + b | |
| 75 | add, | |
| 76 | // a = b | |
| 77 | add_assign, | |
| 78 | // c = (a = b) | |
| 79 | add_wrap, | |
| 80 | add_wrap_assign, | |
| 81 | sub, | |
| 82 | sub_assign, | |
| 83 | sub_wrap, | |
| 84 | sub_wrap_assign, | |
| 85 | mul, | |
| 86 | mul_assign, | |
| 87 | mul_wrap, | |
| 88 | mul_wrap_assign, | |
| 89 | div, | |
| 90 | div_assign, | |
| 91 | shl, | |
| 92 | shl_assign, | |
| 93 | shr, | |
| 94 | shr_assign, | |
| 95 | mod, | |
| 96 | mod_assign, | |
| 97 | @"and", | |
| 98 | @"or", | |
| 99 | less_than, | |
| 100 | less_than_equal, | |
| 101 | greater_than, | |
| 102 | greater_than_equal, | |
| 103 | equal, | |
| 104 | not_equal, | |
| 105 | bit_and, | |
| 106 | bit_and_assign, | |
| 107 | bit_or, | |
| 108 | bit_or_assign, | |
| 109 | bit_xor, | |
| 110 | bit_xor_assign, | |
| 111 | array_cat, | |
| 112 | ellipsis3, | |
| 113 | assign, | |
| 114 | ||
| 115 | /// @intCast(operand) | |
| 116 | int_cast, | |
| 117 | /// @constCast(operand) | |
| 118 | const_cast, | |
| 119 | /// @volatileCast(operand) | |
| 120 | volatile_cast, | |
| 121 | /// @divTrunc(lhs, rhs) | |
| 122 | div_trunc, | |
| 123 | /// @intFromBool(operand) | |
| 124 | int_from_bool, | |
| 125 | /// @as(lhs, rhs) | |
| 126 | as, | |
| 127 | /// @truncate(operand) | |
| 128 | truncate, | |
| 129 | /// @bitCast(operand) | |
| 130 | bit_cast, | |
| 131 | /// @floatCast(operand) | |
| 132 | float_cast, | |
| 133 | /// @intFromFloat(operand) | |
| 134 | int_from_float, | |
| 135 | /// @floatFromInt(operand) | |
| 136 | float_from_int, | |
| 137 | /// @ptrFromInt(operand) | |
| 138 | ptr_from_int, | |
| 139 | /// @intFromPtr(operand) | |
| 140 | int_from_ptr, | |
| 141 | /// @alignCast(operand) | |
| 142 | align_cast, | |
| 143 | /// @ptrCast(operand) | |
| 144 | ptr_cast, | |
| 145 | /// @divExact(lhs, rhs) | |
| 146 | div_exact, | |
| 147 | /// @offsetOf(lhs, rhs) | |
| 148 | offset_of, | |
| 149 | /// @splat(operand) | |
| 150 | vector_zero_init, | |
| 151 | /// @shuffle(type, a, b, mask) | |
| 152 | shuffle, | |
| 153 | /// @extern(ty, .{ .name = n }) | |
| 154 | builtin_extern, | |
| 155 | ||
| 156 | /// @byteSwap(operand) | |
| 157 | byte_swap, | |
| 158 | /// @ceil(operand) | |
| 159 | ceil, | |
| 160 | /// @cos(operand) | |
| 161 | cos, | |
| 162 | /// @sin(operand) | |
| 163 | sin, | |
| 164 | /// @exp(operand) | |
| 165 | exp, | |
| 166 | /// @exp2(operand) | |
| 167 | exp2, | |
| 168 | /// @exp10(operand) | |
| 169 | exp10, | |
| 170 | /// @abs(operand) | |
| 171 | abs, | |
| 172 | /// @log(operand) | |
| 173 | log, | |
| 174 | /// @log2(operand) | |
| 175 | log2, | |
| 176 | /// @log10(operand) | |
| 177 | log10, | |
| 178 | /// @round(operand) | |
| 179 | round, | |
| 180 | /// @sqrt(operand) | |
| 181 | sqrt, | |
| 182 | /// @trunc(operand) | |
| 183 | trunc, | |
| 184 | /// @floor(operand) | |
| 185 | floor, | |
| 186 | ||
| 187 | /// __helpers.<name>(argshelper_call) | |
| 188 | helper_call, | |
| 189 | /// __helpers.<name> | |
| 190 | helper_ref, | |
| 191 | ||
| 192 | asm_simple, | |
| 193 | ||
| 194 | negate, | |
| 195 | negate_wrap, | |
| 196 | bit_not, | |
| 197 | not, | |
| 198 | address_of, | |
| 199 | /// .? | |
| 200 | unwrap, | |
| 201 | /// .* | |
| 202 | deref, | |
| 203 | ||
| 204 | block, | |
| 205 | /// { operand } | |
| 206 | block_single, | |
| 207 | ||
| 208 | sizeof, | |
| 209 | alignof, | |
| 210 | typeof, | |
| 211 | typeinfo, | |
| 212 | type, | |
| 213 | ||
| 214 | optional_type, | |
| 215 | c_pointer, | |
| 216 | single_pointer, | |
| 217 | array_type, | |
| 218 | null_sentinel_array_type, | |
| 219 | ||
| 220 | /// @Vector(lhs, rhs) | |
| 221 | vector, | |
| 222 | /// @import("std").mem.zeroes(operand) | |
| 223 | std_mem_zeroes, | |
| 224 | /// @import("std").mem.zeroInit(lhs, rhs) | |
| 225 | std_mem_zeroinit, | |
| 226 | // pub const name = @compileError(msg); | |
| 227 | fail_decl, | |
| 228 | // var actual = mangled; | |
| 229 | arg_redecl, | |
| 230 | /// pub const alias = actual; | |
| 231 | alias, | |
| 232 | /// const name = init; | |
| 233 | var_simple, | |
| 234 | /// pub const name = init; | |
| 235 | pub_var_simple, | |
| 236 | /// pub? const name (: type)? = value | |
| 237 | enum_constant, | |
| 238 | ||
| 239 | /// pub inline fn name(params) return_type body | |
| 240 | pub_inline_fn, | |
| 241 | ||
| 242 | /// array_type{} | |
| 243 | empty_array, | |
| 244 | /// [1]type{val} ** count | |
| 245 | array_filler, | |
| 246 | ||
| 247 | /// comptime { if (!(lhs)) @compileError(rhs); } | |
| 248 | static_assert, | |
| 249 | ||
| 250 | pub const last_no_payload_tag = Tag.@"break"; | |
| 251 | pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1; | |
| 252 | ||
| 253 | pub fn Type(comptime t: Tag) type { | |
| 254 | return switch (t) { | |
| 255 | .declaration, | |
| 256 | .null_literal, | |
| 257 | .undefined_literal, | |
| 258 | .opaque_literal, | |
| 259 | .true_literal, | |
| 260 | .false_literal, | |
| 261 | .empty_block, | |
| 262 | .return_void, | |
| 263 | .zero_literal, | |
| 264 | .one_literal, | |
| 265 | .void_type, | |
| 266 | .noreturn_type, | |
| 267 | .@"anytype", | |
| 268 | .@"continue", | |
| 269 | .@"break", | |
| 270 | .@"unreachable", | |
| 271 | => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), | |
| 272 | ||
| 273 | .std_mem_zeroes, | |
| 274 | .@"return", | |
| 275 | .@"comptime", | |
| 276 | .@"defer", | |
| 277 | .asm_simple, | |
| 278 | .negate, | |
| 279 | .negate_wrap, | |
| 280 | .bit_not, | |
| 281 | .not, | |
| 282 | .optional_type, | |
| 283 | .address_of, | |
| 284 | .unwrap, | |
| 285 | .deref, | |
| 286 | .int_from_ptr, | |
| 287 | .empty_array, | |
| 288 | .while_true, | |
| 289 | .if_not_break, | |
| 290 | .switch_else, | |
| 291 | .block_single, | |
| 292 | .int_from_bool, | |
| 293 | .sizeof, | |
| 294 | .alignof, | |
| 295 | .typeof, | |
| 296 | .typeinfo, | |
| 297 | .align_cast, | |
| 298 | .truncate, | |
| 299 | .bit_cast, | |
| 300 | .float_cast, | |
| 301 | .int_from_float, | |
| 302 | .float_from_int, | |
| 303 | .ptr_from_int, | |
| 304 | .ptr_cast, | |
| 305 | .int_cast, | |
| 306 | .const_cast, | |
| 307 | .volatile_cast, | |
| 308 | .vector_zero_init, | |
| 309 | .byte_swap, | |
| 310 | .ceil, | |
| 311 | .cos, | |
| 312 | .sin, | |
| 313 | .exp, | |
| 314 | .exp2, | |
| 315 | .exp10, | |
| 316 | .abs, | |
| 317 | .log, | |
| 318 | .log2, | |
| 319 | .log10, | |
| 320 | .round, | |
| 321 | .sqrt, | |
| 322 | .trunc, | |
| 323 | .floor, | |
| 324 | => Payload.UnOp, | |
| 325 | ||
| 326 | .add, | |
| 327 | .add_assign, | |
| 328 | .add_wrap, | |
| 329 | .add_wrap_assign, | |
| 330 | .sub, | |
| 331 | .sub_assign, | |
| 332 | .sub_wrap, | |
| 333 | .sub_wrap_assign, | |
| 334 | .mul, | |
| 335 | .mul_assign, | |
| 336 | .mul_wrap, | |
| 337 | .mul_wrap_assign, | |
| 338 | .div, | |
| 339 | .div_assign, | |
| 340 | .shl, | |
| 341 | .shl_assign, | |
| 342 | .shr, | |
| 343 | .shr_assign, | |
| 344 | .mod, | |
| 345 | .mod_assign, | |
| 346 | .@"and", | |
| 347 | .@"or", | |
| 348 | .less_than, | |
| 349 | .less_than_equal, | |
| 350 | .greater_than, | |
| 351 | .greater_than_equal, | |
| 352 | .equal, | |
| 353 | .not_equal, | |
| 354 | .bit_and, | |
| 355 | .bit_and_assign, | |
| 356 | .bit_or, | |
| 357 | .bit_or_assign, | |
| 358 | .bit_xor, | |
| 359 | .bit_xor_assign, | |
| 360 | .div_trunc, | |
| 361 | .as, | |
| 362 | .array_cat, | |
| 363 | .ellipsis3, | |
| 364 | .assign, | |
| 365 | .array_access, | |
| 366 | .std_mem_zeroinit, | |
| 367 | .vector, | |
| 368 | .div_exact, | |
| 369 | .offset_of, | |
| 370 | .static_assert, | |
| 371 | => Payload.BinOp, | |
| 372 | ||
| 373 | .integer_literal, | |
| 374 | .float_literal, | |
| 375 | .string_literal, | |
| 376 | .char_literal, | |
| 377 | .enum_literal, | |
| 378 | .identifier, | |
| 379 | .warning, | |
| 380 | .type, | |
| 381 | => Payload.Value, | |
| 382 | .discard => Payload.Discard, | |
| 383 | .@"if" => Payload.If, | |
| 384 | .@"while" => Payload.While, | |
| 385 | .@"switch", .array_init, .switch_prong => Payload.Switch, | |
| 386 | .break_val => Payload.BreakVal, | |
| 387 | .call => Payload.Call, | |
| 388 | .var_decl => Payload.VarDecl, | |
| 389 | .func => Payload.Func, | |
| 390 | .@"struct", .@"union", .@"opaque" => Payload.Container, | |
| 391 | .tuple => Payload.TupleInit, | |
| 392 | .container_init => Payload.ContainerInit, | |
| 393 | .container_init_dot => Payload.ContainerInitDot, | |
| 394 | .block => Payload.Block, | |
| 395 | .c_pointer, .single_pointer => Payload.Pointer, | |
| 396 | .array_type, .null_sentinel_array_type => Payload.Array, | |
| 397 | .arg_redecl, .alias, .fail_decl => Payload.ArgRedecl, | |
| 398 | .var_simple, .pub_var_simple, .wrapped_local, .mut_str => Payload.SimpleVarDecl, | |
| 399 | .enum_constant => Payload.EnumConstant, | |
| 400 | .array_filler => Payload.ArrayFiller, | |
| 401 | .pub_inline_fn => Payload.PubInlineFn, | |
| 402 | .field_access => Payload.FieldAccess, | |
| 403 | .string_slice => Payload.StringSlice, | |
| 404 | .shuffle => Payload.Shuffle, | |
| 405 | .builtin_extern => Payload.Extern, | |
| 406 | .helper_call => Payload.HelperCall, | |
| 407 | .helper_ref => Payload.HelperRef, | |
| 408 | }; | |
| 409 | } | |
| 410 | ||
| 411 | pub fn init(comptime t: Tag) Node { | |
| 412 | comptime std.debug.assert(@intFromEnum(t) < Tag.no_payload_count); | |
| 413 | return .{ .tag_if_small_enough = @intFromEnum(t) }; | |
| 414 | } | |
| 415 | ||
| 416 | pub fn create(comptime t: Tag, ally: Allocator, data: Data(t)) error{OutOfMemory}!Node { | |
| 417 | const ptr = try ally.create(t.Type()); | |
| 418 | ptr.* = .{ | |
| 419 | .base = .{ .tag = t }, | |
| 420 | .data = data, | |
| 421 | }; | |
| 422 | return Node{ .ptr_otherwise = &ptr.base }; | |
| 423 | } | |
| 424 | ||
| 425 | pub fn Data(comptime t: Tag) type { | |
| 426 | return std.meta.fieldInfo(t.Type(), .data).type; | |
| 427 | } | |
| 428 | }; | |
| 429 | ||
| 430 | pub fn tag(self: Node) Tag { | |
| 431 | if (self.tag_if_small_enough < Tag.no_payload_count) { | |
| 432 | return @enumFromInt(@as(std.meta.Tag(Tag), @intCast(self.tag_if_small_enough))); | |
| 433 | } else { | |
| 434 | return self.ptr_otherwise.tag; | |
| 435 | } | |
| 436 | } | |
| 437 | ||
| 438 | pub fn castTag(self: Node, comptime t: Tag) ?*t.Type() { | |
| 439 | if (self.tag_if_small_enough < Tag.no_payload_count) | |
| 440 | return null; | |
| 441 | ||
| 442 | if (self.ptr_otherwise.tag == t) | |
| 443 | return @alignCast(@fieldParentPtr("base", self.ptr_otherwise)); | |
| 444 | ||
| 445 | return null; | |
| 446 | } | |
| 447 | ||
| 448 | pub fn initPayload(payload: *Payload) Node { | |
| 449 | std.debug.assert(@intFromEnum(payload.tag) >= Tag.no_payload_count); | |
| 450 | return .{ .ptr_otherwise = payload }; | |
| 451 | } | |
| 452 | ||
| 453 | pub fn isNoreturn(node: Node, break_counts: bool) bool { | |
| 454 | switch (node.tag()) { | |
| 455 | .block => { | |
| 456 | const block_node = node.castTag(.block).?; | |
| 457 | if (block_node.data.stmts.len == 0) return false; | |
| 458 | ||
| 459 | const last = block_node.data.stmts[block_node.data.stmts.len - 1]; | |
| 460 | return last.isNoreturn(break_counts); | |
| 461 | }, | |
| 462 | .@"switch" => { | |
| 463 | const switch_node = node.castTag(.@"switch").?; | |
| 464 | ||
| 465 | for (switch_node.data.cases) |case| { | |
| 466 | const body = if (case.castTag(.switch_else)) |some| | |
| 467 | some.data | |
| 468 | else if (case.castTag(.switch_prong)) |some| | |
| 469 | some.data.cond | |
| 470 | else | |
| 471 | unreachable; | |
| 472 | ||
| 473 | if (!body.isNoreturn(break_counts)) return false; | |
| 474 | } | |
| 475 | return true; | |
| 476 | }, | |
| 477 | .@"return", .return_void => return true, | |
| 478 | .@"break" => if (break_counts) return true, | |
| 479 | else => {}, | |
| 480 | } | |
| 481 | return false; | |
| 482 | } | |
| 483 | ||
| 484 | pub fn isBoolRes(res: Node) bool { | |
| 485 | switch (res.tag()) { | |
| 486 | .@"or", | |
| 487 | .@"and", | |
| 488 | .equal, | |
| 489 | .not_equal, | |
| 490 | .less_than, | |
| 491 | .less_than_equal, | |
| 492 | .greater_than, | |
| 493 | .greater_than_equal, | |
| 494 | .not, | |
| 495 | .false_literal, | |
| 496 | .true_literal, | |
| 497 | => return true, | |
| 498 | else => return false, | |
| 499 | } | |
| 500 | } | |
| 501 | }; | |
| 502 | ||
| 503 | pub const Payload = struct { | |
| 504 | tag: Node.Tag, | |
| 505 | ||
| 506 | pub const Value = struct { | |
| 507 | base: Payload, | |
| 508 | data: []const u8, | |
| 509 | }; | |
| 510 | ||
| 511 | pub const UnOp = struct { | |
| 512 | base: Payload, | |
| 513 | data: Node, | |
| 514 | }; | |
| 515 | ||
| 516 | pub const BinOp = struct { | |
| 517 | base: Payload, | |
| 518 | data: struct { | |
| 519 | lhs: Node, | |
| 520 | rhs: Node, | |
| 521 | }, | |
| 522 | }; | |
| 523 | ||
| 524 | pub const Discard = struct { | |
| 525 | base: Payload, | |
| 526 | data: struct { | |
| 527 | should_skip: bool, | |
| 528 | value: Node, | |
| 529 | }, | |
| 530 | }; | |
| 531 | ||
| 532 | pub const If = struct { | |
| 533 | base: Payload, | |
| 534 | data: struct { | |
| 535 | cond: Node, | |
| 536 | then: Node, | |
| 537 | @"else": ?Node, | |
| 538 | }, | |
| 539 | }; | |
| 540 | ||
| 541 | pub const While = struct { | |
| 542 | base: Payload, | |
| 543 | data: struct { | |
| 544 | cond: Node, | |
| 545 | body: Node, | |
| 546 | cont_expr: ?Node, | |
| 547 | }, | |
| 548 | }; | |
| 549 | ||
| 550 | pub const Switch = struct { | |
| 551 | base: Payload, | |
| 552 | data: struct { | |
| 553 | cond: Node, | |
| 554 | cases: []Node, | |
| 555 | }, | |
| 556 | }; | |
| 557 | ||
| 558 | pub const BreakVal = struct { | |
| 559 | base: Payload, | |
| 560 | data: struct { | |
| 561 | label: ?[]const u8, | |
| 562 | val: Node, | |
| 563 | }, | |
| 564 | }; | |
| 565 | ||
| 566 | pub const Call = struct { | |
| 567 | base: Payload, | |
| 568 | data: struct { | |
| 569 | lhs: Node, | |
| 570 | args: []Node, | |
| 571 | }, | |
| 572 | }; | |
| 573 | ||
| 574 | pub const VarDecl = struct { | |
| 575 | base: Payload, | |
| 576 | data: struct { | |
| 577 | is_pub: bool, | |
| 578 | is_const: bool, | |
| 579 | is_extern: bool, | |
| 580 | is_export: bool, | |
| 581 | is_threadlocal: bool, | |
| 582 | alignment: ?c_uint, | |
| 583 | linksection_string: ?[]const u8, | |
| 584 | name: []const u8, | |
| 585 | type: Node, | |
| 586 | init: ?Node, | |
| 587 | }, | |
| 588 | }; | |
| 589 | ||
| 590 | pub const Func = struct { | |
| 591 | base: Payload, | |
| 592 | data: struct { | |
| 593 | is_pub: bool, | |
| 594 | is_extern: bool, | |
| 595 | is_export: bool, | |
| 596 | is_inline: bool, | |
| 597 | is_var_args: bool, | |
| 598 | name: ?[]const u8, | |
| 599 | linksection_string: ?[]const u8, | |
| 600 | explicit_callconv: ?CallingConvention, | |
| 601 | params: []Param, | |
| 602 | return_type: Node, | |
| 603 | body: ?Node, | |
| 604 | alignment: ?c_uint, | |
| 605 | }, | |
| 606 | ||
| 607 | pub const CallingConvention = enum { | |
| 608 | c, | |
| 609 | x86_64_sysv, | |
| 610 | x86_64_win, | |
| 611 | x86_stdcall, | |
| 612 | x86_fastcall, | |
| 613 | x86_thiscall, | |
| 614 | x86_vectorcall, | |
| 615 | x86_regcall, | |
| 616 | aarch64_vfabi, | |
| 617 | aarch64_sve_pcs, | |
| 618 | arm_aapcs, | |
| 619 | arm_aapcs_vfp, | |
| 620 | m68k_rtd, | |
| 621 | riscv_vector, | |
| 622 | }; | |
| 623 | }; | |
| 624 | ||
| 625 | pub const Param = struct { | |
| 626 | is_noalias: bool, | |
| 627 | name: ?[]const u8, | |
| 628 | type: Node, | |
| 629 | }; | |
| 630 | ||
| 631 | pub const Container = struct { | |
| 632 | base: Payload, | |
| 633 | data: struct { | |
| 634 | layout: enum { @"packed", @"extern", none }, | |
| 635 | fields: []Field, | |
| 636 | decls: []Node, | |
| 637 | }, | |
| 638 | ||
| 639 | pub const Field = struct { | |
| 640 | name: []const u8, | |
| 641 | type: Node, | |
| 642 | alignment: ?c_uint, | |
| 643 | default_value: ?Node, | |
| 644 | }; | |
| 645 | }; | |
| 646 | ||
| 647 | pub const TupleInit = struct { | |
| 648 | base: Payload, | |
| 649 | data: []Node, | |
| 650 | }; | |
| 651 | ||
| 652 | pub const ContainerInit = struct { | |
| 653 | base: Payload, | |
| 654 | data: struct { | |
| 655 | lhs: Node, | |
| 656 | inits: []Initializer, | |
| 657 | }, | |
| 658 | ||
| 659 | pub const Initializer = struct { | |
| 660 | name: []const u8, | |
| 661 | value: Node, | |
| 662 | }; | |
| 663 | }; | |
| 664 | ||
| 665 | pub const ContainerInitDot = struct { | |
| 666 | base: Payload, | |
| 667 | data: []Initializer, | |
| 668 | ||
| 669 | pub const Initializer = struct { | |
| 670 | name: []const u8, | |
| 671 | value: Node, | |
| 672 | }; | |
| 673 | }; | |
| 674 | ||
| 675 | pub const Block = struct { | |
| 676 | base: Payload, | |
| 677 | data: struct { | |
| 678 | label: ?[]const u8, | |
| 679 | stmts: []Node, | |
| 680 | }, | |
| 681 | }; | |
| 682 | ||
| 683 | pub const Array = struct { | |
| 684 | base: Payload, | |
| 685 | data: ArrayTypeInfo, | |
| 686 | ||
| 687 | pub const ArrayTypeInfo = struct { | |
| 688 | elem_type: Node, | |
| 689 | len: u64, | |
| 690 | }; | |
| 691 | }; | |
| 692 | ||
| 693 | pub const Pointer = struct { | |
| 694 | base: Payload, | |
| 695 | data: struct { | |
| 696 | elem_type: Node, | |
| 697 | is_const: bool, | |
| 698 | is_volatile: bool, | |
| 699 | is_allowzero: bool, | |
| 700 | }, | |
| 701 | }; | |
| 702 | ||
| 703 | pub const ArgRedecl = struct { | |
| 704 | base: Payload, | |
| 705 | data: struct { | |
| 706 | actual: []const u8, | |
| 707 | mangled: []const u8, | |
| 708 | }, | |
| 709 | }; | |
| 710 | ||
| 711 | pub const SimpleVarDecl = struct { | |
| 712 | base: Payload, | |
| 713 | data: struct { | |
| 714 | name: []const u8, | |
| 715 | init: Node, | |
| 716 | }, | |
| 717 | }; | |
| 718 | ||
| 719 | pub const EnumConstant = struct { | |
| 720 | base: Payload, | |
| 721 | data: struct { | |
| 722 | name: []const u8, | |
| 723 | is_public: bool, | |
| 724 | type: ?Node, | |
| 725 | value: Node, | |
| 726 | }, | |
| 727 | }; | |
| 728 | ||
| 729 | pub const ArrayFiller = struct { | |
| 730 | base: Payload, | |
| 731 | data: struct { | |
| 732 | type: Node, | |
| 733 | filler: Node, | |
| 734 | count: u64, | |
| 735 | }, | |
| 736 | }; | |
| 737 | ||
| 738 | pub const PubInlineFn = struct { | |
| 739 | base: Payload, | |
| 740 | data: struct { | |
| 741 | name: []const u8, | |
| 742 | params: []Param, | |
| 743 | return_type: Node, | |
| 744 | body: Node, | |
| 745 | }, | |
| 746 | }; | |
| 747 | ||
| 748 | pub const FieldAccess = struct { | |
| 749 | base: Payload, | |
| 750 | data: struct { | |
| 751 | lhs: Node, | |
| 752 | field_name: []const u8, | |
| 753 | }, | |
| 754 | }; | |
| 755 | ||
| 756 | pub const StringSlice = struct { | |
| 757 | base: Payload, | |
| 758 | data: struct { | |
| 759 | string: Node, | |
| 760 | end: u64, | |
| 761 | }, | |
| 762 | }; | |
| 763 | ||
| 764 | pub const Shuffle = struct { | |
| 765 | base: Payload, | |
| 766 | data: struct { | |
| 767 | element_type: Node, | |
| 768 | a: Node, | |
| 769 | b: Node, | |
| 770 | mask_vector: Node, | |
| 771 | }, | |
| 772 | }; | |
| 773 | ||
| 774 | pub const Extern = struct { | |
| 775 | base: Payload, | |
| 776 | data: struct { | |
| 777 | type: Node, | |
| 778 | name: Node, | |
| 779 | }, | |
| 780 | }; | |
| 781 | ||
| 782 | pub const HelperCall = struct { | |
| 783 | base: Payload, | |
| 784 | data: struct { | |
| 785 | name: []const u8, | |
| 786 | args: []const Node, | |
| 787 | }, | |
| 788 | }; | |
| 789 | ||
| 790 | pub const HelperRef = struct { | |
| 791 | base: Payload, | |
| 792 | data: []const u8, | |
| 793 | }; | |
| 794 | }; | |
| 795 | ||
| 796 | /// Converts the nodes into a Zig Ast. | |
| 797 | /// Caller must free the source slice. | |
| 798 | pub fn render(gpa: Allocator, nodes: []const Node) !std.zig.Ast { | |
| 799 | var ctx: Context = .{ | |
| 800 | .gpa = gpa, | |
| 801 | .buf = std.array_list.Managed(u8).init(gpa), | |
| 802 | }; | |
| 803 | defer ctx.buf.deinit(); | |
| 804 | defer ctx.nodes.deinit(gpa); | |
| 805 | defer ctx.extra_data.deinit(gpa); | |
| 806 | defer ctx.tokens.deinit(gpa); | |
| 807 | ||
| 808 | // Estimate that each top level node has 10 child nodes. | |
| 809 | const estimated_node_count = nodes.len * 10 + 1; // +1 for the .root node | |
| 810 | try ctx.nodes.ensureTotalCapacity(gpa, estimated_node_count); | |
| 811 | // Estimate that each each node has 2 tokens. | |
| 812 | const estimated_tokens_count = estimated_node_count * 2; | |
| 813 | try ctx.tokens.ensureTotalCapacity(gpa, estimated_tokens_count); | |
| 814 | // Estimate that each each token is 3 bytes long. | |
| 815 | const estimated_buf_len = estimated_tokens_count * 3; | |
| 816 | try ctx.buf.ensureTotalCapacity(estimated_buf_len); | |
| 817 | ||
| 818 | ctx.nodes.appendAssumeCapacity(.{ | |
| 819 | .tag = .root, | |
| 820 | .main_token = 0, | |
| 821 | .data = undefined, | |
| 822 | }); | |
| 823 | ||
| 824 | const root_members = blk: { | |
| 825 | var result = std.array_list.Managed(NodeIndex).init(gpa); | |
| 826 | defer result.deinit(); | |
| 827 | ||
| 828 | for (nodes) |node| { | |
| 829 | const res = (try renderNodeOpt(&ctx, node)) orelse continue; | |
| 830 | try result.append(res); | |
| 831 | } | |
| 832 | break :blk try ctx.listToSpan(result.items); | |
| 833 | }; | |
| 834 | ||
| 835 | ctx.nodes.items(.data)[0] = .{ .extra_range = .{ | |
| 836 | .start = root_members.start, | |
| 837 | .end = root_members.end, | |
| 838 | } }; | |
| 839 | ||
| 840 | try ctx.tokens.append(gpa, .{ | |
| 841 | .tag = .eof, | |
| 842 | .start = @as(u32, @intCast(ctx.buf.items.len)), | |
| 843 | }); | |
| 844 | ||
| 845 | return .{ | |
| 846 | .source = try ctx.buf.toOwnedSliceSentinel(0), | |
| 847 | .tokens = ctx.tokens.toOwnedSlice(), | |
| 848 | .nodes = ctx.nodes.toOwnedSlice(), | |
| 849 | .extra_data = try ctx.extra_data.toOwnedSlice(gpa), | |
| 850 | .errors = &.{}, | |
| 851 | .mode = .zig, | |
| 852 | }; | |
| 853 | } | |
| 854 | ||
| 855 | const NodeIndex = std.zig.Ast.Node.Index; | |
| 856 | const NodeSubRange = std.zig.Ast.Node.SubRange; | |
| 857 | const TokenIndex = std.zig.Ast.TokenIndex; | |
| 858 | const TokenTag = std.zig.Token.Tag; | |
| 859 | ||
| 860 | const Context = struct { | |
| 861 | gpa: Allocator, | |
| 862 | buf: std.array_list.Managed(u8), | |
| 863 | nodes: std.zig.Ast.NodeList = .{}, | |
| 864 | extra_data: std.ArrayListUnmanaged(u32) = .empty, | |
| 865 | tokens: std.zig.Ast.TokenList = .{}, | |
| 866 | ||
| 867 | fn addTokenFmt(c: *Context, tag: TokenTag, comptime format: []const u8, args: anytype) Allocator.Error!TokenIndex { | |
| 868 | const start_index = c.buf.items.len; | |
| 869 | try c.buf.print(format ++ " ", args); | |
| 870 | ||
| 871 | try c.tokens.append(c.gpa, .{ | |
| 872 | .tag = tag, | |
| 873 | .start = @intCast(start_index), | |
| 874 | }); | |
| 875 | ||
| 876 | return @intCast(c.tokens.len - 1); | |
| 877 | } | |
| 878 | ||
| 879 | fn addToken(c: *Context, tag: TokenTag, bytes: []const u8) Allocator.Error!TokenIndex { | |
| 880 | return c.addTokenFmt(tag, "{s}", .{bytes}); | |
| 881 | } | |
| 882 | ||
| 883 | fn addIdentifier(c: *Context, bytes: []const u8) Allocator.Error!TokenIndex { | |
| 884 | if (std.zig.primitives.isPrimitive(bytes)) | |
| 885 | return c.addTokenFmt(.identifier, "@\"{s}\"", .{bytes}); | |
| 886 | return c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtId(bytes)}); | |
| 887 | } | |
| 888 | ||
| 889 | fn listToSpan(c: *Context, list: []const NodeIndex) Allocator.Error!NodeSubRange { | |
| 890 | try c.extra_data.appendSlice(c.gpa, @ptrCast(list)); | |
| 891 | return .{ | |
| 892 | .start = @enumFromInt(c.extra_data.items.len - list.len), | |
| 893 | .end = @enumFromInt(c.extra_data.items.len), | |
| 894 | }; | |
| 895 | } | |
| 896 | ||
| 897 | fn addNode(c: *Context, elem: std.zig.Ast.Node) Allocator.Error!NodeIndex { | |
| 898 | const result: NodeIndex = @enumFromInt(c.nodes.len); | |
| 899 | try c.nodes.append(c.gpa, elem); | |
| 900 | return result; | |
| 901 | } | |
| 902 | ||
| 903 | fn addExtra(c: *Context, extra: anytype) Allocator.Error!std.zig.Ast.ExtraIndex { | |
| 904 | const fields = std.meta.fields(@TypeOf(extra)); | |
| 905 | try c.extra_data.ensureUnusedCapacity(c.gpa, fields.len); | |
| 906 | const result: std.zig.Ast.ExtraIndex = @enumFromInt(c.extra_data.items.len); | |
| 907 | inline for (fields) |field| { | |
| 908 | const data: u32 = switch (field.type) { | |
| 909 | NodeIndex, | |
| 910 | std.zig.Ast.Node.OptionalIndex, | |
| 911 | std.zig.Ast.OptionalTokenIndex, | |
| 912 | std.zig.Ast.ExtraIndex, | |
| 913 | => @intFromEnum(@field(extra, field.name)), | |
| 914 | TokenIndex, | |
| 915 | => @field(extra, field.name), | |
| 916 | else => @compileError("unexpected field type"), | |
| 917 | }; | |
| 918 | c.extra_data.appendAssumeCapacity(data); | |
| 919 | } | |
| 920 | return result; | |
| 921 | } | |
| 922 | }; | |
| 923 | ||
| 924 | fn renderNodeOpt(c: *Context, node: Node) Allocator.Error!?NodeIndex { | |
| 925 | switch (node.tag()) { | |
| 926 | .warning => { | |
| 927 | const payload = node.castTag(.warning).?.data; | |
| 928 | try c.buf.appendSlice(payload); | |
| 929 | try c.buf.append('\n'); | |
| 930 | return null; | |
| 931 | }, | |
| 932 | .discard => { | |
| 933 | const payload = node.castTag(.discard).?.data; | |
| 934 | if (payload.should_skip) return null; | |
| 935 | ||
| 936 | return try renderNode(c, node); | |
| 937 | }, | |
| 938 | else => return try renderNode(c, node), | |
| 939 | } | |
| 940 | } | |
| 941 | ||
| 942 | fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { | |
| 943 | switch (node.tag()) { | |
| 944 | .declaration => unreachable, | |
| 945 | .warning => unreachable, | |
| 946 | .discard => { | |
| 947 | const payload = node.castTag(.discard).?.data; | |
| 948 | std.debug.assert(!payload.should_skip); | |
| 949 | ||
| 950 | const lhs = try c.addNode(.{ | |
| 951 | .tag = .identifier, | |
| 952 | .main_token = try c.addToken(.identifier, "_"), | |
| 953 | .data = undefined, | |
| 954 | }); | |
| 955 | const main_token = try c.addToken(.equal, "="); | |
| 956 | if (payload.value.tag() == .identifier) { | |
| 957 | // Render as `_ = &foo;` to avoid tripping "pointless discard" and "local variable never mutated" errors. | |
| 958 | var addr_of_pl: Payload.UnOp = .{ | |
| 959 | .base = .{ .tag = .address_of }, | |
| 960 | .data = payload.value, | |
| 961 | }; | |
| 962 | const addr_of: Node = .{ .ptr_otherwise = &addr_of_pl.base }; | |
| 963 | return try c.addNode(.{ | |
| 964 | .tag = .assign, | |
| 965 | .main_token = main_token, | |
| 966 | .data = .{ .node_and_node = .{ | |
| 967 | lhs, try renderNode(c, addr_of), | |
| 968 | } }, | |
| 969 | }); | |
| 970 | } else { | |
| 971 | return try c.addNode(.{ | |
| 972 | .tag = .assign, | |
| 973 | .main_token = main_token, | |
| 974 | .data = .{ .node_and_node = .{ | |
| 975 | lhs, try renderNode(c, payload.value), | |
| 976 | } }, | |
| 977 | }); | |
| 978 | } | |
| 979 | }, | |
| 980 | .std_mem_zeroes => { | |
| 981 | const payload = node.castTag(.std_mem_zeroes).?.data; | |
| 982 | const import_node = try renderStdImport(c, &.{ "mem", "zeroes" }); | |
| 983 | return renderCall(c, import_node, &.{payload}); | |
| 984 | }, | |
| 985 | .std_mem_zeroinit => { | |
| 986 | const payload = node.castTag(.std_mem_zeroinit).?.data; | |
| 987 | const import_node = try renderStdImport(c, &.{ "mem", "zeroInit" }); | |
| 988 | return renderCall(c, import_node, &.{ payload.lhs, payload.rhs }); | |
| 989 | }, | |
| 990 | .vector => { | |
| 991 | const payload = node.castTag(.vector).?.data; | |
| 992 | return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs }); | |
| 993 | }, | |
| 994 | .call => { | |
| 995 | const payload = node.castTag(.call).?.data; | |
| 996 | const lhs = try renderNodeGrouped(c, payload.lhs); | |
| 997 | return renderCall(c, lhs, payload.args); | |
| 998 | }, | |
| 999 | .null_literal => return c.addNode(.{ | |
| 1000 | .tag = .identifier, | |
| 1001 | .main_token = try c.addToken(.identifier, "null"), | |
| 1002 | .data = undefined, | |
| 1003 | }), | |
| 1004 | .undefined_literal => return c.addNode(.{ | |
| 1005 | .tag = .identifier, | |
| 1006 | .main_token = try c.addToken(.identifier, "undefined"), | |
| 1007 | .data = undefined, | |
| 1008 | }), | |
| 1009 | .true_literal => return c.addNode(.{ | |
| 1010 | .tag = .identifier, | |
| 1011 | .main_token = try c.addToken(.identifier, "true"), | |
| 1012 | .data = undefined, | |
| 1013 | }), | |
| 1014 | .false_literal => return c.addNode(.{ | |
| 1015 | .tag = .identifier, | |
| 1016 | .main_token = try c.addToken(.identifier, "false"), | |
| 1017 | .data = undefined, | |
| 1018 | }), | |
| 1019 | .zero_literal => return c.addNode(.{ | |
| 1020 | .tag = .number_literal, | |
| 1021 | .main_token = try c.addToken(.number_literal, "0"), | |
| 1022 | .data = undefined, | |
| 1023 | }), | |
| 1024 | .one_literal => return c.addNode(.{ | |
| 1025 | .tag = .number_literal, | |
| 1026 | .main_token = try c.addToken(.number_literal, "1"), | |
| 1027 | .data = undefined, | |
| 1028 | }), | |
| 1029 | .@"unreachable" => return c.addNode(.{ | |
| 1030 | .tag = .unreachable_literal, | |
| 1031 | .main_token = try c.addToken(.keyword_unreachable, "unreachable"), | |
| 1032 | .data = undefined, | |
| 1033 | }), | |
| 1034 | .void_type => return c.addNode(.{ | |
| 1035 | .tag = .identifier, | |
| 1036 | .main_token = try c.addToken(.identifier, "void"), | |
| 1037 | .data = undefined, | |
| 1038 | }), | |
| 1039 | .noreturn_type => return c.addNode(.{ | |
| 1040 | .tag = .identifier, | |
| 1041 | .main_token = try c.addToken(.identifier, "noreturn"), | |
| 1042 | .data = undefined, | |
| 1043 | }), | |
| 1044 | .@"continue" => return c.addNode(.{ | |
| 1045 | .tag = .@"continue", | |
| 1046 | .main_token = try c.addToken(.keyword_continue, "continue"), | |
| 1047 | .data = .{ .opt_token_and_opt_node = .{ | |
| 1048 | .none, .none, | |
| 1049 | } }, | |
| 1050 | }), | |
| 1051 | .return_void => return c.addNode(.{ | |
| 1052 | .tag = .@"return", | |
| 1053 | .main_token = try c.addToken(.keyword_return, "return"), | |
| 1054 | .data = .{ .opt_node = .none }, | |
| 1055 | }), | |
| 1056 | .@"break" => return c.addNode(.{ | |
| 1057 | .tag = .@"break", | |
| 1058 | .main_token = try c.addToken(.keyword_break, "break"), | |
| 1059 | .data = .{ .opt_token_and_opt_node = .{ | |
| 1060 | .none, .none, | |
| 1061 | } }, | |
| 1062 | }), | |
| 1063 | .break_val => { | |
| 1064 | const payload = node.castTag(.break_val).?.data; | |
| 1065 | const tok = try c.addToken(.keyword_break, "break"); | |
| 1066 | const break_label = if (payload.label) |some| blk: { | |
| 1067 | _ = try c.addToken(.colon, ":"); | |
| 1068 | break :blk try c.addIdentifier(some); | |
| 1069 | } else 0; | |
| 1070 | return c.addNode(.{ | |
| 1071 | .tag = .@"break", | |
| 1072 | .main_token = tok, | |
| 1073 | .data = .{ .opt_token_and_opt_node = .{ | |
| 1074 | .fromToken(break_label), (try renderNode(c, payload.val)).toOptional(), | |
| 1075 | } }, | |
| 1076 | }); | |
| 1077 | }, | |
| 1078 | .@"return" => { | |
| 1079 | const payload = node.castTag(.@"return").?.data; | |
| 1080 | return c.addNode(.{ | |
| 1081 | .tag = .@"return", | |
| 1082 | .main_token = try c.addToken(.keyword_return, "return"), | |
| 1083 | .data = .{ .opt_node = (try renderNode(c, payload)).toOptional() }, | |
| 1084 | }); | |
| 1085 | }, | |
| 1086 | .@"comptime" => { | |
| 1087 | const payload = node.castTag(.@"comptime").?.data; | |
| 1088 | return c.addNode(.{ | |
| 1089 | .tag = .@"comptime", | |
| 1090 | .main_token = try c.addToken(.keyword_comptime, "comptime"), | |
| 1091 | .data = .{ | |
| 1092 | .node = try renderNode(c, payload), | |
| 1093 | }, | |
| 1094 | }); | |
| 1095 | }, | |
| 1096 | .@"defer" => { | |
| 1097 | const payload = node.castTag(.@"defer").?.data; | |
| 1098 | return c.addNode(.{ | |
| 1099 | .tag = .@"defer", | |
| 1100 | .main_token = try c.addToken(.keyword_defer, "defer"), | |
| 1101 | .data = .{ | |
| 1102 | .node = try renderNode(c, payload), | |
| 1103 | }, | |
| 1104 | }); | |
| 1105 | }, | |
| 1106 | .asm_simple => { | |
| 1107 | const payload = node.castTag(.asm_simple).?.data; | |
| 1108 | const asm_token = try c.addToken(.keyword_asm, "asm"); | |
| 1109 | _ = try c.addToken(.l_paren, "("); | |
| 1110 | return c.addNode(.{ | |
| 1111 | .tag = .asm_simple, | |
| 1112 | .main_token = asm_token, | |
| 1113 | .data = .{ .node_and_token = .{ | |
| 1114 | try renderNode(c, payload), | |
| 1115 | try c.addToken(.r_paren, ")"), | |
| 1116 | } }, | |
| 1117 | }); | |
| 1118 | }, | |
| 1119 | .type => { | |
| 1120 | const payload = node.castTag(.type).?.data; | |
| 1121 | return c.addNode(.{ | |
| 1122 | .tag = .identifier, | |
| 1123 | .main_token = try c.addToken(.identifier, payload), | |
| 1124 | .data = undefined, | |
| 1125 | }); | |
| 1126 | }, | |
| 1127 | .identifier => { | |
| 1128 | const payload = node.castTag(.identifier).?.data; | |
| 1129 | return c.addNode(.{ | |
| 1130 | .tag = .identifier, | |
| 1131 | .main_token = try c.addIdentifier(payload), | |
| 1132 | .data = undefined, | |
| 1133 | }); | |
| 1134 | }, | |
| 1135 | .float_literal => { | |
| 1136 | const payload = node.castTag(.float_literal).?.data; | |
| 1137 | return c.addNode(.{ | |
| 1138 | .tag = .number_literal, | |
| 1139 | .main_token = try c.addToken(.number_literal, payload), | |
| 1140 | .data = undefined, | |
| 1141 | }); | |
| 1142 | }, | |
| 1143 | .integer_literal => { | |
| 1144 | const payload = node.castTag(.integer_literal).?.data; | |
| 1145 | return c.addNode(.{ | |
| 1146 | .tag = .number_literal, | |
| 1147 | .main_token = try c.addToken(.number_literal, payload), | |
| 1148 | .data = undefined, | |
| 1149 | }); | |
| 1150 | }, | |
| 1151 | .string_literal => { | |
| 1152 | const payload = node.castTag(.string_literal).?.data; | |
| 1153 | return c.addNode(.{ | |
| 1154 | .tag = .string_literal, | |
| 1155 | .main_token = try c.addToken(.string_literal, payload), | |
| 1156 | .data = undefined, | |
| 1157 | }); | |
| 1158 | }, | |
| 1159 | .char_literal => { | |
| 1160 | const payload = node.castTag(.char_literal).?.data; | |
| 1161 | return c.addNode(.{ | |
| 1162 | .tag = .char_literal, | |
| 1163 | .main_token = try c.addToken(.char_literal, payload), | |
| 1164 | .data = undefined, | |
| 1165 | }); | |
| 1166 | }, | |
| 1167 | .enum_literal => { | |
| 1168 | const payload = node.castTag(.enum_literal).?.data; | |
| 1169 | _ = try c.addToken(.period, "."); | |
| 1170 | return c.addNode(.{ | |
| 1171 | .tag = .enum_literal, | |
| 1172 | .main_token = try c.addToken(.identifier, payload), | |
| 1173 | .data = undefined, | |
| 1174 | }); | |
| 1175 | }, | |
| 1176 | .string_slice => { | |
| 1177 | const payload = node.castTag(.string_slice).?.data; | |
| 1178 | ||
| 1179 | const string = try renderNode(c, payload.string); | |
| 1180 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 1181 | const start = try c.addNode(.{ | |
| 1182 | .tag = .number_literal, | |
| 1183 | .main_token = try c.addToken(.number_literal, "0"), | |
| 1184 | .data = undefined, | |
| 1185 | }); | |
| 1186 | _ = try c.addToken(.ellipsis2, ".."); | |
| 1187 | const end = try c.addNode(.{ | |
| 1188 | .tag = .number_literal, | |
| 1189 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.end}), | |
| 1190 | .data = undefined, | |
| 1191 | }); | |
| 1192 | _ = try c.addToken(.r_bracket, "]"); | |
| 1193 | ||
| 1194 | return c.addNode(.{ | |
| 1195 | .tag = .slice, | |
| 1196 | .main_token = l_bracket, | |
| 1197 | .data = .{ .node_and_extra = .{ | |
| 1198 | string, try c.addExtra(std.zig.Ast.Node.Slice{ | |
| 1199 | .start = start, | |
| 1200 | .end = end, | |
| 1201 | }), | |
| 1202 | } }, | |
| 1203 | }); | |
| 1204 | }, | |
| 1205 | .fail_decl => { | |
| 1206 | const payload = node.castTag(.fail_decl).?.data; | |
| 1207 | // pub const name = @compileError(msg); | |
| 1208 | _ = try c.addToken(.keyword_pub, "pub"); | |
| 1209 | const const_tok = try c.addToken(.keyword_const, "const"); | |
| 1210 | _ = try c.addIdentifier(payload.actual); | |
| 1211 | _ = try c.addToken(.equal, "="); | |
| 1212 | ||
| 1213 | const compile_error_tok = try c.addToken(.builtin, "@compileError"); | |
| 1214 | _ = try c.addToken(.l_paren, "("); | |
| 1215 | const err_msg_tok = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(payload.mangled)}); | |
| 1216 | const err_msg = try c.addNode(.{ | |
| 1217 | .tag = .string_literal, | |
| 1218 | .main_token = err_msg_tok, | |
| 1219 | .data = undefined, | |
| 1220 | }); | |
| 1221 | _ = try c.addToken(.r_paren, ")"); | |
| 1222 | const compile_error = try c.addNode(.{ | |
| 1223 | .tag = .builtin_call_two, | |
| 1224 | .main_token = compile_error_tok, | |
| 1225 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1226 | err_msg.toOptional(), .none, | |
| 1227 | } }, | |
| 1228 | }); | |
| 1229 | _ = try c.addToken(.semicolon, ";"); | |
| 1230 | ||
| 1231 | return c.addNode(.{ | |
| 1232 | .tag = .simple_var_decl, | |
| 1233 | .main_token = const_tok, | |
| 1234 | .data = .{ | |
| 1235 | .opt_node_and_opt_node = .{ | |
| 1236 | .none, // Type expression | |
| 1237 | compile_error.toOptional(), // Init expression | |
| 1238 | }, | |
| 1239 | }, | |
| 1240 | }); | |
| 1241 | }, | |
| 1242 | .pub_var_simple, .var_simple => { | |
| 1243 | const payload = @as(*Payload.SimpleVarDecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 1244 | if (node.tag() == .pub_var_simple) _ = try c.addToken(.keyword_pub, "pub"); | |
| 1245 | const const_tok = try c.addToken(.keyword_const, "const"); | |
| 1246 | _ = try c.addIdentifier(payload.name); | |
| 1247 | _ = try c.addToken(.equal, "="); | |
| 1248 | ||
| 1249 | const init = try renderNode(c, payload.init); | |
| 1250 | _ = try c.addToken(.semicolon, ";"); | |
| 1251 | ||
| 1252 | return c.addNode(.{ | |
| 1253 | .tag = .simple_var_decl, | |
| 1254 | .main_token = const_tok, | |
| 1255 | .data = .{ | |
| 1256 | .opt_node_and_opt_node = .{ | |
| 1257 | .none, // Type expression | |
| 1258 | init.toOptional(), // Init expression | |
| 1259 | }, | |
| 1260 | }, | |
| 1261 | }); | |
| 1262 | }, | |
| 1263 | .wrapped_local => { | |
| 1264 | const payload = node.castTag(.wrapped_local).?.data; | |
| 1265 | ||
| 1266 | const const_tok = try c.addToken(.keyword_const, "const"); | |
| 1267 | _ = try c.addIdentifier(payload.name); | |
| 1268 | _ = try c.addToken(.equal, "="); | |
| 1269 | ||
| 1270 | const kind_tok = try c.addToken(.keyword_struct, "struct"); | |
| 1271 | _ = try c.addToken(.l_brace, "{"); | |
| 1272 | ||
| 1273 | const container_def = try c.addNode(.{ | |
| 1274 | .tag = .container_decl_two_trailing, | |
| 1275 | .main_token = kind_tok, | |
| 1276 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1277 | (try renderNode(c, payload.init)).toOptional(), .none, | |
| 1278 | } }, | |
| 1279 | }); | |
| 1280 | _ = try c.addToken(.r_brace, "}"); | |
| 1281 | _ = try c.addToken(.semicolon, ";"); | |
| 1282 | ||
| 1283 | return c.addNode(.{ | |
| 1284 | .tag = .simple_var_decl, | |
| 1285 | .main_token = const_tok, | |
| 1286 | .data = .{ | |
| 1287 | .opt_node_and_opt_node = .{ | |
| 1288 | .none, // Type expression | |
| 1289 | container_def.toOptional(), // Init expression | |
| 1290 | }, | |
| 1291 | }, | |
| 1292 | }); | |
| 1293 | }, | |
| 1294 | .mut_str => { | |
| 1295 | const payload = node.castTag(.mut_str).?.data; | |
| 1296 | ||
| 1297 | const var_tok = try c.addToken(.keyword_var, "var"); | |
| 1298 | _ = try c.addIdentifier(payload.name); | |
| 1299 | _ = try c.addToken(.equal, "="); | |
| 1300 | ||
| 1301 | const deref = try c.addNode(.{ | |
| 1302 | .tag = .deref, | |
| 1303 | .data = .{ | |
| 1304 | .node = try renderNodeGrouped(c, payload.init), | |
| 1305 | }, | |
| 1306 | .main_token = try c.addToken(.period_asterisk, ".*"), | |
| 1307 | }); | |
| 1308 | _ = try c.addToken(.semicolon, ";"); | |
| 1309 | ||
| 1310 | return c.addNode(.{ | |
| 1311 | .tag = .simple_var_decl, | |
| 1312 | .main_token = var_tok, | |
| 1313 | .data = .{ | |
| 1314 | .opt_node_and_opt_node = .{ | |
| 1315 | .none, // Type expression | |
| 1316 | deref.toOptional(), // Init expression | |
| 1317 | }, | |
| 1318 | }, | |
| 1319 | }); | |
| 1320 | }, | |
| 1321 | .var_decl => return renderVar(c, node), | |
| 1322 | .arg_redecl, .alias => { | |
| 1323 | const payload = @as(*Payload.ArgRedecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 1324 | if (node.tag() == .alias) _ = try c.addToken(.keyword_pub, "pub"); | |
| 1325 | const mut_tok = if (node.tag() == .alias) | |
| 1326 | try c.addToken(.keyword_const, "const") | |
| 1327 | else | |
| 1328 | try c.addToken(.keyword_var, "var"); | |
| 1329 | _ = try c.addIdentifier(payload.actual); | |
| 1330 | _ = try c.addToken(.equal, "="); | |
| 1331 | ||
| 1332 | const init = try c.addNode(.{ | |
| 1333 | .tag = .identifier, | |
| 1334 | .main_token = try c.addIdentifier(payload.mangled), | |
| 1335 | .data = undefined, | |
| 1336 | }); | |
| 1337 | _ = try c.addToken(.semicolon, ";"); | |
| 1338 | ||
| 1339 | return c.addNode(.{ | |
| 1340 | .tag = .simple_var_decl, | |
| 1341 | .main_token = mut_tok, | |
| 1342 | .data = .{ | |
| 1343 | .opt_node_and_opt_node = .{ | |
| 1344 | .none, // Type expression | |
| 1345 | init.toOptional(), // Init expression | |
| 1346 | }, | |
| 1347 | }, | |
| 1348 | }); | |
| 1349 | }, | |
| 1350 | .int_cast => { | |
| 1351 | const payload = node.castTag(.int_cast).?.data; | |
| 1352 | return renderBuiltinCall(c, "@intCast", &.{payload}); | |
| 1353 | }, | |
| 1354 | .const_cast => { | |
| 1355 | const payload = node.castTag(.const_cast).?.data; | |
| 1356 | return renderBuiltinCall(c, "@constCast", &.{payload}); | |
| 1357 | }, | |
| 1358 | .volatile_cast => { | |
| 1359 | const payload = node.castTag(.volatile_cast).?.data; | |
| 1360 | return renderBuiltinCall(c, "@volatileCast", &.{payload}); | |
| 1361 | }, | |
| 1362 | .div_trunc => { | |
| 1363 | const payload = node.castTag(.div_trunc).?.data; | |
| 1364 | return renderBuiltinCall(c, "@divTrunc", &.{ payload.lhs, payload.rhs }); | |
| 1365 | }, | |
| 1366 | .int_from_bool => { | |
| 1367 | const payload = node.castTag(.int_from_bool).?.data; | |
| 1368 | return renderBuiltinCall(c, "@intFromBool", &.{payload}); | |
| 1369 | }, | |
| 1370 | .as => { | |
| 1371 | const payload = node.castTag(.as).?.data; | |
| 1372 | return renderBuiltinCall(c, "@as", &.{ payload.lhs, payload.rhs }); | |
| 1373 | }, | |
| 1374 | .truncate => { | |
| 1375 | const payload = node.castTag(.truncate).?.data; | |
| 1376 | return renderBuiltinCall(c, "@truncate", &.{payload}); | |
| 1377 | }, | |
| 1378 | .bit_cast => { | |
| 1379 | const payload = node.castTag(.bit_cast).?.data; | |
| 1380 | return renderBuiltinCall(c, "@bitCast", &.{payload}); | |
| 1381 | }, | |
| 1382 | .float_cast => { | |
| 1383 | const payload = node.castTag(.float_cast).?.data; | |
| 1384 | return renderBuiltinCall(c, "@floatCast", &.{payload}); | |
| 1385 | }, | |
| 1386 | .int_from_float => { | |
| 1387 | const payload = node.castTag(.int_from_float).?.data; | |
| 1388 | return renderBuiltinCall(c, "@intFromFloat", &.{payload}); | |
| 1389 | }, | |
| 1390 | .float_from_int => { | |
| 1391 | const payload = node.castTag(.float_from_int).?.data; | |
| 1392 | return renderBuiltinCall(c, "@floatFromInt", &.{payload}); | |
| 1393 | }, | |
| 1394 | .ptr_from_int => { | |
| 1395 | const payload = node.castTag(.ptr_from_int).?.data; | |
| 1396 | return renderBuiltinCall(c, "@ptrFromInt", &.{payload}); | |
| 1397 | }, | |
| 1398 | .int_from_ptr => { | |
| 1399 | const payload = node.castTag(.int_from_ptr).?.data; | |
| 1400 | return renderBuiltinCall(c, "@intFromPtr", &.{payload}); | |
| 1401 | }, | |
| 1402 | .align_cast => { | |
| 1403 | const payload = node.castTag(.align_cast).?.data; | |
| 1404 | return renderBuiltinCall(c, "@alignCast", &.{payload}); | |
| 1405 | }, | |
| 1406 | .ptr_cast => { | |
| 1407 | const payload = node.castTag(.ptr_cast).?.data; | |
| 1408 | return renderBuiltinCall(c, "@ptrCast", &.{payload}); | |
| 1409 | }, | |
| 1410 | .div_exact => { | |
| 1411 | const payload = node.castTag(.div_exact).?.data; | |
| 1412 | return renderBuiltinCall(c, "@divExact", &.{ payload.lhs, payload.rhs }); | |
| 1413 | }, | |
| 1414 | .offset_of => { | |
| 1415 | const payload = node.castTag(.offset_of).?.data; | |
| 1416 | return renderBuiltinCall(c, "@offsetOf", &.{ payload.lhs, payload.rhs }); | |
| 1417 | }, | |
| 1418 | .sizeof => { | |
| 1419 | const payload = node.castTag(.sizeof).?.data; | |
| 1420 | return renderBuiltinCall(c, "@sizeOf", &.{payload}); | |
| 1421 | }, | |
| 1422 | .shuffle => { | |
| 1423 | const payload = node.castTag(.shuffle).?.data; | |
| 1424 | return renderBuiltinCall(c, "@shuffle", &.{ | |
| 1425 | payload.element_type, | |
| 1426 | payload.a, | |
| 1427 | payload.b, | |
| 1428 | payload.mask_vector, | |
| 1429 | }); | |
| 1430 | }, | |
| 1431 | .builtin_extern => { | |
| 1432 | const payload = node.castTag(.builtin_extern).?.data; | |
| 1433 | ||
| 1434 | var info_inits: [1]Payload.ContainerInitDot.Initializer = .{ | |
| 1435 | .{ .name = "name", .value = payload.name }, | |
| 1436 | }; | |
| 1437 | var info_payload: Payload.ContainerInitDot = .{ | |
| 1438 | .base = .{ .tag = .container_init_dot }, | |
| 1439 | .data = &info_inits, | |
| 1440 | }; | |
| 1441 | ||
| 1442 | return renderBuiltinCall(c, "@extern", &.{ | |
| 1443 | payload.type, | |
| 1444 | .{ .ptr_otherwise = &info_payload.base }, | |
| 1445 | }); | |
| 1446 | }, | |
| 1447 | .helper_call => { | |
| 1448 | const payload = node.castTag(.helper_call).?.data; | |
| 1449 | const helpers_tok = try c.addNode(.{ | |
| 1450 | .tag = .identifier, | |
| 1451 | .main_token = try c.addIdentifier("__helpers"), | |
| 1452 | .data = undefined, | |
| 1453 | }); | |
| 1454 | const func = try renderFieldAccess(c, helpers_tok, payload.name); | |
| 1455 | return renderCall(c, func, payload.args); | |
| 1456 | }, | |
| 1457 | .helper_ref => { | |
| 1458 | const payload = node.castTag(.helper_ref).?.data; | |
| 1459 | const helpers_tok = try c.addNode(.{ | |
| 1460 | .tag = .identifier, | |
| 1461 | .main_token = try c.addIdentifier("__helpers"), | |
| 1462 | .data = undefined, | |
| 1463 | }); | |
| 1464 | return renderFieldAccess(c, helpers_tok, payload); | |
| 1465 | }, | |
| 1466 | .alignof => { | |
| 1467 | const payload = node.castTag(.alignof).?.data; | |
| 1468 | return renderBuiltinCall(c, "@alignOf", &.{payload}); | |
| 1469 | }, | |
| 1470 | .typeof => { | |
| 1471 | const payload = node.castTag(.typeof).?.data; | |
| 1472 | return renderBuiltinCall(c, "@TypeOf", &.{payload}); | |
| 1473 | }, | |
| 1474 | .typeinfo => { | |
| 1475 | const payload = node.castTag(.typeinfo).?.data; | |
| 1476 | return renderBuiltinCall(c, "@typeInfo", &.{payload}); | |
| 1477 | }, | |
| 1478 | .byte_swap => { | |
| 1479 | const payload = node.castTag(.byte_swap).?.data; | |
| 1480 | return renderBuiltinCall(c, "@byteSwap", &.{payload}); | |
| 1481 | }, | |
| 1482 | .ceil => { | |
| 1483 | const payload = node.castTag(.ceil).?.data; | |
| 1484 | return renderBuiltinCall(c, "@ceil", &.{payload}); | |
| 1485 | }, | |
| 1486 | .cos => { | |
| 1487 | const payload = node.castTag(.cos).?.data; | |
| 1488 | return renderBuiltinCall(c, "@cos", &.{payload}); | |
| 1489 | }, | |
| 1490 | .sin => { | |
| 1491 | const payload = node.castTag(.sin).?.data; | |
| 1492 | return renderBuiltinCall(c, "@sin", &.{payload}); | |
| 1493 | }, | |
| 1494 | .exp => { | |
| 1495 | const payload = node.castTag(.exp).?.data; | |
| 1496 | return renderBuiltinCall(c, "@exp", &.{payload}); | |
| 1497 | }, | |
| 1498 | .exp2 => { | |
| 1499 | const payload = node.castTag(.exp2).?.data; | |
| 1500 | return renderBuiltinCall(c, "@exp2", &.{payload}); | |
| 1501 | }, | |
| 1502 | .exp10 => { | |
| 1503 | const payload = node.castTag(.exp10).?.data; | |
| 1504 | return renderBuiltinCall(c, "@exp10", &.{payload}); | |
| 1505 | }, | |
| 1506 | .abs => { | |
| 1507 | const payload = node.castTag(.abs).?.data; | |
| 1508 | return renderBuiltinCall(c, "@abs", &.{payload}); | |
| 1509 | }, | |
| 1510 | .log => { | |
| 1511 | const payload = node.castTag(.log).?.data; | |
| 1512 | return renderBuiltinCall(c, "@log", &.{payload}); | |
| 1513 | }, | |
| 1514 | .log2 => { | |
| 1515 | const payload = node.castTag(.log2).?.data; | |
| 1516 | return renderBuiltinCall(c, "@log2", &.{payload}); | |
| 1517 | }, | |
| 1518 | .log10 => { | |
| 1519 | const payload = node.castTag(.log10).?.data; | |
| 1520 | return renderBuiltinCall(c, "@log10", &.{payload}); | |
| 1521 | }, | |
| 1522 | .round => { | |
| 1523 | const payload = node.castTag(.round).?.data; | |
| 1524 | return renderBuiltinCall(c, "@round", &.{payload}); | |
| 1525 | }, | |
| 1526 | .sqrt => { | |
| 1527 | const payload = node.castTag(.sqrt).?.data; | |
| 1528 | return renderBuiltinCall(c, "@sqrt", &.{payload}); | |
| 1529 | }, | |
| 1530 | .trunc => { | |
| 1531 | const payload = node.castTag(.trunc).?.data; | |
| 1532 | return renderBuiltinCall(c, "@trunc", &.{payload}); | |
| 1533 | }, | |
| 1534 | .floor => { | |
| 1535 | const payload = node.castTag(.floor).?.data; | |
| 1536 | return renderBuiltinCall(c, "@floor", &.{payload}); | |
| 1537 | }, | |
| 1538 | .negate => return renderPrefixOp(c, node, .negation, .minus, "-"), | |
| 1539 | .negate_wrap => return renderPrefixOp(c, node, .negation_wrap, .minus_percent, "-%"), | |
| 1540 | .bit_not => return renderPrefixOp(c, node, .bit_not, .tilde, "~"), | |
| 1541 | .not => return renderPrefixOp(c, node, .bool_not, .bang, "!"), | |
| 1542 | .optional_type => return renderPrefixOp(c, node, .optional_type, .question_mark, "?"), | |
| 1543 | .address_of => { | |
| 1544 | const payload = node.castTag(.address_of).?.data; | |
| 1545 | ||
| 1546 | const ampersand = try c.addToken(.ampersand, "&"); | |
| 1547 | const base = try renderNodeGrouped(c, payload); | |
| 1548 | return c.addNode(.{ | |
| 1549 | .tag = .address_of, | |
| 1550 | .main_token = ampersand, | |
| 1551 | .data = .{ | |
| 1552 | .node = base, | |
| 1553 | }, | |
| 1554 | }); | |
| 1555 | }, | |
| 1556 | .deref => { | |
| 1557 | const payload = node.castTag(.deref).?.data; | |
| 1558 | const operand = try renderNodeGrouped(c, payload); | |
| 1559 | const deref_tok = try c.addToken(.period_asterisk, ".*"); | |
| 1560 | return c.addNode(.{ | |
| 1561 | .tag = .deref, | |
| 1562 | .main_token = deref_tok, | |
| 1563 | .data = .{ | |
| 1564 | .node = operand, | |
| 1565 | }, | |
| 1566 | }); | |
| 1567 | }, | |
| 1568 | .unwrap => { | |
| 1569 | const payload = node.castTag(.unwrap).?.data; | |
| 1570 | const operand = try renderNodeGrouped(c, payload); | |
| 1571 | const period = try c.addToken(.period, "."); | |
| 1572 | const question_mark = try c.addToken(.question_mark, "?"); | |
| 1573 | return c.addNode(.{ | |
| 1574 | .tag = .unwrap_optional, | |
| 1575 | .main_token = period, | |
| 1576 | .data = .{ .node_and_token = .{ | |
| 1577 | operand, question_mark, | |
| 1578 | } }, | |
| 1579 | }); | |
| 1580 | }, | |
| 1581 | .c_pointer, .single_pointer => { | |
| 1582 | const payload = @as(*Payload.Pointer, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 1583 | ||
| 1584 | const main_token = if (node.tag() == .single_pointer) | |
| 1585 | try c.addToken(.asterisk, "*") | |
| 1586 | else blk: { | |
| 1587 | const res = try c.addToken(.l_bracket, "["); | |
| 1588 | _ = try c.addToken(.asterisk, "*"); | |
| 1589 | _ = try c.addIdentifier("c"); | |
| 1590 | _ = try c.addToken(.r_bracket, "]"); | |
| 1591 | break :blk res; | |
| 1592 | }; | |
| 1593 | if (payload.is_const) _ = try c.addToken(.keyword_const, "const"); | |
| 1594 | if (payload.is_volatile) _ = try c.addToken(.keyword_volatile, "volatile"); | |
| 1595 | if (payload.is_allowzero) _ = try c.addToken(.keyword_allowzero, "allowzero"); | |
| 1596 | const elem_type = try renderNodeGrouped(c, payload.elem_type); | |
| 1597 | ||
| 1598 | return c.addNode(.{ | |
| 1599 | .tag = .ptr_type_aligned, | |
| 1600 | .main_token = main_token, | |
| 1601 | .data = .{ | |
| 1602 | .opt_node_and_node = .{ | |
| 1603 | .none, // Align node | |
| 1604 | elem_type, | |
| 1605 | }, | |
| 1606 | }, | |
| 1607 | }); | |
| 1608 | }, | |
| 1609 | .add => return renderBinOpGrouped(c, node, .add, .plus, "+"), | |
| 1610 | .add_assign => return renderBinOp(c, node, .assign_add, .plus_equal, "+="), | |
| 1611 | .add_wrap => return renderBinOpGrouped(c, node, .add_wrap, .plus_percent, "+%"), | |
| 1612 | .add_wrap_assign => return renderBinOp(c, node, .assign_add_wrap, .plus_percent_equal, "+%="), | |
| 1613 | .sub => return renderBinOpGrouped(c, node, .sub, .minus, "-"), | |
| 1614 | .sub_assign => return renderBinOp(c, node, .assign_sub, .minus_equal, "-="), | |
| 1615 | .sub_wrap => return renderBinOpGrouped(c, node, .sub_wrap, .minus_percent, "-%"), | |
| 1616 | .sub_wrap_assign => return renderBinOp(c, node, .assign_sub_wrap, .minus_percent_equal, "-%="), | |
| 1617 | .mul => return renderBinOpGrouped(c, node, .mul, .asterisk, "*"), | |
| 1618 | .mul_assign => return renderBinOp(c, node, .assign_mul, .asterisk_equal, "*="), | |
| 1619 | .mul_wrap => return renderBinOpGrouped(c, node, .mul_wrap, .asterisk_percent, "*%"), | |
| 1620 | .mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="), | |
| 1621 | .div => return renderBinOpGrouped(c, node, .div, .slash, "/"), | |
| 1622 | .div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="), | |
| 1623 | .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"), | |
| 1624 | .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="), | |
| 1625 | .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"), | |
| 1626 | .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="), | |
| 1627 | .mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"), | |
| 1628 | .mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="), | |
| 1629 | .@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"), | |
| 1630 | .@"or" => return renderBinOpGrouped(c, node, .bool_or, .keyword_or, "or"), | |
| 1631 | .less_than => return renderBinOpGrouped(c, node, .less_than, .angle_bracket_left, "<"), | |
| 1632 | .less_than_equal => return renderBinOpGrouped(c, node, .less_or_equal, .angle_bracket_left_equal, "<="), | |
| 1633 | .greater_than => return renderBinOpGrouped(c, node, .greater_than, .angle_bracket_right, ">="), | |
| 1634 | .greater_than_equal => return renderBinOpGrouped(c, node, .greater_or_equal, .angle_bracket_right_equal, ">="), | |
| 1635 | .equal => return renderBinOpGrouped(c, node, .equal_equal, .equal_equal, "=="), | |
| 1636 | .not_equal => return renderBinOpGrouped(c, node, .bang_equal, .bang_equal, "!="), | |
| 1637 | .bit_and => return renderBinOpGrouped(c, node, .bit_and, .ampersand, "&"), | |
| 1638 | .bit_and_assign => return renderBinOp(c, node, .assign_bit_and, .ampersand_equal, "&="), | |
| 1639 | .bit_or => return renderBinOpGrouped(c, node, .bit_or, .pipe, "|"), | |
| 1640 | .bit_or_assign => return renderBinOp(c, node, .assign_bit_or, .pipe_equal, "|="), | |
| 1641 | .bit_xor => return renderBinOpGrouped(c, node, .bit_xor, .caret, "^"), | |
| 1642 | .bit_xor_assign => return renderBinOp(c, node, .assign_bit_xor, .caret_equal, "^="), | |
| 1643 | .array_cat => return renderBinOp(c, node, .array_cat, .plus_plus, "++"), | |
| 1644 | .ellipsis3 => return renderBinOpGrouped(c, node, .switch_range, .ellipsis3, "..."), | |
| 1645 | .assign => return renderBinOp(c, node, .assign, .equal, "="), | |
| 1646 | .empty_block => { | |
| 1647 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 1648 | _ = try c.addToken(.r_brace, "}"); | |
| 1649 | return c.addNode(.{ | |
| 1650 | .tag = .block_two, | |
| 1651 | .main_token = l_brace, | |
| 1652 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1653 | .none, .none, | |
| 1654 | } }, | |
| 1655 | }); | |
| 1656 | }, | |
| 1657 | .block_single => { | |
| 1658 | const payload = node.castTag(.block_single).?.data; | |
| 1659 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 1660 | ||
| 1661 | const stmt = (try renderNodeOpt(c, payload)) orelse { | |
| 1662 | _ = try c.addToken(.r_brace, "}"); | |
| 1663 | return c.addNode(.{ | |
| 1664 | .tag = .block_two, | |
| 1665 | .main_token = l_brace, | |
| 1666 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1667 | .none, .none, | |
| 1668 | } }, | |
| 1669 | }); | |
| 1670 | }; | |
| 1671 | try addSemicolonIfNeeded(c, payload); | |
| 1672 | ||
| 1673 | _ = try c.addToken(.r_brace, "}"); | |
| 1674 | return c.addNode(.{ | |
| 1675 | .tag = .block_two_semicolon, | |
| 1676 | .main_token = l_brace, | |
| 1677 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1678 | stmt.toOptional(), .none, | |
| 1679 | } }, | |
| 1680 | }); | |
| 1681 | }, | |
| 1682 | .block => { | |
| 1683 | const payload = node.castTag(.block).?.data; | |
| 1684 | if (payload.label) |some| { | |
| 1685 | _ = try c.addIdentifier(some); | |
| 1686 | _ = try c.addToken(.colon, ":"); | |
| 1687 | } | |
| 1688 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 1689 | ||
| 1690 | var stmts = std.array_list.Managed(NodeIndex).init(c.gpa); | |
| 1691 | defer stmts.deinit(); | |
| 1692 | for (payload.stmts) |stmt| { | |
| 1693 | const res = (try renderNodeOpt(c, stmt)) orelse continue; | |
| 1694 | try addSemicolonIfNeeded(c, stmt); | |
| 1695 | try stmts.append(res); | |
| 1696 | } | |
| 1697 | const span = try c.listToSpan(stmts.items); | |
| 1698 | _ = try c.addToken(.r_brace, "}"); | |
| 1699 | ||
| 1700 | const semicolon = c.tokens.items(.tag)[c.tokens.len - 2] == .semicolon; | |
| 1701 | return c.addNode(.{ | |
| 1702 | .tag = if (semicolon) .block_semicolon else .block, | |
| 1703 | .main_token = l_brace, | |
| 1704 | .data = .{ .extra_range = span }, | |
| 1705 | }); | |
| 1706 | }, | |
| 1707 | .func => return renderFunc(c, node), | |
| 1708 | .pub_inline_fn => return renderMacroFunc(c, node), | |
| 1709 | .@"while" => { | |
| 1710 | const payload = node.castTag(.@"while").?.data; | |
| 1711 | const while_tok = try c.addToken(.keyword_while, "while"); | |
| 1712 | _ = try c.addToken(.l_paren, "("); | |
| 1713 | const cond = try renderNode(c, payload.cond); | |
| 1714 | _ = try c.addToken(.r_paren, ")"); | |
| 1715 | ||
| 1716 | const cont_expr_opt = if (payload.cont_expr) |some| blk: { | |
| 1717 | _ = try c.addToken(.colon, ":"); | |
| 1718 | _ = try c.addToken(.l_paren, "("); | |
| 1719 | const res = try renderNode(c, some); | |
| 1720 | _ = try c.addToken(.r_paren, ")"); | |
| 1721 | break :blk res; | |
| 1722 | } else null; | |
| 1723 | const body = try renderNode(c, payload.body); | |
| 1724 | ||
| 1725 | if (cont_expr_opt) |cont_expr| { | |
| 1726 | return c.addNode(.{ | |
| 1727 | .tag = .while_cont, | |
| 1728 | .main_token = while_tok, | |
| 1729 | .data = .{ .node_and_extra = .{ | |
| 1730 | cond, | |
| 1731 | try c.addExtra(std.zig.Ast.Node.WhileCont{ | |
| 1732 | .cont_expr = cont_expr, | |
| 1733 | .then_expr = body, | |
| 1734 | }), | |
| 1735 | } }, | |
| 1736 | }); | |
| 1737 | } else { | |
| 1738 | return c.addNode(.{ | |
| 1739 | .tag = .while_simple, | |
| 1740 | .main_token = while_tok, | |
| 1741 | .data = .{ .node_and_node = .{ | |
| 1742 | cond, body, | |
| 1743 | } }, | |
| 1744 | }); | |
| 1745 | } | |
| 1746 | }, | |
| 1747 | .while_true => { | |
| 1748 | const payload = node.castTag(.while_true).?.data; | |
| 1749 | const while_tok = try c.addToken(.keyword_while, "while"); | |
| 1750 | _ = try c.addToken(.l_paren, "("); | |
| 1751 | const cond = try c.addNode(.{ | |
| 1752 | .tag = .identifier, | |
| 1753 | .main_token = try c.addToken(.identifier, "true"), | |
| 1754 | .data = undefined, | |
| 1755 | }); | |
| 1756 | _ = try c.addToken(.r_paren, ")"); | |
| 1757 | const body = try renderNode(c, payload); | |
| 1758 | ||
| 1759 | return c.addNode(.{ | |
| 1760 | .tag = .while_simple, | |
| 1761 | .main_token = while_tok, | |
| 1762 | .data = .{ .node_and_node = .{ | |
| 1763 | cond, body, | |
| 1764 | } }, | |
| 1765 | }); | |
| 1766 | }, | |
| 1767 | .@"if" => { | |
| 1768 | const payload = node.castTag(.@"if").?.data; | |
| 1769 | const if_tok = try c.addToken(.keyword_if, "if"); | |
| 1770 | _ = try c.addToken(.l_paren, "("); | |
| 1771 | const cond = try renderNode(c, payload.cond); | |
| 1772 | _ = try c.addToken(.r_paren, ")"); | |
| 1773 | ||
| 1774 | const then_expr = try renderNode(c, payload.then); | |
| 1775 | const else_node = payload.@"else" orelse return c.addNode(.{ | |
| 1776 | .tag = .if_simple, | |
| 1777 | .main_token = if_tok, | |
| 1778 | .data = .{ .node_and_node = .{ | |
| 1779 | cond, then_expr, | |
| 1780 | } }, | |
| 1781 | }); | |
| 1782 | _ = try c.addToken(.keyword_else, "else"); | |
| 1783 | const else_expr = try renderNode(c, else_node); | |
| 1784 | ||
| 1785 | return c.addNode(.{ | |
| 1786 | .tag = .@"if", | |
| 1787 | .main_token = if_tok, | |
| 1788 | .data = .{ .node_and_extra = .{ | |
| 1789 | cond, | |
| 1790 | try c.addExtra(std.zig.Ast.Node.If{ | |
| 1791 | .then_expr = then_expr, | |
| 1792 | .else_expr = else_expr, | |
| 1793 | }), | |
| 1794 | } }, | |
| 1795 | }); | |
| 1796 | }, | |
| 1797 | .if_not_break => { | |
| 1798 | const payload = node.castTag(.if_not_break).?.data; | |
| 1799 | const if_tok = try c.addToken(.keyword_if, "if"); | |
| 1800 | _ = try c.addToken(.l_paren, "("); | |
| 1801 | const cond = try c.addNode(.{ | |
| 1802 | .tag = .bool_not, | |
| 1803 | .main_token = try c.addToken(.bang, "!"), | |
| 1804 | .data = .{ | |
| 1805 | .node = try renderNodeGrouped(c, payload), | |
| 1806 | }, | |
| 1807 | }); | |
| 1808 | _ = try c.addToken(.r_paren, ")"); | |
| 1809 | const then_expr = try c.addNode(.{ | |
| 1810 | .tag = .@"break", | |
| 1811 | .main_token = try c.addToken(.keyword_break, "break"), | |
| 1812 | .data = .{ .opt_token_and_opt_node = .{ | |
| 1813 | .none, .none, | |
| 1814 | } }, | |
| 1815 | }); | |
| 1816 | ||
| 1817 | return c.addNode(.{ | |
| 1818 | .tag = .if_simple, | |
| 1819 | .main_token = if_tok, | |
| 1820 | .data = .{ .node_and_node = .{ | |
| 1821 | cond, then_expr, | |
| 1822 | } }, | |
| 1823 | }); | |
| 1824 | }, | |
| 1825 | .@"switch" => { | |
| 1826 | const payload = node.castTag(.@"switch").?.data; | |
| 1827 | const switch_tok = try c.addToken(.keyword_switch, "switch"); | |
| 1828 | _ = try c.addToken(.l_paren, "("); | |
| 1829 | const cond = try renderNode(c, payload.cond); | |
| 1830 | _ = try c.addToken(.r_paren, ")"); | |
| 1831 | ||
| 1832 | _ = try c.addToken(.l_brace, "{"); | |
| 1833 | var cases = try c.gpa.alloc(NodeIndex, payload.cases.len); | |
| 1834 | defer c.gpa.free(cases); | |
| 1835 | for (payload.cases, 0..) |case, i| { | |
| 1836 | cases[i] = try renderNode(c, case); | |
| 1837 | _ = try c.addToken(.comma, ","); | |
| 1838 | } | |
| 1839 | const span = try c.listToSpan(cases); | |
| 1840 | _ = try c.addToken(.r_brace, "}"); | |
| 1841 | return c.addNode(.{ | |
| 1842 | .tag = .switch_comma, | |
| 1843 | .main_token = switch_tok, | |
| 1844 | .data = .{ .node_and_extra = .{ | |
| 1845 | cond, | |
| 1846 | try c.addExtra(NodeSubRange{ | |
| 1847 | .start = span.start, | |
| 1848 | .end = span.end, | |
| 1849 | }), | |
| 1850 | } }, | |
| 1851 | }); | |
| 1852 | }, | |
| 1853 | .switch_else => { | |
| 1854 | const payload = node.castTag(.switch_else).?.data; | |
| 1855 | _ = try c.addToken(.keyword_else, "else"); | |
| 1856 | return c.addNode(.{ | |
| 1857 | .tag = .switch_case_one, | |
| 1858 | .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), | |
| 1859 | .data = .{ .opt_node_and_node = .{ | |
| 1860 | .none, try renderNode(c, payload), | |
| 1861 | } }, | |
| 1862 | }); | |
| 1863 | }, | |
| 1864 | .switch_prong => { | |
| 1865 | const payload = node.castTag(.switch_prong).?.data; | |
| 1866 | var items = try c.gpa.alloc(NodeIndex, payload.cases.len); | |
| 1867 | defer c.gpa.free(items); | |
| 1868 | ||
| 1869 | for (payload.cases, 0..) |item, i| { | |
| 1870 | if (i != 0) _ = try c.addToken(.comma, ","); | |
| 1871 | items[i] = try renderNode(c, item); | |
| 1872 | } | |
| 1873 | _ = try c.addToken(.r_brace, "}"); | |
| 1874 | if (items.len < 2) { | |
| 1875 | return c.addNode(.{ | |
| 1876 | .tag = .switch_case_one, | |
| 1877 | .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), | |
| 1878 | .data = .{ .opt_node_and_node = .{ | |
| 1879 | if (payload.cases.len == 1) items[0].toOptional() else .none, | |
| 1880 | try renderNode(c, payload.cond), | |
| 1881 | } }, | |
| 1882 | }); | |
| 1883 | } else { | |
| 1884 | return c.addNode(.{ | |
| 1885 | .tag = .switch_case, | |
| 1886 | .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), | |
| 1887 | .data = .{ .extra_and_node = .{ | |
| 1888 | try c.addExtra(try c.listToSpan(items)), | |
| 1889 | try renderNode(c, payload.cond), | |
| 1890 | } }, | |
| 1891 | }); | |
| 1892 | } | |
| 1893 | }, | |
| 1894 | .opaque_literal => { | |
| 1895 | const opaque_tok = try c.addToken(.keyword_opaque, "opaque"); | |
| 1896 | _ = try c.addToken(.l_brace, "{"); | |
| 1897 | _ = try c.addToken(.r_brace, "}"); | |
| 1898 | ||
| 1899 | return c.addNode(.{ | |
| 1900 | .tag = .container_decl_two, | |
| 1901 | .main_token = opaque_tok, | |
| 1902 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1903 | .none, .none, | |
| 1904 | } }, | |
| 1905 | }); | |
| 1906 | }, | |
| 1907 | .array_access => { | |
| 1908 | const payload = node.castTag(.array_access).?.data; | |
| 1909 | const lhs = try renderNodeGrouped(c, payload.lhs); | |
| 1910 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 1911 | const index_expr = try renderNode(c, payload.rhs); | |
| 1912 | _ = try c.addToken(.r_bracket, "]"); | |
| 1913 | return c.addNode(.{ | |
| 1914 | .tag = .array_access, | |
| 1915 | .main_token = l_bracket, | |
| 1916 | .data = .{ .node_and_node = .{ | |
| 1917 | lhs, index_expr, | |
| 1918 | } }, | |
| 1919 | }); | |
| 1920 | }, | |
| 1921 | .array_type => { | |
| 1922 | const payload = node.castTag(.array_type).?.data; | |
| 1923 | return renderArrayType(c, payload.len, payload.elem_type); | |
| 1924 | }, | |
| 1925 | .null_sentinel_array_type => { | |
| 1926 | const payload = node.castTag(.null_sentinel_array_type).?.data; | |
| 1927 | return renderNullSentinelArrayType(c, payload.len, payload.elem_type); | |
| 1928 | }, | |
| 1929 | .array_filler => { | |
| 1930 | const payload = node.castTag(.array_filler).?.data; | |
| 1931 | ||
| 1932 | const type_expr = try renderArrayType(c, 1, payload.type); | |
| 1933 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 1934 | const val = try renderNode(c, payload.filler); | |
| 1935 | _ = try c.addToken(.r_brace, "}"); | |
| 1936 | ||
| 1937 | const init = try c.addNode(.{ | |
| 1938 | .tag = .array_init_one, | |
| 1939 | .main_token = l_brace, | |
| 1940 | .data = .{ .node_and_node = .{ | |
| 1941 | type_expr, val, | |
| 1942 | } }, | |
| 1943 | }); | |
| 1944 | return c.addNode(.{ | |
| 1945 | .tag = .array_cat, | |
| 1946 | .main_token = try c.addToken(.asterisk_asterisk, "**"), | |
| 1947 | .data = .{ .node_and_node = .{ | |
| 1948 | init, | |
| 1949 | try c.addNode(.{ | |
| 1950 | .tag = .number_literal, | |
| 1951 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.count}), | |
| 1952 | .data = undefined, | |
| 1953 | }), | |
| 1954 | } }, | |
| 1955 | }); | |
| 1956 | }, | |
| 1957 | .empty_array => { | |
| 1958 | const payload = node.castTag(.empty_array).?.data; | |
| 1959 | ||
| 1960 | const type_expr = try renderNode(c, payload); | |
| 1961 | return renderArrayInit(c, type_expr, &.{}); | |
| 1962 | }, | |
| 1963 | .array_init => { | |
| 1964 | const payload = node.castTag(.array_init).?.data; | |
| 1965 | const type_expr = try renderNode(c, payload.cond); | |
| 1966 | return renderArrayInit(c, type_expr, payload.cases); | |
| 1967 | }, | |
| 1968 | .vector_zero_init => { | |
| 1969 | const payload = node.castTag(.vector_zero_init).?.data; | |
| 1970 | return renderBuiltinCall(c, "@splat", &.{payload}); | |
| 1971 | }, | |
| 1972 | .field_access => { | |
| 1973 | const payload = node.castTag(.field_access).?.data; | |
| 1974 | const lhs = try renderNodeGrouped(c, payload.lhs); | |
| 1975 | return renderFieldAccess(c, lhs, payload.field_name); | |
| 1976 | }, | |
| 1977 | .@"struct", .@"union", .@"opaque" => return renderContainer(c, node), | |
| 1978 | .enum_constant => { | |
| 1979 | const payload = node.castTag(.enum_constant).?.data; | |
| 1980 | ||
| 1981 | if (payload.is_public) _ = try c.addToken(.keyword_pub, "pub"); | |
| 1982 | const const_tok = try c.addToken(.keyword_const, "const"); | |
| 1983 | _ = try c.addIdentifier(payload.name); | |
| 1984 | ||
| 1985 | const type_node_opt = if (payload.type) |enum_const_type| blk: { | |
| 1986 | _ = try c.addToken(.colon, ":"); | |
| 1987 | break :blk try renderNode(c, enum_const_type); | |
| 1988 | } else null; | |
| 1989 | ||
| 1990 | _ = try c.addToken(.equal, "="); | |
| 1991 | ||
| 1992 | const init_node = try renderNode(c, payload.value); | |
| 1993 | _ = try c.addToken(.semicolon, ";"); | |
| 1994 | ||
| 1995 | return c.addNode(.{ | |
| 1996 | .tag = .simple_var_decl, | |
| 1997 | .main_token = const_tok, | |
| 1998 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1999 | .fromOptional(type_node_opt), | |
| 2000 | init_node.toOptional(), | |
| 2001 | } }, | |
| 2002 | }); | |
| 2003 | }, | |
| 2004 | .tuple => { | |
| 2005 | const payload = node.castTag(.tuple).?.data; | |
| 2006 | _ = try c.addToken(.period, "."); | |
| 2007 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2008 | var inits = try c.gpa.alloc(NodeIndex, payload.len); | |
| 2009 | defer c.gpa.free(inits); | |
| 2010 | ||
| 2011 | for (payload, 0..) |init, i| { | |
| 2012 | if (i != 0) _ = try c.addToken(.comma, ","); | |
| 2013 | inits[i] = try renderNode(c, init); | |
| 2014 | } | |
| 2015 | _ = try c.addToken(.r_brace, "}"); | |
| 2016 | if (payload.len < 3) { | |
| 2017 | return c.addNode(.{ | |
| 2018 | .tag = .array_init_dot_two, | |
| 2019 | .main_token = l_brace, | |
| 2020 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2021 | if (inits.len >= 1) inits[0].toOptional() else .none, | |
| 2022 | if (inits.len >= 2) inits[1].toOptional() else .none, | |
| 2023 | } }, | |
| 2024 | }); | |
| 2025 | } else { | |
| 2026 | return c.addNode(.{ | |
| 2027 | .tag = .array_init_dot, | |
| 2028 | .main_token = l_brace, | |
| 2029 | .data = .{ .extra_range = try c.listToSpan(inits) }, | |
| 2030 | }); | |
| 2031 | } | |
| 2032 | }, | |
| 2033 | .container_init_dot => { | |
| 2034 | const payload = node.castTag(.container_init_dot).?.data; | |
| 2035 | _ = try c.addToken(.period, "."); | |
| 2036 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2037 | var inits = try c.gpa.alloc(NodeIndex, payload.len); | |
| 2038 | defer c.gpa.free(inits); | |
| 2039 | ||
| 2040 | for (payload, 0..) |init, i| { | |
| 2041 | _ = try c.addToken(.period, "."); | |
| 2042 | _ = try c.addIdentifier(init.name); | |
| 2043 | _ = try c.addToken(.equal, "="); | |
| 2044 | inits[i] = try renderNode(c, init.value); | |
| 2045 | _ = try c.addToken(.comma, ","); | |
| 2046 | } | |
| 2047 | _ = try c.addToken(.r_brace, "}"); | |
| 2048 | ||
| 2049 | if (payload.len < 3) { | |
| 2050 | return c.addNode(.{ | |
| 2051 | .tag = .struct_init_dot_two_comma, | |
| 2052 | .main_token = l_brace, | |
| 2053 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2054 | if (inits.len >= 1) inits[0].toOptional() else .none, | |
| 2055 | if (inits.len >= 2) inits[1].toOptional() else .none, | |
| 2056 | } }, | |
| 2057 | }); | |
| 2058 | } else { | |
| 2059 | return c.addNode(.{ | |
| 2060 | .tag = .struct_init_dot_comma, | |
| 2061 | .main_token = l_brace, | |
| 2062 | .data = .{ .extra_range = try c.listToSpan(inits) }, | |
| 2063 | }); | |
| 2064 | } | |
| 2065 | }, | |
| 2066 | .container_init => { | |
| 2067 | const payload = node.castTag(.container_init).?.data; | |
| 2068 | const lhs = try renderNode(c, payload.lhs); | |
| 2069 | ||
| 2070 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2071 | var inits = try c.gpa.alloc(NodeIndex, payload.inits.len); | |
| 2072 | defer c.gpa.free(inits); | |
| 2073 | ||
| 2074 | for (payload.inits, 0..) |init, i| { | |
| 2075 | _ = try c.addToken(.period, "."); | |
| 2076 | _ = try c.addIdentifier(init.name); | |
| 2077 | _ = try c.addToken(.equal, "="); | |
| 2078 | inits[i] = try renderNode(c, init.value); | |
| 2079 | _ = try c.addToken(.comma, ","); | |
| 2080 | } | |
| 2081 | _ = try c.addToken(.r_brace, "}"); | |
| 2082 | ||
| 2083 | switch (inits.len) { | |
| 2084 | 0 => return c.addNode(.{ | |
| 2085 | .tag = .struct_init_one, | |
| 2086 | .main_token = l_brace, | |
| 2087 | .data = .{ .node_and_opt_node = .{ | |
| 2088 | lhs, .none, | |
| 2089 | } }, | |
| 2090 | }), | |
| 2091 | 1 => return c.addNode(.{ | |
| 2092 | .tag = .struct_init_one_comma, | |
| 2093 | .main_token = l_brace, | |
| 2094 | .data = .{ .node_and_opt_node = .{ | |
| 2095 | lhs, inits[0].toOptional(), | |
| 2096 | } }, | |
| 2097 | }), | |
| 2098 | else => return c.addNode(.{ | |
| 2099 | .tag = .struct_init_comma, | |
| 2100 | .main_token = l_brace, | |
| 2101 | .data = .{ .node_and_extra = .{ | |
| 2102 | lhs, | |
| 2103 | try c.addExtra(try c.listToSpan(inits)), | |
| 2104 | } }, | |
| 2105 | }), | |
| 2106 | } | |
| 2107 | }, | |
| 2108 | .static_assert => { | |
| 2109 | const payload = node.castTag(.static_assert).?.data; | |
| 2110 | const comptime_tok = try c.addToken(.keyword_comptime, "comptime"); | |
| 2111 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2112 | ||
| 2113 | const if_tok = try c.addToken(.keyword_if, "if"); | |
| 2114 | _ = try c.addToken(.l_paren, "("); | |
| 2115 | const cond = try c.addNode(.{ | |
| 2116 | .tag = .bool_not, | |
| 2117 | .main_token = try c.addToken(.bang, "!"), | |
| 2118 | .data = .{ | |
| 2119 | .node = try renderNodeGrouped(c, payload.lhs), | |
| 2120 | }, | |
| 2121 | }); | |
| 2122 | _ = try c.addToken(.r_paren, ")"); | |
| 2123 | ||
| 2124 | const compile_error_tok = try c.addToken(.builtin, "@compileError"); | |
| 2125 | _ = try c.addToken(.l_paren, "("); | |
| 2126 | const err_msg = try renderNode(c, payload.rhs); | |
| 2127 | _ = try c.addToken(.r_paren, ")"); | |
| 2128 | const compile_error = try c.addNode(.{ | |
| 2129 | .tag = .builtin_call_two, | |
| 2130 | .main_token = compile_error_tok, | |
| 2131 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2132 | err_msg.toOptional(), .none, | |
| 2133 | } }, | |
| 2134 | }); | |
| 2135 | ||
| 2136 | const if_node = try c.addNode(.{ | |
| 2137 | .tag = .if_simple, | |
| 2138 | .main_token = if_tok, | |
| 2139 | .data = .{ .node_and_node = .{ | |
| 2140 | cond, compile_error, | |
| 2141 | } }, | |
| 2142 | }); | |
| 2143 | _ = try c.addToken(.semicolon, ";"); | |
| 2144 | _ = try c.addToken(.r_brace, "}"); | |
| 2145 | const block_node = try c.addNode(.{ | |
| 2146 | .tag = .block_two_semicolon, | |
| 2147 | .main_token = l_brace, | |
| 2148 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2149 | if_node.toOptional(), .none, | |
| 2150 | } }, | |
| 2151 | }); | |
| 2152 | ||
| 2153 | return c.addNode(.{ | |
| 2154 | .tag = .@"comptime", | |
| 2155 | .main_token = comptime_tok, | |
| 2156 | .data = .{ | |
| 2157 | .node = block_node, | |
| 2158 | }, | |
| 2159 | }); | |
| 2160 | }, | |
| 2161 | .@"anytype" => unreachable, // Handled in renderParams | |
| 2162 | } | |
| 2163 | } | |
| 2164 | ||
| 2165 | fn renderContainer(c: *Context, node: Node) !NodeIndex { | |
| 2166 | const payload = @as(*Payload.Container, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 2167 | if (payload.layout == .@"packed") | |
| 2168 | _ = try c.addToken(.keyword_packed, "packed") | |
| 2169 | else if (payload.layout == .@"extern") | |
| 2170 | _ = try c.addToken(.keyword_extern, "extern"); | |
| 2171 | const kind_tok = if (node.tag() == .@"struct") | |
| 2172 | try c.addToken(.keyword_struct, "struct") | |
| 2173 | else if (node.tag() == .@"union") | |
| 2174 | try c.addToken(.keyword_union, "union") | |
| 2175 | else if (node.tag() == .@"opaque") | |
| 2176 | try c.addToken(.keyword_opaque, "opaque") | |
| 2177 | else | |
| 2178 | unreachable; | |
| 2179 | ||
| 2180 | _ = try c.addToken(.l_brace, "{"); | |
| 2181 | ||
| 2182 | const num_decls = payload.decls.len; | |
| 2183 | const total_members = payload.fields.len + num_decls; | |
| 2184 | const members = try c.gpa.alloc(NodeIndex, total_members); | |
| 2185 | defer c.gpa.free(members); | |
| 2186 | ||
| 2187 | for (payload.fields, 0..) |field, i| { | |
| 2188 | const name_tok = try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field.name, .{ .allow_primitive = true })}); | |
| 2189 | _ = try c.addToken(.colon, ":"); | |
| 2190 | const type_expr = try renderNode(c, field.type); | |
| 2191 | ||
| 2192 | const align_expr_opt = if (field.alignment) |alignment| blk: { | |
| 2193 | _ = try c.addToken(.keyword_align, "align"); | |
| 2194 | _ = try c.addToken(.l_paren, "("); | |
| 2195 | const align_expr = try c.addNode(.{ | |
| 2196 | .tag = .number_literal, | |
| 2197 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{alignment}), | |
| 2198 | .data = undefined, | |
| 2199 | }); | |
| 2200 | _ = try c.addToken(.r_paren, ")"); | |
| 2201 | break :blk align_expr; | |
| 2202 | } else null; | |
| 2203 | ||
| 2204 | const value_expr_opt = if (field.default_value) |value| blk: { | |
| 2205 | _ = try c.addToken(.equal, "="); | |
| 2206 | break :blk try renderNode(c, value); | |
| 2207 | } else null; | |
| 2208 | ||
| 2209 | if (align_expr_opt) |align_expr| { | |
| 2210 | if (value_expr_opt) |value_expr| { | |
| 2211 | members[i] = try c.addNode(.{ | |
| 2212 | .tag = .container_field, | |
| 2213 | .main_token = name_tok, | |
| 2214 | .data = .{ .node_and_extra = .{ | |
| 2215 | type_expr, | |
| 2216 | try c.addExtra(std.zig.Ast.Node.ContainerField{ | |
| 2217 | .align_expr = align_expr, | |
| 2218 | .value_expr = value_expr, | |
| 2219 | }), | |
| 2220 | } }, | |
| 2221 | }); | |
| 2222 | } else { | |
| 2223 | members[i] = try c.addNode(.{ | |
| 2224 | .tag = .container_field_align, | |
| 2225 | .main_token = name_tok, | |
| 2226 | .data = .{ .node_and_node = .{ | |
| 2227 | type_expr, | |
| 2228 | align_expr, | |
| 2229 | } }, | |
| 2230 | }); | |
| 2231 | } | |
| 2232 | } else { | |
| 2233 | members[i] = try c.addNode(.{ | |
| 2234 | .tag = .container_field_init, | |
| 2235 | .main_token = name_tok, | |
| 2236 | .data = .{ .node_and_opt_node = .{ | |
| 2237 | type_expr, | |
| 2238 | .fromOptional(value_expr_opt), | |
| 2239 | } }, | |
| 2240 | }); | |
| 2241 | } | |
| 2242 | _ = try c.addToken(.comma, ","); | |
| 2243 | } | |
| 2244 | for (members[payload.fields.len..], payload.decls) |*member, decl| { | |
| 2245 | member.* = try renderNode(c, decl); | |
| 2246 | } | |
| 2247 | const trailing = switch (c.tokens.items(.tag)[c.tokens.len - 1]) { | |
| 2248 | .comma, .semicolon => true, | |
| 2249 | else => false, | |
| 2250 | }; | |
| 2251 | _ = try c.addToken(.r_brace, "}"); | |
| 2252 | ||
| 2253 | if (total_members == 0) { | |
| 2254 | return c.addNode(.{ | |
| 2255 | .tag = .container_decl_two, | |
| 2256 | .main_token = kind_tok, | |
| 2257 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2258 | .none, .none, | |
| 2259 | } }, | |
| 2260 | }); | |
| 2261 | } else if (total_members <= 2) { | |
| 2262 | return c.addNode(.{ | |
| 2263 | .tag = if (trailing) .container_decl_two_trailing else .container_decl_two, | |
| 2264 | .main_token = kind_tok, | |
| 2265 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2266 | if (members.len >= 1) members[0].toOptional() else .none, | |
| 2267 | if (members.len >= 2) members[1].toOptional() else .none, | |
| 2268 | } }, | |
| 2269 | }); | |
| 2270 | } else { | |
| 2271 | const span = try c.listToSpan(members); | |
| 2272 | return c.addNode(.{ | |
| 2273 | .tag = if (trailing) .container_decl_trailing else .container_decl, | |
| 2274 | .main_token = kind_tok, | |
| 2275 | .data = .{ .extra_range = span }, | |
| 2276 | }); | |
| 2277 | } | |
| 2278 | } | |
| 2279 | ||
| 2280 | fn renderFieldAccess(c: *Context, lhs: NodeIndex, field_name: []const u8) !NodeIndex { | |
| 2281 | return c.addNode(.{ | |
| 2282 | .tag = .field_access, | |
| 2283 | .main_token = try c.addToken(.period, "."), | |
| 2284 | .data = .{ .node_and_token = .{ | |
| 2285 | lhs, try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field_name, .{ .allow_primitive = true })}), | |
| 2286 | } }, | |
| 2287 | }); | |
| 2288 | } | |
| 2289 | ||
| 2290 | fn renderArrayInit(c: *Context, lhs: NodeIndex, inits: []const Node) !NodeIndex { | |
| 2291 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2292 | var rendered = try c.gpa.alloc(NodeIndex, inits.len); | |
| 2293 | defer c.gpa.free(rendered); | |
| 2294 | ||
| 2295 | for (inits, 0..) |init, i| { | |
| 2296 | rendered[i] = try renderNode(c, init); | |
| 2297 | _ = try c.addToken(.comma, ","); | |
| 2298 | } | |
| 2299 | _ = try c.addToken(.r_brace, "}"); | |
| 2300 | switch (inits.len) { | |
| 2301 | 0 => return c.addNode(.{ | |
| 2302 | .tag = .struct_init_one, | |
| 2303 | .main_token = l_brace, | |
| 2304 | .data = .{ .node_and_opt_node = .{ | |
| 2305 | lhs, .none, | |
| 2306 | } }, | |
| 2307 | }), | |
| 2308 | 1 => return c.addNode(.{ | |
| 2309 | .tag = .array_init_one_comma, | |
| 2310 | .main_token = l_brace, | |
| 2311 | .data = .{ .node_and_node = .{ | |
| 2312 | lhs, rendered[0], | |
| 2313 | } }, | |
| 2314 | }), | |
| 2315 | else => return c.addNode(.{ | |
| 2316 | .tag = .array_init_comma, | |
| 2317 | .main_token = l_brace, | |
| 2318 | .data = .{ .node_and_extra = .{ | |
| 2319 | lhs, | |
| 2320 | try c.addExtra(try c.listToSpan(rendered)), | |
| 2321 | } }, | |
| 2322 | }), | |
| 2323 | } | |
| 2324 | } | |
| 2325 | ||
| 2326 | fn renderArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex { | |
| 2327 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 2328 | const len_expr = try c.addNode(.{ | |
| 2329 | .tag = .number_literal, | |
| 2330 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}), | |
| 2331 | .data = undefined, | |
| 2332 | }); | |
| 2333 | _ = try c.addToken(.r_bracket, "]"); | |
| 2334 | const elem_type_expr = try renderNode(c, elem_type); | |
| 2335 | return c.addNode(.{ | |
| 2336 | .tag = .array_type, | |
| 2337 | .main_token = l_bracket, | |
| 2338 | .data = .{ .node_and_node = .{ | |
| 2339 | len_expr, elem_type_expr, | |
| 2340 | } }, | |
| 2341 | }); | |
| 2342 | } | |
| 2343 | ||
| 2344 | fn renderNullSentinelArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex { | |
| 2345 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 2346 | const len_expr = try c.addNode(.{ | |
| 2347 | .tag = .number_literal, | |
| 2348 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}), | |
| 2349 | .data = undefined, | |
| 2350 | }); | |
| 2351 | _ = try c.addToken(.colon, ":"); | |
| 2352 | ||
| 2353 | const sentinel_expr = try c.addNode(.{ | |
| 2354 | .tag = .number_literal, | |
| 2355 | .main_token = try c.addToken(.number_literal, "0"), | |
| 2356 | .data = undefined, | |
| 2357 | }); | |
| 2358 | ||
| 2359 | _ = try c.addToken(.r_bracket, "]"); | |
| 2360 | const elem_type_expr = try renderNode(c, elem_type); | |
| 2361 | return c.addNode(.{ | |
| 2362 | .tag = .array_type_sentinel, | |
| 2363 | .main_token = l_bracket, | |
| 2364 | .data = .{ .node_and_extra = .{ | |
| 2365 | len_expr, | |
| 2366 | try c.addExtra(std.zig.Ast.Node.ArrayTypeSentinel{ | |
| 2367 | .sentinel = sentinel_expr, | |
| 2368 | .elem_type = elem_type_expr, | |
| 2369 | }), | |
| 2370 | } }, | |
| 2371 | }); | |
| 2372 | } | |
| 2373 | ||
| 2374 | fn addSemicolonIfNeeded(c: *Context, node: Node) !void { | |
| 2375 | switch (node.tag()) { | |
| 2376 | .warning => unreachable, | |
| 2377 | .var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .wrapped_local, .mut_str => {}, | |
| 2378 | .while_true => { | |
| 2379 | const payload = node.castTag(.while_true).?.data; | |
| 2380 | return addSemicolonIfNotBlock(c, payload); | |
| 2381 | }, | |
| 2382 | .@"while" => { | |
| 2383 | const payload = node.castTag(.@"while").?.data; | |
| 2384 | return addSemicolonIfNotBlock(c, payload.body); | |
| 2385 | }, | |
| 2386 | .@"if" => { | |
| 2387 | const payload = node.castTag(.@"if").?.data; | |
| 2388 | if (payload.@"else") |some| | |
| 2389 | return addSemicolonIfNeeded(c, some); | |
| 2390 | return addSemicolonIfNotBlock(c, payload.then); | |
| 2391 | }, | |
| 2392 | else => _ = try c.addToken(.semicolon, ";"), | |
| 2393 | } | |
| 2394 | } | |
| 2395 | ||
| 2396 | fn addSemicolonIfNotBlock(c: *Context, node: Node) !void { | |
| 2397 | switch (node.tag()) { | |
| 2398 | .block, .empty_block, .block_single => {}, | |
| 2399 | else => _ = try c.addToken(.semicolon, ";"), | |
| 2400 | } | |
| 2401 | } | |
| 2402 | ||
| 2403 | fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { | |
| 2404 | switch (node.tag()) { | |
| 2405 | .declaration => unreachable, | |
| 2406 | .null_literal, | |
| 2407 | .undefined_literal, | |
| 2408 | .true_literal, | |
| 2409 | .false_literal, | |
| 2410 | .return_void, | |
| 2411 | .zero_literal, | |
| 2412 | .one_literal, | |
| 2413 | .void_type, | |
| 2414 | .noreturn_type, | |
| 2415 | .@"anytype", | |
| 2416 | .div_trunc, | |
| 2417 | .int_cast, | |
| 2418 | .const_cast, | |
| 2419 | .volatile_cast, | |
| 2420 | .as, | |
| 2421 | .truncate, | |
| 2422 | .bit_cast, | |
| 2423 | .float_cast, | |
| 2424 | .int_from_float, | |
| 2425 | .float_from_int, | |
| 2426 | .ptr_from_int, | |
| 2427 | .std_mem_zeroes, | |
| 2428 | .int_from_ptr, | |
| 2429 | .sizeof, | |
| 2430 | .alignof, | |
| 2431 | .typeof, | |
| 2432 | .typeinfo, | |
| 2433 | .vector, | |
| 2434 | .std_mem_zeroinit, | |
| 2435 | .integer_literal, | |
| 2436 | .float_literal, | |
| 2437 | .string_literal, | |
| 2438 | .string_slice, | |
| 2439 | .char_literal, | |
| 2440 | .enum_literal, | |
| 2441 | .identifier, | |
| 2442 | .field_access, | |
| 2443 | .ptr_cast, | |
| 2444 | .type, | |
| 2445 | .array_access, | |
| 2446 | .align_cast, | |
| 2447 | .optional_type, | |
| 2448 | .c_pointer, | |
| 2449 | .single_pointer, | |
| 2450 | .unwrap, | |
| 2451 | .deref, | |
| 2452 | .not, | |
| 2453 | .negate, | |
| 2454 | .negate_wrap, | |
| 2455 | .bit_not, | |
| 2456 | .func, | |
| 2457 | .call, | |
| 2458 | .array_type, | |
| 2459 | .null_sentinel_array_type, | |
| 2460 | .int_from_bool, | |
| 2461 | .div_exact, | |
| 2462 | .offset_of, | |
| 2463 | .shuffle, | |
| 2464 | .builtin_extern, | |
| 2465 | .wrapped_local, | |
| 2466 | .mut_str, | |
| 2467 | .helper_call, | |
| 2468 | .helper_ref, | |
| 2469 | .byte_swap, | |
| 2470 | .ceil, | |
| 2471 | .cos, | |
| 2472 | .sin, | |
| 2473 | .exp, | |
| 2474 | .exp2, | |
| 2475 | .exp10, | |
| 2476 | .abs, | |
| 2477 | .log, | |
| 2478 | .log2, | |
| 2479 | .log10, | |
| 2480 | .round, | |
| 2481 | .sqrt, | |
| 2482 | .trunc, | |
| 2483 | .floor, | |
| 2484 | => { | |
| 2485 | // no grouping needed | |
| 2486 | return renderNode(c, node); | |
| 2487 | }, | |
| 2488 | ||
| 2489 | .opaque_literal, | |
| 2490 | .@"opaque", | |
| 2491 | .empty_array, | |
| 2492 | .block_single, | |
| 2493 | .add, | |
| 2494 | .add_wrap, | |
| 2495 | .sub, | |
| 2496 | .sub_wrap, | |
| 2497 | .mul, | |
| 2498 | .mul_wrap, | |
| 2499 | .div, | |
| 2500 | .shl, | |
| 2501 | .shr, | |
| 2502 | .mod, | |
| 2503 | .@"and", | |
| 2504 | .@"or", | |
| 2505 | .less_than, | |
| 2506 | .less_than_equal, | |
| 2507 | .greater_than, | |
| 2508 | .greater_than_equal, | |
| 2509 | .equal, | |
| 2510 | .not_equal, | |
| 2511 | .bit_and, | |
| 2512 | .bit_or, | |
| 2513 | .bit_xor, | |
| 2514 | .empty_block, | |
| 2515 | .array_cat, | |
| 2516 | .array_filler, | |
| 2517 | .@"if", | |
| 2518 | .@"struct", | |
| 2519 | .@"union", | |
| 2520 | .array_init, | |
| 2521 | .vector_zero_init, | |
| 2522 | .tuple, | |
| 2523 | .container_init, | |
| 2524 | .container_init_dot, | |
| 2525 | .block, | |
| 2526 | .address_of, | |
| 2527 | => return c.addNode(.{ | |
| 2528 | .tag = .grouped_expression, | |
| 2529 | .main_token = try c.addToken(.l_paren, "("), | |
| 2530 | .data = .{ .node_and_token = .{ | |
| 2531 | try renderNode(c, node), | |
| 2532 | try c.addToken(.r_paren, ")"), | |
| 2533 | } }, | |
| 2534 | }), | |
| 2535 | .ellipsis3, | |
| 2536 | .switch_prong, | |
| 2537 | .warning, | |
| 2538 | .var_decl, | |
| 2539 | .fail_decl, | |
| 2540 | .arg_redecl, | |
| 2541 | .alias, | |
| 2542 | .var_simple, | |
| 2543 | .pub_var_simple, | |
| 2544 | .enum_constant, | |
| 2545 | .@"while", | |
| 2546 | .@"switch", | |
| 2547 | .@"break", | |
| 2548 | .break_val, | |
| 2549 | .pub_inline_fn, | |
| 2550 | .discard, | |
| 2551 | .@"continue", | |
| 2552 | .@"return", | |
| 2553 | .@"comptime", | |
| 2554 | .@"defer", | |
| 2555 | .asm_simple, | |
| 2556 | .while_true, | |
| 2557 | .if_not_break, | |
| 2558 | .switch_else, | |
| 2559 | .add_assign, | |
| 2560 | .add_wrap_assign, | |
| 2561 | .sub_assign, | |
| 2562 | .sub_wrap_assign, | |
| 2563 | .mul_assign, | |
| 2564 | .mul_wrap_assign, | |
| 2565 | .div_assign, | |
| 2566 | .shl_assign, | |
| 2567 | .shr_assign, | |
| 2568 | .mod_assign, | |
| 2569 | .bit_and_assign, | |
| 2570 | .bit_or_assign, | |
| 2571 | .bit_xor_assign, | |
| 2572 | .assign, | |
| 2573 | .static_assert, | |
| 2574 | .@"unreachable", | |
| 2575 | => { | |
| 2576 | // these should never appear in places where grouping might be needed. | |
| 2577 | unreachable; | |
| 2578 | }, | |
| 2579 | } | |
| 2580 | } | |
| 2581 | ||
| 2582 | fn renderPrefixOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { | |
| 2583 | const payload = @as(*Payload.UnOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 2584 | return c.addNode(.{ | |
| 2585 | .tag = tag, | |
| 2586 | .main_token = try c.addToken(tok_tag, bytes), | |
| 2587 | .data = .{ | |
| 2588 | .node = try renderNodeGrouped(c, payload), | |
| 2589 | }, | |
| 2590 | }); | |
| 2591 | } | |
| 2592 | ||
| 2593 | fn renderBinOpGrouped(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { | |
| 2594 | const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 2595 | const lhs = try renderNodeGrouped(c, payload.lhs); | |
| 2596 | return c.addNode(.{ | |
| 2597 | .tag = tag, | |
| 2598 | .main_token = try c.addToken(tok_tag, bytes), | |
| 2599 | .data = .{ .node_and_node = .{ | |
| 2600 | lhs, try renderNodeGrouped(c, payload.rhs), | |
| 2601 | } }, | |
| 2602 | }); | |
| 2603 | } | |
| 2604 | ||
| 2605 | fn renderBinOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { | |
| 2606 | const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 2607 | const lhs = try renderNode(c, payload.lhs); | |
| 2608 | return c.addNode(.{ | |
| 2609 | .tag = tag, | |
| 2610 | .main_token = try c.addToken(tok_tag, bytes), | |
| 2611 | .data = .{ .node_and_node = .{ | |
| 2612 | lhs, try renderNode(c, payload.rhs), | |
| 2613 | } }, | |
| 2614 | }); | |
| 2615 | } | |
| 2616 | ||
| 2617 | fn renderStdImport(c: *Context, parts: []const []const u8) !NodeIndex { | |
| 2618 | const import_tok = try c.addToken(.builtin, "@import"); | |
| 2619 | _ = try c.addToken(.l_paren, "("); | |
| 2620 | const std_tok = try c.addToken(.string_literal, "\"std\""); | |
| 2621 | const std_node = try c.addNode(.{ | |
| 2622 | .tag = .string_literal, | |
| 2623 | .main_token = std_tok, | |
| 2624 | .data = undefined, | |
| 2625 | }); | |
| 2626 | _ = try c.addToken(.r_paren, ")"); | |
| 2627 | ||
| 2628 | const import_node = try c.addNode(.{ | |
| 2629 | .tag = .builtin_call_two, | |
| 2630 | .main_token = import_tok, | |
| 2631 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2632 | std_node.toOptional(), .none, | |
| 2633 | } }, | |
| 2634 | }); | |
| 2635 | ||
| 2636 | var access_chain = import_node; | |
| 2637 | for (parts) |part| { | |
| 2638 | access_chain = try renderFieldAccess(c, access_chain, part); | |
| 2639 | } | |
| 2640 | return access_chain; | |
| 2641 | } | |
| 2642 | ||
| 2643 | fn renderCall(c: *Context, lhs: NodeIndex, args: []const Node) !NodeIndex { | |
| 2644 | const lparen = try c.addToken(.l_paren, "("); | |
| 2645 | const res = switch (args.len) { | |
| 2646 | 0 => try c.addNode(.{ | |
| 2647 | .tag = .call_one, | |
| 2648 | .main_token = lparen, | |
| 2649 | .data = .{ .node_and_opt_node = .{ | |
| 2650 | lhs, .none, | |
| 2651 | } }, | |
| 2652 | }), | |
| 2653 | 1 => try c.addNode(.{ | |
| 2654 | .tag = .call_one, | |
| 2655 | .main_token = lparen, | |
| 2656 | .data = .{ .node_and_opt_node = .{ | |
| 2657 | lhs, (try renderNode(c, args[0])).toOptional(), | |
| 2658 | } }, | |
| 2659 | }), | |
| 2660 | else => blk: { | |
| 2661 | var rendered = try c.gpa.alloc(NodeIndex, args.len); | |
| 2662 | defer c.gpa.free(rendered); | |
| 2663 | ||
| 2664 | for (args, 0..) |arg, i| { | |
| 2665 | if (i != 0) _ = try c.addToken(.comma, ","); | |
| 2666 | rendered[i] = try renderNode(c, arg); | |
| 2667 | } | |
| 2668 | const span = try c.listToSpan(rendered); | |
| 2669 | break :blk try c.addNode(.{ | |
| 2670 | .tag = .call, | |
| 2671 | .main_token = lparen, | |
| 2672 | .data = .{ .node_and_extra = .{ | |
| 2673 | lhs, try c.addExtra(NodeSubRange{ | |
| 2674 | .start = span.start, | |
| 2675 | .end = span.end, | |
| 2676 | }), | |
| 2677 | } }, | |
| 2678 | }); | |
| 2679 | }, | |
| 2680 | }; | |
| 2681 | _ = try c.addToken(.r_paren, ")"); | |
| 2682 | return res; | |
| 2683 | } | |
| 2684 | ||
| 2685 | fn renderBuiltinCall(c: *Context, builtin: []const u8, args: []const Node) !NodeIndex { | |
| 2686 | const builtin_tok = try c.addToken(.builtin, builtin); | |
| 2687 | _ = try c.addToken(.l_paren, "("); | |
| 2688 | var arg_1: ?NodeIndex = null; | |
| 2689 | var arg_2: ?NodeIndex = null; | |
| 2690 | var arg_3: ?NodeIndex = null; | |
| 2691 | var arg_4: ?NodeIndex = null; | |
| 2692 | switch (args.len) { | |
| 2693 | 0 => {}, | |
| 2694 | 1 => { | |
| 2695 | arg_1 = try renderNode(c, args[0]); | |
| 2696 | }, | |
| 2697 | 2 => { | |
| 2698 | arg_1 = try renderNode(c, args[0]); | |
| 2699 | _ = try c.addToken(.comma, ","); | |
| 2700 | arg_2 = try renderNode(c, args[1]); | |
| 2701 | }, | |
| 2702 | 4 => { | |
| 2703 | arg_1 = try renderNode(c, args[0]); | |
| 2704 | _ = try c.addToken(.comma, ","); | |
| 2705 | arg_2 = try renderNode(c, args[1]); | |
| 2706 | _ = try c.addToken(.comma, ","); | |
| 2707 | arg_3 = try renderNode(c, args[2]); | |
| 2708 | _ = try c.addToken(.comma, ","); | |
| 2709 | arg_4 = try renderNode(c, args[3]); | |
| 2710 | }, | |
| 2711 | else => unreachable, // expand this function as needed. | |
| 2712 | } | |
| 2713 | ||
| 2714 | _ = try c.addToken(.r_paren, ")"); | |
| 2715 | if (args.len <= 2) { | |
| 2716 | return c.addNode(.{ | |
| 2717 | .tag = .builtin_call_two, | |
| 2718 | .main_token = builtin_tok, | |
| 2719 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2720 | .fromOptional(arg_1), .fromOptional(arg_2), | |
| 2721 | } }, | |
| 2722 | }); | |
| 2723 | } else { | |
| 2724 | std.debug.assert(args.len == 4); | |
| 2725 | ||
| 2726 | const params = try c.listToSpan(&.{ arg_1.?, arg_2.?, arg_3.?, arg_4.? }); | |
| 2727 | return c.addNode(.{ | |
| 2728 | .tag = .builtin_call, | |
| 2729 | .main_token = builtin_tok, | |
| 2730 | .data = .{ .extra_range = .{ | |
| 2731 | .start = params.start, | |
| 2732 | .end = params.end, | |
| 2733 | } }, | |
| 2734 | }); | |
| 2735 | } | |
| 2736 | } | |
| 2737 | ||
| 2738 | fn renderVar(c: *Context, node: Node) !NodeIndex { | |
| 2739 | const payload = node.castTag(.var_decl).?.data; | |
| 2740 | if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub"); | |
| 2741 | if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern"); | |
| 2742 | if (payload.is_export) _ = try c.addToken(.keyword_export, "export"); | |
| 2743 | if (payload.is_threadlocal) _ = try c.addToken(.keyword_threadlocal, "threadlocal"); | |
| 2744 | const mut_tok = if (payload.is_const) | |
| 2745 | try c.addToken(.keyword_const, "const") | |
| 2746 | else | |
| 2747 | try c.addToken(.keyword_var, "var"); | |
| 2748 | _ = try c.addIdentifier(payload.name); | |
| 2749 | _ = try c.addToken(.colon, ":"); | |
| 2750 | const type_node = try renderNode(c, payload.type); | |
| 2751 | ||
| 2752 | const align_node_opt = if (payload.alignment) |some| blk: { | |
| 2753 | _ = try c.addToken(.keyword_align, "align"); | |
| 2754 | _ = try c.addToken(.l_paren, "("); | |
| 2755 | const res = try c.addNode(.{ | |
| 2756 | .tag = .number_literal, | |
| 2757 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}), | |
| 2758 | .data = undefined, | |
| 2759 | }); | |
| 2760 | _ = try c.addToken(.r_paren, ")"); | |
| 2761 | break :blk res; | |
| 2762 | } else null; | |
| 2763 | ||
| 2764 | const section_node_opt = if (payload.linksection_string) |some| blk: { | |
| 2765 | _ = try c.addToken(.keyword_linksection, "linksection"); | |
| 2766 | _ = try c.addToken(.l_paren, "("); | |
| 2767 | const res = try c.addNode(.{ | |
| 2768 | .tag = .string_literal, | |
| 2769 | .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}), | |
| 2770 | .data = undefined, | |
| 2771 | }); | |
| 2772 | _ = try c.addToken(.r_paren, ")"); | |
| 2773 | break :blk res; | |
| 2774 | } else null; | |
| 2775 | ||
| 2776 | const init_node_opt = if (payload.init) |some| blk: { | |
| 2777 | _ = try c.addToken(.equal, "="); | |
| 2778 | break :blk try renderNode(c, some); | |
| 2779 | } else null; | |
| 2780 | _ = try c.addToken(.semicolon, ";"); | |
| 2781 | ||
| 2782 | if (section_node_opt) |section_node| { | |
| 2783 | return c.addNode(.{ | |
| 2784 | .tag = .global_var_decl, | |
| 2785 | .main_token = mut_tok, | |
| 2786 | .data = .{ .extra_and_opt_node = .{ | |
| 2787 | try c.addExtra(std.zig.Ast.Node.GlobalVarDecl{ | |
| 2788 | .type_node = type_node.toOptional(), | |
| 2789 | .align_node = .fromOptional(align_node_opt), | |
| 2790 | .section_node = section_node.toOptional(), | |
| 2791 | .addrspace_node = .none, | |
| 2792 | }), | |
| 2793 | .fromOptional(init_node_opt), | |
| 2794 | } }, | |
| 2795 | }); | |
| 2796 | } else { | |
| 2797 | if (align_node_opt) |align_node| { | |
| 2798 | return c.addNode(.{ | |
| 2799 | .tag = .local_var_decl, | |
| 2800 | .main_token = mut_tok, | |
| 2801 | .data = .{ .extra_and_opt_node = .{ | |
| 2802 | try c.addExtra(std.zig.Ast.Node.LocalVarDecl{ | |
| 2803 | .type_node = type_node, | |
| 2804 | .align_node = align_node, | |
| 2805 | }), | |
| 2806 | .fromOptional(init_node_opt), | |
| 2807 | } }, | |
| 2808 | }); | |
| 2809 | } else { | |
| 2810 | return c.addNode(.{ | |
| 2811 | .tag = .simple_var_decl, | |
| 2812 | .main_token = mut_tok, | |
| 2813 | .data = .{ | |
| 2814 | .opt_node_and_opt_node = .{ | |
| 2815 | type_node.toOptional(), // Type expression | |
| 2816 | .fromOptional(init_node_opt), // Init expression | |
| 2817 | }, | |
| 2818 | }, | |
| 2819 | }); | |
| 2820 | } | |
| 2821 | } | |
| 2822 | } | |
| 2823 | ||
| 2824 | fn renderFunc(c: *Context, node: Node) !NodeIndex { | |
| 2825 | const payload = node.castTag(.func).?.data; | |
| 2826 | if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub"); | |
| 2827 | if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern"); | |
| 2828 | if (payload.is_export) _ = try c.addToken(.keyword_export, "export"); | |
| 2829 | if (payload.is_inline) _ = try c.addToken(.keyword_inline, "inline"); | |
| 2830 | const fn_token = try c.addToken(.keyword_fn, "fn"); | |
| 2831 | if (payload.name) |some| _ = try c.addIdentifier(some); | |
| 2832 | ||
| 2833 | const params = try renderParams(c, payload.params, payload.is_var_args); | |
| 2834 | defer params.deinit(); | |
| 2835 | var span: NodeSubRange = undefined; | |
| 2836 | if (params.items.len > 1) span = try c.listToSpan(params.items); | |
| 2837 | ||
| 2838 | const align_expr_opt = if (payload.alignment) |some| blk: { | |
| 2839 | _ = try c.addToken(.keyword_align, "align"); | |
| 2840 | _ = try c.addToken(.l_paren, "("); | |
| 2841 | const res = try c.addNode(.{ | |
| 2842 | .tag = .number_literal, | |
| 2843 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}), | |
| 2844 | .data = undefined, | |
| 2845 | }); | |
| 2846 | _ = try c.addToken(.r_paren, ")"); | |
| 2847 | break :blk res; | |
| 2848 | } else null; | |
| 2849 | ||
| 2850 | const section_expr_opt = if (payload.linksection_string) |some| blk: { | |
| 2851 | _ = try c.addToken(.keyword_linksection, "linksection"); | |
| 2852 | _ = try c.addToken(.l_paren, "("); | |
| 2853 | const res = try c.addNode(.{ | |
| 2854 | .tag = .string_literal, | |
| 2855 | .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}), | |
| 2856 | .data = undefined, | |
| 2857 | }); | |
| 2858 | _ = try c.addToken(.r_paren, ")"); | |
| 2859 | break :blk res; | |
| 2860 | } else null; | |
| 2861 | ||
| 2862 | const callconv_expr_opt = if (payload.explicit_callconv) |some| blk: { | |
| 2863 | _ = try c.addToken(.keyword_callconv, "callconv"); | |
| 2864 | _ = try c.addToken(.l_paren, "("); | |
| 2865 | const cc_node = switch (some) { | |
| 2866 | .c => cc_node: { | |
| 2867 | _ = try c.addToken(.period, "."); | |
| 2868 | break :cc_node try c.addNode(.{ | |
| 2869 | .tag = .enum_literal, | |
| 2870 | .main_token = try c.addToken(.identifier, "c"), | |
| 2871 | .data = undefined, | |
| 2872 | }); | |
| 2873 | }, | |
| 2874 | .x86_64_sysv, | |
| 2875 | .x86_64_win, | |
| 2876 | .x86_stdcall, | |
| 2877 | .x86_fastcall, | |
| 2878 | .x86_thiscall, | |
| 2879 | .x86_vectorcall, | |
| 2880 | .x86_regcall, | |
| 2881 | .aarch64_vfabi, | |
| 2882 | .aarch64_sve_pcs, | |
| 2883 | .arm_aapcs, | |
| 2884 | .arm_aapcs_vfp, | |
| 2885 | .m68k_rtd, | |
| 2886 | .riscv_vector, | |
| 2887 | => cc_node: { | |
| 2888 | // .{ .foo = .{} } | |
| 2889 | _ = try c.addToken(.period, "."); | |
| 2890 | const outer_lbrace = try c.addToken(.l_brace, "{"); | |
| 2891 | _ = try c.addToken(.period, "."); | |
| 2892 | _ = try c.addToken(.identifier, @tagName(some)); | |
| 2893 | _ = try c.addToken(.equal, "="); | |
| 2894 | _ = try c.addToken(.period, "."); | |
| 2895 | const inner_lbrace = try c.addToken(.l_brace, "{"); | |
| 2896 | _ = try c.addToken(.r_brace, "}"); | |
| 2897 | _ = try c.addToken(.r_brace, "}"); | |
| 2898 | break :cc_node try c.addNode(.{ | |
| 2899 | .tag = .struct_init_dot_two, | |
| 2900 | .main_token = outer_lbrace, | |
| 2901 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2902 | (try c.addNode(.{ | |
| 2903 | .tag = .struct_init_dot_two, | |
| 2904 | .main_token = inner_lbrace, | |
| 2905 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2906 | .none, .none, | |
| 2907 | } }, | |
| 2908 | })).toOptional(), | |
| 2909 | .none, | |
| 2910 | } }, | |
| 2911 | }); | |
| 2912 | }, | |
| 2913 | }; | |
| 2914 | _ = try c.addToken(.r_paren, ")"); | |
| 2915 | break :blk cc_node; | |
| 2916 | } else null; | |
| 2917 | ||
| 2918 | const return_type_expr = try renderNode(c, payload.return_type); | |
| 2919 | ||
| 2920 | const fn_proto = try blk: { | |
| 2921 | if (align_expr_opt == null and section_expr_opt == null and callconv_expr_opt == null) { | |
| 2922 | if (params.items.len < 2) | |
| 2923 | break :blk c.addNode(.{ | |
| 2924 | .tag = .fn_proto_simple, | |
| 2925 | .main_token = fn_token, | |
| 2926 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2927 | if (params.items.len == 1) params.items[0].toOptional() else .none, | |
| 2928 | return_type_expr.toOptional(), | |
| 2929 | } }, | |
| 2930 | }) | |
| 2931 | else | |
| 2932 | break :blk c.addNode(.{ | |
| 2933 | .tag = .fn_proto_multi, | |
| 2934 | .main_token = fn_token, | |
| 2935 | .data = .{ .extra_and_opt_node = .{ | |
| 2936 | try c.addExtra(span), | |
| 2937 | return_type_expr.toOptional(), | |
| 2938 | } }, | |
| 2939 | }); | |
| 2940 | } | |
| 2941 | if (params.items.len < 2) | |
| 2942 | break :blk c.addNode(.{ | |
| 2943 | .tag = .fn_proto_one, | |
| 2944 | .main_token = fn_token, | |
| 2945 | .data = .{ | |
| 2946 | .extra_and_opt_node = .{ | |
| 2947 | try c.addExtra(std.zig.Ast.Node.FnProtoOne{ | |
| 2948 | .param = if (params.items.len == 1) params.items[0].toOptional() else .none, | |
| 2949 | .align_expr = .fromOptional(align_expr_opt), | |
| 2950 | .addrspace_expr = .none, // TODO | |
| 2951 | .section_expr = .fromOptional(section_expr_opt), | |
| 2952 | .callconv_expr = .fromOptional(callconv_expr_opt), | |
| 2953 | }), | |
| 2954 | return_type_expr.toOptional(), | |
| 2955 | }, | |
| 2956 | }, | |
| 2957 | }) | |
| 2958 | else | |
| 2959 | break :blk c.addNode(.{ | |
| 2960 | .tag = .fn_proto, | |
| 2961 | .main_token = fn_token, | |
| 2962 | .data = .{ | |
| 2963 | .extra_and_opt_node = .{ | |
| 2964 | try c.addExtra(std.zig.Ast.Node.FnProto{ | |
| 2965 | .params_start = span.start, | |
| 2966 | .params_end = span.end, | |
| 2967 | .align_expr = .fromOptional(align_expr_opt), | |
| 2968 | .addrspace_expr = .none, // TODO | |
| 2969 | .section_expr = .fromOptional(section_expr_opt), | |
| 2970 | .callconv_expr = .fromOptional(callconv_expr_opt), | |
| 2971 | }), | |
| 2972 | return_type_expr.toOptional(), | |
| 2973 | }, | |
| 2974 | }, | |
| 2975 | }); | |
| 2976 | }; | |
| 2977 | ||
| 2978 | const payload_body = payload.body orelse { | |
| 2979 | if (payload.is_extern) { | |
| 2980 | _ = try c.addToken(.semicolon, ";"); | |
| 2981 | } | |
| 2982 | return fn_proto; | |
| 2983 | }; | |
| 2984 | const body = try renderNode(c, payload_body); | |
| 2985 | return c.addNode(.{ | |
| 2986 | .tag = .fn_decl, | |
| 2987 | .main_token = fn_token, | |
| 2988 | .data = .{ .node_and_node = .{ | |
| 2989 | fn_proto, body, | |
| 2990 | } }, | |
| 2991 | }); | |
| 2992 | } | |
| 2993 | ||
| 2994 | fn renderMacroFunc(c: *Context, node: Node) !NodeIndex { | |
| 2995 | const payload = node.castTag(.pub_inline_fn).?.data; | |
| 2996 | _ = try c.addToken(.keyword_pub, "pub"); | |
| 2997 | _ = try c.addToken(.keyword_inline, "inline"); | |
| 2998 | const fn_token = try c.addToken(.keyword_fn, "fn"); | |
| 2999 | _ = try c.addIdentifier(payload.name); | |
| 3000 | ||
| 3001 | const params = try renderParams(c, payload.params, false); | |
| 3002 | defer params.deinit(); | |
| 3003 | var span: NodeSubRange = undefined; | |
| 3004 | if (params.items.len > 1) span = try c.listToSpan(params.items); | |
| 3005 | ||
| 3006 | const return_type_expr = try renderNodeGrouped(c, payload.return_type); | |
| 3007 | ||
| 3008 | const fn_proto = blk: { | |
| 3009 | if (params.items.len < 2) { | |
| 3010 | break :blk try c.addNode(.{ | |
| 3011 | .tag = .fn_proto_simple, | |
| 3012 | .main_token = fn_token, | |
| 3013 | .data = .{ .opt_node_and_opt_node = .{ | |
| 3014 | if (params.items.len == 1) params.items[0].toOptional() else .none, | |
| 3015 | return_type_expr.toOptional(), | |
| 3016 | } }, | |
| 3017 | }); | |
| 3018 | } else { | |
| 3019 | break :blk try c.addNode(.{ | |
| 3020 | .tag = .fn_proto_multi, | |
| 3021 | .main_token = fn_token, | |
| 3022 | .data = .{ .extra_and_opt_node = .{ | |
| 3023 | try c.addExtra(span), | |
| 3024 | return_type_expr.toOptional(), | |
| 3025 | } }, | |
| 3026 | }); | |
| 3027 | } | |
| 3028 | }; | |
| 3029 | return c.addNode(.{ | |
| 3030 | .tag = .fn_decl, | |
| 3031 | .main_token = fn_token, | |
| 3032 | .data = .{ .node_and_node = .{ | |
| 3033 | fn_proto, try renderNode(c, payload.body), | |
| 3034 | } }, | |
| 3035 | }); | |
| 3036 | } | |
| 3037 | ||
| 3038 | fn renderParams(c: *Context, params: []Payload.Param, is_var_args: bool) !std.array_list.Managed(NodeIndex) { | |
| 3039 | _ = try c.addToken(.l_paren, "("); | |
| 3040 | var rendered = try std.array_list.Managed(NodeIndex).initCapacity(c.gpa, @max(params.len, 1)); | |
| 3041 | errdefer rendered.deinit(); | |
| 3042 | ||
| 3043 | for (params, 0..) |param, i| { | |
| 3044 | if (i != 0) _ = try c.addToken(.comma, ","); | |
| 3045 | if (param.is_noalias) _ = try c.addToken(.keyword_noalias, "noalias"); | |
| 3046 | if (param.name) |some| { | |
| 3047 | _ = try c.addIdentifier(some); | |
| 3048 | _ = try c.addToken(.colon, ":"); | |
| 3049 | } | |
| 3050 | if (param.type.tag() == .@"anytype") { | |
| 3051 | _ = try c.addToken(.keyword_anytype, "anytype"); | |
| 3052 | continue; | |
| 3053 | } | |
| 3054 | rendered.appendAssumeCapacity(try renderNode(c, param.type)); | |
| 3055 | } | |
| 3056 | if (is_var_args) { | |
| 3057 | if (params.len != 0) _ = try c.addToken(.comma, ","); | |
| 3058 | _ = try c.addToken(.ellipsis3, "..."); | |
| 3059 | } | |
| 3060 | _ = try c.addToken(.r_paren, ")"); | |
| 3061 | ||
| 3062 | return rendered; | |
| 3063 | } |
lib/compiler/translate-c/builtins.zig created+76| ... | ... | @@ -0,0 +1,76 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | const ast = @import("ast.zig"); | |
| 4 | ||
| 5 | /// All builtins need to have a source so that macros can reference them | |
| 6 | /// but for some it is possible to directly call an equivalent Zig builtin | |
| 7 | /// which is preferrable. | |
| 8 | pub const Builtin = struct { | |
| 9 | /// The name of the builtin in `c_builtins.zig`. | |
| 10 | name: []const u8, | |
| 11 | tag: ?ast.Node.Tag = null, | |
| 12 | }; | |
| 13 | ||
| 14 | pub const map = std.StaticStringMap(Builtin).initComptime([_]struct { []const u8, Builtin }{ | |
| 15 | .{ "__builtin_abs", .{ .name = "abs" } }, | |
| 16 | .{ "__builtin_assume", .{ .name = "assume" } }, | |
| 17 | .{ "__builtin_bswap16", .{ .name = "bswap16", .tag = .byte_swap } }, | |
| 18 | .{ "__builtin_bswap32", .{ .name = "bswap32", .tag = .byte_swap } }, | |
| 19 | .{ "__builtin_bswap64", .{ .name = "bswap64", .tag = .byte_swap } }, | |
| 20 | .{ "__builtin_ceilf", .{ .name = "ceilf", .tag = .ceil } }, | |
| 21 | .{ "__builtin_ceil", .{ .name = "ceil", .tag = .ceil } }, | |
| 22 | .{ "__builtin_clz", .{ .name = "clz" } }, | |
| 23 | .{ "__builtin_constant_p", .{ .name = "constant_p" } }, | |
| 24 | .{ "__builtin_cosf", .{ .name = "cosf", .tag = .cos } }, | |
| 25 | .{ "__builtin_cos", .{ .name = "cos", .tag = .cos } }, | |
| 26 | .{ "__builtin_ctz", .{ .name = "ctz" } }, | |
| 27 | .{ "__builtin_exp2f", .{ .name = "exp2f", .tag = .exp2 } }, | |
| 28 | .{ "__builtin_exp2", .{ .name = "exp2", .tag = .exp2 } }, | |
| 29 | .{ "__builtin_expf", .{ .name = "expf", .tag = .exp } }, | |
| 30 | .{ "__builtin_exp", .{ .name = "exp", .tag = .exp } }, | |
| 31 | .{ "__builtin_expect", .{ .name = "expect" } }, | |
| 32 | .{ "__builtin_fabsf", .{ .name = "fabsf", .tag = .abs } }, | |
| 33 | .{ "__builtin_fabs", .{ .name = "fabs", .tag = .abs } }, | |
| 34 | .{ "__builtin_floorf", .{ .name = "floorf", .tag = .floor } }, | |
| 35 | .{ "__builtin_floor", .{ .name = "floor", .tag = .floor } }, | |
| 36 | .{ "__builtin_huge_valf", .{ .name = "huge_valf" } }, | |
| 37 | .{ "__builtin_inff", .{ .name = "inff" } }, | |
| 38 | .{ "__builtin_isinf_sign", .{ .name = "isinf_sign" } }, | |
| 39 | .{ "__builtin_isinf", .{ .name = "isinf" } }, | |
| 40 | .{ "__builtin_isnan", .{ .name = "isnan" } }, | |
| 41 | .{ "__builtin_labs", .{ .name = "labs" } }, | |
| 42 | .{ "__builtin_llabs", .{ .name = "llabs" } }, | |
| 43 | .{ "__builtin_log10f", .{ .name = "log10f", .tag = .log10 } }, | |
| 44 | .{ "__builtin_log10", .{ .name = "log10", .tag = .log10 } }, | |
| 45 | .{ "__builtin_log2f", .{ .name = "log2f", .tag = .log2 } }, | |
| 46 | .{ "__builtin_log2", .{ .name = "log2", .tag = .log2 } }, | |
| 47 | .{ "__builtin_logf", .{ .name = "logf", .tag = .log } }, | |
| 48 | .{ "__builtin_log", .{ .name = "log", .tag = .log } }, | |
| 49 | .{ "__builtin___memcpy_chk", .{ .name = "memcpy_chk" } }, | |
| 50 | .{ "__builtin_memcpy", .{ .name = "memcpy" } }, | |
| 51 | .{ "__builtin___memset_chk", .{ .name = "memset_chk" } }, | |
| 52 | .{ "__builtin_memset", .{ .name = "memset" } }, | |
| 53 | .{ "__builtin_mul_overflow", .{ .name = "mul_overflow" } }, | |
| 54 | .{ "__builtin_nanf", .{ .name = "nanf" } }, | |
| 55 | .{ "__builtin_object_size", .{ .name = "object_size" } }, | |
| 56 | .{ "__builtin_popcount", .{ .name = "popcount" } }, | |
| 57 | .{ "__builtin_roundf", .{ .name = "roundf", .tag = .round } }, | |
| 58 | .{ "__builtin_round", .{ .name = "round", .tag = .round } }, | |
| 59 | .{ "__builtin_signbitf", .{ .name = "signbitf" } }, | |
| 60 | .{ "__builtin_signbit", .{ .name = "signbit" } }, | |
| 61 | .{ "__builtin_sinf", .{ .name = "sinf", .tag = .sin } }, | |
| 62 | .{ "__builtin_sin", .{ .name = "sin", .tag = .sin } }, | |
| 63 | .{ "__builtin_sqrtf", .{ .name = "sqrtf", .tag = .sqrt } }, | |
| 64 | .{ "__builtin_sqrt", .{ .name = "sqrt", .tag = .sqrt } }, | |
| 65 | .{ "__builtin_strcmp", .{ .name = "strcmp" } }, | |
| 66 | .{ "__builtin_strlen", .{ .name = "strlen" } }, | |
| 67 | .{ "__builtin_truncf", .{ .name = "truncf", .tag = .trunc } }, | |
| 68 | .{ "__builtin_trunc", .{ .name = "trunc", .tag = .trunc } }, | |
| 69 | .{ "__builtin_unreachable", .{ .name = "unreachable", .tag = .@"unreachable" } }, | |
| 70 | .{ "__has_builtin", .{ .name = "has_builtin" } }, | |
| 71 | ||
| 72 | // __builtin_alloca_with_align is not currently implemented. | |
| 73 | // It is used in a run and a translate test to ensure that non-implemented | |
| 74 | // builtins are correctly demoted. If you implement __builtin_alloca_with_align, | |
| 75 | // please update the tests to use a different non-implemented builtin. | |
| 76 | }); |
lib/compiler/translate-c/helpers.zig created+327| ... | ... | @@ -0,0 +1,327 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const testing = std.testing; | |
| 4 | const math = std.math; | |
| 5 | ||
| 6 | const helpers = @import("helpers"); | |
| 7 | ||
| 8 | const cast = helpers.cast; | |
| 9 | ||
| 10 | test cast { | |
| 11 | var i = @as(i64, 10); | |
| 12 | ||
| 13 | try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16))); | |
| 14 | try testing.expect(cast(*u64, &i).* == @as(u64, 10)); | |
| 15 | try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i); | |
| 16 | ||
| 17 | try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2))); | |
| 18 | try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i); | |
| 19 | try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i); | |
| 20 | ||
| 21 | try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4)))); | |
| 22 | try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4)))); | |
| 23 | try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); | |
| 24 | ||
| 25 | try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000))); | |
| 26 | ||
| 27 | try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2)))); | |
| 28 | try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2)))); | |
| 29 | ||
| 30 | try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2)))); | |
| 31 | ||
| 32 | var foo: c_int = -1; | |
| 33 | _ = &foo; | |
| 34 | try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 35 | try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 36 | try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 37 | try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 38 | ||
| 39 | const FnPtr = ?*align(1) const fn (*anyopaque) void; | |
| 40 | try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0)))); | |
| 41 | try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 42 | ||
| 43 | const complexFunction = struct { | |
| 44 | fn f(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize { | |
| 45 | return 0; | |
| 46 | } | |
| 47 | }.f; | |
| 48 | ||
| 49 | const SDL_FunctionPointer = ?*const fn () callconv(.c) void; | |
| 50 | const fn_ptr = cast(SDL_FunctionPointer, complexFunction); | |
| 51 | try testing.expect(fn_ptr != null); | |
| 52 | } | |
| 53 | ||
| 54 | const sizeof = helpers.sizeof; | |
| 55 | ||
| 56 | test sizeof { | |
| 57 | const S = extern struct { a: u32 }; | |
| 58 | ||
| 59 | const ptr_size = @sizeOf(*anyopaque); | |
| 60 | ||
| 61 | try testing.expect(sizeof(u32) == 4); | |
| 62 | try testing.expect(sizeof(@as(u32, 2)) == 4); | |
| 63 | try testing.expect(sizeof(2) == @sizeOf(c_int)); | |
| 64 | ||
| 65 | try testing.expect(sizeof(2.0) == @sizeOf(f64)); | |
| 66 | ||
| 67 | try testing.expect(sizeof(S) == 4); | |
| 68 | ||
| 69 | try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12); | |
| 70 | try testing.expect(sizeof([3]u32) == 12); | |
| 71 | try testing.expect(sizeof([3:0]u32) == 16); | |
| 72 | try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size); | |
| 73 | ||
| 74 | try testing.expect(sizeof(*u32) == ptr_size); | |
| 75 | try testing.expect(sizeof([*]u32) == ptr_size); | |
| 76 | try testing.expect(sizeof([*c]u32) == ptr_size); | |
| 77 | try testing.expect(sizeof(?*u32) == ptr_size); | |
| 78 | try testing.expect(sizeof(?[*]u32) == ptr_size); | |
| 79 | try testing.expect(sizeof(*anyopaque) == ptr_size); | |
| 80 | try testing.expect(sizeof(*void) == ptr_size); | |
| 81 | try testing.expect(sizeof(null) == ptr_size); | |
| 82 | ||
| 83 | try testing.expect(sizeof("foobar") == 7); | |
| 84 | try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14); | |
| 85 | try testing.expect(sizeof(*const [4:0]u8) == 5); | |
| 86 | try testing.expect(sizeof(*[4:0]u8) == ptr_size); | |
| 87 | try testing.expect(sizeof([*]const [4:0]u8) == ptr_size); | |
| 88 | try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size); | |
| 89 | try testing.expect(sizeof(*const [4]u8) == ptr_size); | |
| 90 | ||
| 91 | if (false) { // TODO | |
| 92 | try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof))); | |
| 93 | try testing.expect(sizeof(sizeof) == 1); | |
| 94 | } | |
| 95 | ||
| 96 | try testing.expect(sizeof(void) == 1); | |
| 97 | try testing.expect(sizeof(anyopaque) == 1); | |
| 98 | } | |
| 99 | ||
| 100 | const promoteIntLiteral = helpers.promoteIntLiteral; | |
| 101 | ||
| 102 | test promoteIntLiteral { | |
| 103 | const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex); | |
| 104 | try testing.expectEqual(c_uint, @TypeOf(signed_hex)); | |
| 105 | ||
| 106 | if (math.maxInt(c_longlong) == math.maxInt(c_int)) return; | |
| 107 | ||
| 108 | const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal); | |
| 109 | const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex); | |
| 110 | ||
| 111 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 112 | try testing.expectEqual(c_long, @TypeOf(signed_decimal)); | |
| 113 | try testing.expectEqual(c_ulong, @TypeOf(unsigned)); | |
| 114 | } else { | |
| 115 | try testing.expectEqual(c_longlong, @TypeOf(signed_decimal)); | |
| 116 | try testing.expectEqual(c_ulonglong, @TypeOf(unsigned)); | |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | const shuffleVectorIndex = helpers.shuffleVectorIndex; | |
| 121 | ||
| 122 | test shuffleVectorIndex { | |
| 123 | const vector_len: usize = 4; | |
| 124 | ||
| 125 | _ = shuffleVectorIndex(-1, vector_len); | |
| 126 | ||
| 127 | try testing.expect(shuffleVectorIndex(0, vector_len) == 0); | |
| 128 | try testing.expect(shuffleVectorIndex(1, vector_len) == 1); | |
| 129 | try testing.expect(shuffleVectorIndex(2, vector_len) == 2); | |
| 130 | try testing.expect(shuffleVectorIndex(3, vector_len) == 3); | |
| 131 | ||
| 132 | try testing.expect(shuffleVectorIndex(4, vector_len) == -1); | |
| 133 | try testing.expect(shuffleVectorIndex(5, vector_len) == -2); | |
| 134 | try testing.expect(shuffleVectorIndex(6, vector_len) == -3); | |
| 135 | try testing.expect(shuffleVectorIndex(7, vector_len) == -4); | |
| 136 | } | |
| 137 | ||
| 138 | const FlexibleArrayType = helpers.FlexibleArrayType; | |
| 139 | ||
| 140 | test FlexibleArrayType { | |
| 141 | const Container = extern struct { | |
| 142 | size: usize, | |
| 143 | }; | |
| 144 | ||
| 145 | try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int); | |
| 146 | try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int); | |
| 147 | try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int); | |
| 148 | try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int); | |
| 149 | } | |
| 150 | ||
| 151 | const signedRemainder = helpers.signedRemainder; | |
| 152 | ||
| 153 | test signedRemainder { | |
| 154 | // TODO add test | |
| 155 | return error.SkipZigTest; | |
| 156 | } | |
| 157 | ||
| 158 | const ArithmeticConversion = helpers.ArithmeticConversion; | |
| 159 | ||
| 160 | test ArithmeticConversion { | |
| 161 | // Promotions not necessarily the same for other platforms | |
| 162 | if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest; | |
| 163 | ||
| 164 | const Test = struct { | |
| 165 | /// Order of operands should not matter for arithmetic conversions | |
| 166 | fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void { | |
| 167 | try std.testing.expect(ArithmeticConversion(A, B) == Expected); | |
| 168 | try std.testing.expect(ArithmeticConversion(B, A) == Expected); | |
| 169 | } | |
| 170 | }; | |
| 171 | ||
| 172 | try Test.checkPromotion(c_longdouble, c_int, c_longdouble); | |
| 173 | try Test.checkPromotion(c_int, f64, f64); | |
| 174 | try Test.checkPromotion(f32, bool, f32); | |
| 175 | ||
| 176 | try Test.checkPromotion(bool, c_short, c_int); | |
| 177 | try Test.checkPromotion(c_int, c_int, c_int); | |
| 178 | try Test.checkPromotion(c_short, c_int, c_int); | |
| 179 | ||
| 180 | try Test.checkPromotion(c_int, c_long, c_long); | |
| 181 | ||
| 182 | try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong); | |
| 183 | ||
| 184 | try Test.checkPromotion(c_uint, c_int, c_uint); | |
| 185 | ||
| 186 | try Test.checkPromotion(c_uint, c_long, c_long); | |
| 187 | ||
| 188 | try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong); | |
| 189 | ||
| 190 | // stdint.h | |
| 191 | try Test.checkPromotion(u8, i8, c_int); | |
| 192 | try Test.checkPromotion(u16, i16, c_int); | |
| 193 | try Test.checkPromotion(i32, c_int, c_int); | |
| 194 | try Test.checkPromotion(u32, c_int, c_uint); | |
| 195 | try Test.checkPromotion(i64, c_int, c_long); | |
| 196 | try Test.checkPromotion(u64, c_int, c_ulong); | |
| 197 | try Test.checkPromotion(isize, c_int, c_long); | |
| 198 | try Test.checkPromotion(usize, c_int, c_ulong); | |
| 199 | } | |
| 200 | ||
| 201 | const F_SUFFIX = helpers.F_SUFFIX; | |
| 202 | ||
| 203 | test F_SUFFIX { | |
| 204 | try testing.expect(@TypeOf(F_SUFFIX(1)) == f32); | |
| 205 | } | |
| 206 | ||
| 207 | const U_SUFFIX = helpers.U_SUFFIX; | |
| 208 | ||
| 209 | test U_SUFFIX { | |
| 210 | try testing.expect(@TypeOf(U_SUFFIX(1)) == c_uint); | |
| 211 | if (math.maxInt(c_ulong) > math.maxInt(c_uint)) { | |
| 212 | try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong); | |
| 213 | } | |
| 214 | if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { | |
| 215 | try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); | |
| 216 | } | |
| 217 | } | |
| 218 | ||
| 219 | const L_SUFFIX = helpers.L_SUFFIX; | |
| 220 | ||
| 221 | test L_SUFFIX { | |
| 222 | try testing.expect(@TypeOf(L_SUFFIX(1)) == c_long); | |
| 223 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 224 | try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_int) + 1)) == c_long); | |
| 225 | } | |
| 226 | if (math.maxInt(c_longlong) > math.maxInt(c_long)) { | |
| 227 | try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); | |
| 228 | } | |
| 229 | } | |
| 230 | const UL_SUFFIX = helpers.UL_SUFFIX; | |
| 231 | ||
| 232 | test UL_SUFFIX { | |
| 233 | try testing.expect(@TypeOf(UL_SUFFIX(1)) == c_ulong); | |
| 234 | if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { | |
| 235 | try testing.expect(@TypeOf(UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); | |
| 236 | } | |
| 237 | } | |
| 238 | const LL_SUFFIX = helpers.LL_SUFFIX; | |
| 239 | ||
| 240 | test LL_SUFFIX { | |
| 241 | try testing.expect(@TypeOf(LL_SUFFIX(1)) == c_longlong); | |
| 242 | } | |
| 243 | const ULL_SUFFIX = helpers.ULL_SUFFIX; | |
| 244 | ||
| 245 | test ULL_SUFFIX { | |
| 246 | try testing.expect(@TypeOf(ULL_SUFFIX(1)) == c_ulonglong); | |
| 247 | } | |
| 248 | ||
| 249 | test "Extended C ABI casting" { | |
| 250 | if (math.maxInt(c_long) > math.maxInt(c_char)) { | |
| 251 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char | |
| 252 | } | |
| 253 | if (math.maxInt(c_long) > math.maxInt(c_short)) { | |
| 254 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short | |
| 255 | } | |
| 256 | ||
| 257 | if (math.maxInt(c_long) > math.maxInt(c_ushort)) { | |
| 258 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort | |
| 259 | } | |
| 260 | ||
| 261 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 262 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int | |
| 263 | } | |
| 264 | ||
| 265 | if (math.maxInt(c_long) > math.maxInt(c_uint)) { | |
| 266 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint | |
| 267 | try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long | |
| 268 | } | |
| 269 | ||
| 270 | if (math.maxInt(c_longlong) > math.maxInt(c_long)) { | |
| 271 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long | |
| 272 | try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong | |
| 273 | } | |
| 274 | } | |
| 275 | ||
| 276 | const WL_CONTAINER_OF = helpers.WL_CONTAINER_OF; | |
| 277 | ||
| 278 | test WL_CONTAINER_OF { | |
| 279 | const S = struct { | |
| 280 | a: u32 = 0, | |
| 281 | b: u32 = 0, | |
| 282 | }; | |
| 283 | const x = S{}; | |
| 284 | const y = S{}; | |
| 285 | const ptr = WL_CONTAINER_OF(&x.b, &y, "b"); | |
| 286 | try testing.expectEqual(&x, ptr); | |
| 287 | } | |
| 288 | ||
| 289 | const CAST_OR_CALL = helpers.CAST_OR_CALL; | |
| 290 | ||
| 291 | test "CAST_OR_CALL casting" { | |
| 292 | const arg: c_int = 1000; | |
| 293 | const casted = CAST_OR_CALL(u8, arg); | |
| 294 | try testing.expectEqual(cast(u8, arg), casted); | |
| 295 | ||
| 296 | const S = struct { | |
| 297 | x: u32 = 0, | |
| 298 | }; | |
| 299 | var s: S = .{}; | |
| 300 | const casted_ptr = CAST_OR_CALL(*u8, &s); | |
| 301 | try testing.expectEqual(cast(*u8, &s), casted_ptr); | |
| 302 | } | |
| 303 | ||
| 304 | test "CAST_OR_CALL calling" { | |
| 305 | const Helper = struct { | |
| 306 | var last_val: bool = false; | |
| 307 | fn returnsVoid(val: bool) void { | |
| 308 | last_val = val; | |
| 309 | } | |
| 310 | fn returnsBool(f: f32) bool { | |
| 311 | return f > 0; | |
| 312 | } | |
| 313 | fn identity(self: c_uint) c_uint { | |
| 314 | return self; | |
| 315 | } | |
| 316 | }; | |
| 317 | ||
| 318 | CAST_OR_CALL(Helper.returnsVoid, true); | |
| 319 | try testing.expectEqual(true, Helper.last_val); | |
| 320 | CAST_OR_CALL(Helper.returnsVoid, false); | |
| 321 | try testing.expectEqual(false, Helper.last_val); | |
| 322 | ||
| 323 | try testing.expectEqual(Helper.returnsBool(1), CAST_OR_CALL(Helper.returnsBool, @as(f32, 1))); | |
| 324 | try testing.expectEqual(Helper.returnsBool(-1), CAST_OR_CALL(Helper.returnsBool, @as(f32, -1))); | |
| 325 | ||
| 326 | try testing.expectEqual(Helper.identity(@as(c_uint, 100)), CAST_OR_CALL(Helper.identity, @as(c_uint, 100))); | |
| 327 | } |
lib/compiler/translate-c/lib/c_builtins.zig deleted-301| ... | ... | @@ -1,301 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | /// Standard C Library bug: The absolute value of the most negative integer remains negative. | |
| 4 | pub inline fn abs(val: c_int) c_int { | |
| 5 | return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); | |
| 6 | } | |
| 7 | ||
| 8 | pub inline fn assume(cond: bool) void { | |
| 9 | if (!cond) unreachable; | |
| 10 | } | |
| 11 | ||
| 12 | pub inline fn bswap16(val: u16) u16 { | |
| 13 | return @byteSwap(val); | |
| 14 | } | |
| 15 | ||
| 16 | pub inline fn bswap32(val: u32) u32 { | |
| 17 | return @byteSwap(val); | |
| 18 | } | |
| 19 | ||
| 20 | pub inline fn bswap64(val: u64) u64 { | |
| 21 | return @byteSwap(val); | |
| 22 | } | |
| 23 | ||
| 24 | pub inline fn ceilf(val: f32) f32 { | |
| 25 | return @ceil(val); | |
| 26 | } | |
| 27 | ||
| 28 | pub inline fn ceil(val: f64) f64 { | |
| 29 | return @ceil(val); | |
| 30 | } | |
| 31 | ||
| 32 | /// Returns the number of leading 0-bits in x, starting at the most significant bit position. | |
| 33 | /// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint | |
| 34 | pub inline fn clz(val: c_uint) c_int { | |
| 35 | @setRuntimeSafety(false); | |
| 36 | return @as(c_int, @bitCast(@as(c_uint, @clz(val)))); | |
| 37 | } | |
| 38 | ||
| 39 | pub inline fn constant_p(expr: anytype) c_int { | |
| 40 | _ = expr; | |
| 41 | return @intFromBool(false); | |
| 42 | } | |
| 43 | ||
| 44 | pub inline fn cosf(val: f32) f32 { | |
| 45 | return @cos(val); | |
| 46 | } | |
| 47 | ||
| 48 | pub inline fn cos(val: f64) f64 { | |
| 49 | return @cos(val); | |
| 50 | } | |
| 51 | ||
| 52 | /// Returns the number of trailing 0-bits in val, starting at the least significant bit position. | |
| 53 | /// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint | |
| 54 | pub inline fn ctz(val: c_uint) c_int { | |
| 55 | @setRuntimeSafety(false); | |
| 56 | return @as(c_int, @bitCast(@as(c_uint, @ctz(val)))); | |
| 57 | } | |
| 58 | ||
| 59 | pub inline fn exp2f(val: f32) f32 { | |
| 60 | return @exp2(val); | |
| 61 | } | |
| 62 | ||
| 63 | pub inline fn exp2(val: f64) f64 { | |
| 64 | return @exp2(val); | |
| 65 | } | |
| 66 | ||
| 67 | pub inline fn expf(val: f32) f32 { | |
| 68 | return @exp(val); | |
| 69 | } | |
| 70 | ||
| 71 | pub inline fn exp(val: f64) f64 { | |
| 72 | return @exp(val); | |
| 73 | } | |
| 74 | ||
| 75 | /// The return value of __builtin_expect is `expr`. `c` is the expected value | |
| 76 | /// of `expr` and is used as a hint to the compiler in C. Here it is unused. | |
| 77 | pub inline fn expect(expr: c_long, c: c_long) c_long { | |
| 78 | _ = c; | |
| 79 | return expr; | |
| 80 | } | |
| 81 | ||
| 82 | pub inline fn fabsf(val: f32) f32 { | |
| 83 | return @abs(val); | |
| 84 | } | |
| 85 | ||
| 86 | pub inline fn fabs(val: f64) f64 { | |
| 87 | return @abs(val); | |
| 88 | } | |
| 89 | ||
| 90 | pub inline fn floorf(val: f32) f32 { | |
| 91 | return @floor(val); | |
| 92 | } | |
| 93 | ||
| 94 | pub inline fn floor(val: f64) f64 { | |
| 95 | return @floor(val); | |
| 96 | } | |
| 97 | ||
| 98 | pub inline fn has_builtin(func: anytype) c_int { | |
| 99 | _ = func; | |
| 100 | return @intFromBool(true); | |
| 101 | } | |
| 102 | ||
| 103 | pub inline fn huge_valf() f32 { | |
| 104 | return std.math.inf(f32); | |
| 105 | } | |
| 106 | ||
| 107 | pub inline fn inff() f32 { | |
| 108 | return std.math.inf(f32); | |
| 109 | } | |
| 110 | ||
| 111 | /// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf. | |
| 112 | pub inline fn isinf_sign(x: anytype) c_int { | |
| 113 | if (!std.math.isInf(x)) return 0; | |
| 114 | return if (std.math.isPositiveInf(x)) 1 else -1; | |
| 115 | } | |
| 116 | ||
| 117 | pub inline fn isinf(x: anytype) c_int { | |
| 118 | return @intFromBool(std.math.isInf(x)); | |
| 119 | } | |
| 120 | ||
| 121 | pub inline fn isnan(x: anytype) c_int { | |
| 122 | return @intFromBool(std.math.isNan(x)); | |
| 123 | } | |
| 124 | ||
| 125 | /// Standard C Library bug: The absolute value of the most negative integer remains negative. | |
| 126 | pub inline fn labs(val: c_long) c_long { | |
| 127 | return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val)); | |
| 128 | } | |
| 129 | ||
| 130 | /// Standard C Library bug: The absolute value of the most negative integer remains negative. | |
| 131 | pub inline fn llabs(val: c_longlong) c_longlong { | |
| 132 | return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val)); | |
| 133 | } | |
| 134 | ||
| 135 | pub inline fn log10f(val: f32) f32 { | |
| 136 | return @log10(val); | |
| 137 | } | |
| 138 | ||
| 139 | pub inline fn log10(val: f64) f64 { | |
| 140 | return @log10(val); | |
| 141 | } | |
| 142 | ||
| 143 | pub inline fn log2f(val: f32) f32 { | |
| 144 | return @log2(val); | |
| 145 | } | |
| 146 | ||
| 147 | pub inline fn log2(val: f64) f64 { | |
| 148 | return @log2(val); | |
| 149 | } | |
| 150 | ||
| 151 | pub inline fn logf(val: f32) f32 { | |
| 152 | return @log(val); | |
| 153 | } | |
| 154 | ||
| 155 | pub inline fn log(val: f64) f64 { | |
| 156 | return @log(val); | |
| 157 | } | |
| 158 | ||
| 159 | pub inline fn memcpy_chk( | |
| 160 | noalias dst: ?*anyopaque, | |
| 161 | noalias src: ?*const anyopaque, | |
| 162 | len: usize, | |
| 163 | remaining: usize, | |
| 164 | ) ?*anyopaque { | |
| 165 | if (len > remaining) @panic("__builtin___memcpy_chk called with len > remaining"); | |
| 166 | if (len > 0) @memcpy( | |
| 167 | @as([*]u8, @ptrCast(dst.?))[0..len], | |
| 168 | @as([*]const u8, @ptrCast(src.?)), | |
| 169 | ); | |
| 170 | return dst; | |
| 171 | } | |
| 172 | ||
| 173 | pub inline fn memcpy( | |
| 174 | noalias dst: ?*anyopaque, | |
| 175 | noalias src: ?*const anyopaque, | |
| 176 | len: usize, | |
| 177 | ) ?*anyopaque { | |
| 178 | if (len > 0) @memcpy( | |
| 179 | @as([*]u8, @ptrCast(dst.?))[0..len], | |
| 180 | @as([*]const u8, @ptrCast(src.?)), | |
| 181 | ); | |
| 182 | return dst; | |
| 183 | } | |
| 184 | ||
| 185 | pub inline fn memset_chk( | |
| 186 | dst: ?*anyopaque, | |
| 187 | val: c_int, | |
| 188 | len: usize, | |
| 189 | remaining: usize, | |
| 190 | ) ?*anyopaque { | |
| 191 | if (len > remaining) @panic("__builtin___memset_chk called with len > remaining"); | |
| 192 | const dst_cast = @as([*c]u8, @ptrCast(dst)); | |
| 193 | @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); | |
| 194 | return dst; | |
| 195 | } | |
| 196 | ||
| 197 | pub inline fn memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { | |
| 198 | const dst_cast = @as([*c]u8, @ptrCast(dst)); | |
| 199 | @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); | |
| 200 | return dst; | |
| 201 | } | |
| 202 | ||
| 203 | pub fn mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int { | |
| 204 | const res = @mulWithOverflow(a, b); | |
| 205 | result.* = res[0]; | |
| 206 | return res[1]; | |
| 207 | } | |
| 208 | ||
| 209 | /// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an | |
| 210 | /// implementation-defined way. | |
| 211 | /// This implementation is based on the description for nan provided in the GCC docs at | |
| 212 | /// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan | |
| 213 | /// Comment is reproduced below: | |
| 214 | /// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description | |
| 215 | /// of the parsing is in order. | |
| 216 | /// The string is parsed as by strtol; that is, the base is recognized by leading ‘0’ or ‘0x’ prefixes. | |
| 217 | /// The number parsed is placed in the significand such that the least significant bit of the number is | |
| 218 | /// at the least significant bit of the significand. | |
| 219 | /// The number is truncated to fit the significand field provided. | |
| 220 | /// The significand is forced to be a quiet NaN. | |
| 221 | /// | |
| 222 | /// If tagp contains any non-numeric characters, the function returns a NaN whose significand is zero. | |
| 223 | /// If tagp is empty, the function returns a NaN whose significand is zero. | |
| 224 | pub inline fn nanf(tagp: []const u8) f32 { | |
| 225 | const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0; | |
| 226 | const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits | |
| 227 | return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32)))); | |
| 228 | } | |
| 229 | ||
| 230 | pub inline fn object_size(ptr: ?*const anyopaque, ty: c_int) usize { | |
| 231 | _ = ptr; | |
| 232 | // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html | |
| 233 | // If it is not possible to determine which objects ptr points to at compile time, | |
| 234 | // object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 | |
| 235 | // for type 2 or 3. | |
| 236 | if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1))); | |
| 237 | if (ty == 2 or ty == 3) return 0; | |
| 238 | unreachable; | |
| 239 | } | |
| 240 | ||
| 241 | /// popcount of a c_uint will never exceed the capacity of a c_int | |
| 242 | pub inline fn popcount(val: c_uint) c_int { | |
| 243 | @setRuntimeSafety(false); | |
| 244 | return @as(c_int, @bitCast(@as(c_uint, @popCount(val)))); | |
| 245 | } | |
| 246 | ||
| 247 | pub inline fn roundf(val: f32) f32 { | |
| 248 | return @round(val); | |
| 249 | } | |
| 250 | ||
| 251 | pub inline fn round(val: f64) f64 { | |
| 252 | return @round(val); | |
| 253 | } | |
| 254 | ||
| 255 | pub inline fn signbitf(val: f32) c_int { | |
| 256 | return @intFromBool(std.math.signbit(val)); | |
| 257 | } | |
| 258 | ||
| 259 | pub inline fn signbit(val: f64) c_int { | |
| 260 | return @intFromBool(std.math.signbit(val)); | |
| 261 | } | |
| 262 | ||
| 263 | pub inline fn sinf(val: f32) f32 { | |
| 264 | return @sin(val); | |
| 265 | } | |
| 266 | ||
| 267 | pub inline fn sin(val: f64) f64 { | |
| 268 | return @sin(val); | |
| 269 | } | |
| 270 | ||
| 271 | pub inline fn sqrtf(val: f32) f32 { | |
| 272 | return @sqrt(val); | |
| 273 | } | |
| 274 | ||
| 275 | pub inline fn sqrt(val: f64) f64 { | |
| 276 | return @sqrt(val); | |
| 277 | } | |
| 278 | ||
| 279 | pub inline fn strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int { | |
| 280 | return switch (std.mem.orderZ(u8, s1, s2)) { | |
| 281 | .lt => -1, | |
| 282 | .eq => 0, | |
| 283 | .gt => 1, | |
| 284 | }; | |
| 285 | } | |
| 286 | ||
| 287 | pub inline fn strlen(s: [*c]const u8) usize { | |
| 288 | return std.mem.sliceTo(s, 0).len; | |
| 289 | } | |
| 290 | ||
| 291 | pub inline fn truncf(val: f32) f32 { | |
| 292 | return @trunc(val); | |
| 293 | } | |
| 294 | ||
| 295 | pub inline fn trunc(val: f64) f64 { | |
| 296 | return @trunc(val); | |
| 297 | } | |
| 298 | ||
| 299 | pub inline fn @"unreachable"() noreturn { | |
| 300 | unreachable; | |
| 301 | } |
lib/compiler/translate-c/lib/helpers.zig deleted-413| ... | ... | @@ -1,413 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | /// "Usual arithmetic conversions" from C11 standard 6.3.1.8 | |
| 4 | pub fn ArithmeticConversion(comptime A: type, comptime B: type) type { | |
| 5 | if (A == c_longdouble or B == c_longdouble) return c_longdouble; | |
| 6 | if (A == f80 or B == f80) return f80; | |
| 7 | if (A == f64 or B == f64) return f64; | |
| 8 | if (A == f32 or B == f32) return f32; | |
| 9 | ||
| 10 | const A_Promoted = PromotedIntType(A); | |
| 11 | const B_Promoted = PromotedIntType(B); | |
| 12 | comptime { | |
| 13 | std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int)); | |
| 14 | std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int)); | |
| 15 | } | |
| 16 | ||
| 17 | if (A_Promoted == B_Promoted) return A_Promoted; | |
| 18 | ||
| 19 | const a_signed = @typeInfo(A_Promoted).int.signedness == .signed; | |
| 20 | const b_signed = @typeInfo(B_Promoted).int.signedness == .signed; | |
| 21 | ||
| 22 | if (a_signed == b_signed) { | |
| 23 | return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted; | |
| 24 | } | |
| 25 | ||
| 26 | const SignedType = if (a_signed) A_Promoted else B_Promoted; | |
| 27 | const UnsignedType = if (!a_signed) A_Promoted else B_Promoted; | |
| 28 | ||
| 29 | if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType; | |
| 30 | ||
| 31 | if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType; | |
| 32 | ||
| 33 | return ToUnsigned(SignedType); | |
| 34 | } | |
| 35 | ||
| 36 | /// Integer promotion described in C11 6.3.1.1.2 | |
| 37 | fn PromotedIntType(comptime T: type) type { | |
| 38 | return switch (T) { | |
| 39 | bool, c_short => c_int, | |
| 40 | c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int, | |
| 41 | c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T, | |
| 42 | else => switch (@typeInfo(T)) { | |
| 43 | .comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"), | |
| 44 | // promote to c_int if it can represent all values of T | |
| 45 | .int => |int_info| if (int_info.bits < @bitSizeOf(c_int)) | |
| 46 | c_int | |
| 47 | // otherwise, restore the original C type | |
| 48 | else if (int_info.bits == @bitSizeOf(c_int)) | |
| 49 | if (int_info.signedness == .unsigned) c_uint else c_int | |
| 50 | else if (int_info.bits <= @bitSizeOf(c_long)) | |
| 51 | if (int_info.signedness == .unsigned) c_ulong else c_long | |
| 52 | else if (int_info.bits <= @bitSizeOf(c_longlong)) | |
| 53 | if (int_info.signedness == .unsigned) c_ulonglong else c_longlong | |
| 54 | else | |
| 55 | @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required"), | |
| 56 | else => @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`"), | |
| 57 | }, | |
| 58 | }; | |
| 59 | } | |
| 60 | ||
| 61 | /// C11 6.3.1.1.1 | |
| 62 | fn integerRank(comptime T: type) u8 { | |
| 63 | return switch (T) { | |
| 64 | bool => 0, | |
| 65 | u8, i8 => 1, | |
| 66 | c_short, c_ushort => 2, | |
| 67 | c_int, c_uint => 3, | |
| 68 | c_long, c_ulong => 4, | |
| 69 | c_longlong, c_ulonglong => 5, | |
| 70 | else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"), | |
| 71 | }; | |
| 72 | } | |
| 73 | ||
| 74 | fn ToUnsigned(comptime T: type) type { | |
| 75 | return switch (T) { | |
| 76 | c_int => c_uint, | |
| 77 | c_long => c_ulong, | |
| 78 | c_longlong => c_ulonglong, | |
| 79 | else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"), | |
| 80 | }; | |
| 81 | } | |
| 82 | ||
| 83 | /// Constructs a [*c] pointer with the const and volatile annotations | |
| 84 | /// from SelfType for pointing to a C flexible array of ElementType. | |
| 85 | pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type { | |
| 86 | switch (@typeInfo(SelfType)) { | |
| 87 | .pointer => |ptr| { | |
| 88 | return @Type(.{ .pointer = .{ | |
| 89 | .size = .c, | |
| 90 | .is_const = ptr.is_const, | |
| 91 | .is_volatile = ptr.is_volatile, | |
| 92 | .alignment = @alignOf(ElementType), | |
| 93 | .address_space = .generic, | |
| 94 | .child = ElementType, | |
| 95 | .is_allowzero = true, | |
| 96 | .sentinel_ptr = null, | |
| 97 | } }); | |
| 98 | }, | |
| 99 | else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)), | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | /// Promote the type of an integer literal until it fits as C would. | |
| 104 | pub fn promoteIntLiteral( | |
| 105 | comptime SuffixType: type, | |
| 106 | comptime number: comptime_int, | |
| 107 | comptime base: CIntLiteralBase, | |
| 108 | ) PromoteIntLiteralReturnType(SuffixType, number, base) { | |
| 109 | return number; | |
| 110 | } | |
| 111 | ||
| 112 | const CIntLiteralBase = enum { decimal, octal, hex }; | |
| 113 | ||
| 114 | fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type { | |
| 115 | const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong }; | |
| 116 | const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; | |
| 117 | const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; | |
| 118 | ||
| 119 | const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned) | |
| 120 | &unsigned | |
| 121 | else if (base == .decimal) | |
| 122 | &signed_decimal | |
| 123 | else | |
| 124 | &signed_oct_hex; | |
| 125 | ||
| 126 | var pos = std.mem.indexOfScalar(type, list, SuffixType).?; | |
| 127 | while (pos < list.len) : (pos += 1) { | |
| 128 | if (number >= std.math.minInt(list[pos]) and number <= std.math.maxInt(list[pos])) { | |
| 129 | return list[pos]; | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 133 | @compileError("Integer literal is too large"); | |
| 134 | } | |
| 135 | ||
| 136 | /// Convert from clang __builtin_shufflevector index to Zig @shuffle index | |
| 137 | /// clang requires __builtin_shufflevector index arguments to be integer constants. | |
| 138 | /// negative values for `this_index` indicate "don't care". | |
| 139 | /// clang enforces that `this_index` is less than the total number of vector elements | |
| 140 | /// See https://ziglang.org/documentation/master/#shuffle | |
| 141 | /// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector | |
| 142 | pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 { | |
| 143 | const positive_index = std.math.cast(usize, this_index) orelse return undefined; | |
| 144 | if (positive_index < source_vector_len) return @as(i32, @intCast(this_index)); | |
| 145 | const b_index = positive_index - source_vector_len; | |
| 146 | return ~@as(i32, @intCast(b_index)); | |
| 147 | } | |
| 148 | ||
| 149 | /// C `%` operator for signed integers | |
| 150 | /// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a" | |
| 151 | /// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for | |
| 152 | /// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety | |
| 153 | /// checked undefined behavior | |
| 154 | pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) { | |
| 155 | std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed); | |
| 156 | if (denominator > 0) return @rem(numerator, denominator); | |
| 157 | return numerator - @divTrunc(numerator, denominator) * denominator; | |
| 158 | } | |
| 159 | ||
| 160 | /// Given a type and value, cast the value to the type as c would. | |
| 161 | pub fn cast(comptime DestType: type, target: anytype) DestType { | |
| 162 | // this function should behave like transCCast in translate-c, except it's for macros | |
| 163 | const SourceType = @TypeOf(target); | |
| 164 | switch (@typeInfo(DestType)) { | |
| 165 | .@"fn" => return castToPtr(*const DestType, SourceType, target), | |
| 166 | .pointer => return castToPtr(DestType, SourceType, target), | |
| 167 | .optional => |dest_opt| { | |
| 168 | if (@typeInfo(dest_opt.child) == .pointer) { | |
| 169 | return castToPtr(DestType, SourceType, target); | |
| 170 | } else if (@typeInfo(dest_opt.child) == .@"fn") { | |
| 171 | return castToPtr(?*const dest_opt.child, SourceType, target); | |
| 172 | } | |
| 173 | }, | |
| 174 | .int => { | |
| 175 | switch (@typeInfo(SourceType)) { | |
| 176 | .pointer => { | |
| 177 | return castInt(DestType, @intFromPtr(target)); | |
| 178 | }, | |
| 179 | .optional => |opt| { | |
| 180 | if (@typeInfo(opt.child) == .pointer) { | |
| 181 | return castInt(DestType, @intFromPtr(target)); | |
| 182 | } | |
| 183 | }, | |
| 184 | .int => { | |
| 185 | return castInt(DestType, target); | |
| 186 | }, | |
| 187 | .@"fn" => { | |
| 188 | return castInt(DestType, @intFromPtr(&target)); | |
| 189 | }, | |
| 190 | .bool => { | |
| 191 | return @intFromBool(target); | |
| 192 | }, | |
| 193 | else => {}, | |
| 194 | } | |
| 195 | }, | |
| 196 | .float => { | |
| 197 | switch (@typeInfo(SourceType)) { | |
| 198 | .int => return @as(DestType, @floatFromInt(target)), | |
| 199 | .float => return @as(DestType, @floatCast(target)), | |
| 200 | .bool => return @as(DestType, @floatFromInt(@intFromBool(target))), | |
| 201 | else => {}, | |
| 202 | } | |
| 203 | }, | |
| 204 | .@"union" => |info| { | |
| 205 | inline for (info.fields) |field| { | |
| 206 | if (field.type == SourceType) return @unionInit(DestType, field.name, target); | |
| 207 | } | |
| 208 | ||
| 209 | @compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union"); | |
| 210 | }, | |
| 211 | .bool => return cast(usize, target) != 0, | |
| 212 | else => {}, | |
| 213 | } | |
| 214 | ||
| 215 | return @as(DestType, target); | |
| 216 | } | |
| 217 | ||
| 218 | fn castInt(comptime DestType: type, target: anytype) DestType { | |
| 219 | const dest = @typeInfo(DestType).int; | |
| 220 | const source = @typeInfo(@TypeOf(target)).int; | |
| 221 | ||
| 222 | const Int = @Type(.{ .int = .{ .bits = dest.bits, .signedness = source.signedness } }); | |
| 223 | ||
| 224 | if (dest.bits < source.bits) | |
| 225 | return @as(DestType, @bitCast(@as(Int, @truncate(target)))) | |
| 226 | else | |
| 227 | return @as(DestType, @bitCast(@as(Int, target))); | |
| 228 | } | |
| 229 | ||
| 230 | fn castPtr(comptime DestType: type, target: anytype) DestType { | |
| 231 | return @constCast(@volatileCast(@alignCast(@ptrCast(target)))); | |
| 232 | } | |
| 233 | ||
| 234 | fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType { | |
| 235 | switch (@typeInfo(SourceType)) { | |
| 236 | .int => { | |
| 237 | return @as(DestType, @ptrFromInt(castInt(usize, target))); | |
| 238 | }, | |
| 239 | .comptime_int => { | |
| 240 | if (target < 0) | |
| 241 | return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target)))))) | |
| 242 | else | |
| 243 | return @as(DestType, @ptrFromInt(@as(usize, @intCast(target)))); | |
| 244 | }, | |
| 245 | .pointer => { | |
| 246 | return castPtr(DestType, target); | |
| 247 | }, | |
| 248 | .@"fn" => { | |
| 249 | return castPtr(DestType, &target); | |
| 250 | }, | |
| 251 | .optional => |target_opt| { | |
| 252 | if (@typeInfo(target_opt.child) == .pointer) { | |
| 253 | return castPtr(DestType, target); | |
| 254 | } | |
| 255 | }, | |
| 256 | else => {}, | |
| 257 | } | |
| 258 | ||
| 259 | return @as(DestType, target); | |
| 260 | } | |
| 261 | ||
| 262 | /// Given a value returns its size as C's sizeof operator would. | |
| 263 | pub fn sizeof(target: anytype) usize { | |
| 264 | const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); | |
| 265 | switch (@typeInfo(T)) { | |
| 266 | .float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T), | |
| 267 | .@"fn" => { | |
| 268 | // sizeof(main) in C returns 1 | |
| 269 | return 1; | |
| 270 | }, | |
| 271 | .null => return @sizeOf(*anyopaque), | |
| 272 | .void => { | |
| 273 | // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. | |
| 274 | return 1; | |
| 275 | }, | |
| 276 | .@"opaque" => { | |
| 277 | if (T == anyopaque) { | |
| 278 | // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. | |
| 279 | return 1; | |
| 280 | } else { | |
| 281 | @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); | |
| 282 | } | |
| 283 | }, | |
| 284 | .optional => |opt| { | |
| 285 | if (@typeInfo(opt.child) == .pointer) { | |
| 286 | return sizeof(opt.child); | |
| 287 | } else { | |
| 288 | @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); | |
| 289 | } | |
| 290 | }, | |
| 291 | .pointer => |ptr| { | |
| 292 | if (ptr.size == .slice) { | |
| 293 | @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); | |
| 294 | } | |
| 295 | ||
| 296 | // for strings, sizeof("a") returns 2. | |
| 297 | // normal pointer decay scenarios from C are handled | |
| 298 | // in the .array case above, but strings remain literals | |
| 299 | // and are therefore always pointers, so they need to be | |
| 300 | // specially handled here. | |
| 301 | if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) { | |
| 302 | const array_info = @typeInfo(ptr.child).array; | |
| 303 | if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) { | |
| 304 | // length of the string plus one for the null terminator. | |
| 305 | return (array_info.len + 1) * @sizeOf(array_info.child); | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | // When zero sized pointers are removed, this case will no | |
| 310 | // longer be reachable and can be deleted. | |
| 311 | if (@sizeOf(T) == 0) { | |
| 312 | return @sizeOf(*anyopaque); | |
| 313 | } | |
| 314 | ||
| 315 | return @sizeOf(T); | |
| 316 | }, | |
| 317 | .comptime_float => return @sizeOf(f64), // TODO c_double #3999 | |
| 318 | .comptime_int => { | |
| 319 | // TODO to get the correct result we have to translate | |
| 320 | // `1073741824 * 4` as `int(1073741824) *% int(4)` since | |
| 321 | // sizeof(1073741824 * 4) != sizeof(4294967296). | |
| 322 | ||
| 323 | // TODO test if target fits in int, long or long long | |
| 324 | return @sizeOf(c_int); | |
| 325 | }, | |
| 326 | else => @compileError("__helpers.sizeof does not support type " ++ @typeName(T)), | |
| 327 | } | |
| 328 | } | |
| 329 | ||
| 330 | pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { | |
| 331 | const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); | |
| 332 | const a_casted = cast(ResType, a); | |
| 333 | const b_casted = cast(ResType, b); | |
| 334 | switch (@typeInfo(ResType)) { | |
| 335 | .float => return a_casted / b_casted, | |
| 336 | .int => return @divTrunc(a_casted, b_casted), | |
| 337 | else => unreachable, | |
| 338 | } | |
| 339 | } | |
| 340 | ||
| 341 | pub fn rem(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { | |
| 342 | const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); | |
| 343 | const a_casted = cast(ResType, a); | |
| 344 | const b_casted = cast(ResType, b); | |
| 345 | switch (@typeInfo(ResType)) { | |
| 346 | .int => { | |
| 347 | if (@typeInfo(ResType).int.signedness == .signed) { | |
| 348 | return signedRemainder(a_casted, b_casted); | |
| 349 | } else { | |
| 350 | return a_casted % b_casted; | |
| 351 | } | |
| 352 | }, | |
| 353 | else => unreachable, | |
| 354 | } | |
| 355 | } | |
| 356 | ||
| 357 | /// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) | |
| 358 | /// could be either: cast B to A, or call A with the value B. | |
| 359 | pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { | |
| 360 | .type => a, | |
| 361 | .@"fn" => |fn_info| fn_info.return_type orelse void, | |
| 362 | else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), | |
| 363 | } { | |
| 364 | switch (@typeInfo(@TypeOf(a))) { | |
| 365 | .type => return cast(a, b), | |
| 366 | .@"fn" => return a(b), | |
| 367 | else => unreachable, // return type will be a compile error otherwise | |
| 368 | } | |
| 369 | } | |
| 370 | ||
| 371 | pub inline fn DISCARD(x: anytype) void { | |
| 372 | _ = x; | |
| 373 | } | |
| 374 | ||
| 375 | pub fn F_SUFFIX(comptime f: comptime_float) f32 { | |
| 376 | return @as(f32, f); | |
| 377 | } | |
| 378 | ||
| 379 | fn L_SUFFIX_ReturnType(comptime number: anytype) type { | |
| 380 | switch (@typeInfo(@TypeOf(number))) { | |
| 381 | .int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), | |
| 382 | .float, .comptime_float => return c_longdouble, | |
| 383 | else => @compileError("Invalid value for L suffix"), | |
| 384 | } | |
| 385 | } | |
| 386 | ||
| 387 | pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { | |
| 388 | switch (@typeInfo(@TypeOf(number))) { | |
| 389 | .int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal), | |
| 390 | .float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), | |
| 391 | else => @compileError("Invalid value for L suffix"), | |
| 392 | } | |
| 393 | } | |
| 394 | ||
| 395 | pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { | |
| 396 | return promoteIntLiteral(c_longlong, n, .decimal); | |
| 397 | } | |
| 398 | ||
| 399 | pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { | |
| 400 | return promoteIntLiteral(c_uint, n, .decimal); | |
| 401 | } | |
| 402 | ||
| 403 | pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { | |
| 404 | return promoteIntLiteral(c_ulong, n, .decimal); | |
| 405 | } | |
| 406 | ||
| 407 | pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { | |
| 408 | return promoteIntLiteral(c_ulonglong, n, .decimal); | |
| 409 | } | |
| 410 | ||
| 411 | pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { | |
| 412 | return @fieldParentPtr(member, ptr); | |
| 413 | } |
lib/compiler/translate-c/main.zig created+251| ... | ... | @@ -0,0 +1,251 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const mem = std.mem; | |
| 4 | const process = std.process; | |
| 5 | const aro = @import("aro"); | |
| 6 | const Translator = @import("Translator.zig"); | |
| 7 | ||
| 8 | const fast_exit = @import("builtin").mode != .Debug; | |
| 9 | ||
| 10 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; | |
| 11 | ||
| 12 | pub fn main() u8 { | |
| 13 | const gpa = general_purpose_allocator.allocator(); | |
| 14 | defer _ = general_purpose_allocator.deinit(); | |
| 15 | ||
| 16 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 17 | defer arena_instance.deinit(); | |
| 18 | const arena = arena_instance.allocator(); | |
| 19 | ||
| 20 | const args = process.argsAlloc(arena) catch { | |
| 21 | std.debug.print("ran out of memory allocating arguments\n", .{}); | |
| 22 | if (fast_exit) process.exit(1); | |
| 23 | return 1; | |
| 24 | }; | |
| 25 | ||
| 26 | var stderr_buf: [1024]u8 = undefined; | |
| 27 | var stderr = std.fs.File.stderr().writer(&stderr_buf); | |
| 28 | var diagnostics: aro.Diagnostics = .{ | |
| 29 | .output = .{ .to_writer = .{ | |
| 30 | .color = .detect(stderr.file), | |
| 31 | .writer = &stderr.interface, | |
| 32 | } }, | |
| 33 | }; | |
| 34 | ||
| 35 | var comp = aro.Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |err| switch (err) { | |
| 36 | error.OutOfMemory => { | |
| 37 | std.debug.print("ran out of memory initializing C compilation\n", .{}); | |
| 38 | if (fast_exit) process.exit(1); | |
| 39 | return 1; | |
| 40 | }, | |
| 41 | }; | |
| 42 | defer comp.deinit(); | |
| 43 | ||
| 44 | const exe_name = std.fs.selfExePathAlloc(gpa) catch { | |
| 45 | std.debug.print("unable to find translate-c executable path\n", .{}); | |
| 46 | if (fast_exit) process.exit(1); | |
| 47 | return 1; | |
| 48 | }; | |
| 49 | defer gpa.free(exe_name); | |
| 50 | ||
| 51 | var driver: aro.Driver = .{ .comp = &comp, .diagnostics = &diagnostics, .aro_name = exe_name }; | |
| 52 | defer driver.deinit(); | |
| 53 | ||
| 54 | var toolchain: aro.Toolchain = .{ .driver = &driver, .filesystem = .{ .real = comp.cwd } }; | |
| 55 | defer toolchain.deinit(); | |
| 56 | ||
| 57 | translate(&driver, &toolchain, args) catch |err| switch (err) { | |
| 58 | error.OutOfMemory => { | |
| 59 | std.debug.print("ran out of memory translating\n", .{}); | |
| 60 | if (fast_exit) process.exit(1); | |
| 61 | return 1; | |
| 62 | }, | |
| 63 | error.FatalError => { | |
| 64 | if (fast_exit) process.exit(1); | |
| 65 | return 1; | |
| 66 | }, | |
| 67 | error.WriteFailed => { | |
| 68 | std.debug.print("unable to write to stdout\n", .{}); | |
| 69 | if (fast_exit) process.exit(1); | |
| 70 | return 1; | |
| 71 | }, | |
| 72 | }; | |
| 73 | if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0)); | |
| 74 | return @intFromBool(comp.diagnostics.errors != 0); | |
| 75 | } | |
| 76 | ||
| 77 | pub const usage = | |
| 78 | \\Usage {s}: [options] file [CC options] | |
| 79 | \\ | |
| 80 | \\Options: | |
| 81 | \\ --help Print this message | |
| 82 | \\ --version Print translate-c version | |
| 83 | \\ -fmodule-libs Import libraries as modules | |
| 84 | \\ -fno-module-libs (default) Install libraries next to output file | |
| 85 | \\ | |
| 86 | \\ | |
| 87 | ; | |
| 88 | ||
| 89 | fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void { | |
| 90 | const gpa = d.comp.gpa; | |
| 91 | ||
| 92 | var module_libs = false; | |
| 93 | ||
| 94 | const aro_args = args: { | |
| 95 | var i: usize = 0; | |
| 96 | for (args) |arg| { | |
| 97 | args[i] = arg; | |
| 98 | if (mem.eql(u8, arg, "--help")) { | |
| 99 | var stdout_buf: [512]u8 = undefined; | |
| 100 | var stdout = std.fs.File.stdout().writer(&stdout_buf); | |
| 101 | try stdout.interface.print(usage, .{args[0]}); | |
| 102 | try stdout.interface.flush(); | |
| 103 | return; | |
| 104 | } else if (mem.eql(u8, arg, "--version")) { | |
| 105 | var stdout_buf: [512]u8 = undefined; | |
| 106 | var stdout = std.fs.File.stdout().writer(&stdout_buf); | |
| 107 | // TODO add version | |
| 108 | try stdout.interface.writeAll("0.0.0-dev\n"); | |
| 109 | try stdout.interface.flush(); | |
| 110 | return; | |
| 111 | } else if (mem.eql(u8, arg, "-fmodule-libs")) { | |
| 112 | module_libs = true; | |
| 113 | } else if (mem.eql(u8, arg, "-fno-module-libs")) { | |
| 114 | module_libs = false; | |
| 115 | } else { | |
| 116 | i += 1; | |
| 117 | } | |
| 118 | } | |
| 119 | break :args args[0..i]; | |
| 120 | }; | |
| 121 | const user_macros = macros: { | |
| 122 | var macro_buf: std.ArrayListUnmanaged(u8) = .empty; | |
| 123 | defer macro_buf.deinit(gpa); | |
| 124 | ||
| 125 | try macro_buf.appendSlice(gpa, "#define __TRANSLATE_C__ 1\n"); | |
| 126 | ||
| 127 | var discard_buf: [256]u8 = undefined; | |
| 128 | var discarding: std.io.Writer.Discarding = .init(&discard_buf); | |
| 129 | assert(!try d.parseArgs(&discarding.writer, &macro_buf, aro_args)); | |
| 130 | if (macro_buf.items.len > std.math.maxInt(u32)) { | |
| 131 | return d.fatal("user provided macro source exceeded max size", .{}); | |
| 132 | } | |
| 133 | ||
| 134 | const content = try macro_buf.toOwnedSlice(gpa); | |
| 135 | errdefer gpa.free(content); | |
| 136 | ||
| 137 | break :macros try d.comp.addSourceFromOwnedBuffer("<command line>", content, .user); | |
| 138 | }; | |
| 139 | ||
| 140 | if (d.inputs.items.len != 1) { | |
| 141 | return d.fatal("expected exactly one input file", .{}); | |
| 142 | } | |
| 143 | const source = d.inputs.items[0]; | |
| 144 | ||
| 145 | tc.discover() catch |er| switch (er) { | |
| 146 | error.OutOfMemory => return error.OutOfMemory, | |
| 147 | error.TooManyMultilibs => return d.fatal("found more than one multilib with the same priority", .{}), | |
| 148 | }; | |
| 149 | tc.defineSystemIncludes() catch |er| switch (er) { | |
| 150 | error.OutOfMemory => return error.OutOfMemory, | |
| 151 | error.AroIncludeNotFound => return d.fatal("unable to find Aro builtin headers", .{}), | |
| 152 | }; | |
| 153 | ||
| 154 | const builtin_macros = d.comp.generateBuiltinMacros(.include_system_defines) catch |err| switch (err) { | |
| 155 | error.FileTooBig => return d.fatal("builtin macro source exceeded max size", .{}), | |
| 156 | else => |e| return e, | |
| 157 | }; | |
| 158 | ||
| 159 | var pp = try aro.Preprocessor.initDefault(d.comp); | |
| 160 | defer pp.deinit(); | |
| 161 | ||
| 162 | try pp.preprocessSources(&.{ source, builtin_macros, user_macros }); | |
| 163 | ||
| 164 | var c_tree = try pp.parse(); | |
| 165 | defer c_tree.deinit(); | |
| 166 | ||
| 167 | if (d.diagnostics.errors != 0) { | |
| 168 | if (fast_exit) process.exit(1); | |
| 169 | return error.FatalError; | |
| 170 | } | |
| 171 | ||
| 172 | const rendered_zig = try Translator.translate(.{ | |
| 173 | .gpa = gpa, | |
| 174 | .comp = d.comp, | |
| 175 | .pp = &pp, | |
| 176 | .tree = &c_tree, | |
| 177 | .module_libs = module_libs, | |
| 178 | }); | |
| 179 | defer gpa.free(rendered_zig); | |
| 180 | ||
| 181 | var close_out_file = false; | |
| 182 | var out_file_path: []const u8 = "<stdout>"; | |
| 183 | var out_file: std.fs.File = .stdout(); | |
| 184 | defer if (close_out_file) out_file.close(); | |
| 185 | ||
| 186 | if (d.output_name) |path| blk: { | |
| 187 | if (std.mem.eql(u8, path, "-")) break :blk; | |
| 188 | if (std.fs.path.dirname(path)) |dirname| { | |
| 189 | std.fs.cwd().makePath(dirname) catch |err| | |
| 190 | return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); | |
| 191 | } | |
| 192 | out_file = std.fs.cwd().createFile(path, .{}) catch |err| { | |
| 193 | return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); | |
| 194 | }; | |
| 195 | close_out_file = true; | |
| 196 | out_file_path = path; | |
| 197 | } | |
| 198 | ||
| 199 | var out_buf: [4096]u8 = undefined; | |
| 200 | var out_writer = out_file.writer(&out_buf); | |
| 201 | out_writer.interface.writeAll(rendered_zig) catch | |
| 202 | return d.fatal("failed to write result to '{s}': {s}", .{ out_file_path, aro.Driver.errorDescription(out_writer.err.?) }); | |
| 203 | ||
| 204 | if (!module_libs) { | |
| 205 | const dest_path = if (d.output_name) |path| std.fs.path.dirname(path) else null; | |
| 206 | installLibs(d, dest_path) catch |err| | |
| 207 | return d.fatal("failed to install library files: {s}", .{aro.Driver.errorDescription(err)}); | |
| 208 | } | |
| 209 | ||
| 210 | if (fast_exit) process.exit(0); | |
| 211 | } | |
| 212 | ||
| 213 | fn installLibs(d: *aro.Driver, dest_path: ?[]const u8) !void { | |
| 214 | const gpa = d.comp.gpa; | |
| 215 | const cwd = std.fs.cwd(); | |
| 216 | ||
| 217 | const self_exe_path = try std.fs.selfExePathAlloc(gpa); | |
| 218 | defer gpa.free(self_exe_path); | |
| 219 | ||
| 220 | var cur_dir: []const u8 = self_exe_path; | |
| 221 | while (std.fs.path.dirname(cur_dir)) |dirname| : (cur_dir = dirname) { | |
| 222 | var base_dir = cwd.openDir(dirname, .{}) catch continue; | |
| 223 | defer base_dir.close(); | |
| 224 | ||
| 225 | var lib_dir = base_dir.openDir("lib", .{}) catch continue; | |
| 226 | defer lib_dir.close(); | |
| 227 | ||
| 228 | lib_dir.access("c_builtins.zig", .{}) catch continue; | |
| 229 | ||
| 230 | { | |
| 231 | const install_path = try std.fs.path.join(gpa, &.{ dest_path orelse "", "c_builtins.zig" }); | |
| 232 | defer gpa.free(install_path); | |
| 233 | try lib_dir.copyFile("c_builtins.zig", cwd, install_path, .{}); | |
| 234 | } | |
| 235 | { | |
| 236 | const install_path = try std.fs.path.join(gpa, &.{ dest_path orelse "", "helpers.zig" }); | |
| 237 | defer gpa.free(install_path); | |
| 238 | try lib_dir.copyFile("helpers.zig", cwd, install_path, .{}); | |
| 239 | } | |
| 240 | return; | |
| 241 | } | |
| 242 | return error.FileNotFound; | |
| 243 | } | |
| 244 | ||
| 245 | comptime { | |
| 246 | if (@import("builtin").is_test) { | |
| 247 | _ = Translator; | |
| 248 | _ = @import("helpers.zig"); | |
| 249 | _ = @import("PatternList.zig"); | |
| 250 | } | |
| 251 | } |
lib/compiler/translate-c/src/MacroTranslator.zig deleted-1307| ... | ... | @@ -1,1307 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const math = std.math; | |
| 3 | const mem = std.mem; | |
| 4 | const assert = std.debug.assert; | |
| 5 | ||
| 6 | const aro = @import("aro"); | |
| 7 | const CToken = aro.Tokenizer.Token; | |
| 8 | ||
| 9 | const ast = @import("ast.zig"); | |
| 10 | const builtins = @import("builtins.zig"); | |
| 11 | const ZigNode = ast.Node; | |
| 12 | const ZigTag = ZigNode.Tag; | |
| 13 | const Scope = @import("Scope.zig"); | |
| 14 | const Translator = @import("Translator.zig"); | |
| 15 | ||
| 16 | const Error = Translator.Error; | |
| 17 | pub const ParseError = Error || error{ParseError}; | |
| 18 | ||
| 19 | const MacroTranslator = @This(); | |
| 20 | ||
| 21 | t: *Translator, | |
| 22 | macro: aro.Preprocessor.Macro, | |
| 23 | name: []const u8, | |
| 24 | ||
| 25 | tokens: []const CToken, | |
| 26 | source: []const u8, | |
| 27 | i: usize = 0, | |
| 28 | /// If an object macro references a global var it needs to be converted into | |
| 29 | /// an inline function. | |
| 30 | refs_var_decl: bool = false, | |
| 31 | ||
| 32 | fn peek(mt: *MacroTranslator) CToken.Id { | |
| 33 | if (mt.i >= mt.tokens.len) return .eof; | |
| 34 | return mt.tokens[mt.i].id; | |
| 35 | } | |
| 36 | ||
| 37 | fn eat(mt: *MacroTranslator, expected_id: CToken.Id) bool { | |
| 38 | if (mt.peek() == expected_id) { | |
| 39 | mt.i += 1; | |
| 40 | return true; | |
| 41 | } | |
| 42 | return false; | |
| 43 | } | |
| 44 | ||
| 45 | fn expect(mt: *MacroTranslator, expected_id: CToken.Id) ParseError!void { | |
| 46 | const next_id = mt.peek(); | |
| 47 | if (next_id != expected_id and !(expected_id == .identifier and next_id == .extended_identifier)) { | |
| 48 | try mt.fail( | |
| 49 | "unable to translate C expr: expected '{s}' instead got '{s}'", | |
| 50 | .{ expected_id.symbol(), next_id.symbol() }, | |
| 51 | ); | |
| 52 | return error.ParseError; | |
| 53 | } | |
| 54 | mt.i += 1; | |
| 55 | } | |
| 56 | ||
| 57 | fn fail(mt: *MacroTranslator, comptime fmt: []const u8, args: anytype) !void { | |
| 58 | return mt.t.failDeclExtra(&mt.t.global_scope.base, mt.macro.loc, mt.name, fmt, args); | |
| 59 | } | |
| 60 | ||
| 61 | fn tokSlice(mt: *const MacroTranslator) []const u8 { | |
| 62 | const tok = mt.tokens[mt.i]; | |
| 63 | return mt.source[tok.start..tok.end]; | |
| 64 | } | |
| 65 | ||
| 66 | pub fn transFnMacro(mt: *MacroTranslator) ParseError!void { | |
| 67 | var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false); | |
| 68 | defer block_scope.deinit(); | |
| 69 | const scope = &block_scope.base; | |
| 70 | ||
| 71 | const fn_params = try mt.t.arena.alloc(ast.Payload.Param, mt.macro.params.len); | |
| 72 | for (fn_params, mt.macro.params) |*param, param_name| { | |
| 73 | const mangled_name = try block_scope.makeMangledName(param_name); | |
| 74 | param.* = .{ | |
| 75 | .is_noalias = false, | |
| 76 | .name = mangled_name, | |
| 77 | .type = ZigTag.@"anytype".init(), | |
| 78 | }; | |
| 79 | try block_scope.discardVariable(mangled_name); | |
| 80 | } | |
| 81 | ||
| 82 | const expr = try mt.parseCExpr(scope); | |
| 83 | const last = mt.peek(); | |
| 84 | if (last != .eof) | |
| 85 | return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()}); | |
| 86 | ||
| 87 | const typeof_arg = if (expr.castTag(.block)) |some| blk: { | |
| 88 | const stmts = some.data.stmts; | |
| 89 | const blk_last = stmts[stmts.len - 1]; | |
| 90 | const br = blk_last.castTag(.break_val).?; | |
| 91 | break :blk br.data.val; | |
| 92 | } else expr; | |
| 93 | ||
| 94 | const return_type = ret: { | |
| 95 | if (typeof_arg.castTag(.helper_call)) |some| { | |
| 96 | if (std.mem.eql(u8, some.data.name, "cast")) { | |
| 97 | break :ret some.data.args[0]; | |
| 98 | } | |
| 99 | } | |
| 100 | if (typeof_arg.castTag(.std_mem_zeroinit)) |some| break :ret some.data.lhs; | |
| 101 | if (typeof_arg.castTag(.std_mem_zeroes)) |some| break :ret some.data; | |
| 102 | break :ret try ZigTag.typeof.create(mt.t.arena, typeof_arg); | |
| 103 | }; | |
| 104 | ||
| 105 | const return_expr = try ZigTag.@"return".create(mt.t.arena, expr); | |
| 106 | try block_scope.statements.append(mt.t.gpa, return_expr); | |
| 107 | ||
| 108 | const fn_decl = try ZigTag.pub_inline_fn.create(mt.t.arena, .{ | |
| 109 | .name = mt.name, | |
| 110 | .params = fn_params, | |
| 111 | .return_type = return_type, | |
| 112 | .body = try block_scope.complete(), | |
| 113 | }); | |
| 114 | try mt.t.addTopLevelDecl(mt.name, fn_decl); | |
| 115 | } | |
| 116 | ||
| 117 | pub fn transMacro(mt: *MacroTranslator) ParseError!void { | |
| 118 | const scope = &mt.t.global_scope.base; | |
| 119 | ||
| 120 | // Check if the macro only uses other blank macros. | |
| 121 | while (true) { | |
| 122 | switch (mt.peek()) { | |
| 123 | .identifier, .extended_identifier => { | |
| 124 | if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) { | |
| 125 | mt.i += 1; | |
| 126 | continue; | |
| 127 | } | |
| 128 | }, | |
| 129 | .eof, .nl => { | |
| 130 | try mt.t.global_scope.blank_macros.put(mt.t.gpa, mt.name, {}); | |
| 131 | const init_node = try ZigTag.string_literal.create(mt.t.arena, "\"\""); | |
| 132 | const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node }); | |
| 133 | try mt.t.addTopLevelDecl(mt.name, var_decl); | |
| 134 | return; | |
| 135 | }, | |
| 136 | else => {}, | |
| 137 | } | |
| 138 | break; | |
| 139 | } | |
| 140 | ||
| 141 | const init_node = try mt.parseCExpr(scope); | |
| 142 | const last = mt.peek(); | |
| 143 | if (last != .eof) | |
| 144 | return mt.fail("unable to translate C expr: unexpected token '{s}'", .{last.symbol()}); | |
| 145 | ||
| 146 | const node = node: { | |
| 147 | const var_decl = try ZigTag.pub_var_simple.create(mt.t.arena, .{ .name = mt.name, .init = init_node }); | |
| 148 | ||
| 149 | if (mt.t.getFnProto(var_decl)) |proto_node| { | |
| 150 | // If a macro aliases a global variable which is a function pointer, we conclude that | |
| 151 | // the macro is intended to represent a function that assumes the function pointer | |
| 152 | // variable is non-null and calls it. | |
| 153 | break :node try mt.createMacroFn(mt.name, var_decl, proto_node); | |
| 154 | } else if (mt.refs_var_decl) { | |
| 155 | const return_type = try ZigTag.typeof.create(mt.t.arena, init_node); | |
| 156 | const return_expr = try ZigTag.@"return".create(mt.t.arena, init_node); | |
| 157 | const block = try ZigTag.block_single.create(mt.t.arena, return_expr); | |
| 158 | ||
| 159 | const loc_str = try mt.t.locStr(mt.macro.loc); | |
| 160 | const value = try std.fmt.allocPrint(mt.t.arena, "\n// {s}: warning: macro '{s}' contains a runtime value, translated to function", .{ loc_str, mt.name }); | |
| 161 | try scope.appendNode(try ZigTag.warning.create(mt.t.arena, value)); | |
| 162 | ||
| 163 | break :node try ZigTag.pub_inline_fn.create(mt.t.arena, .{ | |
| 164 | .name = mt.name, | |
| 165 | .params = &.{}, | |
| 166 | .return_type = return_type, | |
| 167 | .body = block, | |
| 168 | }); | |
| 169 | } | |
| 170 | ||
| 171 | break :node var_decl; | |
| 172 | }; | |
| 173 | ||
| 174 | try mt.t.addTopLevelDecl(mt.name, node); | |
| 175 | } | |
| 176 | ||
| 177 | fn createMacroFn(mt: *MacroTranslator, name: []const u8, ref: ZigNode, proto_alias: *ast.Payload.Func) !ZigNode { | |
| 178 | var fn_params = std.ArrayList(ast.Payload.Param).init(mt.t.gpa); | |
| 179 | defer fn_params.deinit(); | |
| 180 | ||
| 181 | var block_scope = try Scope.Block.init(mt.t, &mt.t.global_scope.base, false); | |
| 182 | defer block_scope.deinit(); | |
| 183 | ||
| 184 | for (proto_alias.data.params) |param| { | |
| 185 | const param_name = try block_scope.makeMangledName(param.name orelse "arg"); | |
| 186 | ||
| 187 | try fn_params.append(.{ | |
| 188 | .name = param_name, | |
| 189 | .type = param.type, | |
| 190 | .is_noalias = param.is_noalias, | |
| 191 | }); | |
| 192 | } | |
| 193 | ||
| 194 | const init = if (ref.castTag(.var_decl)) |v| | |
| 195 | v.data.init.? | |
| 196 | else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v| | |
| 197 | v.data.init | |
| 198 | else | |
| 199 | unreachable; | |
| 200 | ||
| 201 | const unwrap_expr = try ZigTag.unwrap.create(mt.t.arena, init); | |
| 202 | const args = try mt.t.arena.alloc(ZigNode, fn_params.items.len); | |
| 203 | for (fn_params.items, 0..) |param, i| { | |
| 204 | args[i] = try ZigTag.identifier.create(mt.t.arena, param.name.?); | |
| 205 | } | |
| 206 | const call_expr = try ZigTag.call.create(mt.t.arena, .{ | |
| 207 | .lhs = unwrap_expr, | |
| 208 | .args = args, | |
| 209 | }); | |
| 210 | const return_expr = try ZigTag.@"return".create(mt.t.arena, call_expr); | |
| 211 | const block = try ZigTag.block_single.create(mt.t.arena, return_expr); | |
| 212 | ||
| 213 | return ZigTag.pub_inline_fn.create(mt.t.arena, .{ | |
| 214 | .name = name, | |
| 215 | .params = try mt.t.arena.dupe(ast.Payload.Param, fn_params.items), | |
| 216 | .return_type = proto_alias.data.return_type, | |
| 217 | .body = block, | |
| 218 | }); | |
| 219 | } | |
| 220 | ||
| 221 | fn parseCExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 222 | // TODO parseCAssignExpr here | |
| 223 | var block_scope = try Scope.Block.init(mt.t, scope, true); | |
| 224 | defer block_scope.deinit(); | |
| 225 | ||
| 226 | const node = try mt.parseCCondExpr(&block_scope.base); | |
| 227 | if (!mt.eat(.comma)) return node; | |
| 228 | ||
| 229 | var last = node; | |
| 230 | while (true) { | |
| 231 | // suppress result | |
| 232 | const ignore = try ZigTag.discard.create(mt.t.arena, .{ .should_skip = false, .value = last }); | |
| 233 | try block_scope.statements.append(mt.t.gpa, ignore); | |
| 234 | ||
| 235 | last = try mt.parseCCondExpr(&block_scope.base); | |
| 236 | if (!mt.eat(.comma)) break; | |
| 237 | } | |
| 238 | ||
| 239 | const break_node = try ZigTag.break_val.create(mt.t.arena, .{ | |
| 240 | .label = block_scope.label, | |
| 241 | .val = last, | |
| 242 | }); | |
| 243 | try block_scope.statements.append(mt.t.gpa, break_node); | |
| 244 | return try block_scope.complete(); | |
| 245 | } | |
| 246 | ||
| 247 | fn parseCNumLit(mt: *MacroTranslator) ParseError!ZigNode { | |
| 248 | const lit_bytes = mt.tokSlice(); | |
| 249 | mt.i += 1; | |
| 250 | ||
| 251 | var bytes = try std.ArrayListUnmanaged(u8).initCapacity(mt.t.arena, lit_bytes.len + 3); | |
| 252 | ||
| 253 | const prefix = aro.Tree.Token.NumberPrefix.fromString(lit_bytes); | |
| 254 | switch (prefix) { | |
| 255 | .binary => bytes.appendSliceAssumeCapacity("0b"), | |
| 256 | .octal => bytes.appendSliceAssumeCapacity("0o"), | |
| 257 | .hex => bytes.appendSliceAssumeCapacity("0x"), | |
| 258 | .decimal => {}, | |
| 259 | } | |
| 260 | ||
| 261 | const after_prefix = lit_bytes[prefix.stringLen()..]; | |
| 262 | const after_int = for (after_prefix, 0..) |c, i| switch (c) { | |
| 263 | '.' => { | |
| 264 | if (i == 0) { | |
| 265 | bytes.appendAssumeCapacity('0'); | |
| 266 | } | |
| 267 | break after_prefix[i..]; | |
| 268 | }, | |
| 269 | 'e', 'E' => { | |
| 270 | if (prefix != .hex) break after_prefix[i..]; | |
| 271 | bytes.appendAssumeCapacity(c); | |
| 272 | }, | |
| 273 | 'p', 'P' => break after_prefix[i..], | |
| 274 | '0'...'9', 'a'...'d', 'A'...'D', 'f', 'F' => { | |
| 275 | if (!prefix.digitAllowed(c)) break after_prefix[i..]; | |
| 276 | bytes.appendAssumeCapacity(c); | |
| 277 | }, | |
| 278 | '\'' => { | |
| 279 | bytes.appendAssumeCapacity('_'); | |
| 280 | }, | |
| 281 | else => break after_prefix[i..], | |
| 282 | } else ""; | |
| 283 | ||
| 284 | const after_frac = frac: { | |
| 285 | if (after_int.len == 0 or after_int[0] != '.') break :frac after_int; | |
| 286 | bytes.appendAssumeCapacity('.'); | |
| 287 | for (after_int[1..], 1..) |c, i| { | |
| 288 | if (c == '\'') { | |
| 289 | bytes.appendAssumeCapacity('_'); | |
| 290 | continue; | |
| 291 | } | |
| 292 | if (!prefix.digitAllowed(c)) break :frac after_int[i..]; | |
| 293 | bytes.appendAssumeCapacity(c); | |
| 294 | } | |
| 295 | break :frac ""; | |
| 296 | }; | |
| 297 | ||
| 298 | const suffix_str = exponent: { | |
| 299 | if (after_frac.len == 0) break :exponent after_frac; | |
| 300 | switch (after_frac[0]) { | |
| 301 | 'e', 'E' => {}, | |
| 302 | 'p', 'P' => if (prefix != .hex) break :exponent after_frac, | |
| 303 | else => break :exponent after_frac, | |
| 304 | } | |
| 305 | bytes.appendAssumeCapacity(after_frac[0]); | |
| 306 | for (after_frac[1..], 1..) |c, i| switch (c) { | |
| 307 | '+', '-', '0'...'9' => { | |
| 308 | bytes.appendAssumeCapacity(c); | |
| 309 | }, | |
| 310 | '\'' => { | |
| 311 | bytes.appendAssumeCapacity('_'); | |
| 312 | }, | |
| 313 | else => break :exponent after_frac[i..], | |
| 314 | }; | |
| 315 | break :exponent ""; | |
| 316 | }; | |
| 317 | ||
| 318 | const is_float = after_int.len != suffix_str.len; | |
| 319 | const suffix = aro.Tree.Token.NumberSuffix.fromString(suffix_str, if (is_float) .float else .int) orelse { | |
| 320 | try mt.fail("invalid number suffix: '{s}'", .{suffix_str}); | |
| 321 | return error.ParseError; | |
| 322 | }; | |
| 323 | if (suffix.isImaginary()) { | |
| 324 | try mt.fail("TODO: imaginary literals", .{}); | |
| 325 | return error.ParseError; | |
| 326 | } | |
| 327 | if (suffix.isBitInt()) { | |
| 328 | try mt.fail("TODO: _BitInt literals", .{}); | |
| 329 | return error.ParseError; | |
| 330 | } | |
| 331 | ||
| 332 | if (is_float) { | |
| 333 | const type_node = try ZigTag.type.create(mt.t.arena, switch (suffix) { | |
| 334 | .F16 => "f16", | |
| 335 | .F => "f32", | |
| 336 | .None => "f64", | |
| 337 | .L => "c_longdouble", | |
| 338 | .W => "f80", | |
| 339 | .Q, .F128 => "f128", | |
| 340 | else => unreachable, | |
| 341 | }); | |
| 342 | const rhs = try ZigTag.float_literal.create(mt.t.arena, bytes.items); | |
| 343 | return ZigTag.as.create(mt.t.arena, .{ .lhs = type_node, .rhs = rhs }); | |
| 344 | } else { | |
| 345 | const type_node = try ZigTag.type.create(mt.t.arena, switch (suffix) { | |
| 346 | .None => "c_int", | |
| 347 | .U => "c_uint", | |
| 348 | .L => "c_long", | |
| 349 | .UL => "c_ulong", | |
| 350 | .LL => "c_longlong", | |
| 351 | .ULL => "c_ulonglong", | |
| 352 | else => unreachable, | |
| 353 | }); | |
| 354 | const value = std.fmt.parseInt(i128, bytes.items, 0) catch math.maxInt(i128); | |
| 355 | ||
| 356 | // make the output less noisy by skipping promoteIntLiteral where | |
| 357 | // it's guaranteed to not be required because of C standard type constraints | |
| 358 | const guaranteed_to_fit = switch (suffix) { | |
| 359 | .None => math.cast(i16, value) != null, | |
| 360 | .U => math.cast(u16, value) != null, | |
| 361 | .L => math.cast(i32, value) != null, | |
| 362 | .UL => math.cast(u32, value) != null, | |
| 363 | .LL => math.cast(i64, value) != null, | |
| 364 | .ULL => math.cast(u64, value) != null, | |
| 365 | else => unreachable, | |
| 366 | }; | |
| 367 | ||
| 368 | const literal_node = try ZigTag.integer_literal.create(mt.t.arena, bytes.items); | |
| 369 | if (guaranteed_to_fit) { | |
| 370 | return ZigTag.as.create(mt.t.arena, .{ .lhs = type_node, .rhs = literal_node }); | |
| 371 | } else { | |
| 372 | return mt.t.createHelperCallNode(.promoteIntLiteral, &.{ type_node, literal_node, try ZigTag.enum_literal.create(mt.t.arena, @tagName(prefix)) }); | |
| 373 | } | |
| 374 | } | |
| 375 | } | |
| 376 | ||
| 377 | fn zigifyEscapeSequences(mt: *MacroTranslator, slice: []const u8) ![]const u8 { | |
| 378 | var source = slice; | |
| 379 | for (source, 0..) |c, i| { | |
| 380 | if (c == '\"' or c == '\'') { | |
| 381 | source = source[i..]; | |
| 382 | break; | |
| 383 | } | |
| 384 | } | |
| 385 | for (source) |c| { | |
| 386 | if (c == '\\' or c == '\t') { | |
| 387 | break; | |
| 388 | } | |
| 389 | } else return source; | |
| 390 | const bytes = try mt.t.arena.alloc(u8, source.len * 2); | |
| 391 | var state: enum { | |
| 392 | start, | |
| 393 | escape, | |
| 394 | hex, | |
| 395 | octal, | |
| 396 | } = .start; | |
| 397 | var i: usize = 0; | |
| 398 | var count: u8 = 0; | |
| 399 | var num: u8 = 0; | |
| 400 | for (source) |c| { | |
| 401 | switch (state) { | |
| 402 | .escape => { | |
| 403 | switch (c) { | |
| 404 | 'n', 'r', 't', '\\', '\'', '\"' => { | |
| 405 | bytes[i] = c; | |
| 406 | }, | |
| 407 | '0'...'7' => { | |
| 408 | count += 1; | |
| 409 | num += c - '0'; | |
| 410 | state = .octal; | |
| 411 | bytes[i] = 'x'; | |
| 412 | }, | |
| 413 | 'x' => { | |
| 414 | state = .hex; | |
| 415 | bytes[i] = 'x'; | |
| 416 | }, | |
| 417 | 'a' => { | |
| 418 | bytes[i] = 'x'; | |
| 419 | i += 1; | |
| 420 | bytes[i] = '0'; | |
| 421 | i += 1; | |
| 422 | bytes[i] = '7'; | |
| 423 | }, | |
| 424 | 'b' => { | |
| 425 | bytes[i] = 'x'; | |
| 426 | i += 1; | |
| 427 | bytes[i] = '0'; | |
| 428 | i += 1; | |
| 429 | bytes[i] = '8'; | |
| 430 | }, | |
| 431 | 'f' => { | |
| 432 | bytes[i] = 'x'; | |
| 433 | i += 1; | |
| 434 | bytes[i] = '0'; | |
| 435 | i += 1; | |
| 436 | bytes[i] = 'C'; | |
| 437 | }, | |
| 438 | 'v' => { | |
| 439 | bytes[i] = 'x'; | |
| 440 | i += 1; | |
| 441 | bytes[i] = '0'; | |
| 442 | i += 1; | |
| 443 | bytes[i] = 'B'; | |
| 444 | }, | |
| 445 | '?' => { | |
| 446 | i -= 1; | |
| 447 | bytes[i] = '?'; | |
| 448 | }, | |
| 449 | 'u', 'U' => { | |
| 450 | try mt.fail("macro tokenizing failed: TODO unicode escape sequences", .{}); | |
| 451 | return error.ParseError; | |
| 452 | }, | |
| 453 | else => { | |
| 454 | try mt.fail("macro tokenizing failed: unknown escape sequence", .{}); | |
| 455 | return error.ParseError; | |
| 456 | }, | |
| 457 | } | |
| 458 | i += 1; | |
| 459 | if (state == .escape) | |
| 460 | state = .start; | |
| 461 | }, | |
| 462 | .start => { | |
| 463 | if (c == '\t') { | |
| 464 | bytes[i] = '\\'; | |
| 465 | i += 1; | |
| 466 | bytes[i] = 't'; | |
| 467 | i += 1; | |
| 468 | continue; | |
| 469 | } | |
| 470 | if (c == '\\') { | |
| 471 | state = .escape; | |
| 472 | } | |
| 473 | bytes[i] = c; | |
| 474 | i += 1; | |
| 475 | }, | |
| 476 | .hex => { | |
| 477 | switch (c) { | |
| 478 | '0'...'9' => { | |
| 479 | num = std.math.mul(u8, num, 16) catch { | |
| 480 | try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); | |
| 481 | return error.ParseError; | |
| 482 | }; | |
| 483 | num += c - '0'; | |
| 484 | }, | |
| 485 | 'a'...'f' => { | |
| 486 | num = std.math.mul(u8, num, 16) catch { | |
| 487 | try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); | |
| 488 | return error.ParseError; | |
| 489 | }; | |
| 490 | num += c - 'a' + 10; | |
| 491 | }, | |
| 492 | 'A'...'F' => { | |
| 493 | num = std.math.mul(u8, num, 16) catch { | |
| 494 | try mt.fail("macro tokenizing failed: hex literal overflowed", .{}); | |
| 495 | return error.ParseError; | |
| 496 | }; | |
| 497 | num += c - 'A' + 10; | |
| 498 | }, | |
| 499 | else => { | |
| 500 | i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); | |
| 501 | num = 0; | |
| 502 | if (c == '\\') | |
| 503 | state = .escape | |
| 504 | else | |
| 505 | state = .start; | |
| 506 | bytes[i] = c; | |
| 507 | i += 1; | |
| 508 | }, | |
| 509 | } | |
| 510 | }, | |
| 511 | .octal => { | |
| 512 | const accept_digit = switch (c) { | |
| 513 | // The maximum length of a octal literal is 3 digits | |
| 514 | '0'...'7' => count < 3, | |
| 515 | else => false, | |
| 516 | }; | |
| 517 | ||
| 518 | if (accept_digit) { | |
| 519 | count += 1; | |
| 520 | num = std.math.mul(u8, num, 8) catch { | |
| 521 | try mt.fail("macro tokenizing failed: octal literal overflowed", .{}); | |
| 522 | return error.ParseError; | |
| 523 | }; | |
| 524 | num += c - '0'; | |
| 525 | } else { | |
| 526 | i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); | |
| 527 | num = 0; | |
| 528 | count = 0; | |
| 529 | if (c == '\\') | |
| 530 | state = .escape | |
| 531 | else | |
| 532 | state = .start; | |
| 533 | bytes[i] = c; | |
| 534 | i += 1; | |
| 535 | } | |
| 536 | }, | |
| 537 | } | |
| 538 | } | |
| 539 | if (state == .hex or state == .octal) { | |
| 540 | i += std.fmt.printInt(bytes[i..], num, 16, .lower, .{ .fill = '0', .width = 2 }); | |
| 541 | } | |
| 542 | ||
| 543 | return bytes[0..i]; | |
| 544 | } | |
| 545 | ||
| 546 | /// non-ASCII characters (mt > 127) are also treated as non-printable by fmtSliceEscapeLower. | |
| 547 | /// If a C string literal or char literal in a macro is not valid UTF-8, we need to escape | |
| 548 | /// non-ASCII characters so that the Zig source we output will itself be UTF-8. | |
| 549 | fn escapeUnprintables(mt: *MacroTranslator) ![]const u8 { | |
| 550 | const slice = mt.tokSlice(); | |
| 551 | mt.i += 1; | |
| 552 | ||
| 553 | const zigified = try mt.zigifyEscapeSequences(slice); | |
| 554 | if (std.unicode.utf8ValidateSlice(zigified)) return zigified; | |
| 555 | ||
| 556 | const formatter = std.ascii.hexEscape(zigified, .lower); | |
| 557 | const encoded_size = @as(usize, @intCast(std.fmt.count("{f}", .{formatter}))); | |
| 558 | const output = try mt.t.arena.alloc(u8, encoded_size); | |
| 559 | return std.fmt.bufPrint(output, "{f}", .{formatter}) catch |err| switch (err) { | |
| 560 | error.NoSpaceLeft => unreachable, | |
| 561 | else => |e| return e, | |
| 562 | }; | |
| 563 | } | |
| 564 | ||
| 565 | fn parseCPrimaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 566 | const tok = mt.peek(); | |
| 567 | switch (tok) { | |
| 568 | .char_literal, | |
| 569 | .char_literal_utf_8, | |
| 570 | .char_literal_utf_16, | |
| 571 | .char_literal_utf_32, | |
| 572 | .char_literal_wide, | |
| 573 | => { | |
| 574 | const slice = mt.tokSlice(); | |
| 575 | if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) { | |
| 576 | return ZigTag.char_literal.create(mt.t.arena, try mt.escapeUnprintables()); | |
| 577 | } else { | |
| 578 | mt.i += 1; | |
| 579 | ||
| 580 | const str = try std.fmt.allocPrint(mt.t.arena, "0x{x}", .{slice[1 .. slice.len - 1]}); | |
| 581 | return ZigTag.integer_literal.create(mt.t.arena, str); | |
| 582 | } | |
| 583 | }, | |
| 584 | .string_literal, | |
| 585 | .string_literal_utf_16, | |
| 586 | .string_literal_utf_8, | |
| 587 | .string_literal_utf_32, | |
| 588 | .string_literal_wide, | |
| 589 | => return ZigTag.string_literal.create(mt.t.arena, try mt.escapeUnprintables()), | |
| 590 | .pp_num => return mt.parseCNumLit(), | |
| 591 | .l_paren => { | |
| 592 | mt.i += 1; | |
| 593 | const inner_node = try mt.parseCExpr(scope); | |
| 594 | ||
| 595 | try mt.expect(.r_paren); | |
| 596 | return inner_node; | |
| 597 | }, | |
| 598 | .macro_param, .macro_param_no_expand => { | |
| 599 | const param = mt.macro.params[mt.tokens[mt.i].end]; | |
| 600 | mt.i += 1; | |
| 601 | ||
| 602 | const mangled_name = scope.getAlias(param) orelse param; | |
| 603 | return try ZigTag.identifier.create(mt.t.arena, mangled_name); | |
| 604 | }, | |
| 605 | .identifier, .extended_identifier => { | |
| 606 | const slice = mt.tokSlice(); | |
| 607 | mt.i += 1; | |
| 608 | ||
| 609 | const mangled_name = scope.getAlias(slice) orelse slice; | |
| 610 | if (Translator.builtin_typedef_map.get(mangled_name)) |ty| { | |
| 611 | return ZigTag.type.create(mt.t.arena, ty); | |
| 612 | } | |
| 613 | if (builtins.map.get(mangled_name)) |builtin| { | |
| 614 | const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin"); | |
| 615 | return ZigTag.field_access.create(mt.t.arena, .{ | |
| 616 | .lhs = builtin_identifier, | |
| 617 | .field_name = builtin.name, | |
| 618 | }); | |
| 619 | } | |
| 620 | ||
| 621 | const identifier = try ZigTag.identifier.create(mt.t.arena, mangled_name); | |
| 622 | scope.skipVariableDiscard(mangled_name); | |
| 623 | refs_var: { | |
| 624 | const ident_node = mt.t.global_scope.sym_table.get(slice) orelse break :refs_var; | |
| 625 | const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var; | |
| 626 | if (!var_decl_node.data.is_const) mt.refs_var_decl = true; | |
| 627 | } | |
| 628 | return identifier; | |
| 629 | }, | |
| 630 | else => {}, | |
| 631 | } | |
| 632 | ||
| 633 | // for handling type macros (EVIL) | |
| 634 | // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList? | |
| 635 | if (try mt.parseCTypeName(scope, true)) |type_name| { | |
| 636 | return type_name; | |
| 637 | } | |
| 638 | ||
| 639 | try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()}); | |
| 640 | return error.ParseError; | |
| 641 | } | |
| 642 | ||
| 643 | fn macroIntFromBool(mt: *MacroTranslator, node: ZigNode) !ZigNode { | |
| 644 | if (!node.isBoolRes()) return node; | |
| 645 | ||
| 646 | return ZigTag.int_from_bool.create(mt.t.arena, node); | |
| 647 | } | |
| 648 | ||
| 649 | fn macroIntToBool(mt: *MacroTranslator, node: ZigNode) !ZigNode { | |
| 650 | if (node.isBoolRes()) return node; | |
| 651 | ||
| 652 | if (node.tag() == .string_literal) { | |
| 653 | // @intFromPtr(node) != 0 | |
| 654 | const int_from_ptr = try ZigTag.int_from_ptr.create(mt.t.arena, node); | |
| 655 | return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); | |
| 656 | } | |
| 657 | // node != 0 | |
| 658 | return ZigTag.not_equal.create(mt.t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); | |
| 659 | } | |
| 660 | ||
| 661 | fn parseCCondExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 662 | const node = try mt.parseCOrExpr(scope); | |
| 663 | if (!mt.eat(.question_mark)) return node; | |
| 664 | ||
| 665 | const then_body = try mt.parseCOrExpr(scope); | |
| 666 | try mt.expect(.colon); | |
| 667 | const else_body = try mt.parseCCondExpr(scope); | |
| 668 | return ZigTag.@"if".create(mt.t.arena, .{ .cond = node, .then = then_body, .@"else" = else_body }); | |
| 669 | } | |
| 670 | ||
| 671 | fn parseCOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 672 | var node = try mt.parseCAndExpr(scope); | |
| 673 | while (mt.eat(.pipe_pipe)) { | |
| 674 | const lhs = try mt.macroIntToBool(node); | |
| 675 | const rhs = try mt.macroIntToBool(try mt.parseCAndExpr(scope)); | |
| 676 | node = try ZigTag.@"or".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 677 | } | |
| 678 | return node; | |
| 679 | } | |
| 680 | ||
| 681 | fn parseCAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 682 | var node = try mt.parseCBitOrExpr(scope); | |
| 683 | while (mt.eat(.ampersand_ampersand)) { | |
| 684 | const lhs = try mt.macroIntToBool(node); | |
| 685 | const rhs = try mt.macroIntToBool(try mt.parseCBitOrExpr(scope)); | |
| 686 | node = try ZigTag.@"and".create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 687 | } | |
| 688 | return node; | |
| 689 | } | |
| 690 | ||
| 691 | fn parseCBitOrExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 692 | var node = try mt.parseCBitXorExpr(scope); | |
| 693 | while (mt.eat(.pipe)) { | |
| 694 | const lhs = try mt.macroIntFromBool(node); | |
| 695 | const rhs = try mt.macroIntFromBool(try mt.parseCBitXorExpr(scope)); | |
| 696 | node = try ZigTag.bit_or.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 697 | } | |
| 698 | return node; | |
| 699 | } | |
| 700 | ||
| 701 | fn parseCBitXorExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 702 | var node = try mt.parseCBitAndExpr(scope); | |
| 703 | while (mt.eat(.caret)) { | |
| 704 | const lhs = try mt.macroIntFromBool(node); | |
| 705 | const rhs = try mt.macroIntFromBool(try mt.parseCBitAndExpr(scope)); | |
| 706 | node = try ZigTag.bit_xor.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 707 | } | |
| 708 | return node; | |
| 709 | } | |
| 710 | ||
| 711 | fn parseCBitAndExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 712 | var node = try mt.parseCEqExpr(scope); | |
| 713 | while (mt.eat(.ampersand)) { | |
| 714 | const lhs = try mt.macroIntFromBool(node); | |
| 715 | const rhs = try mt.macroIntFromBool(try mt.parseCEqExpr(scope)); | |
| 716 | node = try ZigTag.bit_and.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 717 | } | |
| 718 | return node; | |
| 719 | } | |
| 720 | ||
| 721 | fn parseCEqExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 722 | var node = try mt.parseCRelExpr(scope); | |
| 723 | while (true) { | |
| 724 | switch (mt.peek()) { | |
| 725 | .bang_equal => { | |
| 726 | mt.i += 1; | |
| 727 | const lhs = try mt.macroIntFromBool(node); | |
| 728 | const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope)); | |
| 729 | node = try ZigTag.not_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 730 | }, | |
| 731 | .equal_equal => { | |
| 732 | mt.i += 1; | |
| 733 | const lhs = try mt.macroIntFromBool(node); | |
| 734 | const rhs = try mt.macroIntFromBool(try mt.parseCRelExpr(scope)); | |
| 735 | node = try ZigTag.equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 736 | }, | |
| 737 | else => return node, | |
| 738 | } | |
| 739 | } | |
| 740 | } | |
| 741 | ||
| 742 | fn parseCRelExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 743 | var node = try mt.parseCShiftExpr(scope); | |
| 744 | while (true) { | |
| 745 | switch (mt.peek()) { | |
| 746 | .angle_bracket_right => { | |
| 747 | mt.i += 1; | |
| 748 | const lhs = try mt.macroIntFromBool(node); | |
| 749 | const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); | |
| 750 | node = try ZigTag.greater_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 751 | }, | |
| 752 | .angle_bracket_right_equal => { | |
| 753 | mt.i += 1; | |
| 754 | const lhs = try mt.macroIntFromBool(node); | |
| 755 | const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); | |
| 756 | node = try ZigTag.greater_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 757 | }, | |
| 758 | .angle_bracket_left => { | |
| 759 | mt.i += 1; | |
| 760 | const lhs = try mt.macroIntFromBool(node); | |
| 761 | const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); | |
| 762 | node = try ZigTag.less_than.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 763 | }, | |
| 764 | .angle_bracket_left_equal => { | |
| 765 | mt.i += 1; | |
| 766 | const lhs = try mt.macroIntFromBool(node); | |
| 767 | const rhs = try mt.macroIntFromBool(try mt.parseCShiftExpr(scope)); | |
| 768 | node = try ZigTag.less_than_equal.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 769 | }, | |
| 770 | else => return node, | |
| 771 | } | |
| 772 | } | |
| 773 | } | |
| 774 | ||
| 775 | fn parseCShiftExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 776 | var node = try mt.parseCAddSubExpr(scope); | |
| 777 | while (true) { | |
| 778 | switch (mt.peek()) { | |
| 779 | .angle_bracket_angle_bracket_left => { | |
| 780 | mt.i += 1; | |
| 781 | const lhs = try mt.macroIntFromBool(node); | |
| 782 | const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope)); | |
| 783 | node = try ZigTag.shl.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 784 | }, | |
| 785 | .angle_bracket_angle_bracket_right => { | |
| 786 | mt.i += 1; | |
| 787 | const lhs = try mt.macroIntFromBool(node); | |
| 788 | const rhs = try mt.macroIntFromBool(try mt.parseCAddSubExpr(scope)); | |
| 789 | node = try ZigTag.shr.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 790 | }, | |
| 791 | else => return node, | |
| 792 | } | |
| 793 | } | |
| 794 | } | |
| 795 | ||
| 796 | fn parseCAddSubExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 797 | var node = try mt.parseCMulExpr(scope); | |
| 798 | while (true) { | |
| 799 | switch (mt.peek()) { | |
| 800 | .plus => { | |
| 801 | mt.i += 1; | |
| 802 | const lhs = try mt.macroIntFromBool(node); | |
| 803 | const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope)); | |
| 804 | node = try ZigTag.add.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 805 | }, | |
| 806 | .minus => { | |
| 807 | mt.i += 1; | |
| 808 | const lhs = try mt.macroIntFromBool(node); | |
| 809 | const rhs = try mt.macroIntFromBool(try mt.parseCMulExpr(scope)); | |
| 810 | node = try ZigTag.sub.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 811 | }, | |
| 812 | else => return node, | |
| 813 | } | |
| 814 | } | |
| 815 | } | |
| 816 | ||
| 817 | fn parseCMulExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 818 | var node = try mt.parseCCastExpr(scope); | |
| 819 | while (true) { | |
| 820 | switch (mt.peek()) { | |
| 821 | .asterisk => { | |
| 822 | mt.i += 1; | |
| 823 | const lhs = try mt.macroIntFromBool(node); | |
| 824 | const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 825 | node = try ZigTag.mul.create(mt.t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 826 | }, | |
| 827 | .slash => { | |
| 828 | mt.i += 1; | |
| 829 | const lhs = try mt.macroIntFromBool(node); | |
| 830 | const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 831 | node = try mt.t.createHelperCallNode(.div, &.{ lhs, rhs }); | |
| 832 | }, | |
| 833 | .percent => { | |
| 834 | mt.i += 1; | |
| 835 | const lhs = try mt.macroIntFromBool(node); | |
| 836 | const rhs = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 837 | node = try mt.t.createHelperCallNode(.rem, &.{ lhs, rhs }); | |
| 838 | }, | |
| 839 | else => return node, | |
| 840 | } | |
| 841 | } | |
| 842 | } | |
| 843 | ||
| 844 | fn parseCCastExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 845 | if (mt.eat(.l_paren)) { | |
| 846 | if (try mt.parseCTypeName(scope, true)) |type_name| { | |
| 847 | while (true) { | |
| 848 | const next_tok = mt.peek(); | |
| 849 | if (next_tok == .r_paren) { | |
| 850 | mt.i += 1; | |
| 851 | break; | |
| 852 | } | |
| 853 | // Skip trailing blank defined before the RParen. | |
| 854 | if ((next_tok == .identifier or next_tok == .extended_identifier) and | |
| 855 | mt.t.global_scope.blank_macros.contains(mt.tokSlice())) | |
| 856 | { | |
| 857 | mt.i += 1; | |
| 858 | continue; | |
| 859 | } | |
| 860 | ||
| 861 | try mt.fail( | |
| 862 | "unable to translate C expr: expected ')' instead got '{s}'", | |
| 863 | .{next_tok.symbol()}, | |
| 864 | ); | |
| 865 | return error.ParseError; | |
| 866 | } | |
| 867 | if (mt.peek() == .l_brace) { | |
| 868 | // initializer list | |
| 869 | return mt.parseCPostfixExpr(scope, type_name); | |
| 870 | } | |
| 871 | const node_to_cast = try mt.parseCCastExpr(scope); | |
| 872 | return mt.t.createHelperCallNode(.cast, &.{ type_name, node_to_cast }); | |
| 873 | } | |
| 874 | mt.i -= 1; // l_paren | |
| 875 | } | |
| 876 | return mt.parseCUnaryExpr(scope); | |
| 877 | } | |
| 878 | ||
| 879 | // allow_fail is set when unsure if we are parsing a type-name | |
| 880 | fn parseCTypeName(mt: *MacroTranslator, scope: *Scope, allow_fail: bool) ParseError!?ZigNode { | |
| 881 | if (try mt.parseCSpecifierQualifierList(scope, allow_fail)) |node| { | |
| 882 | return try mt.parseCAbstractDeclarator(node); | |
| 883 | } | |
| 884 | return null; | |
| 885 | } | |
| 886 | ||
| 887 | fn parseCSpecifierQualifierList(mt: *MacroTranslator, scope: *Scope, allow_fail: bool) ParseError!?ZigNode { | |
| 888 | const tok = mt.peek(); | |
| 889 | switch (tok) { | |
| 890 | .macro_param, .macro_param_no_expand => { | |
| 891 | const param = mt.macro.params[mt.tokens[mt.i].end]; | |
| 892 | ||
| 893 | // Assume that this is only a cast if the next token is ')' | |
| 894 | // e.g. param)identifier | |
| 895 | if (allow_fail and (mt.macro.tokens.len < mt.i + 3 or | |
| 896 | mt.macro.tokens[mt.i + 1].id != .r_paren or | |
| 897 | mt.macro.tokens[mt.i + 2].id != .identifier)) | |
| 898 | return null; | |
| 899 | ||
| 900 | mt.i += 1; | |
| 901 | const mangled_name = scope.getAlias(param) orelse param; | |
| 902 | return try ZigTag.identifier.create(mt.t.arena, mangled_name); | |
| 903 | }, | |
| 904 | .identifier, .extended_identifier => { | |
| 905 | const slice = mt.tokSlice(); | |
| 906 | const mangled_name = scope.getAlias(slice) orelse slice; | |
| 907 | ||
| 908 | if (mt.t.global_scope.blank_macros.contains(slice)) { | |
| 909 | mt.i += 1; | |
| 910 | return try mt.parseCSpecifierQualifierList(scope, allow_fail); | |
| 911 | } | |
| 912 | ||
| 913 | if (!allow_fail or mt.t.typedefs.contains(mangled_name)) { | |
| 914 | mt.i += 1; | |
| 915 | if (Translator.builtin_typedef_map.get(mangled_name)) |ty| { | |
| 916 | return try ZigTag.type.create(mt.t.arena, ty); | |
| 917 | } | |
| 918 | if (builtins.map.get(mangled_name)) |builtin| { | |
| 919 | const builtin_identifier = try ZigTag.identifier.create(mt.t.arena, "__builtin"); | |
| 920 | return try ZigTag.field_access.create(mt.t.arena, .{ | |
| 921 | .lhs = builtin_identifier, | |
| 922 | .field_name = builtin.name, | |
| 923 | }); | |
| 924 | } | |
| 925 | ||
| 926 | return try ZigTag.identifier.create(mt.t.arena, mangled_name); | |
| 927 | } | |
| 928 | }, | |
| 929 | .keyword_void => { | |
| 930 | mt.i += 1; | |
| 931 | return try ZigTag.type.create(mt.t.arena, "anyopaque"); | |
| 932 | }, | |
| 933 | .keyword_bool => { | |
| 934 | mt.i += 1; | |
| 935 | return try ZigTag.type.create(mt.t.arena, "bool"); | |
| 936 | }, | |
| 937 | .keyword_char, | |
| 938 | .keyword_int, | |
| 939 | .keyword_short, | |
| 940 | .keyword_long, | |
| 941 | .keyword_float, | |
| 942 | .keyword_double, | |
| 943 | .keyword_signed, | |
| 944 | .keyword_unsigned, | |
| 945 | .keyword_complex, | |
| 946 | => return try mt.parseCNumericType(), | |
| 947 | .keyword_enum, .keyword_struct, .keyword_union => { | |
| 948 | const tag_name = mt.tokSlice(); | |
| 949 | mt.i += 1; | |
| 950 | ||
| 951 | // struct Foo will be declared as struct_Foo by transRecordDecl | |
| 952 | const identifier = mt.tokSlice(); | |
| 953 | try mt.expect(.identifier); | |
| 954 | ||
| 955 | const name = try std.fmt.allocPrint(mt.t.arena, "{s}_{s}", .{ tag_name, identifier }); | |
| 956 | return try ZigTag.identifier.create(mt.t.arena, name); | |
| 957 | }, | |
| 958 | else => {}, | |
| 959 | } | |
| 960 | ||
| 961 | if (allow_fail) return null; | |
| 962 | ||
| 963 | try mt.fail("unable to translate C expr: unexpected token '{s}'", .{tok.symbol()}); | |
| 964 | return error.ParseError; | |
| 965 | } | |
| 966 | ||
| 967 | fn parseCNumericType(mt: *MacroTranslator) ParseError!ZigNode { | |
| 968 | const KwCounter = struct { | |
| 969 | double: u8 = 0, | |
| 970 | long: u8 = 0, | |
| 971 | int: u8 = 0, | |
| 972 | float: u8 = 0, | |
| 973 | short: u8 = 0, | |
| 974 | char: u8 = 0, | |
| 975 | unsigned: u8 = 0, | |
| 976 | signed: u8 = 0, | |
| 977 | complex: u8 = 0, | |
| 978 | ||
| 979 | fn eql(self: @This(), other: @This()) bool { | |
| 980 | return std.meta.eql(self, other); | |
| 981 | } | |
| 982 | }; | |
| 983 | ||
| 984 | // Yes, these can be in *any* order | |
| 985 | // This still doesn't cover cases where for example volatile is intermixed | |
| 986 | ||
| 987 | var kw = KwCounter{}; | |
| 988 | // prevent overflow | |
| 989 | var i: u8 = 0; | |
| 990 | while (i < math.maxInt(u8)) : (i += 1) { | |
| 991 | switch (mt.peek()) { | |
| 992 | .keyword_double => kw.double += 1, | |
| 993 | .keyword_long => kw.long += 1, | |
| 994 | .keyword_int => kw.int += 1, | |
| 995 | .keyword_float => kw.float += 1, | |
| 996 | .keyword_short => kw.short += 1, | |
| 997 | .keyword_char => kw.char += 1, | |
| 998 | .keyword_unsigned => kw.unsigned += 1, | |
| 999 | .keyword_signed => kw.signed += 1, | |
| 1000 | .keyword_complex => kw.complex += 1, | |
| 1001 | else => break, | |
| 1002 | } | |
| 1003 | mt.i += 1; | |
| 1004 | } | |
| 1005 | ||
| 1006 | if (kw.eql(.{ .int = 1 }) or kw.eql(.{ .signed = 1 }) or kw.eql(.{ .signed = 1, .int = 1 })) | |
| 1007 | return ZigTag.type.create(mt.t.arena, "c_int"); | |
| 1008 | ||
| 1009 | if (kw.eql(.{ .unsigned = 1 }) or kw.eql(.{ .unsigned = 1, .int = 1 })) | |
| 1010 | return ZigTag.type.create(mt.t.arena, "c_uint"); | |
| 1011 | ||
| 1012 | if (kw.eql(.{ .long = 1 }) or kw.eql(.{ .signed = 1, .long = 1 }) or kw.eql(.{ .long = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 1, .int = 1 })) | |
| 1013 | return ZigTag.type.create(mt.t.arena, "c_long"); | |
| 1014 | ||
| 1015 | if (kw.eql(.{ .unsigned = 1, .long = 1 }) or kw.eql(.{ .unsigned = 1, .long = 1, .int = 1 })) | |
| 1016 | return ZigTag.type.create(mt.t.arena, "c_ulong"); | |
| 1017 | ||
| 1018 | if (kw.eql(.{ .long = 2 }) or kw.eql(.{ .signed = 1, .long = 2 }) or kw.eql(.{ .long = 2, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 2, .int = 1 })) | |
| 1019 | return ZigTag.type.create(mt.t.arena, "c_longlong"); | |
| 1020 | ||
| 1021 | if (kw.eql(.{ .unsigned = 1, .long = 2 }) or kw.eql(.{ .unsigned = 1, .long = 2, .int = 1 })) | |
| 1022 | return ZigTag.type.create(mt.t.arena, "c_ulonglong"); | |
| 1023 | ||
| 1024 | if (kw.eql(.{ .signed = 1, .char = 1 })) | |
| 1025 | return ZigTag.type.create(mt.t.arena, "i8"); | |
| 1026 | ||
| 1027 | if (kw.eql(.{ .char = 1 }) or kw.eql(.{ .unsigned = 1, .char = 1 })) | |
| 1028 | return ZigTag.type.create(mt.t.arena, "u8"); | |
| 1029 | ||
| 1030 | if (kw.eql(.{ .short = 1 }) or kw.eql(.{ .signed = 1, .short = 1 }) or kw.eql(.{ .short = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .short = 1, .int = 1 })) | |
| 1031 | return ZigTag.type.create(mt.t.arena, "c_short"); | |
| 1032 | ||
| 1033 | if (kw.eql(.{ .unsigned = 1, .short = 1 }) or kw.eql(.{ .unsigned = 1, .short = 1, .int = 1 })) | |
| 1034 | return ZigTag.type.create(mt.t.arena, "c_ushort"); | |
| 1035 | ||
| 1036 | if (kw.eql(.{ .float = 1 })) | |
| 1037 | return ZigTag.type.create(mt.t.arena, "f32"); | |
| 1038 | ||
| 1039 | if (kw.eql(.{ .double = 1 })) | |
| 1040 | return ZigTag.type.create(mt.t.arena, "f64"); | |
| 1041 | ||
| 1042 | if (kw.eql(.{ .long = 1, .double = 1 })) { | |
| 1043 | try mt.fail("unable to translate: TODO long double", .{}); | |
| 1044 | return error.ParseError; | |
| 1045 | } | |
| 1046 | ||
| 1047 | if (kw.eql(.{ .float = 1, .complex = 1 })) { | |
| 1048 | try mt.fail("unable to translate: TODO _Complex", .{}); | |
| 1049 | return error.ParseError; | |
| 1050 | } | |
| 1051 | ||
| 1052 | if (kw.eql(.{ .double = 1, .complex = 1 })) { | |
| 1053 | try mt.fail("unable to translate: TODO _Complex", .{}); | |
| 1054 | return error.ParseError; | |
| 1055 | } | |
| 1056 | ||
| 1057 | if (kw.eql(.{ .long = 1, .double = 1, .complex = 1 })) { | |
| 1058 | try mt.fail("unable to translate: TODO _Complex", .{}); | |
| 1059 | return error.ParseError; | |
| 1060 | } | |
| 1061 | ||
| 1062 | try mt.fail("unable to translate: invalid numeric type", .{}); | |
| 1063 | return error.ParseError; | |
| 1064 | } | |
| 1065 | ||
| 1066 | fn parseCAbstractDeclarator(mt: *MacroTranslator, node: ZigNode) ParseError!ZigNode { | |
| 1067 | if (mt.eat(.asterisk)) { | |
| 1068 | if (node.castTag(.type)) |some| { | |
| 1069 | if (std.mem.eql(u8, some.data, "anyopaque")) { | |
| 1070 | const ptr = try ZigTag.single_pointer.create(mt.t.arena, .{ | |
| 1071 | .is_const = false, | |
| 1072 | .is_volatile = false, | |
| 1073 | .is_allowzero = false, | |
| 1074 | .elem_type = node, | |
| 1075 | }); | |
| 1076 | return ZigTag.optional_type.create(mt.t.arena, ptr); | |
| 1077 | } | |
| 1078 | } | |
| 1079 | return ZigTag.c_pointer.create(mt.t.arena, .{ | |
| 1080 | .is_const = false, | |
| 1081 | .is_volatile = false, | |
| 1082 | .is_allowzero = false, | |
| 1083 | .elem_type = node, | |
| 1084 | }); | |
| 1085 | } | |
| 1086 | return node; | |
| 1087 | } | |
| 1088 | ||
| 1089 | fn parseCPostfixExpr(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode { | |
| 1090 | var node = try mt.parseCPostfixExprInner(scope, type_name); | |
| 1091 | // In C the preprocessor would handle concatting strings while expanding macros. | |
| 1092 | // This should do approximately the same by concatting any strings and identifiers | |
| 1093 | // after a primary or postfix expression. | |
| 1094 | while (true) { | |
| 1095 | switch (mt.peek()) { | |
| 1096 | .string_literal, | |
| 1097 | .string_literal_utf_16, | |
| 1098 | .string_literal_utf_8, | |
| 1099 | .string_literal_utf_32, | |
| 1100 | .string_literal_wide, | |
| 1101 | => {}, | |
| 1102 | .identifier, .extended_identifier => { | |
| 1103 | if (mt.t.global_scope.blank_macros.contains(mt.tokSlice())) { | |
| 1104 | mt.i += 1; | |
| 1105 | continue; | |
| 1106 | } | |
| 1107 | }, | |
| 1108 | else => break, | |
| 1109 | } | |
| 1110 | const rhs = try mt.parseCPostfixExprInner(scope, type_name); | |
| 1111 | node = try ZigTag.array_cat.create(mt.t.arena, .{ .lhs = node, .rhs = rhs }); | |
| 1112 | } | |
| 1113 | return node; | |
| 1114 | } | |
| 1115 | ||
| 1116 | fn parseCPostfixExprInner(mt: *MacroTranslator, scope: *Scope, type_name: ?ZigNode) ParseError!ZigNode { | |
| 1117 | var node = type_name orelse try mt.parseCPrimaryExpr(scope); | |
| 1118 | while (true) { | |
| 1119 | switch (mt.peek()) { | |
| 1120 | .period => { | |
| 1121 | mt.i += 1; | |
| 1122 | const field_name = mt.tokSlice(); | |
| 1123 | try mt.expect(.identifier); | |
| 1124 | ||
| 1125 | node = try ZigTag.field_access.create(mt.t.arena, .{ .lhs = node, .field_name = field_name }); | |
| 1126 | }, | |
| 1127 | .arrow => { | |
| 1128 | mt.i += 1; | |
| 1129 | const field_name = mt.tokSlice(); | |
| 1130 | try mt.expect(.identifier); | |
| 1131 | ||
| 1132 | const deref = try ZigTag.deref.create(mt.t.arena, node); | |
| 1133 | node = try ZigTag.field_access.create(mt.t.arena, .{ .lhs = deref, .field_name = field_name }); | |
| 1134 | }, | |
| 1135 | .l_bracket => { | |
| 1136 | mt.i += 1; | |
| 1137 | ||
| 1138 | const index_val = try mt.macroIntFromBool(try mt.parseCExpr(scope)); | |
| 1139 | const index = try ZigTag.as.create(mt.t.arena, .{ | |
| 1140 | .lhs = try ZigTag.type.create(mt.t.arena, "usize"), | |
| 1141 | .rhs = try ZigTag.int_cast.create(mt.t.arena, index_val), | |
| 1142 | }); | |
| 1143 | node = try ZigTag.array_access.create(mt.t.arena, .{ .lhs = node, .rhs = index }); | |
| 1144 | try mt.expect(.r_bracket); | |
| 1145 | }, | |
| 1146 | .l_paren => { | |
| 1147 | mt.i += 1; | |
| 1148 | ||
| 1149 | if (mt.eat(.r_paren)) { | |
| 1150 | node = try ZigTag.call.create(mt.t.arena, .{ .lhs = node, .args = &.{} }); | |
| 1151 | } else { | |
| 1152 | var args = std.ArrayList(ZigNode).init(mt.t.gpa); | |
| 1153 | defer args.deinit(); | |
| 1154 | ||
| 1155 | while (true) { | |
| 1156 | const arg = try mt.parseCCondExpr(scope); | |
| 1157 | try args.append(arg); | |
| 1158 | ||
| 1159 | const next_id = mt.peek(); | |
| 1160 | switch (next_id) { | |
| 1161 | .comma => { | |
| 1162 | mt.i += 1; | |
| 1163 | }, | |
| 1164 | .r_paren => { | |
| 1165 | mt.i += 1; | |
| 1166 | break; | |
| 1167 | }, | |
| 1168 | else => { | |
| 1169 | try mt.fail("unable to translate C expr: expected ',' or ')' instead got '{s}'", .{next_id.symbol()}); | |
| 1170 | return error.ParseError; | |
| 1171 | }, | |
| 1172 | } | |
| 1173 | } | |
| 1174 | node = try ZigTag.call.create(mt.t.arena, .{ .lhs = node, .args = try mt.t.arena.dupe(ZigNode, args.items) }); | |
| 1175 | } | |
| 1176 | }, | |
| 1177 | .l_brace => { | |
| 1178 | mt.i += 1; | |
| 1179 | ||
| 1180 | // Check for designated field initializers | |
| 1181 | if (mt.peek() == .period) { | |
| 1182 | var init_vals = std.ArrayList(ast.Payload.ContainerInitDot.Initializer).init(mt.t.gpa); | |
| 1183 | defer init_vals.deinit(); | |
| 1184 | ||
| 1185 | while (true) { | |
| 1186 | try mt.expect(.period); | |
| 1187 | const name = mt.tokSlice(); | |
| 1188 | try mt.expect(.identifier); | |
| 1189 | try mt.expect(.equal); | |
| 1190 | ||
| 1191 | const val = try mt.parseCCondExpr(scope); | |
| 1192 | try init_vals.append(.{ .name = name, .value = val }); | |
| 1193 | ||
| 1194 | const next_id = mt.peek(); | |
| 1195 | switch (next_id) { | |
| 1196 | .comma => { | |
| 1197 | mt.i += 1; | |
| 1198 | }, | |
| 1199 | .r_brace => { | |
| 1200 | mt.i += 1; | |
| 1201 | break; | |
| 1202 | }, | |
| 1203 | else => { | |
| 1204 | try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()}); | |
| 1205 | return error.ParseError; | |
| 1206 | }, | |
| 1207 | } | |
| 1208 | } | |
| 1209 | const tuple_node = try ZigTag.container_init_dot.create(mt.t.arena, try mt.t.arena.dupe(ast.Payload.ContainerInitDot.Initializer, init_vals.items)); | |
| 1210 | node = try ZigTag.std_mem_zeroinit.create(mt.t.arena, .{ .lhs = node, .rhs = tuple_node }); | |
| 1211 | continue; | |
| 1212 | } | |
| 1213 | ||
| 1214 | var init_vals = std.ArrayList(ZigNode).init(mt.t.gpa); | |
| 1215 | defer init_vals.deinit(); | |
| 1216 | ||
| 1217 | while (true) { | |
| 1218 | const val = try mt.parseCCondExpr(scope); | |
| 1219 | try init_vals.append(val); | |
| 1220 | ||
| 1221 | const next_id = mt.peek(); | |
| 1222 | switch (next_id) { | |
| 1223 | .comma => { | |
| 1224 | mt.i += 1; | |
| 1225 | }, | |
| 1226 | .r_brace => { | |
| 1227 | mt.i += 1; | |
| 1228 | break; | |
| 1229 | }, | |
| 1230 | else => { | |
| 1231 | try mt.fail("unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()}); | |
| 1232 | return error.ParseError; | |
| 1233 | }, | |
| 1234 | } | |
| 1235 | } | |
| 1236 | const tuple_node = try ZigTag.tuple.create(mt.t.arena, try mt.t.arena.dupe(ZigNode, init_vals.items)); | |
| 1237 | node = try ZigTag.std_mem_zeroinit.create(mt.t.arena, .{ .lhs = node, .rhs = tuple_node }); | |
| 1238 | }, | |
| 1239 | .plus_plus, .minus_minus => { | |
| 1240 | try mt.fail("TODO postfix inc/dec expr", .{}); | |
| 1241 | return error.ParseError; | |
| 1242 | }, | |
| 1243 | else => return node, | |
| 1244 | } | |
| 1245 | } | |
| 1246 | } | |
| 1247 | ||
| 1248 | fn parseCUnaryExpr(mt: *MacroTranslator, scope: *Scope) ParseError!ZigNode { | |
| 1249 | switch (mt.peek()) { | |
| 1250 | .bang => { | |
| 1251 | mt.i += 1; | |
| 1252 | const operand = try mt.macroIntToBool(try mt.parseCCastExpr(scope)); | |
| 1253 | return ZigTag.not.create(mt.t.arena, operand); | |
| 1254 | }, | |
| 1255 | .minus => { | |
| 1256 | mt.i += 1; | |
| 1257 | const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 1258 | return ZigTag.negate.create(mt.t.arena, operand); | |
| 1259 | }, | |
| 1260 | .plus => { | |
| 1261 | mt.i += 1; | |
| 1262 | return try mt.parseCCastExpr(scope); | |
| 1263 | }, | |
| 1264 | .tilde => { | |
| 1265 | mt.i += 1; | |
| 1266 | const operand = try mt.macroIntFromBool(try mt.parseCCastExpr(scope)); | |
| 1267 | return ZigTag.bit_not.create(mt.t.arena, operand); | |
| 1268 | }, | |
| 1269 | .asterisk => { | |
| 1270 | mt.i += 1; | |
| 1271 | const operand = try mt.parseCCastExpr(scope); | |
| 1272 | return ZigTag.deref.create(mt.t.arena, operand); | |
| 1273 | }, | |
| 1274 | .ampersand => { | |
| 1275 | mt.i += 1; | |
| 1276 | const operand = try mt.parseCCastExpr(scope); | |
| 1277 | return ZigTag.address_of.create(mt.t.arena, operand); | |
| 1278 | }, | |
| 1279 | .keyword_sizeof => { | |
| 1280 | mt.i += 1; | |
| 1281 | const operand = if (mt.eat(.l_paren)) blk: { | |
| 1282 | const inner = (try mt.parseCTypeName(scope, false)).?; | |
| 1283 | try mt.expect(.r_paren); | |
| 1284 | break :blk inner; | |
| 1285 | } else try mt.parseCUnaryExpr(scope); | |
| 1286 | ||
| 1287 | return mt.t.createHelperCallNode(.sizeof, &.{operand}); | |
| 1288 | }, | |
| 1289 | .keyword_alignof => { | |
| 1290 | mt.i += 1; | |
| 1291 | // TODO this won't work if using <stdalign.h>'s | |
| 1292 | // #define alignof _Alignof | |
| 1293 | try mt.expect(.l_paren); | |
| 1294 | const operand = (try mt.parseCTypeName(scope, false)).?; | |
| 1295 | try mt.expect(.r_paren); | |
| 1296 | ||
| 1297 | return ZigTag.alignof.create(mt.t.arena, operand); | |
| 1298 | }, | |
| 1299 | .plus_plus, .minus_minus => { | |
| 1300 | try mt.fail("TODO unary inc/dec expr", .{}); | |
| 1301 | return error.ParseError; | |
| 1302 | }, | |
| 1303 | else => {}, | |
| 1304 | } | |
| 1305 | ||
| 1306 | return try mt.parseCPostfixExpr(scope, null); | |
| 1307 | } |
lib/compiler/translate-c/src/PatternList.zig deleted-288| ... | ... | @@ -1,288 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const mem = std.mem; | |
| 3 | const assert = std.debug.assert; | |
| 4 | ||
| 5 | const aro = @import("aro"); | |
| 6 | const CToken = aro.Tokenizer.Token; | |
| 7 | ||
| 8 | const helpers = @import("helpers.zig"); | |
| 9 | const Translator = @import("Translator.zig"); | |
| 10 | const Error = Translator.Error; | |
| 11 | pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; | |
| 12 | ||
| 13 | const Impl = std.meta.DeclEnum(@import("helpers")); | |
| 14 | const Template = struct { []const u8, Impl }; | |
| 15 | ||
| 16 | /// Templates must be function-like macros | |
| 17 | /// first element is macro source, second element is the name of the function | |
| 18 | /// in __helpers which implements it | |
| 19 | const templates = [_]Template{ | |
| 20 | .{ "f_SUFFIX(X) (X ## f)", .F_SUFFIX }, | |
| 21 | .{ "F_SUFFIX(X) (X ## F)", .F_SUFFIX }, | |
| 22 | ||
| 23 | .{ "u_SUFFIX(X) (X ## u)", .U_SUFFIX }, | |
| 24 | .{ "U_SUFFIX(X) (X ## U)", .U_SUFFIX }, | |
| 25 | ||
| 26 | .{ "l_SUFFIX(X) (X ## l)", .L_SUFFIX }, | |
| 27 | .{ "L_SUFFIX(X) (X ## L)", .L_SUFFIX }, | |
| 28 | ||
| 29 | .{ "ul_SUFFIX(X) (X ## ul)", .UL_SUFFIX }, | |
| 30 | .{ "uL_SUFFIX(X) (X ## uL)", .UL_SUFFIX }, | |
| 31 | .{ "Ul_SUFFIX(X) (X ## Ul)", .UL_SUFFIX }, | |
| 32 | .{ "UL_SUFFIX(X) (X ## UL)", .UL_SUFFIX }, | |
| 33 | ||
| 34 | .{ "ll_SUFFIX(X) (X ## ll)", .LL_SUFFIX }, | |
| 35 | .{ "LL_SUFFIX(X) (X ## LL)", .LL_SUFFIX }, | |
| 36 | ||
| 37 | .{ "ull_SUFFIX(X) (X ## ull)", .ULL_SUFFIX }, | |
| 38 | .{ "uLL_SUFFIX(X) (X ## uLL)", .ULL_SUFFIX }, | |
| 39 | .{ "Ull_SUFFIX(X) (X ## Ull)", .ULL_SUFFIX }, | |
| 40 | .{ "ULL_SUFFIX(X) (X ## ULL)", .ULL_SUFFIX }, | |
| 41 | ||
| 42 | .{ "f_SUFFIX(X) X ## f", .F_SUFFIX }, | |
| 43 | .{ "F_SUFFIX(X) X ## F", .F_SUFFIX }, | |
| 44 | ||
| 45 | .{ "u_SUFFIX(X) X ## u", .U_SUFFIX }, | |
| 46 | .{ "U_SUFFIX(X) X ## U", .U_SUFFIX }, | |
| 47 | ||
| 48 | .{ "l_SUFFIX(X) X ## l", .L_SUFFIX }, | |
| 49 | .{ "L_SUFFIX(X) X ## L", .L_SUFFIX }, | |
| 50 | ||
| 51 | .{ "ul_SUFFIX(X) X ## ul", .UL_SUFFIX }, | |
| 52 | .{ "uL_SUFFIX(X) X ## uL", .UL_SUFFIX }, | |
| 53 | .{ "Ul_SUFFIX(X) X ## Ul", .UL_SUFFIX }, | |
| 54 | .{ "UL_SUFFIX(X) X ## UL", .UL_SUFFIX }, | |
| 55 | ||
| 56 | .{ "ll_SUFFIX(X) X ## ll", .LL_SUFFIX }, | |
| 57 | .{ "LL_SUFFIX(X) X ## LL", .LL_SUFFIX }, | |
| 58 | ||
| 59 | .{ "ull_SUFFIX(X) X ## ull", .ULL_SUFFIX }, | |
| 60 | .{ "uLL_SUFFIX(X) X ## uLL", .ULL_SUFFIX }, | |
| 61 | .{ "Ull_SUFFIX(X) X ## Ull", .ULL_SUFFIX }, | |
| 62 | .{ "ULL_SUFFIX(X) X ## ULL", .ULL_SUFFIX }, | |
| 63 | ||
| 64 | .{ "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL }, | |
| 65 | .{ "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL }, | |
| 66 | ||
| 67 | .{ | |
| 68 | \\wl_container_of(ptr, sample, member) \ | |
| 69 | \\(__typeof__(sample))((char *)(ptr) - \ | |
| 70 | \\ offsetof(__typeof__(*sample), member)) | |
| 71 | , | |
| 72 | .WL_CONTAINER_OF, | |
| 73 | }, | |
| 74 | ||
| 75 | .{ "IGNORE_ME(X) ((void)(X))", .DISCARD }, | |
| 76 | .{ "IGNORE_ME(X) (void)(X)", .DISCARD }, | |
| 77 | .{ "IGNORE_ME(X) ((const void)(X))", .DISCARD }, | |
| 78 | .{ "IGNORE_ME(X) (const void)(X)", .DISCARD }, | |
| 79 | .{ "IGNORE_ME(X) ((volatile void)(X))", .DISCARD }, | |
| 80 | .{ "IGNORE_ME(X) (volatile void)(X)", .DISCARD }, | |
| 81 | .{ "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD }, | |
| 82 | .{ "IGNORE_ME(X) (const volatile void)(X)", .DISCARD }, | |
| 83 | .{ "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD }, | |
| 84 | .{ "IGNORE_ME(X) (volatile const void)(X)", .DISCARD }, | |
| 85 | }; | |
| 86 | ||
| 87 | const Pattern = struct { | |
| 88 | slicer: MacroSlicer, | |
| 89 | impl: Impl, | |
| 90 | ||
| 91 | fn init(pl: *Pattern, allocator: mem.Allocator, template: Template) Error!void { | |
| 92 | const source = template[0]; | |
| 93 | const impl = template[1]; | |
| 94 | var tok_list = std.ArrayList(CToken).init(allocator); | |
| 95 | defer tok_list.deinit(); | |
| 96 | ||
| 97 | pl.* = .{ | |
| 98 | .slicer = try tokenizeMacro(source, &tok_list), | |
| 99 | .impl = impl, | |
| 100 | }; | |
| 101 | } | |
| 102 | ||
| 103 | fn deinit(pl: *Pattern, allocator: mem.Allocator) void { | |
| 104 | allocator.free(pl.slicer.tokens); | |
| 105 | pl.* = undefined; | |
| 106 | } | |
| 107 | ||
| 108 | /// This function assumes that `ms` has already been validated to contain a function-like | |
| 109 | /// macro, and that the parsed template macro in `pl` also contains a function-like | |
| 110 | /// macro. Please review this logic carefully if changing that assumption. Two | |
| 111 | /// function-like macros are considered equivalent if and only if they contain the same | |
| 112 | /// list of tokens, modulo parameter names. | |
| 113 | fn matches(pat: Pattern, ms: MacroSlicer) bool { | |
| 114 | if (ms.params != pat.slicer.params) return false; | |
| 115 | if (ms.tokens.len != pat.slicer.tokens.len) return false; | |
| 116 | ||
| 117 | for (ms.tokens, pat.slicer.tokens) |macro_tok, pat_tok| { | |
| 118 | if (macro_tok.id != pat_tok.id) return false; | |
| 119 | switch (macro_tok.id) { | |
| 120 | .macro_param, .macro_param_no_expand => { | |
| 121 | // `.end` is the parameter index. | |
| 122 | if (macro_tok.end != pat_tok.end) return false; | |
| 123 | }, | |
| 124 | .identifier, .extended_identifier, .string_literal, .char_literal, .pp_num => { | |
| 125 | const macro_bytes = ms.slice(macro_tok); | |
| 126 | const pattern_bytes = pat.slicer.slice(pat_tok); | |
| 127 | ||
| 128 | if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; | |
| 129 | }, | |
| 130 | else => { | |
| 131 | // other tags correspond to keywords and operators that do not contain a "payload" | |
| 132 | // that can vary | |
| 133 | }, | |
| 134 | } | |
| 135 | } | |
| 136 | return true; | |
| 137 | } | |
| 138 | }; | |
| 139 | ||
| 140 | const PatternList = @This(); | |
| 141 | ||
| 142 | patterns: []Pattern, | |
| 143 | ||
| 144 | pub const MacroSlicer = struct { | |
| 145 | source: []const u8, | |
| 146 | tokens: []const CToken, | |
| 147 | params: u32, | |
| 148 | ||
| 149 | fn slice(pl: MacroSlicer, token: CToken) []const u8 { | |
| 150 | return pl.source[token.start..token.end]; | |
| 151 | } | |
| 152 | }; | |
| 153 | ||
| 154 | pub fn init(allocator: mem.Allocator) Error!PatternList { | |
| 155 | const patterns = try allocator.alloc(Pattern, templates.len); | |
| 156 | for (patterns, templates) |*pattern, template| { | |
| 157 | try pattern.init(allocator, template); | |
| 158 | } | |
| 159 | return .{ .patterns = patterns }; | |
| 160 | } | |
| 161 | ||
| 162 | pub fn deinit(pl: *PatternList, allocator: mem.Allocator) void { | |
| 163 | for (pl.patterns) |*pattern| pattern.deinit(allocator); | |
| 164 | allocator.free(pl.patterns); | |
| 165 | pl.* = undefined; | |
| 166 | } | |
| 167 | ||
| 168 | pub fn match(pl: PatternList, ms: MacroSlicer) Error!?Impl { | |
| 169 | for (pl.patterns) |pattern| if (pattern.matches(ms)) return pattern.impl; | |
| 170 | return null; | |
| 171 | } | |
| 172 | ||
| 173 | fn tokenizeMacro(source: []const u8, tok_list: *std.ArrayList(CToken)) Error!MacroSlicer { | |
| 174 | var param_count: u32 = 0; | |
| 175 | var param_buf: [8][]const u8 = undefined; | |
| 176 | ||
| 177 | var tokenizer: aro.Tokenizer = .{ | |
| 178 | .buf = source, | |
| 179 | .source = .unused, | |
| 180 | .langopts = .{}, | |
| 181 | }; | |
| 182 | { | |
| 183 | const name_tok = tokenizer.nextNoWS(); | |
| 184 | assert(name_tok.id == .identifier); | |
| 185 | const l_paren = tokenizer.nextNoWS(); | |
| 186 | assert(l_paren.id == .l_paren); | |
| 187 | } | |
| 188 | ||
| 189 | while (true) { | |
| 190 | const param = tokenizer.nextNoWS(); | |
| 191 | if (param.id == .r_paren) break; | |
| 192 | assert(param.id == .identifier); | |
| 193 | const slice = source[param.start..param.end]; | |
| 194 | param_buf[param_count] = slice; | |
| 195 | param_count += 1; | |
| 196 | ||
| 197 | const comma = tokenizer.nextNoWS(); | |
| 198 | if (comma.id == .r_paren) break; | |
| 199 | assert(comma.id == .comma); | |
| 200 | } | |
| 201 | ||
| 202 | outer: while (true) { | |
| 203 | const tok = tokenizer.next(); | |
| 204 | switch (tok.id) { | |
| 205 | .whitespace, .comment => continue, | |
| 206 | .identifier => { | |
| 207 | const slice = source[tok.start..tok.end]; | |
| 208 | for (param_buf[0..param_count], 0..) |param, i| { | |
| 209 | if (std.mem.eql(u8, param, slice)) { | |
| 210 | try tok_list.append(.{ | |
| 211 | .id = .macro_param, | |
| 212 | .source = .unused, | |
| 213 | .end = @intCast(i), | |
| 214 | }); | |
| 215 | continue :outer; | |
| 216 | } | |
| 217 | } | |
| 218 | }, | |
| 219 | .hash_hash => { | |
| 220 | if (tok_list.items[tok_list.items.len - 1].id == .macro_param) { | |
| 221 | tok_list.items[tok_list.items.len - 1].id = .macro_param_no_expand; | |
| 222 | } | |
| 223 | }, | |
| 224 | .nl, .eof => break, | |
| 225 | else => {}, | |
| 226 | } | |
| 227 | try tok_list.append(tok); | |
| 228 | } | |
| 229 | ||
| 230 | return .{ | |
| 231 | .source = source, | |
| 232 | .tokens = try tok_list.toOwnedSlice(), | |
| 233 | .params = param_count, | |
| 234 | }; | |
| 235 | } | |
| 236 | ||
| 237 | test "Macro matching" { | |
| 238 | const testing = std.testing; | |
| 239 | const helper = struct { | |
| 240 | fn checkMacro( | |
| 241 | allocator: mem.Allocator, | |
| 242 | pattern_list: PatternList, | |
| 243 | source: []const u8, | |
| 244 | comptime expected_match: ?Impl, | |
| 245 | ) !void { | |
| 246 | var tok_list = std.ArrayList(CToken).init(allocator); | |
| 247 | defer tok_list.deinit(); | |
| 248 | const ms = try tokenizeMacro(source, &tok_list); | |
| 249 | defer allocator.free(ms.tokens); | |
| 250 | ||
| 251 | const matched = try pattern_list.match(ms); | |
| 252 | if (expected_match) |expected| { | |
| 253 | try testing.expectEqual(expected, matched); | |
| 254 | } else { | |
| 255 | try testing.expectEqual(@as(@TypeOf(matched), null), matched); | |
| 256 | } | |
| 257 | } | |
| 258 | }; | |
| 259 | const allocator = std.testing.allocator; | |
| 260 | var pattern_list = try PatternList.init(allocator); | |
| 261 | defer pattern_list.deinit(allocator); | |
| 262 | ||
| 263 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", .F_SUFFIX); | |
| 264 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", .U_SUFFIX); | |
| 265 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", .L_SUFFIX); | |
| 266 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", .LL_SUFFIX); | |
| 267 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", .UL_SUFFIX); | |
| 268 | try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", .ULL_SUFFIX); | |
| 269 | try helper.checkMacro(allocator, pattern_list, | |
| 270 | \\container_of(a, b, c) \ | |
| 271 | \\(__typeof__(b))((char *)(a) - \ | |
| 272 | \\ offsetof(__typeof__(*b), c)) | |
| 273 | , .WL_CONTAINER_OF); | |
| 274 | ||
| 275 | try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null); | |
| 276 | try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL); | |
| 277 | try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL); | |
| 278 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", .DISCARD); | |
| 279 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", .DISCARD); | |
| 280 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", .DISCARD); | |
| 281 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", .DISCARD); | |
| 282 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", .DISCARD); | |
| 283 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", .DISCARD); | |
| 284 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", .DISCARD); | |
| 285 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD); | |
| 286 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", .DISCARD); | |
| 287 | try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD); | |
| 288 | } |
lib/compiler/translate-c/src/Scope.zig deleted-399| ... | ... | @@ -1,399 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | const aro = @import("aro"); | |
| 4 | ||
| 5 | const ast = @import("ast.zig"); | |
| 6 | const Translator = @import("Translator.zig"); | |
| 7 | ||
| 8 | const Scope = @This(); | |
| 9 | ||
| 10 | pub const SymbolTable = std.StringArrayHashMapUnmanaged(ast.Node); | |
| 11 | pub const AliasList = std.ArrayListUnmanaged(struct { | |
| 12 | alias: []const u8, | |
| 13 | name: []const u8, | |
| 14 | }); | |
| 15 | ||
| 16 | /// Associates a container (structure or union) with its relevant member functions. | |
| 17 | pub const ContainerMemberFns = struct { | |
| 18 | container_decl_ptr: *ast.Node, | |
| 19 | member_fns: std.ArrayListUnmanaged(*ast.Payload.Func) = .empty, | |
| 20 | }; | |
| 21 | pub const ContainerMemberFnsHashMap = std.AutoArrayHashMapUnmanaged(aro.QualType, ContainerMemberFns); | |
| 22 | ||
| 23 | id: Id, | |
| 24 | parent: ?*Scope, | |
| 25 | ||
| 26 | pub const Id = enum { | |
| 27 | block, | |
| 28 | root, | |
| 29 | condition, | |
| 30 | loop, | |
| 31 | do_loop, | |
| 32 | }; | |
| 33 | ||
| 34 | /// Used for the scope of condition expressions, for example `if (cond)`. | |
| 35 | /// The block is lazily initialized because it is only needed for rare | |
| 36 | /// cases of comma operators being used. | |
| 37 | pub const Condition = struct { | |
| 38 | base: Scope, | |
| 39 | block: ?Block = null, | |
| 40 | ||
| 41 | fn getBlockScope(cond: *Condition, t: *Translator) !*Block { | |
| 42 | if (cond.block) |*b| return b; | |
| 43 | cond.block = try Block.init(t, &cond.base, true); | |
| 44 | return &cond.block.?; | |
| 45 | } | |
| 46 | ||
| 47 | pub fn deinit(cond: *Condition) void { | |
| 48 | if (cond.block) |*b| b.deinit(); | |
| 49 | } | |
| 50 | }; | |
| 51 | ||
| 52 | /// Represents an in-progress Node.Block. This struct is stack-allocated. | |
| 53 | /// When it is deinitialized, it produces an Node.Block which is allocated | |
| 54 | /// into the main arena. | |
| 55 | pub const Block = struct { | |
| 56 | base: Scope, | |
| 57 | translator: *Translator, | |
| 58 | statements: std.ArrayListUnmanaged(ast.Node), | |
| 59 | variables: AliasList, | |
| 60 | mangle_count: u32 = 0, | |
| 61 | label: ?[]const u8 = null, | |
| 62 | ||
| 63 | /// By default all variables are discarded, since we do not know in advance if they | |
| 64 | /// will be used. This maps the variable's name to the Discard payload, so that if | |
| 65 | /// the variable is subsequently referenced we can indicate that the discard should | |
| 66 | /// be skipped during the intermediate AST -> Zig AST render step. | |
| 67 | variable_discards: std.StringArrayHashMapUnmanaged(*ast.Payload.Discard), | |
| 68 | ||
| 69 | /// When the block corresponds to a function, keep track of the return type | |
| 70 | /// so that the return expression can be cast, if necessary | |
| 71 | return_type: ?aro.QualType = null, | |
| 72 | ||
| 73 | /// C static local variables are wrapped in a block-local struct. The struct | |
| 74 | /// is named `mangle(static_local_ + name)` and the Zig variable within the | |
| 75 | /// struct keeps the name of the C variable. | |
| 76 | pub const static_local_prefix = "static_local"; | |
| 77 | ||
| 78 | /// C extern local variables are wrapped in a block-local struct. The struct | |
| 79 | /// is named `mangle(extern_local + name)` and the Zig variable within the | |
| 80 | /// struct keeps the name of the C variable. | |
| 81 | pub const extern_local_prefix = "extern_local"; | |
| 82 | ||
| 83 | pub fn init(t: *Translator, parent: *Scope, labeled: bool) !Block { | |
| 84 | var blk: Block = .{ | |
| 85 | .base = .{ | |
| 86 | .id = .block, | |
| 87 | .parent = parent, | |
| 88 | }, | |
| 89 | .translator = t, | |
| 90 | .statements = .empty, | |
| 91 | .variables = .empty, | |
| 92 | .variable_discards = .empty, | |
| 93 | }; | |
| 94 | if (labeled) { | |
| 95 | blk.label = try blk.makeMangledName("blk"); | |
| 96 | } | |
| 97 | return blk; | |
| 98 | } | |
| 99 | ||
| 100 | pub fn deinit(block: *Block) void { | |
| 101 | block.statements.deinit(block.translator.gpa); | |
| 102 | block.variables.deinit(block.translator.gpa); | |
| 103 | block.variable_discards.deinit(block.translator.gpa); | |
| 104 | block.* = undefined; | |
| 105 | } | |
| 106 | ||
| 107 | pub fn complete(block: *Block) !ast.Node { | |
| 108 | const arena = block.translator.arena; | |
| 109 | if (block.base.parent.?.id == .do_loop) { | |
| 110 | // We reserve 1 extra statement if the parent is a do_loop. This is in case of | |
| 111 | // do while, we want to put `if (cond) break;` at the end. | |
| 112 | const alloc_len = block.statements.items.len + @intFromBool(block.base.parent.?.id == .do_loop); | |
| 113 | var stmts = try arena.alloc(ast.Node, alloc_len); | |
| 114 | stmts.len = block.statements.items.len; | |
| 115 | @memcpy(stmts[0..block.statements.items.len], block.statements.items); | |
| 116 | return ast.Node.Tag.block.create(arena, .{ | |
| 117 | .label = block.label, | |
| 118 | .stmts = stmts, | |
| 119 | }); | |
| 120 | } | |
| 121 | if (block.statements.items.len == 0) return ast.Node.Tag.empty_block.init(); | |
| 122 | return ast.Node.Tag.block.create(arena, .{ | |
| 123 | .label = block.label, | |
| 124 | .stmts = try arena.dupe(ast.Node, block.statements.items), | |
| 125 | }); | |
| 126 | } | |
| 127 | ||
| 128 | /// Given the desired name, return a name that does not shadow anything from outer scopes. | |
| 129 | /// Inserts the returned name into the scope. | |
| 130 | /// The name will not be visible to callers of getAlias. | |
| 131 | pub fn reserveMangledName(block: *Block, name: []const u8) ![]const u8 { | |
| 132 | return block.createMangledName(name, true, null); | |
| 133 | } | |
| 134 | ||
| 135 | /// Same as reserveMangledName, but enables the alias immediately. | |
| 136 | pub fn makeMangledName(block: *Block, name: []const u8) ![]const u8 { | |
| 137 | return block.createMangledName(name, false, null); | |
| 138 | } | |
| 139 | ||
| 140 | pub fn createMangledName(block: *Block, name: []const u8, reservation: bool, prefix_opt: ?[]const u8) ![]const u8 { | |
| 141 | const arena = block.translator.arena; | |
| 142 | const name_copy = try arena.dupe(u8, name); | |
| 143 | const alias_base = if (prefix_opt) |prefix| | |
| 144 | try std.fmt.allocPrint(arena, "{s}_{s}", .{ prefix, name }) | |
| 145 | else | |
| 146 | name; | |
| 147 | var proposed_name = alias_base; | |
| 148 | while (block.contains(proposed_name)) { | |
| 149 | block.mangle_count += 1; | |
| 150 | proposed_name = try std.fmt.allocPrint(arena, "{s}_{d}", .{ alias_base, block.mangle_count }); | |
| 151 | } | |
| 152 | const new_mangle = try block.variables.addOne(block.translator.gpa); | |
| 153 | if (reservation) { | |
| 154 | new_mangle.* = .{ .name = name_copy, .alias = name_copy }; | |
| 155 | } else { | |
| 156 | new_mangle.* = .{ .name = name_copy, .alias = proposed_name }; | |
| 157 | } | |
| 158 | return proposed_name; | |
| 159 | } | |
| 160 | ||
| 161 | fn getAlias(block: *Block, name: []const u8) ?[]const u8 { | |
| 162 | for (block.variables.items) |p| { | |
| 163 | if (std.mem.eql(u8, p.name, name)) | |
| 164 | return p.alias; | |
| 165 | } | |
| 166 | return block.base.parent.?.getAlias(name); | |
| 167 | } | |
| 168 | ||
| 169 | fn localContains(block: *Block, name: []const u8) bool { | |
| 170 | for (block.variables.items) |p| { | |
| 171 | if (std.mem.eql(u8, p.alias, name)) | |
| 172 | return true; | |
| 173 | } | |
| 174 | return false; | |
| 175 | } | |
| 176 | ||
| 177 | fn contains(block: *Block, name: []const u8) bool { | |
| 178 | if (block.localContains(name)) | |
| 179 | return true; | |
| 180 | return block.base.parent.?.contains(name); | |
| 181 | } | |
| 182 | ||
| 183 | pub fn discardVariable(block: *Block, name: []const u8) Translator.Error!void { | |
| 184 | const gpa = block.translator.gpa; | |
| 185 | const arena = block.translator.arena; | |
| 186 | const name_node = try ast.Node.Tag.identifier.create(arena, name); | |
| 187 | const discard = try ast.Node.Tag.discard.create(arena, .{ .should_skip = false, .value = name_node }); | |
| 188 | try block.statements.append(gpa, discard); | |
| 189 | try block.variable_discards.putNoClobber(gpa, name, discard.castTag(.discard).?); | |
| 190 | } | |
| 191 | }; | |
| 192 | ||
| 193 | pub const Root = struct { | |
| 194 | base: Scope, | |
| 195 | translator: *Translator, | |
| 196 | sym_table: SymbolTable, | |
| 197 | blank_macros: std.StringArrayHashMapUnmanaged(void), | |
| 198 | nodes: std.ArrayListUnmanaged(ast.Node), | |
| 199 | container_member_fns_map: ContainerMemberFnsHashMap, | |
| 200 | ||
| 201 | pub fn init(t: *Translator) Root { | |
| 202 | return .{ | |
| 203 | .base = .{ | |
| 204 | .id = .root, | |
| 205 | .parent = null, | |
| 206 | }, | |
| 207 | .translator = t, | |
| 208 | .sym_table = .empty, | |
| 209 | .blank_macros = .empty, | |
| 210 | .nodes = .empty, | |
| 211 | .container_member_fns_map = .empty, | |
| 212 | }; | |
| 213 | } | |
| 214 | ||
| 215 | pub fn deinit(root: *Root) void { | |
| 216 | root.sym_table.deinit(root.translator.gpa); | |
| 217 | root.blank_macros.deinit(root.translator.gpa); | |
| 218 | root.nodes.deinit(root.translator.gpa); | |
| 219 | for (root.container_member_fns_map.values()) |*members| { | |
| 220 | members.member_fns.deinit(root.translator.gpa); | |
| 221 | } | |
| 222 | root.container_member_fns_map.deinit(root.translator.gpa); | |
| 223 | } | |
| 224 | ||
| 225 | /// Check if the global scope contains this name, without looking into the "future", e.g. | |
| 226 | /// ignore the preprocessed decl and macro names. | |
| 227 | pub fn containsNow(root: *Root, name: []const u8) bool { | |
| 228 | return root.sym_table.contains(name); | |
| 229 | } | |
| 230 | ||
| 231 | /// Check if the global scope contains the name, includes all decls that haven't been translated yet. | |
| 232 | pub fn contains(root: *Root, name: []const u8) bool { | |
| 233 | return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name); | |
| 234 | } | |
| 235 | ||
| 236 | pub fn addMemberFunction(root: *Root, func_ty: aro.Type.Func, func: *ast.Payload.Func) !void { | |
| 237 | std.debug.assert(func.data.name != null); | |
| 238 | if (func_ty.params.len == 0) return; | |
| 239 | ||
| 240 | const param1_base = func_ty.params[0].qt.base(root.translator.comp); | |
| 241 | const container_qt = if (param1_base.type == .pointer) | |
| 242 | param1_base.type.pointer.child.base(root.translator.comp).qt | |
| 243 | else | |
| 244 | param1_base.qt; | |
| 245 | ||
| 246 | if (root.container_member_fns_map.getPtr(container_qt)) |members| { | |
| 247 | try members.member_fns.append(root.translator.gpa, func); | |
| 248 | } | |
| 249 | } | |
| 250 | ||
| 251 | pub fn processContainerMemberFns(root: *Root) !void { | |
| 252 | const gpa = root.translator.gpa; | |
| 253 | const arena = root.translator.arena; | |
| 254 | ||
| 255 | var member_names: std.StringArrayHashMapUnmanaged(u32) = .empty; | |
| 256 | defer member_names.deinit(gpa); | |
| 257 | for (root.container_member_fns_map.values()) |members| { | |
| 258 | member_names.clearRetainingCapacity(); | |
| 259 | const decls_ptr = switch (members.container_decl_ptr.tag()) { | |
| 260 | .@"struct", .@"union" => blk_record: { | |
| 261 | const payload: *ast.Payload.Container = @alignCast(@fieldParentPtr("base", members.container_decl_ptr.ptr_otherwise)); | |
| 262 | // Avoid duplication with field names | |
| 263 | for (payload.data.fields) |field| { | |
| 264 | try member_names.put(gpa, field.name, 0); | |
| 265 | } | |
| 266 | break :blk_record &payload.data.decls; | |
| 267 | }, | |
| 268 | .opaque_literal => blk_opaque: { | |
| 269 | const container_decl = try ast.Node.Tag.@"opaque".create(arena, .{ | |
| 270 | .layout = .none, | |
| 271 | .fields = &.{}, | |
| 272 | .decls = &.{}, | |
| 273 | }); | |
| 274 | members.container_decl_ptr.* = container_decl; | |
| 275 | break :blk_opaque &container_decl.castTag(.@"opaque").?.data.decls; | |
| 276 | }, | |
| 277 | else => return, | |
| 278 | }; | |
| 279 | ||
| 280 | const old_decls = decls_ptr.*; | |
| 281 | const new_decls = try arena.alloc(ast.Node, old_decls.len + members.member_fns.items.len); | |
| 282 | @memcpy(new_decls[0..old_decls.len], old_decls); | |
| 283 | // Assume the allocator of payload.data.decls is arena, | |
| 284 | // so don't add arena.free(old_variables). | |
| 285 | const func_ref_vars = new_decls[old_decls.len..]; | |
| 286 | var count: u32 = 0; | |
| 287 | for (members.member_fns.items) |func| { | |
| 288 | const func_name = func.data.name.?; | |
| 289 | ||
| 290 | const last_index = std.mem.lastIndexOf(u8, func_name, "_"); | |
| 291 | const last_name = if (last_index) |index| func_name[index + 1 ..] else continue; | |
| 292 | var same_count: u32 = 0; | |
| 293 | const gop = try member_names.getOrPutValue(gpa, last_name, same_count); | |
| 294 | if (gop.found_existing) { | |
| 295 | gop.value_ptr.* += 1; | |
| 296 | same_count = gop.value_ptr.*; | |
| 297 | } | |
| 298 | const var_name = if (same_count == 0) | |
| 299 | last_name | |
| 300 | else | |
| 301 | try std.fmt.allocPrint(arena, "{s}{d}", .{ last_name, same_count }); | |
| 302 | ||
| 303 | func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{ | |
| 304 | .name = var_name, | |
| 305 | .init = try ast.Node.Tag.identifier.create(arena, func_name), | |
| 306 | }); | |
| 307 | count += 1; | |
| 308 | } | |
| 309 | decls_ptr.* = new_decls[0 .. old_decls.len + count]; | |
| 310 | } | |
| 311 | } | |
| 312 | }; | |
| 313 | ||
| 314 | pub fn findBlockScope(inner: *Scope, t: *Translator) !*Block { | |
| 315 | var scope = inner; | |
| 316 | while (true) { | |
| 317 | switch (scope.id) { | |
| 318 | .root => unreachable, | |
| 319 | .block => return @fieldParentPtr("base", scope), | |
| 320 | .condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(t), | |
| 321 | else => scope = scope.parent.?, | |
| 322 | } | |
| 323 | } | |
| 324 | } | |
| 325 | ||
| 326 | pub fn findBlockReturnType(inner: *Scope) aro.QualType { | |
| 327 | var scope = inner; | |
| 328 | while (true) { | |
| 329 | switch (scope.id) { | |
| 330 | .root => unreachable, | |
| 331 | .block => { | |
| 332 | const block: *Block = @fieldParentPtr("base", scope); | |
| 333 | if (block.return_type) |qt| return qt; | |
| 334 | scope = scope.parent.?; | |
| 335 | }, | |
| 336 | else => scope = scope.parent.?, | |
| 337 | } | |
| 338 | } | |
| 339 | } | |
| 340 | ||
| 341 | pub fn getAlias(scope: *Scope, name: []const u8) ?[]const u8 { | |
| 342 | return switch (scope.id) { | |
| 343 | .root => null, | |
| 344 | .block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name), | |
| 345 | .loop, .do_loop, .condition => scope.parent.?.getAlias(name), | |
| 346 | }; | |
| 347 | } | |
| 348 | ||
| 349 | fn contains(scope: *Scope, name: []const u8) bool { | |
| 350 | return switch (scope.id) { | |
| 351 | .root => @as(*Root, @fieldParentPtr("base", scope)).contains(name), | |
| 352 | .block => @as(*Block, @fieldParentPtr("base", scope)).contains(name), | |
| 353 | .loop, .do_loop, .condition => scope.parent.?.contains(name), | |
| 354 | }; | |
| 355 | } | |
| 356 | ||
| 357 | /// Appends a node to the first block scope if inside a function, or to the root tree if not. | |
| 358 | pub fn appendNode(inner: *Scope, node: ast.Node) !void { | |
| 359 | var scope = inner; | |
| 360 | while (true) { | |
| 361 | switch (scope.id) { | |
| 362 | .root => { | |
| 363 | const root: *Root = @fieldParentPtr("base", scope); | |
| 364 | return root.nodes.append(root.translator.gpa, node); | |
| 365 | }, | |
| 366 | .block => { | |
| 367 | const block: *Block = @fieldParentPtr("base", scope); | |
| 368 | return block.statements.append(block.translator.gpa, node); | |
| 369 | }, | |
| 370 | else => scope = scope.parent.?, | |
| 371 | } | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void { | |
| 376 | if (true) { | |
| 377 | // TODO: due to 'local variable is never mutated' errors, we can | |
| 378 | // only skip discards if a variable is used as an lvalue, which | |
| 379 | // we don't currently have detection for in translate-c. | |
| 380 | // Once #17584 is completed, perhaps we can do away with this | |
| 381 | // logic entirely, and instead rely on render to fixup code. | |
| 382 | return; | |
| 383 | } | |
| 384 | var scope = inner; | |
| 385 | while (true) { | |
| 386 | switch (scope.id) { | |
| 387 | .root => return, | |
| 388 | .block => { | |
| 389 | const block: *Block = @fieldParentPtr("base", scope); | |
| 390 | if (block.variable_discards.get(name)) |discard| { | |
| 391 | discard.data.should_skip = true; | |
| 392 | return; | |
| 393 | } | |
| 394 | }, | |
| 395 | else => {}, | |
| 396 | } | |
| 397 | scope = scope.parent.?; | |
| 398 | } | |
| 399 | } |
lib/compiler/translate-c/src/Translator.zig deleted-4183| ... | ... | @@ -1,4183 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const mem = std.mem; | |
| 3 | const assert = std.debug.assert; | |
| 4 | const CallingConvention = std.builtin.CallingConvention; | |
| 5 | ||
| 6 | const aro = @import("aro"); | |
| 7 | const CToken = aro.Tokenizer.Token; | |
| 8 | const Tree = aro.Tree; | |
| 9 | const Node = Tree.Node; | |
| 10 | const TokenIndex = Tree.TokenIndex; | |
| 11 | const QualType = aro.QualType; | |
| 12 | ||
| 13 | const ast = @import("ast.zig"); | |
| 14 | const ZigNode = ast.Node; | |
| 15 | const ZigTag = ZigNode.Tag; | |
| 16 | const builtins = @import("builtins.zig"); | |
| 17 | const helpers = @import("helpers.zig"); | |
| 18 | const MacroTranslator = @import("MacroTranslator.zig"); | |
| 19 | const PatternList = @import("PatternList.zig"); | |
| 20 | const Scope = @import("Scope.zig"); | |
| 21 | ||
| 22 | pub const Error = std.mem.Allocator.Error; | |
| 23 | pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; | |
| 24 | pub const TypeError = Error || error{UnsupportedType}; | |
| 25 | pub const TransError = TypeError || error{UnsupportedTranslation}; | |
| 26 | ||
| 27 | const Translator = @This(); | |
| 28 | ||
| 29 | /// The C AST to be translated. | |
| 30 | tree: *const Tree, | |
| 31 | /// The compilation corresponding to the AST. | |
| 32 | comp: *aro.Compilation, | |
| 33 | /// The Preprocessor that produced the source for `tree`. | |
| 34 | pp: *const aro.Preprocessor, | |
| 35 | ||
| 36 | gpa: mem.Allocator, | |
| 37 | arena: mem.Allocator, | |
| 38 | ||
| 39 | alias_list: Scope.AliasList, | |
| 40 | global_scope: *Scope.Root, | |
| 41 | /// Running number used for creating new unique identifiers. | |
| 42 | mangle_count: u32 = 0, | |
| 43 | ||
| 44 | /// Table of declarations for enum, struct, union and typedef types. | |
| 45 | type_decls: std.AutoArrayHashMapUnmanaged(Node.Index, []const u8) = .empty, | |
| 46 | /// Table of record decls that have been demoted to opaques. | |
| 47 | opaque_demotes: std.AutoHashMapUnmanaged(QualType, void) = .empty, | |
| 48 | /// Table of unnamed enums and records that are child types of typedefs. | |
| 49 | unnamed_typedefs: std.AutoHashMapUnmanaged(QualType, []const u8) = .empty, | |
| 50 | /// Table of anonymous record to generated field names. | |
| 51 | anonymous_record_field_names: std.AutoHashMapUnmanaged(struct { | |
| 52 | parent: QualType, | |
| 53 | field: QualType, | |
| 54 | }, []const u8) = .empty, | |
| 55 | ||
| 56 | /// This one is different than the root scope's name table. This contains | |
| 57 | /// a list of names that we found by visiting all the top level decls without | |
| 58 | /// translating them. The other maps are updated as we translate; this one is updated | |
| 59 | /// up front in a pre-processing step. | |
| 60 | global_names: std.StringArrayHashMapUnmanaged(void) = .empty, | |
| 61 | ||
| 62 | /// This is similar to `global_names`, but contains names which we would | |
| 63 | /// *like* to use, but do not strictly *have* to if they are unavailable. | |
| 64 | /// These are relevant to types, which ideally we would name like | |
| 65 | /// 'struct_foo' with an alias 'foo', but if either of those names is taken, | |
| 66 | /// may be mangled. | |
| 67 | /// This is distinct from `global_names` so we can detect at a type | |
| 68 | /// declaration whether or not the name is available. | |
| 69 | weak_global_names: std.StringArrayHashMapUnmanaged(void) = .empty, | |
| 70 | ||
| 71 | /// Set of identifiers known to refer to typedef declarations. | |
| 72 | /// Used when parsing macros. | |
| 73 | typedefs: std.StringArrayHashMapUnmanaged(void) = .empty, | |
| 74 | ||
| 75 | /// The lhs lval of a compound assignment expression. | |
| 76 | compound_assign_dummy: ?ZigNode = null, | |
| 77 | ||
| 78 | pub fn getMangle(t: *Translator) u32 { | |
| 79 | t.mangle_count += 1; | |
| 80 | return t.mangle_count; | |
| 81 | } | |
| 82 | ||
| 83 | /// Convert an `aro.Source.Location` to a 'file:line:column' string. | |
| 84 | pub fn locStr(t: *Translator, loc: aro.Source.Location) ![]const u8 { | |
| 85 | const source = t.comp.getSource(loc.id); | |
| 86 | const line_col = source.lineCol(loc); | |
| 87 | const filename = source.path; | |
| 88 | ||
| 89 | const line = source.physicalLine(loc); | |
| 90 | const col = line_col.col; | |
| 91 | ||
| 92 | return std.fmt.allocPrint(t.arena, "{s}:{d}:{d}", .{ filename, line, col }); | |
| 93 | } | |
| 94 | ||
| 95 | fn maybeSuppressResult(t: *Translator, used: ResultUsed, result: ZigNode) TransError!ZigNode { | |
| 96 | if (used == .used) return result; | |
| 97 | return ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = result }); | |
| 98 | } | |
| 99 | ||
| 100 | pub fn addTopLevelDecl(t: *Translator, name: []const u8, decl_node: ZigNode) !void { | |
| 101 | const gop = try t.global_scope.sym_table.getOrPut(t.gpa, name); | |
| 102 | if (!gop.found_existing) { | |
| 103 | gop.value_ptr.* = decl_node; | |
| 104 | try t.global_scope.nodes.append(t.gpa, decl_node); | |
| 105 | } | |
| 106 | } | |
| 107 | ||
| 108 | fn fail( | |
| 109 | t: *Translator, | |
| 110 | err: anytype, | |
| 111 | source_loc: TokenIndex, | |
| 112 | comptime format: []const u8, | |
| 113 | args: anytype, | |
| 114 | ) (@TypeOf(err) || error{OutOfMemory}) { | |
| 115 | try t.warn(&t.global_scope.base, source_loc, format, args); | |
| 116 | return err; | |
| 117 | } | |
| 118 | ||
| 119 | pub fn failDecl( | |
| 120 | t: *Translator, | |
| 121 | scope: *Scope, | |
| 122 | tok_idx: TokenIndex, | |
| 123 | name: []const u8, | |
| 124 | comptime format: []const u8, | |
| 125 | args: anytype, | |
| 126 | ) Error!void { | |
| 127 | const loc = t.tree.tokens.items(.loc)[tok_idx]; | |
| 128 | return t.failDeclExtra(scope, loc, name, format, args); | |
| 129 | } | |
| 130 | ||
| 131 | pub fn failDeclExtra( | |
| 132 | t: *Translator, | |
| 133 | scope: *Scope, | |
| 134 | loc: aro.Source.Location, | |
| 135 | name: []const u8, | |
| 136 | comptime format: []const u8, | |
| 137 | args: anytype, | |
| 138 | ) Error!void { | |
| 139 | // location | |
| 140 | // pub const name = @compileError(msg); | |
| 141 | const fail_msg = try std.fmt.allocPrint(t.arena, format, args); | |
| 142 | const fail_decl = try ZigTag.fail_decl.create(t.arena, .{ .actual = name, .mangled = fail_msg }); | |
| 143 | ||
| 144 | const str = try t.locStr(loc); | |
| 145 | const location_comment = try std.fmt.allocPrint(t.arena, "// {s}", .{str}); | |
| 146 | const loc_node = try ZigTag.warning.create(t.arena, location_comment); | |
| 147 | ||
| 148 | if (scope.id == .root) { | |
| 149 | try t.addTopLevelDecl(name, fail_decl); | |
| 150 | try scope.appendNode(loc_node); | |
| 151 | } else { | |
| 152 | try scope.appendNode(fail_decl); | |
| 153 | try scope.appendNode(loc_node); | |
| 154 | ||
| 155 | const bs = try scope.findBlockScope(t); | |
| 156 | try bs.discardVariable(name); | |
| 157 | } | |
| 158 | } | |
| 159 | ||
| 160 | fn warn(t: *Translator, scope: *Scope, tok_idx: TokenIndex, comptime format: []const u8, args: anytype) !void { | |
| 161 | const loc = t.tree.tokens.items(.loc)[tok_idx]; | |
| 162 | const str = try t.locStr(loc); | |
| 163 | const value = try std.fmt.allocPrint(t.arena, "// {s}: warning: " ++ format, .{str} ++ args); | |
| 164 | try scope.appendNode(try ZigTag.warning.create(t.arena, value)); | |
| 165 | } | |
| 166 | ||
| 167 | pub const Options = struct { | |
| 168 | gpa: mem.Allocator, | |
| 169 | comp: *aro.Compilation, | |
| 170 | pp: *const aro.Preprocessor, | |
| 171 | tree: *const aro.Tree, | |
| 172 | module_libs: bool, | |
| 173 | }; | |
| 174 | ||
| 175 | pub fn translate(options: Options) ![]u8 { | |
| 176 | const gpa = options.gpa; | |
| 177 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); | |
| 178 | defer arena_allocator.deinit(); | |
| 179 | const arena = arena_allocator.allocator(); | |
| 180 | ||
| 181 | var translator: Translator = .{ | |
| 182 | .gpa = gpa, | |
| 183 | .arena = arena, | |
| 184 | .alias_list = .empty, | |
| 185 | .global_scope = try arena.create(Scope.Root), | |
| 186 | .comp = options.comp, | |
| 187 | .pp = options.pp, | |
| 188 | .tree = options.tree, | |
| 189 | }; | |
| 190 | translator.global_scope.* = Scope.Root.init(&translator); | |
| 191 | defer { | |
| 192 | translator.type_decls.deinit(gpa); | |
| 193 | translator.alias_list.deinit(gpa); | |
| 194 | translator.global_names.deinit(gpa); | |
| 195 | translator.weak_global_names.deinit(gpa); | |
| 196 | translator.opaque_demotes.deinit(gpa); | |
| 197 | translator.unnamed_typedefs.deinit(gpa); | |
| 198 | translator.anonymous_record_field_names.deinit(gpa); | |
| 199 | translator.typedefs.deinit(gpa); | |
| 200 | translator.global_scope.deinit(); | |
| 201 | } | |
| 202 | ||
| 203 | try translator.prepopulateGlobalNameTable(); | |
| 204 | try translator.transTopLevelDecls(); | |
| 205 | ||
| 206 | // Insert empty line before macros. | |
| 207 | try translator.global_scope.nodes.append(gpa, try ZigTag.warning.create(arena, "\n")); | |
| 208 | ||
| 209 | try translator.transMacros(); | |
| 210 | ||
| 211 | for (translator.alias_list.items) |alias| { | |
| 212 | if (!translator.global_scope.sym_table.contains(alias.alias)) { | |
| 213 | const node = try ZigTag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name }); | |
| 214 | try translator.addTopLevelDecl(alias.alias, node); | |
| 215 | } | |
| 216 | } | |
| 217 | ||
| 218 | try translator.global_scope.processContainerMemberFns(); | |
| 219 | ||
| 220 | var buf: std.ArrayList(u8) = .init(gpa); | |
| 221 | defer buf.deinit(); | |
| 222 | ||
| 223 | if (options.module_libs) { | |
| 224 | try buf.appendSlice( | |
| 225 | \\pub const __builtin = @import("c_builtins"); | |
| 226 | \\pub const __helpers = @import("helpers"); | |
| 227 | \\ | |
| 228 | \\ | |
| 229 | ); | |
| 230 | } else { | |
| 231 | try buf.appendSlice( | |
| 232 | \\pub const __builtin = @import("c_builtins.zig"); | |
| 233 | \\pub const __helpers = @import("helpers.zig"); | |
| 234 | \\ | |
| 235 | \\ | |
| 236 | ); | |
| 237 | } | |
| 238 | ||
| 239 | var zig_ast = try ast.render(gpa, translator.global_scope.nodes.items); | |
| 240 | defer { | |
| 241 | gpa.free(zig_ast.source); | |
| 242 | zig_ast.deinit(gpa); | |
| 243 | } | |
| 244 | try zig_ast.renderToArrayList(&buf, .{}); | |
| 245 | return buf.toOwnedSlice(); | |
| 246 | } | |
| 247 | ||
| 248 | fn prepopulateGlobalNameTable(t: *Translator) !void { | |
| 249 | for (t.tree.root_decls.items) |decl| { | |
| 250 | switch (decl.get(t.tree)) { | |
| 251 | .typedef => |typedef_decl| { | |
| 252 | const decl_name = t.tree.tokSlice(typedef_decl.name_tok); | |
| 253 | try t.global_names.put(t.gpa, decl_name, {}); | |
| 254 | ||
| 255 | // Check for typedefs with unnamed enum/record child types. | |
| 256 | const base = typedef_decl.qt.base(t.comp); | |
| 257 | switch (base.type) { | |
| 258 | .@"enum" => |enum_ty| { | |
| 259 | if (enum_ty.name.lookup(t.comp)[0] != '(') continue; | |
| 260 | }, | |
| 261 | .@"struct", .@"union" => |record_ty| { | |
| 262 | if (record_ty.name.lookup(t.comp)[0] != '(') continue; | |
| 263 | }, | |
| 264 | else => continue, | |
| 265 | } | |
| 266 | ||
| 267 | const gop = try t.unnamed_typedefs.getOrPut(t.gpa, base.qt); | |
| 268 | if (gop.found_existing) { | |
| 269 | // One typedef can declare multiple names. | |
| 270 | // TODO Don't put this one in `decl_table` so it's processed later. | |
| 271 | continue; | |
| 272 | } | |
| 273 | gop.value_ptr.* = decl_name; | |
| 274 | }, | |
| 275 | ||
| 276 | .struct_decl, | |
| 277 | .union_decl, | |
| 278 | .struct_forward_decl, | |
| 279 | .union_forward_decl, | |
| 280 | .enum_decl, | |
| 281 | .enum_forward_decl, | |
| 282 | => { | |
| 283 | const decl_qt = decl.qt(t.tree); | |
| 284 | const prefix, const name = switch (decl_qt.base(t.comp).type) { | |
| 285 | .@"struct" => |struct_ty| .{ "struct", struct_ty.name.lookup(t.comp) }, | |
| 286 | .@"union" => |union_ty| .{ "union", union_ty.name.lookup(t.comp) }, | |
| 287 | .@"enum" => |enum_ty| .{ "enum", enum_ty.name.lookup(t.comp) }, | |
| 288 | else => unreachable, | |
| 289 | }; | |
| 290 | const prefixed_name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ prefix, name }); | |
| 291 | // `name` and `prefixed_name` are the preferred names for this type. | |
| 292 | // However, we can name it anything else if necessary, so these are "weak names". | |
| 293 | try t.weak_global_names.ensureUnusedCapacity(t.gpa, 2); | |
| 294 | t.weak_global_names.putAssumeCapacity(name, {}); | |
| 295 | t.weak_global_names.putAssumeCapacity(prefixed_name, {}); | |
| 296 | }, | |
| 297 | ||
| 298 | .function, .variable => { | |
| 299 | const decl_name = t.tree.tokSlice(decl.tok(t.tree)); | |
| 300 | try t.global_names.put(t.gpa, decl_name, {}); | |
| 301 | }, | |
| 302 | .static_assert => {}, | |
| 303 | .empty_decl => {}, | |
| 304 | .global_asm => {}, | |
| 305 | else => unreachable, | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { | |
| 310 | if (macro.is_builtin) continue; | |
| 311 | if (!t.isSelfDefinedMacro(name, macro)) { | |
| 312 | try t.global_names.put(t.gpa, name, {}); | |
| 313 | } | |
| 314 | } | |
| 315 | } | |
| 316 | ||
| 317 | /// Determines whether macro is of the form: `#define FOO FOO` (Possibly with trailing tokens) | |
| 318 | /// Macros of this form will not be translated. | |
| 319 | fn isSelfDefinedMacro(t: *Translator, name: []const u8, macro: aro.Preprocessor.Macro) bool { | |
| 320 | if (macro.is_func) return false; | |
| 321 | ||
| 322 | if (macro.tokens.len < 1) return false; | |
| 323 | const first_tok = macro.tokens[0]; | |
| 324 | ||
| 325 | const source = t.comp.getSource(macro.loc.id); | |
| 326 | const slice = source.buf[first_tok.start..first_tok.end]; | |
| 327 | ||
| 328 | return std.mem.eql(u8, name, slice); | |
| 329 | } | |
| 330 | ||
| 331 | // ======================= | |
| 332 | // Declaration translation | |
| 333 | // ======================= | |
| 334 | ||
| 335 | fn transTopLevelDecls(t: *Translator) !void { | |
| 336 | for (t.tree.root_decls.items) |decl| { | |
| 337 | try t.transDecl(&t.global_scope.base, decl); | |
| 338 | } | |
| 339 | } | |
| 340 | ||
| 341 | fn transDecl(t: *Translator, scope: *Scope, decl: Node.Index) !void { | |
| 342 | switch (decl.get(t.tree)) { | |
| 343 | .typedef => |typedef_decl| { | |
| 344 | // Implicit typedefs are translated only if referenced. | |
| 345 | if (typedef_decl.implicit) return; | |
| 346 | try t.transTypeDef(scope, decl); | |
| 347 | }, | |
| 348 | ||
| 349 | .struct_decl, .union_decl => |record_decl| { | |
| 350 | try t.transRecordDecl(scope, record_decl.container_qt); | |
| 351 | }, | |
| 352 | ||
| 353 | .enum_decl => |enum_decl| { | |
| 354 | try t.transEnumDecl(scope, enum_decl.container_qt); | |
| 355 | }, | |
| 356 | ||
| 357 | .enum_field, | |
| 358 | .record_field, | |
| 359 | .struct_forward_decl, | |
| 360 | .union_forward_decl, | |
| 361 | .enum_forward_decl, | |
| 362 | => return, | |
| 363 | ||
| 364 | .function => |function| { | |
| 365 | if (function.definition) |definition| { | |
| 366 | return t.transFnDecl(scope, definition.get(t.tree).function); | |
| 367 | } | |
| 368 | try t.transFnDecl(scope, function); | |
| 369 | }, | |
| 370 | ||
| 371 | .variable => |variable| { | |
| 372 | if (variable.definition != null) return; | |
| 373 | try t.transVarDecl(scope, variable); | |
| 374 | }, | |
| 375 | .static_assert => |static_assert| { | |
| 376 | try t.transStaticAssert(&t.global_scope.base, static_assert); | |
| 377 | }, | |
| 378 | .global_asm => |global_asm| { | |
| 379 | try t.transGlobalAsm(&t.global_scope.base, global_asm); | |
| 380 | }, | |
| 381 | .empty_decl => {}, | |
| 382 | else => unreachable, | |
| 383 | } | |
| 384 | } | |
| 385 | ||
| 386 | pub const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{ | |
| 387 | .{ "uint8_t", "u8" }, | |
| 388 | .{ "int8_t", "i8" }, | |
| 389 | .{ "uint16_t", "u16" }, | |
| 390 | .{ "int16_t", "i16" }, | |
| 391 | .{ "uint32_t", "u32" }, | |
| 392 | .{ "int32_t", "i32" }, | |
| 393 | .{ "uint64_t", "u64" }, | |
| 394 | .{ "int64_t", "i64" }, | |
| 395 | .{ "intptr_t", "isize" }, | |
| 396 | .{ "uintptr_t", "usize" }, | |
| 397 | .{ "ssize_t", "isize" }, | |
| 398 | .{ "size_t", "usize" }, | |
| 399 | }); | |
| 400 | ||
| 401 | fn transTypeDef(t: *Translator, scope: *Scope, typedef_node: Node.Index) Error!void { | |
| 402 | const typedef_decl = typedef_node.get(t.tree).typedef; | |
| 403 | if (t.type_decls.get(typedef_node)) |_| | |
| 404 | return; // Avoid processing this decl twice | |
| 405 | ||
| 406 | const toplevel = scope.id == .root; | |
| 407 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; | |
| 408 | ||
| 409 | var name: []const u8 = t.tree.tokSlice(typedef_decl.name_tok); | |
| 410 | try t.typedefs.put(t.gpa, name, {}); | |
| 411 | ||
| 412 | if (builtin_typedef_map.get(name)) |builtin| { | |
| 413 | return t.type_decls.putNoClobber(t.gpa, typedef_node, builtin); | |
| 414 | } | |
| 415 | if (!toplevel) name = try bs.makeMangledName(name); | |
| 416 | try t.type_decls.putNoClobber(t.gpa, typedef_node, name); | |
| 417 | ||
| 418 | const typedef_loc = typedef_decl.name_tok; | |
| 419 | const init_node = t.transType(scope, typedef_decl.qt, typedef_loc) catch |err| switch (err) { | |
| 420 | error.UnsupportedType => { | |
| 421 | return t.failDecl(scope, typedef_loc, name, "unable to resolve typedef child type", .{}); | |
| 422 | }, | |
| 423 | error.OutOfMemory => |e| return e, | |
| 424 | }; | |
| 425 | ||
| 426 | const payload = try t.arena.create(ast.Payload.SimpleVarDecl); | |
| 427 | payload.* = .{ | |
| 428 | .base = .{ .tag = if (toplevel) .pub_var_simple else .var_simple }, | |
| 429 | .data = .{ | |
| 430 | .name = name, | |
| 431 | .init = init_node, | |
| 432 | }, | |
| 433 | }; | |
| 434 | const node = ZigNode.initPayload(&payload.base); | |
| 435 | ||
| 436 | if (toplevel) { | |
| 437 | try t.addTopLevelDecl(name, node); | |
| 438 | } else { | |
| 439 | try scope.appendNode(node); | |
| 440 | try bs.discardVariable(name); | |
| 441 | } | |
| 442 | } | |
| 443 | ||
| 444 | fn mangleWeakGlobalName(t: *Translator, want_name: []const u8) Error![]const u8 { | |
| 445 | var cur_name = want_name; | |
| 446 | ||
| 447 | if (!t.weak_global_names.contains(want_name)) { | |
| 448 | // This type wasn't noticed by the name detection pass, so nothing has been treating this as | |
| 449 | // a weak global name. We must mangle it to avoid conflicts with locals. | |
| 450 | cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); | |
| 451 | } | |
| 452 | ||
| 453 | while (t.global_names.contains(cur_name)) { | |
| 454 | cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); | |
| 455 | } | |
| 456 | return cur_name; | |
| 457 | } | |
| 458 | ||
| 459 | fn transRecordDecl(t: *Translator, scope: *Scope, record_qt: QualType) Error!void { | |
| 460 | const base = record_qt.base(t.comp); | |
| 461 | const record_ty = switch (base.type) { | |
| 462 | .@"struct", .@"union" => |record_ty| record_ty, | |
| 463 | else => unreachable, | |
| 464 | }; | |
| 465 | ||
| 466 | if (t.type_decls.get(record_ty.decl_node)) |_| | |
| 467 | return; // Avoid processing this decl twice | |
| 468 | ||
| 469 | const toplevel = scope.id == .root; | |
| 470 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; | |
| 471 | ||
| 472 | const container_kind: ZigTag = if (base.type == .@"union") .@"union" else .@"struct"; | |
| 473 | const container_kind_name = @tagName(container_kind); | |
| 474 | ||
| 475 | var bare_name = record_ty.name.lookup(t.comp); | |
| 476 | var is_unnamed = false; | |
| 477 | var name = bare_name; | |
| 478 | ||
| 479 | if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { | |
| 480 | bare_name = typedef_name; | |
| 481 | name = typedef_name; | |
| 482 | } else { | |
| 483 | if (record_ty.isAnonymous(t.comp)) { | |
| 484 | bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); | |
| 485 | is_unnamed = true; | |
| 486 | } | |
| 487 | name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ container_kind_name, bare_name }); | |
| 488 | if (toplevel and !is_unnamed) { | |
| 489 | name = try t.mangleWeakGlobalName(name); | |
| 490 | } | |
| 491 | } | |
| 492 | if (!toplevel) name = try bs.makeMangledName(name); | |
| 493 | try t.type_decls.putNoClobber(t.gpa, record_ty.decl_node, name); | |
| 494 | ||
| 495 | const is_pub = toplevel and !is_unnamed; | |
| 496 | const init_node = init: { | |
| 497 | if (record_ty.layout == null) { | |
| 498 | try t.opaque_demotes.put(t.gpa, base.qt, {}); | |
| 499 | break :init ZigTag.opaque_literal.init(); | |
| 500 | } | |
| 501 | ||
| 502 | var fields = try std.ArrayList(ast.Payload.Container.Field).initCapacity(t.gpa, record_ty.fields.len); | |
| 503 | defer fields.deinit(); | |
| 504 | ||
| 505 | var functions = std.ArrayList(ZigNode).init(t.gpa); | |
| 506 | defer functions.deinit(); | |
| 507 | ||
| 508 | var unnamed_field_count: u32 = 0; | |
| 509 | ||
| 510 | // If a record doesn't have any attributes that would affect the alignment and | |
| 511 | // layout, then we can just use a simple `extern` type. If it does have attributes, | |
| 512 | // then we need to inspect the layout and assign an `align` value for each field. | |
| 513 | const has_alignment_attributes = aligned: { | |
| 514 | if (record_qt.hasAttribute(t.comp, .@"packed")) break :aligned true; | |
| 515 | if (record_qt.hasAttribute(t.comp, .aligned)) break :aligned true; | |
| 516 | for (record_ty.fields) |field| { | |
| 517 | const field_attrs = field.attributes(t.comp); | |
| 518 | for (field_attrs) |field_attr| { | |
| 519 | switch (field_attr.tag) { | |
| 520 | .@"packed", .aligned => break :aligned true, | |
| 521 | else => {}, | |
| 522 | } | |
| 523 | } | |
| 524 | } | |
| 525 | break :aligned false; | |
| 526 | }; | |
| 527 | const head_field_alignment: ?c_uint = if (has_alignment_attributes) t.headFieldAlignment(record_ty) else null; | |
| 528 | ||
| 529 | for (record_ty.fields, 0..) |field, field_index| { | |
| 530 | const field_loc = field.name_tok; | |
| 531 | ||
| 532 | // Demote record to opaque if it contains a bitfield | |
| 533 | if (field.bit_width != .null) { | |
| 534 | try t.opaque_demotes.put(t.gpa, base.qt, {}); | |
| 535 | try t.warn(scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name}); | |
| 536 | break :init ZigTag.opaque_literal.init(); | |
| 537 | } | |
| 538 | ||
| 539 | var field_name = field.name.lookup(t.comp); | |
| 540 | if (field.name_tok == 0) { | |
| 541 | field_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{unnamed_field_count}); | |
| 542 | unnamed_field_count += 1; | |
| 543 | try t.anonymous_record_field_names.put(t.gpa, .{ | |
| 544 | .parent = base.qt, | |
| 545 | .field = field.qt, | |
| 546 | }, field_name); | |
| 547 | } | |
| 548 | ||
| 549 | const field_alignment = if (has_alignment_attributes) | |
| 550 | t.alignmentForField(record_ty, head_field_alignment, field_index) | |
| 551 | else | |
| 552 | null; | |
| 553 | ||
| 554 | const field_type = field_type: { | |
| 555 | // Check if this is a flexible array member. | |
| 556 | flexible: { | |
| 557 | if (field_index != record_ty.fields.len - 1 and container_kind != .@"union") break :flexible; | |
| 558 | const array_ty = field.qt.get(t.comp, .array) orelse break :flexible; | |
| 559 | if (array_ty.len != .incomplete and (array_ty.len != .fixed or array_ty.len.fixed != 0)) break :flexible; | |
| 560 | ||
| 561 | const elem_type = t.transType(scope, array_ty.elem, field_loc) catch |err| switch (err) { | |
| 562 | error.UnsupportedType => break :flexible, | |
| 563 | else => |e| return e, | |
| 564 | }; | |
| 565 | const zero_array = try ZigTag.array_type.create(t.arena, .{ .len = 0, .elem_type = elem_type }); | |
| 566 | ||
| 567 | const member_name = field_name; | |
| 568 | field_name = try std.fmt.allocPrint(t.arena, "_{s}", .{field_name}); | |
| 569 | ||
| 570 | const member = try t.createFlexibleMemberFn(member_name, field_name); | |
| 571 | try functions.append(member); | |
| 572 | ||
| 573 | break :field_type zero_array; | |
| 574 | } | |
| 575 | ||
| 576 | break :field_type t.transType(scope, field.qt, field_loc) catch |err| switch (err) { | |
| 577 | error.UnsupportedType => { | |
| 578 | try t.opaque_demotes.put(t.gpa, base.qt, {}); | |
| 579 | try t.warn(scope, field.name_tok, "{s} demoted to opaque type - unable to translate type of field {s}", .{ | |
| 580 | container_kind_name, | |
| 581 | field_name, | |
| 582 | }); | |
| 583 | break :init ZigTag.opaque_literal.init(); | |
| 584 | }, | |
| 585 | else => |e| return e, | |
| 586 | }; | |
| 587 | }; | |
| 588 | ||
| 589 | // C99 introduced designated initializers for structs. Omitted fields are implicitly | |
| 590 | // initialized to zero. Some C APIs are designed with this in mind. Defaulting to zero | |
| 591 | // values for translated struct fields permits Zig code to comfortably use such an API. | |
| 592 | const default_value = if (container_kind == .@"struct") | |
| 593 | try t.createZeroValueNode(field.qt, field_type, .no_as) | |
| 594 | else | |
| 595 | null; | |
| 596 | ||
| 597 | fields.appendAssumeCapacity(.{ | |
| 598 | .name = field_name, | |
| 599 | .type = field_type, | |
| 600 | .alignment = field_alignment, | |
| 601 | .default_value = default_value, | |
| 602 | }); | |
| 603 | } | |
| 604 | ||
| 605 | // A record is empty if it has no fields or only flexible array fields. | |
| 606 | if (record_ty.fields.len == functions.items.len and | |
| 607 | t.comp.target.os.tag == .windows and t.comp.target.abi == .msvc) | |
| 608 | { | |
| 609 | // In MSVC empty records have the same size as their alignment. | |
| 610 | const padding_bits = record_ty.layout.?.size_bits; | |
| 611 | const alignment_bits = record_ty.layout.?.field_alignment_bits; | |
| 612 | ||
| 613 | try fields.append(.{ | |
| 614 | .name = "_padding", | |
| 615 | .type = try ZigTag.type.create(t.arena, try std.fmt.allocPrint(t.arena, "u{d}", .{padding_bits})), | |
| 616 | .alignment = @divExact(alignment_bits, 8), | |
| 617 | .default_value = if (container_kind == .@"struct") | |
| 618 | ZigTag.zero_literal.init() | |
| 619 | else | |
| 620 | null, | |
| 621 | }); | |
| 622 | } | |
| 623 | ||
| 624 | const container_payload = try t.arena.create(ast.Payload.Container); | |
| 625 | container_payload.* = .{ | |
| 626 | .base = .{ .tag = container_kind }, | |
| 627 | .data = .{ | |
| 628 | .layout = .@"extern", | |
| 629 | .fields = try t.arena.dupe(ast.Payload.Container.Field, fields.items), | |
| 630 | .decls = try t.arena.dupe(ZigNode, functions.items), | |
| 631 | }, | |
| 632 | }; | |
| 633 | break :init ZigNode.initPayload(&container_payload.base); | |
| 634 | }; | |
| 635 | ||
| 636 | const payload = try t.arena.create(ast.Payload.SimpleVarDecl); | |
| 637 | payload.* = .{ | |
| 638 | .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, | |
| 639 | .data = .{ | |
| 640 | .name = name, | |
| 641 | .init = init_node, | |
| 642 | }, | |
| 643 | }; | |
| 644 | const node = ZigNode.initPayload(&payload.base); | |
| 645 | if (toplevel) { | |
| 646 | try t.addTopLevelDecl(name, node); | |
| 647 | // Only add the alias if the name is available *and* it was caught by | |
| 648 | // name detection. Don't bother performing a weak mangle, since a | |
| 649 | // mangled name is of no real use here. | |
| 650 | if (!is_unnamed and !t.global_names.contains(bare_name) and t.weak_global_names.contains(bare_name)) | |
| 651 | try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); | |
| 652 | try t.global_scope.container_member_fns_map.put(t.gpa, record_qt, .{ | |
| 653 | .container_decl_ptr = &payload.data.init, | |
| 654 | }); | |
| 655 | } else { | |
| 656 | try scope.appendNode(node); | |
| 657 | try bs.discardVariable(name); | |
| 658 | } | |
| 659 | } | |
| 660 | ||
| 661 | fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!void { | |
| 662 | const func_ty = function.qt.get(t.comp, .func).?; | |
| 663 | ||
| 664 | const is_pub = scope.id == .root; | |
| 665 | ||
| 666 | const fn_name = t.tree.tokSlice(function.name_tok); | |
| 667 | if (scope.getAlias(fn_name) != null or t.global_scope.containsNow(fn_name)) | |
| 668 | return; // Avoid processing this decl twice | |
| 669 | ||
| 670 | const fn_decl_loc = function.name_tok; | |
| 671 | const has_body = function.body != null and func_ty.kind != .variadic; | |
| 672 | if (function.body != null and func_ty.kind == .variadic) { | |
| 673 | try t.warn(scope, function.name_tok, "TODO unable to translate variadic function, demoted to extern", .{}); | |
| 674 | } | |
| 675 | ||
| 676 | const is_always_inline = has_body and function.qt.getAttribute(t.comp, .always_inline) != null; | |
| 677 | const proto_ctx: FnProtoContext = .{ | |
| 678 | .fn_name = fn_name, | |
| 679 | .is_always_inline = is_always_inline, | |
| 680 | .is_extern = !has_body, | |
| 681 | .is_export = !function.static and has_body and !is_always_inline and !function.@"inline", | |
| 682 | .is_pub = is_pub, | |
| 683 | .has_body = has_body, | |
| 684 | .cc = if (function.qt.getAttribute(t.comp, .calling_convention)) |some| switch (some.cc) { | |
| 685 | .c => .c, | |
| 686 | .stdcall => .x86_stdcall, | |
| 687 | .thiscall => .x86_thiscall, | |
| 688 | .fastcall => .x86_fastcall, | |
| 689 | .regcall => .x86_regcall, | |
| 690 | .riscv_vector => .riscv_vector, | |
| 691 | .aarch64_sve_pcs => .aarch64_sve_pcs, | |
| 692 | .aarch64_vector_pcs => .aarch64_vfabi, | |
| 693 | .arm_aapcs => .arm_aapcs, | |
| 694 | .arm_aapcs_vfp => .arm_aapcs_vfp, | |
| 695 | .vectorcall => switch (t.comp.target.cpu.arch) { | |
| 696 | .x86 => .x86_vectorcall, | |
| 697 | .aarch64, .aarch64_be => .aarch64_vfabi, | |
| 698 | else => .c, | |
| 699 | }, | |
| 700 | .x86_64_sysv => .x86_64_sysv, | |
| 701 | .x86_64_win => .x86_64_win, | |
| 702 | } else .c, | |
| 703 | }; | |
| 704 | ||
| 705 | const proto_node = t.transFnType(&t.global_scope.base, function.qt, func_ty, fn_decl_loc, proto_ctx) catch |err| switch (err) { | |
| 706 | error.UnsupportedType => { | |
| 707 | return t.failDecl(scope, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{}); | |
| 708 | }, | |
| 709 | error.OutOfMemory => |e| return e, | |
| 710 | }; | |
| 711 | ||
| 712 | const proto_payload = proto_node.castTag(.func).?; | |
| 713 | if (!has_body) { | |
| 714 | if (scope.id != .root) { | |
| 715 | const bs: *Scope.Block = try scope.findBlockScope(t); | |
| 716 | const mangled_name = try bs.createMangledName(fn_name, false, Scope.Block.extern_local_prefix); | |
| 717 | const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = mangled_name, .init = proto_node }); | |
| 718 | try scope.appendNode(wrapped); | |
| 719 | try bs.discardVariable(mangled_name); | |
| 720 | return; | |
| 721 | } | |
| 722 | try t.global_scope.addMemberFunction(func_ty, proto_payload); | |
| 723 | return t.addTopLevelDecl(fn_name, proto_node); | |
| 724 | } | |
| 725 | ||
| 726 | // actual function definition with body | |
| 727 | const body_stmt = function.body.?.get(t.tree).compound_stmt; | |
| 728 | var block_scope = try Scope.Block.init(t, &t.global_scope.base, false); | |
| 729 | block_scope.return_type = func_ty.return_type; | |
| 730 | defer block_scope.deinit(); | |
| 731 | ||
| 732 | var param_id: c_uint = 0; | |
| 733 | for (proto_payload.data.params, func_ty.params) |*param, param_info| { | |
| 734 | const param_name = param.name orelse { | |
| 735 | proto_payload.data.is_extern = true; | |
| 736 | proto_payload.data.is_export = false; | |
| 737 | proto_payload.data.is_inline = false; | |
| 738 | try t.warn(&t.global_scope.base, fn_decl_loc, "function {s} parameter has no name, demoted to extern", .{fn_name}); | |
| 739 | return t.addTopLevelDecl(fn_name, proto_node); | |
| 740 | }; | |
| 741 | ||
| 742 | const is_const = param_info.qt.@"const"; | |
| 743 | ||
| 744 | const mangled_param_name = try block_scope.makeMangledName(param_name); | |
| 745 | param.name = mangled_param_name; | |
| 746 | ||
| 747 | if (!is_const) { | |
| 748 | const bare_arg_name = try std.fmt.allocPrint(t.arena, "arg_{s}", .{mangled_param_name}); | |
| 749 | const arg_name = try block_scope.makeMangledName(bare_arg_name); | |
| 750 | param.name = arg_name; | |
| 751 | ||
| 752 | const redecl_node = try ZigTag.arg_redecl.create(t.arena, .{ .actual = mangled_param_name, .mangled = arg_name }); | |
| 753 | try block_scope.statements.append(t.gpa, redecl_node); | |
| 754 | } | |
| 755 | try block_scope.discardVariable(mangled_param_name); | |
| 756 | ||
| 757 | param_id += 1; | |
| 758 | } | |
| 759 | ||
| 760 | t.transCompoundStmtInline(body_stmt, &block_scope) catch |err| switch (err) { | |
| 761 | error.OutOfMemory => |e| return e, | |
| 762 | error.UnsupportedTranslation, | |
| 763 | error.UnsupportedType, | |
| 764 | => { | |
| 765 | proto_payload.data.is_extern = true; | |
| 766 | proto_payload.data.is_export = false; | |
| 767 | proto_payload.data.is_inline = false; | |
| 768 | try t.warn(&t.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{}); | |
| 769 | return t.addTopLevelDecl(fn_name, proto_node); | |
| 770 | }, | |
| 771 | }; | |
| 772 | ||
| 773 | try t.global_scope.addMemberFunction(func_ty, proto_payload); | |
| 774 | proto_payload.data.body = try block_scope.complete(); | |
| 775 | return t.addTopLevelDecl(fn_name, proto_node); | |
| 776 | } | |
| 777 | ||
| 778 | fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!void { | |
| 779 | const base_name = t.tree.tokSlice(variable.name_tok); | |
| 780 | const toplevel = scope.id == .root; | |
| 781 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; | |
| 782 | const name, const use_base_name = blk: { | |
| 783 | if (toplevel) break :blk .{ base_name, false }; | |
| 784 | ||
| 785 | // Local extern and static variables are wrapped in a struct. | |
| 786 | const prefix: ?[]const u8 = switch (variable.storage_class) { | |
| 787 | .@"extern" => Scope.Block.extern_local_prefix, | |
| 788 | .static => Scope.Block.static_local_prefix, | |
| 789 | else => null, | |
| 790 | }; | |
| 791 | break :blk .{ try bs.createMangledName(base_name, false, prefix), prefix != null }; | |
| 792 | }; | |
| 793 | ||
| 794 | if (t.typeWasDemotedToOpaque(variable.qt)) { | |
| 795 | if (variable.storage_class != .@"extern" and scope.id == .root) { | |
| 796 | return t.failDecl(scope, variable.name_tok, name, "non-extern variable has opaque type", .{}); | |
| 797 | } else { | |
| 798 | return t.failDecl(scope, variable.name_tok, name, "local variable has opaque type", .{}); | |
| 799 | } | |
| 800 | } | |
| 801 | ||
| 802 | const type_node = (if (variable.initializer) |init| | |
| 803 | t.transTypeInit(scope, variable.qt, init, variable.name_tok) | |
| 804 | else | |
| 805 | t.transType(scope, variable.qt, variable.name_tok)) catch |err| switch (err) { | |
| 806 | error.UnsupportedType => { | |
| 807 | return t.failDecl(scope, variable.name_tok, name, "unable to translate variable declaration type", .{}); | |
| 808 | }, | |
| 809 | else => |e| return e, | |
| 810 | }; | |
| 811 | ||
| 812 | const array_ty = variable.qt.get(t.comp, .array); | |
| 813 | var is_const = variable.qt.@"const" or (array_ty != null and array_ty.?.elem.@"const"); | |
| 814 | var is_extern = variable.storage_class == .@"extern"; | |
| 815 | ||
| 816 | const init_node = init: { | |
| 817 | if (variable.initializer) |init| { | |
| 818 | const maybe_literal = init.get(t.tree); | |
| 819 | const init_node = (if (maybe_literal == .string_literal_expr) | |
| 820 | t.transStringLiteralInitializer(init, maybe_literal.string_literal_expr, type_node) | |
| 821 | else | |
| 822 | t.transExprCoercing(scope, init, .used)) catch |err| switch (err) { | |
| 823 | error.UnsupportedTranslation, error.UnsupportedType => { | |
| 824 | return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{}); | |
| 825 | }, | |
| 826 | else => |e| return e, | |
| 827 | }; | |
| 828 | ||
| 829 | if (!variable.qt.is(t.comp, .bool) and init_node.isBoolRes()) { | |
| 830 | break :init try ZigTag.int_from_bool.create(t.arena, init_node); | |
| 831 | } else { | |
| 832 | break :init init_node; | |
| 833 | } | |
| 834 | } | |
| 835 | if (variable.storage_class == .@"extern") { | |
| 836 | if (array_ty != null and array_ty.?.len == .incomplete) { | |
| 837 | // Oh no, an extern array of unknown size! These are really fun because there's no | |
| 838 | // direct equivalent in Zig. To translate correctly, we'll have to create a C-pointer | |
| 839 | // to the data initialized via @extern. | |
| 840 | ||
| 841 | // Since this is really a pointer to the underlying data, we tweak a few properties. | |
| 842 | is_extern = false; | |
| 843 | is_const = true; | |
| 844 | ||
| 845 | const name_str = try std.fmt.allocPrint(t.arena, "\"{s}\"", .{base_name}); | |
| 846 | break :init try ZigTag.builtin_extern.create(t.arena, .{ | |
| 847 | .type = type_node, | |
| 848 | .name = try ZigTag.string_literal.create(t.arena, name_str), | |
| 849 | }); | |
| 850 | } | |
| 851 | break :init null; | |
| 852 | } | |
| 853 | if (toplevel or variable.storage_class == .static or variable.thread_local) { | |
| 854 | // The C language specification states that variables with static or threadlocal | |
| 855 | // storage without an initializer are initialized to a zero value. | |
| 856 | break :init try t.createZeroValueNode(variable.qt, type_node, .no_as); | |
| 857 | } | |
| 858 | break :init ZigTag.undefined_literal.init(); | |
| 859 | }; | |
| 860 | ||
| 861 | const linksection_string = blk: { | |
| 862 | if (variable.qt.getAttribute(t.comp, .section)) |section| { | |
| 863 | break :blk t.comp.interner.get(section.name.ref()).bytes; | |
| 864 | } | |
| 865 | break :blk null; | |
| 866 | }; | |
| 867 | ||
| 868 | const alignment: ?c_uint = variable.qt.requestedAlignment(t.comp) orelse null; | |
| 869 | var node = try ZigTag.var_decl.create(t.arena, .{ | |
| 870 | .is_pub = toplevel, | |
| 871 | .is_const = is_const, | |
| 872 | .is_extern = is_extern, | |
| 873 | .is_export = toplevel and variable.storage_class == .auto, | |
| 874 | .is_threadlocal = variable.thread_local, | |
| 875 | .linksection_string = linksection_string, | |
| 876 | .alignment = alignment, | |
| 877 | .name = if (use_base_name) base_name else name, | |
| 878 | .type = type_node, | |
| 879 | .init = init_node, | |
| 880 | }); | |
| 881 | ||
| 882 | if (toplevel) { | |
| 883 | try t.addTopLevelDecl(name, node); | |
| 884 | } else { | |
| 885 | if (use_base_name) { | |
| 886 | node = try ZigTag.wrapped_local.create(t.arena, .{ .name = name, .init = node }); | |
| 887 | } | |
| 888 | try scope.appendNode(node); | |
| 889 | try bs.discardVariable(name); | |
| 890 | ||
| 891 | if (variable.qt.getAttribute(t.comp, .cleanup)) |cleanup_attr| { | |
| 892 | const cleanup_fn_name = t.tree.tokSlice(cleanup_attr.function.tok); | |
| 893 | const mangled_fn_name = scope.getAlias(cleanup_fn_name) orelse cleanup_fn_name; | |
| 894 | const fn_id = try ZigTag.identifier.create(t.arena, mangled_fn_name); | |
| 895 | ||
| 896 | const varname = try ZigTag.identifier.create(t.arena, name); | |
| 897 | const args = try t.arena.alloc(ZigNode, 1); | |
| 898 | args[0] = try ZigTag.address_of.create(t.arena, varname); | |
| 899 | ||
| 900 | const cleanup_call = try ZigTag.call.create(t.arena, .{ .lhs = fn_id, .args = args }); | |
| 901 | const discard = try ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = cleanup_call }); | |
| 902 | const deferred_cleanup = try ZigTag.@"defer".create(t.arena, discard); | |
| 903 | ||
| 904 | try bs.statements.append(t.gpa, deferred_cleanup); | |
| 905 | } | |
| 906 | } | |
| 907 | } | |
| 908 | ||
| 909 | fn transEnumDecl(t: *Translator, scope: *Scope, enum_qt: QualType) Error!void { | |
| 910 | const base = enum_qt.base(t.comp); | |
| 911 | const enum_ty = base.type.@"enum"; | |
| 912 | ||
| 913 | if (t.type_decls.get(enum_ty.decl_node)) |_| | |
| 914 | return; // Avoid processing this decl twice | |
| 915 | ||
| 916 | const toplevel = scope.id == .root; | |
| 917 | const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; | |
| 918 | ||
| 919 | var bare_name = enum_ty.name.lookup(t.comp); | |
| 920 | var is_unnamed = false; | |
| 921 | var name = bare_name; | |
| 922 | if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { | |
| 923 | bare_name = typedef_name; | |
| 924 | name = typedef_name; | |
| 925 | } else { | |
| 926 | if (enum_ty.isAnonymous(t.comp)) { | |
| 927 | bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); | |
| 928 | is_unnamed = true; | |
| 929 | } | |
| 930 | name = try std.fmt.allocPrint(t.arena, "enum_{s}", .{bare_name}); | |
| 931 | } | |
| 932 | if (!toplevel) name = try bs.makeMangledName(name); | |
| 933 | try t.type_decls.putNoClobber(t.gpa, enum_ty.decl_node, name); | |
| 934 | ||
| 935 | const enum_type_node = if (!base.qt.hasIncompleteSize(t.comp)) blk: { | |
| 936 | const enum_decl = enum_ty.decl_node.get(t.tree).enum_decl; | |
| 937 | for (enum_ty.fields, enum_decl.fields) |field, field_node| { | |
| 938 | var enum_val_name = field.name.lookup(t.comp); | |
| 939 | if (!toplevel) { | |
| 940 | enum_val_name = try bs.makeMangledName(enum_val_name); | |
| 941 | } | |
| 942 | ||
| 943 | const enum_const_type_node: ?ZigNode = t.transType(scope, field.qt, field.name_tok) catch |err| switch (err) { | |
| 944 | error.UnsupportedType => null, | |
| 945 | else => |e| return e, | |
| 946 | }; | |
| 947 | ||
| 948 | const val = t.tree.value_map.get(field_node).?; | |
| 949 | const enum_const_def = try ZigTag.enum_constant.create(t.arena, .{ | |
| 950 | .name = enum_val_name, | |
| 951 | .is_public = toplevel, | |
| 952 | .type = enum_const_type_node, | |
| 953 | .value = try t.createIntNode(val), | |
| 954 | }); | |
| 955 | if (toplevel) | |
| 956 | try t.addTopLevelDecl(enum_val_name, enum_const_def) | |
| 957 | else { | |
| 958 | try scope.appendNode(enum_const_def); | |
| 959 | try bs.discardVariable(enum_val_name); | |
| 960 | } | |
| 961 | } | |
| 962 | ||
| 963 | break :blk t.transType(scope, enum_ty.tag.?, enum_decl.name_or_kind_tok) catch |err| switch (err) { | |
| 964 | error.UnsupportedType => { | |
| 965 | return t.failDecl(scope, enum_decl.name_or_kind_tok, name, "unable to translate enum integer type", .{}); | |
| 966 | }, | |
| 967 | else => |e| return e, | |
| 968 | }; | |
| 969 | } else blk: { | |
| 970 | try t.opaque_demotes.put(t.gpa, base.qt, {}); | |
| 971 | break :blk ZigTag.opaque_literal.init(); | |
| 972 | }; | |
| 973 | ||
| 974 | const is_pub = toplevel and !is_unnamed; | |
| 975 | const payload = try t.arena.create(ast.Payload.SimpleVarDecl); | |
| 976 | payload.* = .{ | |
| 977 | .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, | |
| 978 | .data = .{ | |
| 979 | .init = enum_type_node, | |
| 980 | .name = name, | |
| 981 | }, | |
| 982 | }; | |
| 983 | const node = ZigNode.initPayload(&payload.base); | |
| 984 | if (toplevel) { | |
| 985 | try t.addTopLevelDecl(name, node); | |
| 986 | if (!is_unnamed) | |
| 987 | try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); | |
| 988 | } else { | |
| 989 | try scope.appendNode(node); | |
| 990 | try bs.discardVariable(name); | |
| 991 | } | |
| 992 | } | |
| 993 | ||
| 994 | fn transStaticAssert(t: *Translator, scope: *Scope, static_assert: Node.StaticAssert) Error!void { | |
| 995 | const condition = t.transExpr(scope, static_assert.cond, .used) catch |err| switch (err) { | |
| 996 | error.UnsupportedTranslation, error.UnsupportedType => { | |
| 997 | return try t.warn(&t.global_scope.base, static_assert.cond.tok(t.tree), "unable to translate _Static_assert condition", .{}); | |
| 998 | }, | |
| 999 | error.OutOfMemory => |e| return e, | |
| 1000 | }; | |
| 1001 | ||
| 1002 | // generate @compileError message that matches C compiler output | |
| 1003 | const diagnostic = if (static_assert.message) |message| str: { | |
| 1004 | // Aro guarantees this to be a string literal. | |
| 1005 | const str_val = t.tree.value_map.get(message).?; | |
| 1006 | const str_qt = message.qt(t.tree); | |
| 1007 | ||
| 1008 | const bytes = t.comp.interner.get(str_val.ref()).bytes; | |
| 1009 | var allocating: std.Io.Writer.Allocating = .init(t.gpa); | |
| 1010 | defer allocating.deinit(); | |
| 1011 | ||
| 1012 | allocating.writer.writeAll("\"static assertion failed \\") catch return error.OutOfMemory; | |
| 1013 | ||
| 1014 | aro.Value.printString(bytes, str_qt, t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 1015 | allocating.writer.end -= 1; // printString adds a terminating " so we need to remove it | |
| 1016 | allocating.writer.writeAll("\\\"\"") catch return error.OutOfMemory; | |
| 1017 | ||
| 1018 | break :str try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); | |
| 1019 | } else try ZigTag.string_literal.create(t.arena, "\"static assertion failed\""); | |
| 1020 | ||
| 1021 | const assert_node = try ZigTag.static_assert.create(t.arena, .{ .lhs = condition, .rhs = diagnostic }); | |
| 1022 | try scope.appendNode(assert_node); | |
| 1023 | } | |
| 1024 | ||
| 1025 | fn transGlobalAsm(t: *Translator, scope: *Scope, global_asm: Node.SimpleAsm) Error!void { | |
| 1026 | const asm_string = t.tree.value_map.get(global_asm.asm_str).?; | |
| 1027 | const bytes = t.comp.interner.get(asm_string.ref()).bytes; | |
| 1028 | ||
| 1029 | var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); | |
| 1030 | defer allocating.deinit(); | |
| 1031 | aro.Value.printString(bytes, global_asm.asm_str.qt(t.tree), t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 1032 | ||
| 1033 | const str_node = try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); | |
| 1034 | ||
| 1035 | const asm_node = try ZigTag.asm_simple.create(t.arena, str_node); | |
| 1036 | const block = try ZigTag.block_single.create(t.arena, asm_node); | |
| 1037 | const comptime_node = try ZigTag.@"comptime".create(t.arena, block); | |
| 1038 | ||
| 1039 | try scope.appendNode(comptime_node); | |
| 1040 | } | |
| 1041 | ||
| 1042 | // ================ | |
| 1043 | // Type translation | |
| 1044 | // ================ | |
| 1045 | ||
| 1046 | fn getTypeStr(t: *Translator, qt: QualType) ![]const u8 { | |
| 1047 | var allocating: std.Io.Writer.Allocating = .init(t.gpa); | |
| 1048 | defer allocating.deinit(); | |
| 1049 | qt.print(t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 1050 | return t.arena.dupe(u8, allocating.getWritten()); | |
| 1051 | } | |
| 1052 | ||
| 1053 | fn transType(t: *Translator, scope: *Scope, qt: QualType, source_loc: TokenIndex) TypeError!ZigNode { | |
| 1054 | loop: switch (qt.type(t.comp)) { | |
| 1055 | .atomic => { | |
| 1056 | const type_name = try t.getTypeStr(qt); | |
| 1057 | return t.fail(error.UnsupportedType, source_loc, "TODO support atomic type: '{s}'", .{type_name}); | |
| 1058 | }, | |
| 1059 | .void => return ZigTag.type.create(t.arena, "anyopaque"), | |
| 1060 | .bool => return ZigTag.type.create(t.arena, "bool"), | |
| 1061 | .int => |int_ty| switch (int_ty) { | |
| 1062 | //.char => return ZigTag.type.create(t.arena, "c_char"), // TODO: this is the preferred translation | |
| 1063 | .char => return ZigTag.type.create(t.arena, "u8"), | |
| 1064 | .schar => return ZigTag.type.create(t.arena, "i8"), | |
| 1065 | .uchar => return ZigTag.type.create(t.arena, "u8"), | |
| 1066 | .short => return ZigTag.type.create(t.arena, "c_short"), | |
| 1067 | .ushort => return ZigTag.type.create(t.arena, "c_ushort"), | |
| 1068 | .int => return ZigTag.type.create(t.arena, "c_int"), | |
| 1069 | .uint => return ZigTag.type.create(t.arena, "c_uint"), | |
| 1070 | .long => return ZigTag.type.create(t.arena, "c_long"), | |
| 1071 | .ulong => return ZigTag.type.create(t.arena, "c_ulong"), | |
| 1072 | .long_long => return ZigTag.type.create(t.arena, "c_longlong"), | |
| 1073 | .ulong_long => return ZigTag.type.create(t.arena, "c_ulonglong"), | |
| 1074 | .int128 => return ZigTag.type.create(t.arena, "i128"), | |
| 1075 | .uint128 => return ZigTag.type.create(t.arena, "u128"), | |
| 1076 | }, | |
| 1077 | .float => |float_ty| switch (float_ty) { | |
| 1078 | .fp16, .float16 => return ZigTag.type.create(t.arena, "f16"), | |
| 1079 | .float => return ZigTag.type.create(t.arena, "f32"), | |
| 1080 | .double => return ZigTag.type.create(t.arena, "f64"), | |
| 1081 | .long_double => return ZigTag.type.create(t.arena, "c_longdouble"), | |
| 1082 | .float128 => return ZigTag.type.create(t.arena, "f128"), | |
| 1083 | }, | |
| 1084 | .pointer => |pointer_ty| { | |
| 1085 | const child_qt = pointer_ty.child; | |
| 1086 | ||
| 1087 | const is_fn_proto = child_qt.is(t.comp, .func); | |
| 1088 | const is_const = is_fn_proto or child_qt.@"const"; | |
| 1089 | const is_volatile = child_qt.@"volatile"; | |
| 1090 | const elem_type = try t.transType(scope, child_qt, source_loc); | |
| 1091 | const ptr_info: @FieldType(ast.Payload.Pointer, "data") = .{ | |
| 1092 | .is_const = is_const, | |
| 1093 | .is_volatile = is_volatile, | |
| 1094 | .elem_type = elem_type, | |
| 1095 | .is_allowzero = false, | |
| 1096 | }; | |
| 1097 | if (is_fn_proto or | |
| 1098 | t.typeIsOpaque(child_qt) or | |
| 1099 | t.typeWasDemotedToOpaque(child_qt)) | |
| 1100 | { | |
| 1101 | const ptr = try ZigTag.single_pointer.create(t.arena, ptr_info); | |
| 1102 | return ZigTag.optional_type.create(t.arena, ptr); | |
| 1103 | } | |
| 1104 | ||
| 1105 | return ZigTag.c_pointer.create(t.arena, ptr_info); | |
| 1106 | }, | |
| 1107 | .array => |array_ty| { | |
| 1108 | const elem_qt = array_ty.elem; | |
| 1109 | switch (array_ty.len) { | |
| 1110 | .incomplete, .unspecified_variable => { | |
| 1111 | const elem_type = try t.transType(scope, elem_qt, source_loc); | |
| 1112 | return ZigTag.c_pointer.create(t.arena, .{ | |
| 1113 | .is_const = elem_qt.@"const", | |
| 1114 | .is_volatile = elem_qt.@"volatile", | |
| 1115 | .is_allowzero = false, | |
| 1116 | .elem_type = elem_type, | |
| 1117 | }); | |
| 1118 | }, | |
| 1119 | .fixed, .static => |len| { | |
| 1120 | const elem_type = try t.transType(scope, elem_qt, source_loc); | |
| 1121 | return ZigTag.array_type.create(t.arena, .{ .len = len, .elem_type = elem_type }); | |
| 1122 | }, | |
| 1123 | .variable => return t.fail(error.UnsupportedType, source_loc, "VLA unsupported '{s}'", .{try t.getTypeStr(qt)}), | |
| 1124 | } | |
| 1125 | }, | |
| 1126 | .func => |func_ty| return t.transFnType(scope, qt, func_ty, source_loc, .{}), | |
| 1127 | .@"struct", .@"union" => |record_ty| { | |
| 1128 | var trans_scope = scope; | |
| 1129 | if (!record_ty.isAnonymous(t.comp)) { | |
| 1130 | if (t.weak_global_names.contains(record_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; | |
| 1131 | } | |
| 1132 | try t.transRecordDecl(trans_scope, qt); | |
| 1133 | const name = t.type_decls.get(record_ty.decl_node).?; | |
| 1134 | return ZigTag.identifier.create(t.arena, name); | |
| 1135 | }, | |
| 1136 | .@"enum" => |enum_ty| { | |
| 1137 | var trans_scope = scope; | |
| 1138 | const is_anonymous = enum_ty.isAnonymous(t.comp); | |
| 1139 | if (!is_anonymous) { | |
| 1140 | if (t.weak_global_names.contains(enum_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; | |
| 1141 | } | |
| 1142 | try t.transEnumDecl(trans_scope, qt); | |
| 1143 | const name = t.type_decls.get(enum_ty.decl_node).?; | |
| 1144 | return ZigTag.identifier.create(t.arena, name); | |
| 1145 | }, | |
| 1146 | .typedef => |typedef_ty| { | |
| 1147 | var trans_scope = scope; | |
| 1148 | const typedef_name = typedef_ty.name.lookup(t.comp); | |
| 1149 | if (builtin_typedef_map.get(typedef_name)) |builtin| return ZigTag.type.create(t.arena, builtin); | |
| 1150 | if (t.global_names.contains(typedef_name)) trans_scope = &t.global_scope.base; | |
| 1151 | ||
| 1152 | try t.transTypeDef(trans_scope, typedef_ty.decl_node); | |
| 1153 | const name = t.type_decls.get(typedef_ty.decl_node).?; | |
| 1154 | return ZigTag.identifier.create(t.arena, name); | |
| 1155 | }, | |
| 1156 | .attributed => |attributed_ty| continue :loop attributed_ty.base.type(t.comp), | |
| 1157 | .typeof => |typeof_ty| continue :loop typeof_ty.base.type(t.comp), | |
| 1158 | .vector => |vector_ty| { | |
| 1159 | const len = try t.createNumberNode(vector_ty.len, .int); | |
| 1160 | const elem_type = try t.transType(scope, vector_ty.elem, source_loc); | |
| 1161 | return ZigTag.vector.create(t.arena, .{ .lhs = len, .rhs = elem_type }); | |
| 1162 | }, | |
| 1163 | else => return t.fail(error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{try t.getTypeStr(qt)}), | |
| 1164 | } | |
| 1165 | } | |
| 1166 | ||
| 1167 | /// Look ahead through the fields of the record to determine what the alignment of the record | |
| 1168 | /// would be without any align/packed/etc. attributes. This helps us determine whether or not | |
| 1169 | /// the fields with 0 offset need an `align` qualifier. Strictly speaking, we could just | |
| 1170 | /// pedantically assign those fields the same alignment as the parent's pointer alignment, | |
| 1171 | /// but this helps the generated code to be a little less verbose. | |
| 1172 | fn headFieldAlignment(t: *Translator, record_decl: aro.Type.Record) ?c_uint { | |
| 1173 | const bits_per_byte = 8; | |
| 1174 | const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; | |
| 1175 | const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; | |
| 1176 | var max_field_alignment_bits: u64 = 0; | |
| 1177 | for (record_decl.fields) |field| { | |
| 1178 | if (field.qt.getRecord(t.comp)) |field_record_decl| { | |
| 1179 | const child_record_alignment = field_record_decl.layout.?.field_alignment_bits; | |
| 1180 | if (child_record_alignment > max_field_alignment_bits) | |
| 1181 | max_field_alignment_bits = child_record_alignment; | |
| 1182 | } else { | |
| 1183 | const field_size = field.layout.size_bits; | |
| 1184 | if (field_size > max_field_alignment_bits) | |
| 1185 | max_field_alignment_bits = field_size; | |
| 1186 | } | |
| 1187 | } | |
| 1188 | if (max_field_alignment_bits != parent_ptr_alignment_bits) { | |
| 1189 | return parent_ptr_alignment; | |
| 1190 | } else { | |
| 1191 | return null; | |
| 1192 | } | |
| 1193 | } | |
| 1194 | ||
| 1195 | /// This function inspects the generated layout of a record to determine the alignment for a | |
| 1196 | /// particular field. This approach is necessary because unlike Zig, a C compiler is not | |
| 1197 | /// required to fulfill the requested alignment, which means we'd risk generating different code | |
| 1198 | /// if we only look at the user-requested alignment. | |
| 1199 | /// | |
| 1200 | /// Returns a ?c_uint to match Clang's behavior of using c_uint. The return type can be changed | |
| 1201 | /// after the Clang frontend for translate-c is removed. A null value indicates that a field is | |
| 1202 | /// 'naturally aligned'. | |
| 1203 | fn alignmentForField( | |
| 1204 | t: *Translator, | |
| 1205 | record_decl: aro.Type.Record, | |
| 1206 | head_field_alignment: ?c_uint, | |
| 1207 | field_index: usize, | |
| 1208 | ) ?c_uint { | |
| 1209 | const fields = record_decl.fields; | |
| 1210 | assert(fields.len != 0); | |
| 1211 | const field = fields[field_index]; | |
| 1212 | ||
| 1213 | const bits_per_byte = 8; | |
| 1214 | const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; | |
| 1215 | const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; | |
| 1216 | ||
| 1217 | // bitfields aren't supported yet. Until support is added, records with bitfields | |
| 1218 | // should be demoted to opaque, and this function shouldn't be called for them. | |
| 1219 | if (field.bit_width != .null) { | |
| 1220 | @panic("TODO: add bitfield support for records"); | |
| 1221 | } | |
| 1222 | ||
| 1223 | const field_offset_bits: u64 = field.layout.offset_bits; | |
| 1224 | const field_size_bits: u64 = field.layout.size_bits; | |
| 1225 | ||
| 1226 | // Fields with zero width always have an alignment of 1 | |
| 1227 | if (field_size_bits == 0) { | |
| 1228 | return 1; | |
| 1229 | } | |
| 1230 | ||
| 1231 | // Fields with 0 offset inherit the parent's pointer alignment. | |
| 1232 | if (field_offset_bits == 0) { | |
| 1233 | return head_field_alignment; | |
| 1234 | } | |
| 1235 | ||
| 1236 | // Records have a natural alignment when used as a field, and their size is | |
| 1237 | // a multiple of this alignment value. For all other types, the natural alignment | |
| 1238 | // is their size. | |
| 1239 | const field_natural_alignment_bits: u64 = if (field.qt.getRecord(t.comp)) |record| | |
| 1240 | record.layout.?.field_alignment_bits | |
| 1241 | else | |
| 1242 | field_size_bits; | |
| 1243 | const rem_bits = field_offset_bits % field_natural_alignment_bits; | |
| 1244 | ||
| 1245 | // If there's a remainder, then the alignment is smaller than the field's | |
| 1246 | // natural alignment | |
| 1247 | if (rem_bits > 0) { | |
| 1248 | const rem_alignment = rem_bits / bits_per_byte; | |
| 1249 | if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) { | |
| 1250 | const actual_alignment = @min(rem_alignment, parent_ptr_alignment); | |
| 1251 | return @as(c_uint, @truncate(actual_alignment)); | |
| 1252 | } else { | |
| 1253 | return 1; | |
| 1254 | } | |
| 1255 | } | |
| 1256 | ||
| 1257 | // A field may have an offset which positions it to be naturally aligned, but the | |
| 1258 | // parent's pointer alignment determines if this is actually true, so we take the minimum | |
| 1259 | // value. | |
| 1260 | // For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural | |
| 1261 | // alignment, but if the parent pointer alignment is 2, then the actual alignment of the | |
| 1262 | // float is 2. | |
| 1263 | const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte; | |
| 1264 | const offset_alignment = field_offset_bits / bits_per_byte; | |
| 1265 | const possible_alignment = @min(parent_ptr_alignment, offset_alignment); | |
| 1266 | if (possible_alignment == field_natural_alignment) { | |
| 1267 | return null; | |
| 1268 | } else if (possible_alignment < field_natural_alignment) { | |
| 1269 | if (std.math.isPowerOfTwo(possible_alignment)) { | |
| 1270 | return possible_alignment; | |
| 1271 | } else { | |
| 1272 | return 1; | |
| 1273 | } | |
| 1274 | } else { // possible_alignment > field_natural_alignment | |
| 1275 | // Here, the field is positioned be at a higher alignment than it's natural alignment. This means we | |
| 1276 | // need to determine whether it's a specified alignment. We can determine that from the padding preceding | |
| 1277 | // the field. | |
| 1278 | const padding_from_prev_field: u64 = blk: { | |
| 1279 | if (field_offset_bits != 0) { | |
| 1280 | const previous_field = fields[field_index - 1]; | |
| 1281 | break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits; | |
| 1282 | } else { | |
| 1283 | break :blk 0; | |
| 1284 | } | |
| 1285 | }; | |
| 1286 | if (padding_from_prev_field < field_natural_alignment_bits) { | |
| 1287 | return null; | |
| 1288 | } else { | |
| 1289 | return possible_alignment; | |
| 1290 | } | |
| 1291 | } | |
| 1292 | } | |
| 1293 | ||
| 1294 | const FnProtoContext = struct { | |
| 1295 | is_pub: bool = false, | |
| 1296 | is_export: bool = false, | |
| 1297 | is_extern: bool = false, | |
| 1298 | is_always_inline: bool = false, | |
| 1299 | fn_name: ?[]const u8 = null, | |
| 1300 | has_body: bool = false, | |
| 1301 | cc: ast.Payload.Func.CallingConvention = .c, | |
| 1302 | }; | |
| 1303 | ||
| 1304 | fn transFnType( | |
| 1305 | t: *Translator, | |
| 1306 | scope: *Scope, | |
| 1307 | func_qt: QualType, | |
| 1308 | func_ty: aro.Type.Func, | |
| 1309 | source_loc: TokenIndex, | |
| 1310 | ctx: FnProtoContext, | |
| 1311 | ) !ZigNode { | |
| 1312 | const param_count: usize = func_ty.params.len; | |
| 1313 | const fn_params = try t.arena.alloc(ast.Payload.Param, param_count); | |
| 1314 | ||
| 1315 | for (func_ty.params, fn_params) |param_info, *param_node| { | |
| 1316 | const param_qt = param_info.qt; | |
| 1317 | const is_noalias = param_qt.restrict; | |
| 1318 | ||
| 1319 | const param_name: ?[]const u8 = if (param_info.name == .empty) | |
| 1320 | null | |
| 1321 | else | |
| 1322 | param_info.name.lookup(t.comp); | |
| 1323 | ||
| 1324 | const type_node = try t.transType(scope, param_qt, param_info.name_tok); | |
| 1325 | param_node.* = .{ | |
| 1326 | .is_noalias = is_noalias, | |
| 1327 | .name = param_name, | |
| 1328 | .type = type_node, | |
| 1329 | }; | |
| 1330 | } | |
| 1331 | ||
| 1332 | const linksection_string = blk: { | |
| 1333 | if (func_qt.getAttribute(t.comp, .section)) |section| { | |
| 1334 | break :blk t.comp.interner.get(section.name.ref()).bytes; | |
| 1335 | } | |
| 1336 | break :blk null; | |
| 1337 | }; | |
| 1338 | ||
| 1339 | const alignment: ?c_uint = func_qt.requestedAlignment(t.comp) orelse null; | |
| 1340 | ||
| 1341 | const explicit_callconv = if ((ctx.is_always_inline or ctx.is_export or ctx.is_extern) and ctx.cc == .c) null else ctx.cc; | |
| 1342 | ||
| 1343 | const return_type_node = blk: { | |
| 1344 | if (func_qt.getAttribute(t.comp, .noreturn) != null) { | |
| 1345 | break :blk ZigTag.noreturn_type.init(); | |
| 1346 | } else { | |
| 1347 | const return_qt = func_ty.return_type; | |
| 1348 | if (return_qt.is(t.comp, .void)) { | |
| 1349 | // convert primitive anyopaque to actual void (only for return type) | |
| 1350 | break :blk ZigTag.void_type.init(); | |
| 1351 | } else { | |
| 1352 | break :blk t.transType(scope, return_qt, source_loc) catch |err| switch (err) { | |
| 1353 | error.UnsupportedType => { | |
| 1354 | try t.warn(scope, source_loc, "unsupported function proto return type", .{}); | |
| 1355 | return err; | |
| 1356 | }, | |
| 1357 | error.OutOfMemory => |e| return e, | |
| 1358 | }; | |
| 1359 | } | |
| 1360 | } | |
| 1361 | }; | |
| 1362 | ||
| 1363 | const payload = try t.arena.create(ast.Payload.Func); | |
| 1364 | payload.* = .{ | |
| 1365 | .base = .{ .tag = .func }, | |
| 1366 | .data = .{ | |
| 1367 | .is_pub = ctx.is_pub, | |
| 1368 | .is_extern = ctx.is_extern, | |
| 1369 | .is_export = ctx.is_export, | |
| 1370 | .is_inline = ctx.is_always_inline, | |
| 1371 | .is_var_args = switch (func_ty.kind) { | |
| 1372 | .normal => false, | |
| 1373 | .variadic => true, | |
| 1374 | .old_style => !ctx.is_export and !ctx.is_always_inline and !ctx.has_body, | |
| 1375 | }, | |
| 1376 | .name = ctx.fn_name, | |
| 1377 | .linksection_string = linksection_string, | |
| 1378 | .explicit_callconv = explicit_callconv, | |
| 1379 | .params = fn_params, | |
| 1380 | .return_type = return_type_node, | |
| 1381 | .body = null, | |
| 1382 | .alignment = alignment, | |
| 1383 | }, | |
| 1384 | }; | |
| 1385 | return ZigNode.initPayload(&payload.base); | |
| 1386 | } | |
| 1387 | ||
| 1388 | /// Produces a Zig AST node by translating a Type, respecting the width, but modifying the signed-ness. | |
| 1389 | /// Asserts the type is an integer. | |
| 1390 | fn transTypeIntWidthOf(t: *Translator, qt: QualType, is_signed: bool) TypeError!ZigNode { | |
| 1391 | return ZigTag.type.create(t.arena, loop: switch (qt.base(t.comp).type) { | |
| 1392 | .int => |int_ty| switch (int_ty) { | |
| 1393 | .char, .schar, .uchar => if (is_signed) "i8" else "u8", | |
| 1394 | .short, .ushort => if (is_signed) "c_short" else "c_ushort", | |
| 1395 | .int, .uint => if (is_signed) "c_int" else "c_uint", | |
| 1396 | .long, .ulong => if (is_signed) "c_long" else "c_ulong", | |
| 1397 | .long_long, .ulong_long => if (is_signed) "c_longlong" else "c_ulonglong", | |
| 1398 | .int128, .uint128 => if (is_signed) "i128" else "u128", | |
| 1399 | }, | |
| 1400 | .bit_int => |bit_int_ty| try std.fmt.allocPrint(t.arena, "{s}{d}", .{ | |
| 1401 | if (is_signed) "i" else "u", | |
| 1402 | bit_int_ty.bits, | |
| 1403 | }), | |
| 1404 | .@"enum" => |enum_ty| blk: { | |
| 1405 | const tag_ty = enum_ty.tag orelse | |
| 1406 | break :blk if (is_signed) "c_int" else "c_uint"; | |
| 1407 | ||
| 1408 | continue :loop tag_ty.base(t.comp).type; | |
| 1409 | }, | |
| 1410 | else => unreachable, // only call this function when it has already been determined the type is int | |
| 1411 | }); | |
| 1412 | } | |
| 1413 | ||
| 1414 | fn transTypeInit( | |
| 1415 | t: *Translator, | |
| 1416 | scope: *Scope, | |
| 1417 | qt: QualType, | |
| 1418 | init: Node.Index, | |
| 1419 | source_loc: TokenIndex, | |
| 1420 | ) TypeError!ZigNode { | |
| 1421 | switch (init.get(t.tree)) { | |
| 1422 | .string_literal_expr => |literal| { | |
| 1423 | const elem_ty = try t.transType(scope, qt.childType(t.comp), source_loc); | |
| 1424 | ||
| 1425 | const string_lit_size = literal.qt.arrayLen(t.comp).?; | |
| 1426 | const array_size = qt.arrayLen(t.comp).?; | |
| 1427 | ||
| 1428 | if (array_size == string_lit_size) { | |
| 1429 | return ZigTag.null_sentinel_array_type.create(t.arena, .{ .len = array_size - 1, .elem_type = elem_ty }); | |
| 1430 | } else { | |
| 1431 | return ZigTag.array_type.create(t.arena, .{ .len = array_size, .elem_type = elem_ty }); | |
| 1432 | } | |
| 1433 | }, | |
| 1434 | else => {}, | |
| 1435 | } | |
| 1436 | return t.transType(scope, qt, source_loc); | |
| 1437 | } | |
| 1438 | ||
| 1439 | // ============ | |
| 1440 | // Type helpers | |
| 1441 | // ============ | |
| 1442 | ||
| 1443 | fn typeIsOpaque(t: *Translator, qt: QualType) bool { | |
| 1444 | return switch (qt.base(t.comp).type) { | |
| 1445 | .void => true, | |
| 1446 | .@"struct", .@"union" => |record_ty| { | |
| 1447 | if (record_ty.layout == null) return true; | |
| 1448 | for (record_ty.fields) |field| { | |
| 1449 | if (field.bit_width != .null) return true; | |
| 1450 | } | |
| 1451 | return false; | |
| 1452 | }, | |
| 1453 | else => false, | |
| 1454 | }; | |
| 1455 | } | |
| 1456 | ||
| 1457 | fn typeWasDemotedToOpaque(t: *Translator, qt: QualType) bool { | |
| 1458 | const base = qt.base(t.comp); | |
| 1459 | switch (base.type) { | |
| 1460 | .@"struct", .@"union" => |record_ty| { | |
| 1461 | if (t.opaque_demotes.contains(base.qt)) return true; | |
| 1462 | for (record_ty.fields) |field| { | |
| 1463 | if (t.typeWasDemotedToOpaque(field.qt)) return true; | |
| 1464 | } | |
| 1465 | return false; | |
| 1466 | }, | |
| 1467 | .@"enum" => return t.opaque_demotes.contains(base.qt), | |
| 1468 | else => return false, | |
| 1469 | } | |
| 1470 | } | |
| 1471 | ||
| 1472 | fn typeHasWrappingOverflow(t: *Translator, qt: QualType) bool { | |
| 1473 | if (t.signedness(qt) == .unsigned) { | |
| 1474 | // unsigned integer overflow wraps around. | |
| 1475 | return true; | |
| 1476 | } else { | |
| 1477 | // float, signed integer, and pointer overflow is undefined behavior. | |
| 1478 | return false; | |
| 1479 | } | |
| 1480 | } | |
| 1481 | ||
| 1482 | /// Signedness of type when translated to Zig. | |
| 1483 | /// Different from `QualType.signedness()` for `char` and enums. | |
| 1484 | /// Returns null for non-int types. | |
| 1485 | fn signedness(t: *Translator, qt: QualType) ?std.builtin.Signedness { | |
| 1486 | return loop: switch (qt.base(t.comp).type) { | |
| 1487 | .bool => .unsigned, | |
| 1488 | .bit_int => |bit_int| bit_int.signedness, | |
| 1489 | .int => |int_ty| switch (int_ty) { | |
| 1490 | .char => .unsigned, // Always translated as u8 | |
| 1491 | .schar, .short, .int, .long, .long_long, .int128 => .signed, | |
| 1492 | .uchar, .ushort, .uint, .ulong, .ulong_long, .uint128 => .unsigned, | |
| 1493 | }, | |
| 1494 | .@"enum" => |enum_ty| { | |
| 1495 | const tag_qt = enum_ty.tag orelse return .signed; | |
| 1496 | continue :loop tag_qt.base(t.comp).type; | |
| 1497 | }, | |
| 1498 | else => return null, | |
| 1499 | }; | |
| 1500 | } | |
| 1501 | ||
| 1502 | // ===================== | |
| 1503 | // Statement translation | |
| 1504 | // ===================== | |
| 1505 | ||
| 1506 | fn transStmt(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { | |
| 1507 | switch (stmt.get(t.tree)) { | |
| 1508 | .compound_stmt => |compound| { | |
| 1509 | return t.transCompoundStmt(scope, compound); | |
| 1510 | }, | |
| 1511 | .static_assert => |static_assert| { | |
| 1512 | try t.transStaticAssert(scope, static_assert); | |
| 1513 | return ZigTag.declaration.init(); | |
| 1514 | }, | |
| 1515 | .return_stmt => |return_stmt| return t.transReturnStmt(scope, return_stmt), | |
| 1516 | .null_stmt => return ZigTag.empty_block.init(), | |
| 1517 | .if_stmt => |if_stmt| return t.transIfStmt(scope, if_stmt), | |
| 1518 | .while_stmt => |while_stmt| return t.transWhileStmt(scope, while_stmt), | |
| 1519 | .do_while_stmt => |do_while_stmt| return t.transDoWhileStmt(scope, do_while_stmt), | |
| 1520 | .for_stmt => |for_stmt| return t.transForStmt(scope, for_stmt), | |
| 1521 | .continue_stmt => return ZigTag.@"continue".init(), | |
| 1522 | .break_stmt => return ZigTag.@"break".init(), | |
| 1523 | .typedef => |typedef_decl| { | |
| 1524 | assert(!typedef_decl.implicit); | |
| 1525 | try t.transTypeDef(scope, stmt); | |
| 1526 | return ZigTag.declaration.init(); | |
| 1527 | }, | |
| 1528 | .struct_decl, .union_decl => |record_decl| { | |
| 1529 | try t.transRecordDecl(scope, record_decl.container_qt); | |
| 1530 | return ZigTag.declaration.init(); | |
| 1531 | }, | |
| 1532 | .enum_decl => |enum_decl| { | |
| 1533 | try t.transEnumDecl(scope, enum_decl.container_qt); | |
| 1534 | return ZigTag.declaration.init(); | |
| 1535 | }, | |
| 1536 | .function => |function| { | |
| 1537 | try t.transFnDecl(scope, function); | |
| 1538 | return ZigTag.declaration.init(); | |
| 1539 | }, | |
| 1540 | .variable => |variable| { | |
| 1541 | try t.transVarDecl(scope, variable); | |
| 1542 | return ZigTag.declaration.init(); | |
| 1543 | }, | |
| 1544 | .switch_stmt => |switch_stmt| return t.transSwitch(scope, switch_stmt), | |
| 1545 | .case_stmt, .default_stmt => { | |
| 1546 | return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO complex switch", .{}); | |
| 1547 | }, | |
| 1548 | .goto_stmt, .computed_goto_stmt, .labeled_stmt => { | |
| 1549 | return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO goto", .{}); | |
| 1550 | }, | |
| 1551 | else => return t.transExprCoercing(scope, stmt, .unused), | |
| 1552 | } | |
| 1553 | } | |
| 1554 | ||
| 1555 | fn transCompoundStmtInline(t: *Translator, compound: Node.CompoundStmt, block: *Scope.Block) TransError!void { | |
| 1556 | for (compound.body) |stmt| { | |
| 1557 | const result = try t.transStmt(&block.base, stmt); | |
| 1558 | switch (result.tag()) { | |
| 1559 | .declaration, .empty_block => {}, | |
| 1560 | else => try block.statements.append(t.gpa, result), | |
| 1561 | } | |
| 1562 | } | |
| 1563 | } | |
| 1564 | ||
| 1565 | fn transCompoundStmt(t: *Translator, scope: *Scope, compound: Node.CompoundStmt) TransError!ZigNode { | |
| 1566 | var block_scope = try Scope.Block.init(t, scope, false); | |
| 1567 | defer block_scope.deinit(); | |
| 1568 | try t.transCompoundStmtInline(compound, &block_scope); | |
| 1569 | return try block_scope.complete(); | |
| 1570 | } | |
| 1571 | ||
| 1572 | fn transReturnStmt(t: *Translator, scope: *Scope, return_stmt: Node.ReturnStmt) TransError!ZigNode { | |
| 1573 | switch (return_stmt.operand) { | |
| 1574 | .none => return ZigTag.return_void.init(), | |
| 1575 | .expr => |operand| { | |
| 1576 | var rhs = try t.transExprCoercing(scope, operand, .used); | |
| 1577 | const return_qt = scope.findBlockReturnType(); | |
| 1578 | if (rhs.isBoolRes() and !return_qt.is(t.comp, .bool)) { | |
| 1579 | rhs = try ZigTag.int_from_bool.create(t.arena, rhs); | |
| 1580 | } | |
| 1581 | return ZigTag.@"return".create(t.arena, rhs); | |
| 1582 | }, | |
| 1583 | .implicit => |zero| { | |
| 1584 | if (zero) return ZigTag.@"return".create(t.arena, ZigTag.zero_literal.init()); | |
| 1585 | ||
| 1586 | const return_qt = scope.findBlockReturnType(); | |
| 1587 | if (return_qt.is(t.comp, .void)) return ZigTag.empty_block.init(); | |
| 1588 | ||
| 1589 | return ZigTag.@"return".create(t.arena, ZigTag.undefined_literal.init()); | |
| 1590 | }, | |
| 1591 | } | |
| 1592 | } | |
| 1593 | ||
| 1594 | /// If a statement can possibly translate to a Zig assignment (either directly because it's | |
| 1595 | /// an assignment in C or indirectly via result assignment to `_`) AND it's the sole statement | |
| 1596 | /// in the body of an if statement or loop, then we need to put the statement into its own block. | |
| 1597 | /// The `else` case here corresponds to statements that could result in an assignment. If a statement | |
| 1598 | /// class never needs a block, add its enum to the top prong. | |
| 1599 | fn maybeBlockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { | |
| 1600 | switch (stmt.get(t.tree)) { | |
| 1601 | .break_stmt, | |
| 1602 | .continue_stmt, | |
| 1603 | .compound_stmt, | |
| 1604 | .decl_ref_expr, | |
| 1605 | .enumeration_ref, | |
| 1606 | .do_while_stmt, | |
| 1607 | .for_stmt, | |
| 1608 | .if_stmt, | |
| 1609 | .return_stmt, | |
| 1610 | .null_stmt, | |
| 1611 | .while_stmt, | |
| 1612 | => return t.transStmt(scope, stmt), | |
| 1613 | else => return t.blockify(scope, stmt), | |
| 1614 | } | |
| 1615 | } | |
| 1616 | ||
| 1617 | /// Translate statement and place it in its own block. | |
| 1618 | fn blockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { | |
| 1619 | var block_scope = try Scope.Block.init(t, scope, false); | |
| 1620 | defer block_scope.deinit(); | |
| 1621 | const result = try t.transStmt(&block_scope.base, stmt); | |
| 1622 | try block_scope.statements.append(t.gpa, result); | |
| 1623 | return block_scope.complete(); | |
| 1624 | } | |
| 1625 | ||
| 1626 | fn transIfStmt(t: *Translator, scope: *Scope, if_stmt: Node.IfStmt) TransError!ZigNode { | |
| 1627 | var cond_scope: Scope.Condition = .{ | |
| 1628 | .base = .{ | |
| 1629 | .parent = scope, | |
| 1630 | .id = .condition, | |
| 1631 | }, | |
| 1632 | }; | |
| 1633 | defer cond_scope.deinit(); | |
| 1634 | const cond = try t.transBoolExpr(&cond_scope.base, if_stmt.cond); | |
| 1635 | ||
| 1636 | // block needed to keep else statement from attaching to inner while | |
| 1637 | const must_blockify = (if_stmt.else_body != null) and switch (if_stmt.then_body.get(t.tree)) { | |
| 1638 | .while_stmt, .do_while_stmt, .for_stmt => true, | |
| 1639 | else => false, | |
| 1640 | }; | |
| 1641 | ||
| 1642 | const then_node = if (must_blockify) | |
| 1643 | try t.blockify(scope, if_stmt.then_body) | |
| 1644 | else | |
| 1645 | try t.maybeBlockify(scope, if_stmt.then_body); | |
| 1646 | ||
| 1647 | const else_node = if (if_stmt.else_body) |stmt| | |
| 1648 | try t.maybeBlockify(scope, stmt) | |
| 1649 | else | |
| 1650 | null; | |
| 1651 | return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_node, .@"else" = else_node }); | |
| 1652 | } | |
| 1653 | ||
| 1654 | fn transWhileStmt(t: *Translator, scope: *Scope, while_stmt: Node.WhileStmt) TransError!ZigNode { | |
| 1655 | var cond_scope: Scope.Condition = .{ | |
| 1656 | .base = .{ | |
| 1657 | .parent = scope, | |
| 1658 | .id = .condition, | |
| 1659 | }, | |
| 1660 | }; | |
| 1661 | defer cond_scope.deinit(); | |
| 1662 | const cond = try t.transBoolExpr(&cond_scope.base, while_stmt.cond); | |
| 1663 | ||
| 1664 | var loop_scope: Scope = .{ | |
| 1665 | .parent = scope, | |
| 1666 | .id = .loop, | |
| 1667 | }; | |
| 1668 | const body = try t.maybeBlockify(&loop_scope, while_stmt.body); | |
| 1669 | return ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = null }); | |
| 1670 | } | |
| 1671 | ||
| 1672 | fn transDoWhileStmt(t: *Translator, scope: *Scope, do_stmt: Node.DoWhileStmt) TransError!ZigNode { | |
| 1673 | var loop_scope: Scope = .{ | |
| 1674 | .parent = scope, | |
| 1675 | .id = .do_loop, | |
| 1676 | }; | |
| 1677 | ||
| 1678 | // if (!cond) break; | |
| 1679 | var cond_scope: Scope.Condition = .{ | |
| 1680 | .base = .{ | |
| 1681 | .parent = scope, | |
| 1682 | .id = .condition, | |
| 1683 | }, | |
| 1684 | }; | |
| 1685 | defer cond_scope.deinit(); | |
| 1686 | const cond = try t.transBoolExpr(&cond_scope.base, do_stmt.cond); | |
| 1687 | const if_not_break = switch (cond.tag()) { | |
| 1688 | .true_literal => { | |
| 1689 | const body_node = try t.maybeBlockify(scope, do_stmt.body); | |
| 1690 | return ZigTag.while_true.create(t.arena, body_node); | |
| 1691 | }, | |
| 1692 | else => try ZigTag.if_not_break.create(t.arena, cond), | |
| 1693 | }; | |
| 1694 | ||
| 1695 | var body_node = try t.transStmt(&loop_scope, do_stmt.body); | |
| 1696 | if (body_node.isNoreturn(true)) { | |
| 1697 | // The body node ends in a noreturn statement. Simply put it in a while (true) | |
| 1698 | // in case it contains breaks or continues. | |
| 1699 | } else if (do_stmt.body.get(t.tree) == .compound_stmt) { | |
| 1700 | // there's already a block in C, so we'll append our condition to it. | |
| 1701 | // c: do { | |
| 1702 | // c: a; | |
| 1703 | // c: b; | |
| 1704 | // c: } while(c); | |
| 1705 | // zig: while (true) { | |
| 1706 | // zig: a; | |
| 1707 | // zig: b; | |
| 1708 | // zig: if (!cond) break; | |
| 1709 | // zig: } | |
| 1710 | const block = body_node.castTag(.block).?; | |
| 1711 | block.data.stmts.len += 1; // This is safe since we reserve one extra space in Scope.Block.complete. | |
| 1712 | block.data.stmts[block.data.stmts.len - 1] = if_not_break; | |
| 1713 | } else { | |
| 1714 | // the C statement is without a block, so we need to create a block to contain it. | |
| 1715 | // c: do | |
| 1716 | // c: a; | |
| 1717 | // c: while(c); | |
| 1718 | // zig: while (true) { | |
| 1719 | // zig: a; | |
| 1720 | // zig: if (!cond) break; | |
| 1721 | // zig: } | |
| 1722 | const statements = try t.arena.alloc(ZigNode, 2); | |
| 1723 | statements[0] = body_node; | |
| 1724 | statements[1] = if_not_break; | |
| 1725 | body_node = try ZigTag.block.create(t.arena, .{ .label = null, .stmts = statements }); | |
| 1726 | } | |
| 1727 | return ZigTag.while_true.create(t.arena, body_node); | |
| 1728 | } | |
| 1729 | ||
| 1730 | fn transForStmt(t: *Translator, scope: *Scope, for_stmt: Node.ForStmt) TransError!ZigNode { | |
| 1731 | var loop_scope: Scope = .{ | |
| 1732 | .parent = scope, | |
| 1733 | .id = .loop, | |
| 1734 | }; | |
| 1735 | ||
| 1736 | var block_scope: ?Scope.Block = null; | |
| 1737 | defer if (block_scope) |*bs| bs.deinit(); | |
| 1738 | ||
| 1739 | switch (for_stmt.init) { | |
| 1740 | .decls => |decls| { | |
| 1741 | block_scope = try Scope.Block.init(t, scope, false); | |
| 1742 | loop_scope.parent = &block_scope.?.base; | |
| 1743 | for (decls) |decl| { | |
| 1744 | try t.transDecl(&block_scope.?.base, decl); | |
| 1745 | } | |
| 1746 | }, | |
| 1747 | .expr => |maybe_init| if (maybe_init) |init| { | |
| 1748 | block_scope = try Scope.Block.init(t, scope, false); | |
| 1749 | loop_scope.parent = &block_scope.?.base; | |
| 1750 | const init_node = try t.transStmt(&block_scope.?.base, init); | |
| 1751 | try loop_scope.appendNode(init_node); | |
| 1752 | }, | |
| 1753 | } | |
| 1754 | var cond_scope: Scope.Condition = .{ | |
| 1755 | .base = .{ | |
| 1756 | .parent = &loop_scope, | |
| 1757 | .id = .condition, | |
| 1758 | }, | |
| 1759 | }; | |
| 1760 | defer cond_scope.deinit(); | |
| 1761 | ||
| 1762 | const cond = if (for_stmt.cond) |cond| | |
| 1763 | try t.transBoolExpr(&cond_scope.base, cond) | |
| 1764 | else | |
| 1765 | ZigTag.true_literal.init(); | |
| 1766 | ||
| 1767 | const cont_expr = if (for_stmt.incr) |incr| | |
| 1768 | try t.transExpr(&cond_scope.base, incr, .unused) | |
| 1769 | else | |
| 1770 | null; | |
| 1771 | ||
| 1772 | const body = try t.maybeBlockify(&loop_scope, for_stmt.body); | |
| 1773 | const while_node = try ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr }); | |
| 1774 | if (block_scope) |*bs| { | |
| 1775 | try bs.statements.append(t.gpa, while_node); | |
| 1776 | return try bs.complete(); | |
| 1777 | } else { | |
| 1778 | return while_node; | |
| 1779 | } | |
| 1780 | } | |
| 1781 | ||
| 1782 | fn transSwitch(t: *Translator, scope: *Scope, switch_stmt: Node.SwitchStmt) TransError!ZigNode { | |
| 1783 | var loop_scope: Scope = .{ | |
| 1784 | .parent = scope, | |
| 1785 | .id = .loop, | |
| 1786 | }; | |
| 1787 | ||
| 1788 | var block_scope = try Scope.Block.init(t, &loop_scope, false); | |
| 1789 | defer block_scope.deinit(); | |
| 1790 | ||
| 1791 | const base_scope = &block_scope.base; | |
| 1792 | ||
| 1793 | var cond_scope: Scope.Condition = .{ | |
| 1794 | .base = .{ | |
| 1795 | .parent = base_scope, | |
| 1796 | .id = .condition, | |
| 1797 | }, | |
| 1798 | }; | |
| 1799 | defer cond_scope.deinit(); | |
| 1800 | const switch_expr = try t.transExpr(&cond_scope.base, switch_stmt.cond, .used); | |
| 1801 | ||
| 1802 | var cases = std.ArrayList(ZigNode).init(t.gpa); | |
| 1803 | defer cases.deinit(); | |
| 1804 | var has_default = false; | |
| 1805 | ||
| 1806 | const body_node = switch_stmt.body.get(t.tree); | |
| 1807 | if (body_node != .compound_stmt) { | |
| 1808 | return t.fail(error.UnsupportedTranslation, switch_stmt.switch_tok, "TODO complex switch", .{}); | |
| 1809 | } | |
| 1810 | const body = body_node.compound_stmt.body; | |
| 1811 | // Iterate over switch body and collect all cases. | |
| 1812 | // Fallthrough is handled by duplicating statements. | |
| 1813 | for (body, 0..) |stmt, i| { | |
| 1814 | switch (stmt.get(t.tree)) { | |
| 1815 | .case_stmt => { | |
| 1816 | var items = std.ArrayList(ZigNode).init(t.gpa); | |
| 1817 | defer items.deinit(); | |
| 1818 | const sub = try t.transCaseStmt(base_scope, stmt, &items); | |
| 1819 | const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); | |
| 1820 | ||
| 1821 | if (items.items.len == 0) { | |
| 1822 | has_default = true; | |
| 1823 | const switch_else = try ZigTag.switch_else.create(t.arena, res); | |
| 1824 | try cases.append(switch_else); | |
| 1825 | } else { | |
| 1826 | const switch_prong = try ZigTag.switch_prong.create(t.arena, .{ | |
| 1827 | .cases = try t.arena.dupe(ZigNode, items.items), | |
| 1828 | .cond = res, | |
| 1829 | }); | |
| 1830 | try cases.append(switch_prong); | |
| 1831 | } | |
| 1832 | }, | |
| 1833 | .default_stmt => |default_stmt| { | |
| 1834 | has_default = true; | |
| 1835 | ||
| 1836 | var sub = default_stmt.body; | |
| 1837 | while (true) switch (sub.get(t.tree)) { | |
| 1838 | .case_stmt => |sub_case| sub = sub_case.body, | |
| 1839 | .default_stmt => |sub_default| sub = sub_default.body, | |
| 1840 | else => break, | |
| 1841 | }; | |
| 1842 | ||
| 1843 | const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); | |
| 1844 | ||
| 1845 | const switch_else = try ZigTag.switch_else.create(t.arena, res); | |
| 1846 | try cases.append(switch_else); | |
| 1847 | }, | |
| 1848 | else => {}, // collected in transSwitchProngStmt | |
| 1849 | } | |
| 1850 | } | |
| 1851 | ||
| 1852 | if (!has_default) { | |
| 1853 | const else_prong = try ZigTag.switch_else.create(t.arena, ZigTag.empty_block.init()); | |
| 1854 | try cases.append(else_prong); | |
| 1855 | } | |
| 1856 | ||
| 1857 | const switch_node = try ZigTag.@"switch".create(t.arena, .{ | |
| 1858 | .cond = switch_expr, | |
| 1859 | .cases = try t.arena.dupe(ZigNode, cases.items), | |
| 1860 | }); | |
| 1861 | try block_scope.statements.append(t.gpa, switch_node); | |
| 1862 | try block_scope.statements.append(t.gpa, ZigTag.@"break".init()); | |
| 1863 | const while_body = try block_scope.complete(); | |
| 1864 | ||
| 1865 | return ZigTag.while_true.create(t.arena, while_body); | |
| 1866 | } | |
| 1867 | ||
| 1868 | /// Collects all items for this case, returns the first statement after the labels. | |
| 1869 | /// If items ends up empty, the prong should be translated as an else. | |
| 1870 | fn transCaseStmt( | |
| 1871 | t: *Translator, | |
| 1872 | scope: *Scope, | |
| 1873 | stmt: Node.Index, | |
| 1874 | items: *std.ArrayList(ZigNode), | |
| 1875 | ) TransError!Node.Index { | |
| 1876 | var sub = stmt; | |
| 1877 | var seen_default = false; | |
| 1878 | while (true) { | |
| 1879 | switch (sub.get(t.tree)) { | |
| 1880 | .default_stmt => |default_stmt| { | |
| 1881 | seen_default = true; | |
| 1882 | items.items.len = 0; | |
| 1883 | sub = default_stmt.body; | |
| 1884 | }, | |
| 1885 | .case_stmt => |case_stmt| { | |
| 1886 | if (seen_default) { | |
| 1887 | items.items.len = 0; | |
| 1888 | sub = case_stmt.body; | |
| 1889 | continue; | |
| 1890 | } | |
| 1891 | ||
| 1892 | const expr = if (case_stmt.end) |end| blk: { | |
| 1893 | const start_node = try t.transExpr(scope, case_stmt.start, .used); | |
| 1894 | const end_node = try t.transExpr(scope, end, .used); | |
| 1895 | ||
| 1896 | break :blk try ZigTag.ellipsis3.create(t.arena, .{ .lhs = start_node, .rhs = end_node }); | |
| 1897 | } else try t.transExpr(scope, case_stmt.start, .used); | |
| 1898 | ||
| 1899 | try items.append(expr); | |
| 1900 | sub = case_stmt.body; | |
| 1901 | }, | |
| 1902 | else => return sub, | |
| 1903 | } | |
| 1904 | } | |
| 1905 | } | |
| 1906 | ||
| 1907 | /// Collects all statements seen by this case into a block. | |
| 1908 | /// Avoids creating a block if the first statement is a break or return. | |
| 1909 | fn transSwitchProngStmt( | |
| 1910 | t: *Translator, | |
| 1911 | scope: *Scope, | |
| 1912 | stmt: Node.Index, | |
| 1913 | body: []const Node.Index, | |
| 1914 | ) TransError!ZigNode { | |
| 1915 | switch (stmt.get(t.tree)) { | |
| 1916 | .break_stmt => return ZigTag.@"break".init(), | |
| 1917 | .return_stmt => return t.transStmt(scope, stmt), | |
| 1918 | .case_stmt, .default_stmt => unreachable, | |
| 1919 | else => { | |
| 1920 | var block_scope = try Scope.Block.init(t, scope, false); | |
| 1921 | defer block_scope.deinit(); | |
| 1922 | ||
| 1923 | // we do not need to translate `stmt` since it is the first stmt of `body` | |
| 1924 | try t.transSwitchProngStmtInline(&block_scope, body); | |
| 1925 | return try block_scope.complete(); | |
| 1926 | }, | |
| 1927 | } | |
| 1928 | } | |
| 1929 | ||
| 1930 | /// Collects all statements seen by this case into a block. | |
| 1931 | fn transSwitchProngStmtInline( | |
| 1932 | t: *Translator, | |
| 1933 | block: *Scope.Block, | |
| 1934 | body: []const Node.Index, | |
| 1935 | ) TransError!void { | |
| 1936 | for (body) |stmt| { | |
| 1937 | switch (stmt.get(t.tree)) { | |
| 1938 | .return_stmt => { | |
| 1939 | const result = try t.transStmt(&block.base, stmt); | |
| 1940 | try block.statements.append(t.gpa, result); | |
| 1941 | return; | |
| 1942 | }, | |
| 1943 | .break_stmt => { | |
| 1944 | try block.statements.append(t.gpa, ZigTag.@"break".init()); | |
| 1945 | return; | |
| 1946 | }, | |
| 1947 | .case_stmt => |case_stmt| { | |
| 1948 | var sub = case_stmt.body; | |
| 1949 | while (true) switch (sub.get(t.tree)) { | |
| 1950 | .case_stmt => |sub_case| sub = sub_case.body, | |
| 1951 | .default_stmt => |sub_default| sub = sub_default.body, | |
| 1952 | else => break, | |
| 1953 | }; | |
| 1954 | const result = try t.transStmt(&block.base, sub); | |
| 1955 | assert(result.tag() != .declaration); | |
| 1956 | try block.statements.append(t.gpa, result); | |
| 1957 | if (result.isNoreturn(true)) return; | |
| 1958 | }, | |
| 1959 | .default_stmt => |default_stmt| { | |
| 1960 | var sub = default_stmt.body; | |
| 1961 | while (true) switch (sub.get(t.tree)) { | |
| 1962 | .case_stmt => |sub_case| sub = sub_case.body, | |
| 1963 | .default_stmt => |sub_default| sub = sub_default.body, | |
| 1964 | else => break, | |
| 1965 | }; | |
| 1966 | const result = try t.transStmt(&block.base, sub); | |
| 1967 | assert(result.tag() != .declaration); | |
| 1968 | try block.statements.append(t.gpa, result); | |
| 1969 | if (result.isNoreturn(true)) return; | |
| 1970 | }, | |
| 1971 | .compound_stmt => |compound_stmt| { | |
| 1972 | const result = try t.transCompoundStmt(&block.base, compound_stmt); | |
| 1973 | try block.statements.append(t.gpa, result); | |
| 1974 | if (result.isNoreturn(true)) return; | |
| 1975 | }, | |
| 1976 | else => { | |
| 1977 | const result = try t.transStmt(&block.base, stmt); | |
| 1978 | switch (result.tag()) { | |
| 1979 | .declaration, .empty_block => {}, | |
| 1980 | else => try block.statements.append(t.gpa, result), | |
| 1981 | } | |
| 1982 | }, | |
| 1983 | } | |
| 1984 | } | |
| 1985 | } | |
| 1986 | ||
| 1987 | // ====================== | |
| 1988 | // Expression translation | |
| 1989 | // ====================== | |
| 1990 | ||
| 1991 | const ResultUsed = enum { used, unused }; | |
| 1992 | ||
| 1993 | fn transExpr(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { | |
| 1994 | const qt = expr.qt(t.tree); | |
| 1995 | return t.maybeSuppressResult(used, switch (expr.get(t.tree)) { | |
| 1996 | .paren_expr => |paren_expr| { | |
| 1997 | return t.transExpr(scope, paren_expr.operand, used); | |
| 1998 | }, | |
| 1999 | .cast => |cast| return t.transCastExpr(scope, cast, cast.qt, used, .with_as), | |
| 2000 | .decl_ref_expr => |decl_ref| try t.transDeclRefExpr(scope, decl_ref), | |
| 2001 | .enumeration_ref => |enum_ref| try t.transDeclRefExpr(scope, enum_ref), | |
| 2002 | .addr_of_expr => |addr_of_expr| try ZigTag.address_of.create(t.arena, try t.transExpr(scope, addr_of_expr.operand, .used)), | |
| 2003 | .deref_expr => |deref_expr| res: { | |
| 2004 | if (t.typeWasDemotedToOpaque(qt)) | |
| 2005 | return t.fail(error.UnsupportedTranslation, deref_expr.op_tok, "cannot dereference opaque type", .{}); | |
| 2006 | ||
| 2007 | // Dereferencing a function pointer is a no-op. | |
| 2008 | if (qt.is(t.comp, .func)) return t.transExpr(scope, deref_expr.operand, used); | |
| 2009 | ||
| 2010 | break :res try ZigTag.deref.create(t.arena, try t.transExpr(scope, deref_expr.operand, .used)); | |
| 2011 | }, | |
| 2012 | .bool_not_expr => |bool_not_expr| try ZigTag.not.create(t.arena, try t.transBoolExpr(scope, bool_not_expr.operand)), | |
| 2013 | .bit_not_expr => |bit_not_expr| try ZigTag.bit_not.create(t.arena, try t.transExpr(scope, bit_not_expr.operand, .used)), | |
| 2014 | .plus_expr => |plus_expr| return t.transExpr(scope, plus_expr.operand, used), | |
| 2015 | .negate_expr => |negate_expr| res: { | |
| 2016 | const operand_qt = negate_expr.operand.qt(t.tree); | |
| 2017 | if (!t.typeHasWrappingOverflow(operand_qt)) { | |
| 2018 | const sub_expr_node = try t.transExpr(scope, negate_expr.operand, .used); | |
| 2019 | const to_negate = if (sub_expr_node.isBoolRes()) blk: { | |
| 2020 | const ty_node = try ZigTag.type.create(t.arena, "c_int"); | |
| 2021 | const int_node = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); | |
| 2022 | break :blk try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_node }); | |
| 2023 | } else sub_expr_node; | |
| 2024 | ||
| 2025 | break :res try ZigTag.negate.create(t.arena, to_negate); | |
| 2026 | } else if (t.signedness(operand_qt) == .unsigned) { | |
| 2027 | // use -% x for unsigned integers | |
| 2028 | break :res try ZigTag.negate_wrap.create(t.arena, try t.transExpr(scope, negate_expr.operand, .used)); | |
| 2029 | } else return t.fail(error.UnsupportedTranslation, negate_expr.op_tok, "C negation with non float non integer", .{}); | |
| 2030 | }, | |
| 2031 | .div_expr => |div_expr| res: { | |
| 2032 | if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { | |
| 2033 | // signed integer division uses @divTrunc | |
| 2034 | const lhs = try t.transExpr(scope, div_expr.lhs, .used); | |
| 2035 | const rhs = try t.transExpr(scope, div_expr.rhs, .used); | |
| 2036 | break :res try ZigTag.div_trunc.create(t.arena, .{ .lhs = lhs, .rhs = rhs }); | |
| 2037 | } | |
| 2038 | // unsigned/float division uses the operator | |
| 2039 | break :res try t.transBinExpr(scope, div_expr, .div); | |
| 2040 | }, | |
| 2041 | .mod_expr => |mod_expr| res: { | |
| 2042 | if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { | |
| 2043 | // signed integer remainder uses __helpers.signedRemainder | |
| 2044 | const lhs = try t.transExpr(scope, mod_expr.lhs, .used); | |
| 2045 | const rhs = try t.transExpr(scope, mod_expr.rhs, .used); | |
| 2046 | break :res try t.createHelperCallNode(.signedRemainder, &.{ lhs, rhs }); | |
| 2047 | } | |
| 2048 | // unsigned/float division uses the operator | |
| 2049 | break :res try t.transBinExpr(scope, mod_expr, .mod); | |
| 2050 | }, | |
| 2051 | .add_expr => |add_expr| res: { | |
| 2052 | // `ptr + idx` and `idx + ptr` -> ptr + @as(usize, @bitCast(@as(isize, @intCast(idx)))) | |
| 2053 | const lhs_qt = add_expr.lhs.qt(t.tree); | |
| 2054 | const rhs_qt = add_expr.rhs.qt(t.tree); | |
| 2055 | if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or | |
| 2056 | t.signedness(rhs_qt) == .signed)) | |
| 2057 | { | |
| 2058 | break :res try t.transPointerArithmeticSignedOp(scope, add_expr, .add); | |
| 2059 | } | |
| 2060 | ||
| 2061 | if (t.signedness(qt) == .unsigned) { | |
| 2062 | break :res try t.transBinExpr(scope, add_expr, .add_wrap); | |
| 2063 | } else { | |
| 2064 | break :res try t.transBinExpr(scope, add_expr, .add); | |
| 2065 | } | |
| 2066 | }, | |
| 2067 | .sub_expr => |sub_expr| res: { | |
| 2068 | // `ptr - idx` -> ptr - @as(usize, @bitCast(@as(isize, @intCast(idx)))) | |
| 2069 | const lhs_qt = sub_expr.lhs.qt(t.tree); | |
| 2070 | const rhs_qt = sub_expr.rhs.qt(t.tree); | |
| 2071 | if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or | |
| 2072 | t.signedness(rhs_qt) == .signed)) | |
| 2073 | { | |
| 2074 | break :res try t.transPointerArithmeticSignedOp(scope, sub_expr, .sub); | |
| 2075 | } | |
| 2076 | ||
| 2077 | if (sub_expr.lhs.qt(t.tree).isPointer(t.comp) and sub_expr.rhs.qt(t.tree).isPointer(t.comp)) { | |
| 2078 | break :res try t.transPtrDiffExpr(scope, sub_expr); | |
| 2079 | } else if (t.signedness(qt) == .unsigned) { | |
| 2080 | break :res try t.transBinExpr(scope, sub_expr, .sub_wrap); | |
| 2081 | } else { | |
| 2082 | break :res try t.transBinExpr(scope, sub_expr, .sub); | |
| 2083 | } | |
| 2084 | }, | |
| 2085 | .mul_expr => |mul_expr| if (t.signedness(qt) == .unsigned) | |
| 2086 | try t.transBinExpr(scope, mul_expr, .mul_wrap) | |
| 2087 | else | |
| 2088 | try t.transBinExpr(scope, mul_expr, .mul), | |
| 2089 | ||
| 2090 | .less_than_expr => |lt| try t.transBinExpr(scope, lt, .less_than), | |
| 2091 | .greater_than_expr => |gt| try t.transBinExpr(scope, gt, .greater_than), | |
| 2092 | .less_than_equal_expr => |lte| try t.transBinExpr(scope, lte, .less_than_equal), | |
| 2093 | .greater_than_equal_expr => |gte| try t.transBinExpr(scope, gte, .greater_than_equal), | |
| 2094 | .equal_expr => |equal_expr| try t.transBinExpr(scope, equal_expr, .equal), | |
| 2095 | .not_equal_expr => |not_equal_expr| try t.transBinExpr(scope, not_equal_expr, .not_equal), | |
| 2096 | ||
| 2097 | .bool_and_expr => |bool_and_expr| try t.transBoolBinExpr(scope, bool_and_expr, .@"and"), | |
| 2098 | .bool_or_expr => |bool_or_expr| try t.transBoolBinExpr(scope, bool_or_expr, .@"or"), | |
| 2099 | ||
| 2100 | .bit_and_expr => |bit_and_expr| try t.transBinExpr(scope, bit_and_expr, .bit_and), | |
| 2101 | .bit_or_expr => |bit_or_expr| try t.transBinExpr(scope, bit_or_expr, .bit_or), | |
| 2102 | .bit_xor_expr => |bit_xor_expr| try t.transBinExpr(scope, bit_xor_expr, .bit_xor), | |
| 2103 | ||
| 2104 | .shl_expr => |shl_expr| try t.transShiftExpr(scope, shl_expr, .shl), | |
| 2105 | .shr_expr => |shr_expr| try t.transShiftExpr(scope, shr_expr, .shr), | |
| 2106 | ||
| 2107 | .member_access_expr => |member_access| try t.transMemberAccess(scope, .normal, member_access, null), | |
| 2108 | .member_access_ptr_expr => |member_access| try t.transMemberAccess(scope, .ptr, member_access, null), | |
| 2109 | .array_access_expr => |array_access| try t.transArrayAccess(scope, array_access, null), | |
| 2110 | ||
| 2111 | .builtin_ref => unreachable, | |
| 2112 | .builtin_call_expr => |call| return t.transBuiltinCall(scope, call, used), | |
| 2113 | .call_expr => |call| return t.transCall(scope, call, used), | |
| 2114 | ||
| 2115 | .builtin_types_compatible_p => |compatible| blk: { | |
| 2116 | const lhs = try t.transType(scope, compatible.lhs, compatible.builtin_tok); | |
| 2117 | const rhs = try t.transType(scope, compatible.rhs, compatible.builtin_tok); | |
| 2118 | ||
| 2119 | break :blk try ZigTag.equal.create(t.arena, .{ | |
| 2120 | .lhs = lhs, | |
| 2121 | .rhs = rhs, | |
| 2122 | }); | |
| 2123 | }, | |
| 2124 | .builtin_choose_expr => |choose| return t.transCondExpr(scope, choose, used), | |
| 2125 | .cond_expr => |cond_expr| return t.transCondExpr(scope, cond_expr, used), | |
| 2126 | .binary_cond_expr => |conditional| return t.transBinaryCondExpr(scope, conditional, used), | |
| 2127 | .cond_dummy_expr => unreachable, | |
| 2128 | ||
| 2129 | .assign_expr => |assign| return t.transAssignExpr(scope, assign, used), | |
| 2130 | .add_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2131 | .sub_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2132 | .mul_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2133 | .div_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2134 | .mod_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2135 | .shl_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2136 | .shr_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2137 | .bit_and_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2138 | .bit_xor_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2139 | .bit_or_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), | |
| 2140 | .compound_assign_dummy_expr => { | |
| 2141 | assert(used == .used); | |
| 2142 | return t.compound_assign_dummy.?; | |
| 2143 | }, | |
| 2144 | ||
| 2145 | .comma_expr => |comma_expr| return t.transCommaExpr(scope, comma_expr, used), | |
| 2146 | .pre_inc_expr => |un| return t.transIncDecExpr(scope, un, .pre, .inc, used), | |
| 2147 | .pre_dec_expr => |un| return t.transIncDecExpr(scope, un, .pre, .dec, used), | |
| 2148 | .post_inc_expr => |un| return t.transIncDecExpr(scope, un, .post, .inc, used), | |
| 2149 | .post_dec_expr => |un| return t.transIncDecExpr(scope, un, .post, .dec, used), | |
| 2150 | ||
| 2151 | .int_literal => return t.transIntLiteral(scope, expr, used, .with_as), | |
| 2152 | .char_literal => return t.transCharLiteral(scope, expr, used, .with_as), | |
| 2153 | .float_literal => return t.transFloatLiteral(scope, expr, used, .with_as), | |
| 2154 | .string_literal_expr => |literal| try t.transStringLiteral(scope, expr, literal), | |
| 2155 | .bool_literal => res: { | |
| 2156 | const val = t.tree.value_map.get(expr).?; | |
| 2157 | break :res if (val.toBool(t.comp)) | |
| 2158 | ZigTag.true_literal.init() | |
| 2159 | else | |
| 2160 | ZigTag.false_literal.init(); | |
| 2161 | }, | |
| 2162 | .nullptr_literal => ZigTag.null_literal.init(), | |
| 2163 | .imaginary_literal => |literal| { | |
| 2164 | return t.fail(error.UnsupportedTranslation, literal.op_tok, "TODO complex numbers", .{}); | |
| 2165 | }, | |
| 2166 | .compound_literal_expr => |literal| return t.transCompoundLiteral(scope, literal, used), | |
| 2167 | ||
| 2168 | .default_init_expr => |default_init| return t.transDefaultInit(scope, default_init, used, .with_as), | |
| 2169 | .array_init_expr => |array_init| return t.transArrayInit(scope, array_init, used), | |
| 2170 | .union_init_expr => |union_init| return t.transUnionInit(scope, union_init, used), | |
| 2171 | .struct_init_expr => |struct_init| return t.transStructInit(scope, struct_init, used), | |
| 2172 | .array_filler_expr => unreachable, | |
| 2173 | ||
| 2174 | .sizeof_expr => |sizeof| try t.transTypeInfo(scope, .sizeof, sizeof), | |
| 2175 | .alignof_expr => |alignof| try t.transTypeInfo(scope, .alignof, alignof), | |
| 2176 | ||
| 2177 | .imag_expr, .real_expr => |un| { | |
| 2178 | return t.fail(error.UnsupportedTranslation, un.op_tok, "TODO complex numbers", .{}); | |
| 2179 | }, | |
| 2180 | .addr_of_label => |addr_of_label| { | |
| 2181 | return t.fail(error.UnsupportedTranslation, addr_of_label.label_tok, "TODO computed goto", .{}); | |
| 2182 | }, | |
| 2183 | ||
| 2184 | .generic_expr => |generic| return t.transExpr(scope, generic.chosen, used), | |
| 2185 | .generic_association_expr => |generic| return t.transExpr(scope, generic.expr, used), | |
| 2186 | .generic_default_expr => |generic| return t.transExpr(scope, generic.expr, used), | |
| 2187 | ||
| 2188 | .stmt_expr => |stmt_expr| return t.transStmtExpr(scope, stmt_expr, used), | |
| 2189 | ||
| 2190 | .builtin_convertvector => |convertvector| try t.transConvertvectorExpr(scope, convertvector), | |
| 2191 | .builtin_shufflevector => |shufflevector| try t.transShufflevectorExpr(scope, shufflevector), | |
| 2192 | ||
| 2193 | .compound_stmt, | |
| 2194 | .static_assert, | |
| 2195 | .return_stmt, | |
| 2196 | .null_stmt, | |
| 2197 | .if_stmt, | |
| 2198 | .while_stmt, | |
| 2199 | .do_while_stmt, | |
| 2200 | .for_stmt, | |
| 2201 | .continue_stmt, | |
| 2202 | .break_stmt, | |
| 2203 | .labeled_stmt, | |
| 2204 | .switch_stmt, | |
| 2205 | .case_stmt, | |
| 2206 | .default_stmt, | |
| 2207 | .goto_stmt, | |
| 2208 | .computed_goto_stmt, | |
| 2209 | .gnu_asm_simple, | |
| 2210 | .global_asm, | |
| 2211 | .typedef, | |
| 2212 | .struct_decl, | |
| 2213 | .union_decl, | |
| 2214 | .enum_decl, | |
| 2215 | .function, | |
| 2216 | .param, | |
| 2217 | .variable, | |
| 2218 | .enum_field, | |
| 2219 | .record_field, | |
| 2220 | .struct_forward_decl, | |
| 2221 | .union_forward_decl, | |
| 2222 | .enum_forward_decl, | |
| 2223 | .empty_decl, | |
| 2224 | => unreachable, // not an expression | |
| 2225 | }); | |
| 2226 | } | |
| 2227 | ||
| 2228 | /// Same as `transExpr` but with the knowledge that the operand will be type coerced, and therefore | |
| 2229 | /// an `@as` would be redundant. This is used to prevent redundant `@as` in integer literals. | |
| 2230 | fn transExprCoercing(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { | |
| 2231 | switch (expr.get(t.tree)) { | |
| 2232 | .int_literal => return t.transIntLiteral(scope, expr, used, .no_as), | |
| 2233 | .char_literal => return t.transCharLiteral(scope, expr, used, .no_as), | |
| 2234 | .float_literal => return t.transFloatLiteral(scope, expr, used, .no_as), | |
| 2235 | .cast => |cast| switch (cast.kind) { | |
| 2236 | .no_op => { | |
| 2237 | const operand = cast.operand.get(t.tree); | |
| 2238 | if (operand == .cast) { | |
| 2239 | return t.transCastExpr(scope, operand.cast, cast.qt, used, .no_as); | |
| 2240 | } | |
| 2241 | return t.transExprCoercing(scope, cast.operand, used); | |
| 2242 | }, | |
| 2243 | .lval_to_rval => return t.transExprCoercing(scope, cast.operand, used), | |
| 2244 | else => return t.transCastExpr(scope, cast, cast.qt, used, .no_as), | |
| 2245 | }, | |
| 2246 | .default_init_expr => |default_init| return try t.transDefaultInit(scope, default_init, used, .no_as), | |
| 2247 | .compound_literal_expr => |literal| { | |
| 2248 | if (!literal.thread_local and literal.storage_class != .static) { | |
| 2249 | return t.transExprCoercing(scope, literal.initializer, used); | |
| 2250 | } | |
| 2251 | }, | |
| 2252 | else => {}, | |
| 2253 | } | |
| 2254 | ||
| 2255 | return t.transExpr(scope, expr, used); | |
| 2256 | } | |
| 2257 | ||
| 2258 | fn transBoolExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { | |
| 2259 | switch (expr.get(t.tree)) { | |
| 2260 | .int_literal => { | |
| 2261 | const int_val = t.tree.value_map.get(expr).?; | |
| 2262 | return if (int_val.isZero(t.comp)) | |
| 2263 | ZigTag.false_literal.init() | |
| 2264 | else | |
| 2265 | ZigTag.true_literal.init(); | |
| 2266 | }, | |
| 2267 | .cast => |cast| switch (cast.kind) { | |
| 2268 | .bool_to_int => return t.transExpr(scope, cast.operand, .used), | |
| 2269 | .array_to_pointer => { | |
| 2270 | const operand = cast.operand.get(t.tree); | |
| 2271 | if (operand == .string_literal_expr) { | |
| 2272 | // @intFromPtr("foo") != 0, always true | |
| 2273 | const str = try t.transStringLiteral(scope, cast.operand, operand.string_literal_expr); | |
| 2274 | const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, str); | |
| 2275 | return ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); | |
| 2276 | } | |
| 2277 | }, | |
| 2278 | else => {}, | |
| 2279 | }, | |
| 2280 | else => {}, | |
| 2281 | } | |
| 2282 | ||
| 2283 | const maybe_bool_res = try t.transExpr(scope, expr, .used); | |
| 2284 | if (maybe_bool_res.isBoolRes()) { | |
| 2285 | return maybe_bool_res; | |
| 2286 | } | |
| 2287 | ||
| 2288 | return t.finishBoolExpr(expr.qt(t.tree), maybe_bool_res); | |
| 2289 | } | |
| 2290 | ||
| 2291 | fn finishBoolExpr(t: *Translator, qt: QualType, node: ZigNode) TransError!ZigNode { | |
| 2292 | const sk = qt.scalarKind(t.comp); | |
| 2293 | if (sk == .bool) return node; | |
| 2294 | if (sk == .nullptr_t) { | |
| 2295 | // node == null, always true | |
| 2296 | return ZigTag.equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); | |
| 2297 | } | |
| 2298 | if (sk.isPointer()) { | |
| 2299 | // node != null | |
| 2300 | return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); | |
| 2301 | } | |
| 2302 | if (sk != .none) { | |
| 2303 | // node != 0 | |
| 2304 | return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); | |
| 2305 | } | |
| 2306 | unreachable; // Unexpected bool expression type | |
| 2307 | } | |
| 2308 | ||
| 2309 | fn transCastExpr( | |
| 2310 | t: *Translator, | |
| 2311 | scope: *Scope, | |
| 2312 | cast: Node.Cast, | |
| 2313 | dest_qt: QualType, | |
| 2314 | used: ResultUsed, | |
| 2315 | suppress_as: SuppressCast, | |
| 2316 | ) TransError!ZigNode { | |
| 2317 | const operand = switch (cast.kind) { | |
| 2318 | .no_op => { | |
| 2319 | const operand = cast.operand.get(t.tree); | |
| 2320 | if (operand == .cast) { | |
| 2321 | return t.transCastExpr(scope, operand.cast, cast.qt, used, suppress_as); | |
| 2322 | } | |
| 2323 | return t.transExpr(scope, cast.operand, used); | |
| 2324 | }, | |
| 2325 | .lval_to_rval, .function_to_pointer => { | |
| 2326 | return t.transExpr(scope, cast.operand, used); | |
| 2327 | }, | |
| 2328 | .int_cast => int_cast: { | |
| 2329 | const src_qt = cast.operand.qt(t.tree); | |
| 2330 | ||
| 2331 | if (cast.implicit) { | |
| 2332 | if (t.tree.value_map.get(cast.operand)) |val| { | |
| 2333 | const max_int = try aro.Value.maxInt(dest_qt, t.comp); | |
| 2334 | const min_int = try aro.Value.minInt(dest_qt, t.comp); | |
| 2335 | ||
| 2336 | if (val.compare(.lte, max_int, t.comp) and val.compare(.gte, min_int, t.comp)) { | |
| 2337 | break :int_cast try t.transExprCoercing(scope, cast.operand, .used); | |
| 2338 | } | |
| 2339 | } | |
| 2340 | } | |
| 2341 | const operand = try t.transExpr(scope, cast.operand, .used); | |
| 2342 | break :int_cast try t.transIntCast(operand, src_qt, dest_qt); | |
| 2343 | }, | |
| 2344 | .to_void => { | |
| 2345 | assert(used == .unused); | |
| 2346 | return try t.transExpr(scope, cast.operand, .unused); | |
| 2347 | }, | |
| 2348 | .null_to_pointer => ZigTag.null_literal.init(), | |
| 2349 | .array_to_pointer => array_to_pointer: { | |
| 2350 | const child_qt = dest_qt.childType(t.comp); | |
| 2351 | ||
| 2352 | loop: switch (cast.operand.get(t.tree)) { | |
| 2353 | .string_literal_expr => |literal| { | |
| 2354 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2355 | ||
| 2356 | const ref = if (literal.kind == .utf8 or literal.kind == .ascii) | |
| 2357 | sub_expr_node | |
| 2358 | else | |
| 2359 | try ZigTag.address_of.create(t.arena, sub_expr_node); | |
| 2360 | ||
| 2361 | const casted = if (child_qt.@"const") | |
| 2362 | ref | |
| 2363 | else | |
| 2364 | try ZigTag.const_cast.create(t.arena, sub_expr_node); | |
| 2365 | ||
| 2366 | return t.maybeSuppressResult(used, casted); | |
| 2367 | }, | |
| 2368 | .paren_expr => |paren_expr| { | |
| 2369 | continue :loop paren_expr.operand.get(t.tree); | |
| 2370 | }, | |
| 2371 | .generic_expr => |generic| { | |
| 2372 | continue :loop generic.chosen.get(t.tree); | |
| 2373 | }, | |
| 2374 | .generic_association_expr => |generic| { | |
| 2375 | continue :loop generic.expr.get(t.tree); | |
| 2376 | }, | |
| 2377 | .generic_default_expr => |generic| { | |
| 2378 | continue :loop generic.expr.get(t.tree); | |
| 2379 | }, | |
| 2380 | else => {}, | |
| 2381 | } | |
| 2382 | ||
| 2383 | if (cast.operand.qt(t.tree).arrayLen(t.comp) == null) { | |
| 2384 | return try t.transExpr(scope, cast.operand, used); | |
| 2385 | } | |
| 2386 | ||
| 2387 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2388 | const ref = try ZigTag.address_of.create(t.arena, sub_expr_node); | |
| 2389 | const align_cast = try ZigTag.align_cast.create(t.arena, ref); | |
| 2390 | break :array_to_pointer try ZigTag.ptr_cast.create(t.arena, align_cast); | |
| 2391 | }, | |
| 2392 | .int_to_pointer => int_to_pointer: { | |
| 2393 | var sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2394 | const operand_qt = cast.operand.qt(t.tree); | |
| 2395 | if (t.signedness(operand_qt) == .signed or operand_qt.bitSizeof(t.comp) > t.comp.target.ptrBitWidth()) { | |
| 2396 | sub_expr_node = try ZigTag.as.create(t.arena, .{ | |
| 2397 | .lhs = try ZigTag.type.create(t.arena, "usize"), | |
| 2398 | .rhs = try ZigTag.int_cast.create(t.arena, sub_expr_node), | |
| 2399 | }); | |
| 2400 | } | |
| 2401 | break :int_to_pointer try ZigTag.ptr_from_int.create(t.arena, sub_expr_node); | |
| 2402 | }, | |
| 2403 | .int_to_bool => { | |
| 2404 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2405 | if (sub_expr_node.isBoolRes()) return sub_expr_node; | |
| 2406 | if (cast.operand.qt(t.tree).is(t.comp, .bool)) return sub_expr_node; | |
| 2407 | const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); | |
| 2408 | return t.maybeSuppressResult(used, cmp_node); | |
| 2409 | }, | |
| 2410 | .float_to_bool => { | |
| 2411 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2412 | const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); | |
| 2413 | return t.maybeSuppressResult(used, cmp_node); | |
| 2414 | }, | |
| 2415 | .pointer_to_bool => { | |
| 2416 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2417 | ||
| 2418 | // Special case function pointers as @intFromPtr(expr) != 0 | |
| 2419 | if (cast.operand.qt(t.tree).get(t.comp, .pointer)) |ptr_ty| if (ptr_ty.child.is(t.comp, .func)) { | |
| 2420 | const ptr_node = if (sub_expr_node.tag() == .identifier) | |
| 2421 | try ZigTag.address_of.create(t.arena, sub_expr_node) | |
| 2422 | else | |
| 2423 | sub_expr_node; | |
| 2424 | const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, ptr_node); | |
| 2425 | const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); | |
| 2426 | return t.maybeSuppressResult(used, cmp_node); | |
| 2427 | }; | |
| 2428 | ||
| 2429 | const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.null_literal.init() }); | |
| 2430 | return t.maybeSuppressResult(used, cmp_node); | |
| 2431 | }, | |
| 2432 | .bool_to_int => bool_to_int: { | |
| 2433 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2434 | break :bool_to_int try ZigTag.int_from_bool.create(t.arena, sub_expr_node); | |
| 2435 | }, | |
| 2436 | .bool_to_float => bool_to_float: { | |
| 2437 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2438 | const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); | |
| 2439 | break :bool_to_float try ZigTag.float_from_int.create(t.arena, int_from_bool); | |
| 2440 | }, | |
| 2441 | .bool_to_pointer => bool_to_pointer: { | |
| 2442 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2443 | const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); | |
| 2444 | break :bool_to_pointer try ZigTag.ptr_from_int.create(t.arena, int_from_bool); | |
| 2445 | }, | |
| 2446 | .float_cast => float_cast: { | |
| 2447 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2448 | break :float_cast try ZigTag.float_cast.create(t.arena, sub_expr_node); | |
| 2449 | }, | |
| 2450 | .int_to_float => int_to_float: { | |
| 2451 | const sub_expr_node = try t.transExpr(scope, cast.operand, used); | |
| 2452 | const int_node = if (sub_expr_node.isBoolRes()) | |
| 2453 | try ZigTag.int_from_bool.create(t.arena, sub_expr_node) | |
| 2454 | else | |
| 2455 | sub_expr_node; | |
| 2456 | break :int_to_float try ZigTag.float_from_int.create(t.arena, int_node); | |
| 2457 | }, | |
| 2458 | .float_to_int => float_to_int: { | |
| 2459 | const sub_expr_node = try t.transExpr(scope, cast.operand, .used); | |
| 2460 | break :float_to_int try ZigTag.int_from_float.create(t.arena, sub_expr_node); | |
| 2461 | }, | |
| 2462 | .pointer_to_int => pointer_to_int: { | |
| 2463 | const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); | |
| 2464 | const ptr_node = try ZigTag.int_from_ptr.create(t.arena, sub_expr_node); | |
| 2465 | break :pointer_to_int try ZigTag.int_cast.create(t.arena, ptr_node); | |
| 2466 | }, | |
| 2467 | .bitcast => bitcast: { | |
| 2468 | const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); | |
| 2469 | const operand_qt = cast.operand.qt(t.tree); | |
| 2470 | if (dest_qt.isPointer(t.comp) and operand_qt.isPointer(t.comp)) { | |
| 2471 | var casted = try ZigTag.align_cast.create(t.arena, sub_expr_node); | |
| 2472 | casted = try ZigTag.ptr_cast.create(t.arena, casted); | |
| 2473 | ||
| 2474 | const src_elem = operand_qt.childType(t.comp); | |
| 2475 | const dest_elem = dest_qt.childType(t.comp); | |
| 2476 | if ((src_elem.@"const" or src_elem.is(t.comp, .func)) and !dest_elem.@"const") { | |
| 2477 | casted = try ZigTag.const_cast.create(t.arena, casted); | |
| 2478 | } | |
| 2479 | if (src_elem.@"volatile" and !dest_elem.@"volatile") { | |
| 2480 | casted = try ZigTag.volatile_cast.create(t.arena, casted); | |
| 2481 | } | |
| 2482 | break :bitcast casted; | |
| 2483 | } | |
| 2484 | ||
| 2485 | break :bitcast try ZigTag.bit_cast.create(t.arena, sub_expr_node); | |
| 2486 | }, | |
| 2487 | .union_cast => union_cast: { | |
| 2488 | const union_type = try t.transType(scope, dest_qt, cast.l_paren); | |
| 2489 | ||
| 2490 | const operand_qt = cast.operand.qt(t.tree); | |
| 2491 | const union_base = dest_qt.base(t.comp); | |
| 2492 | const field = for (union_base.type.@"union".fields) |field| { | |
| 2493 | if (field.qt.eql(operand_qt, t.comp)) break field; | |
| 2494 | } else unreachable; | |
| 2495 | const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ | |
| 2496 | .parent = union_base.qt, | |
| 2497 | .field = field.qt, | |
| 2498 | }).? else field.name.lookup(t.comp); | |
| 2499 | ||
| 2500 | const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); | |
| 2501 | field_init.* = .{ | |
| 2502 | .name = field_name, | |
| 2503 | .value = try t.transExpr(scope, cast.operand, .used), | |
| 2504 | }; | |
| 2505 | break :union_cast try ZigTag.container_init.create(t.arena, .{ | |
| 2506 | .lhs = union_type, | |
| 2507 | .inits = field_init[0..1], | |
| 2508 | }); | |
| 2509 | }, | |
| 2510 | else => return t.fail(error.UnsupportedTranslation, cast.l_paren, "TODO translate {s} cast", .{@tagName(cast.kind)}), | |
| 2511 | }; | |
| 2512 | if (suppress_as == .no_as) return t.maybeSuppressResult(used, operand); | |
| 2513 | if (used == .unused) return t.maybeSuppressResult(used, operand); | |
| 2514 | const as = try ZigTag.as.create(t.arena, .{ | |
| 2515 | .lhs = try t.transType(scope, dest_qt, cast.l_paren), | |
| 2516 | .rhs = operand, | |
| 2517 | }); | |
| 2518 | return as; | |
| 2519 | } | |
| 2520 | ||
| 2521 | fn transIntCast(t: *Translator, operand: ZigNode, src_qt: QualType, dest_qt: QualType) !ZigNode { | |
| 2522 | const src_dest_order = src_qt.intRankOrder(dest_qt, t.comp); | |
| 2523 | const different_sign = t.signedness(src_qt) != t.signedness(dest_qt); | |
| 2524 | const needs_bitcast = different_sign and !(t.signedness(src_qt) == .unsigned and src_dest_order == .lt); | |
| 2525 | ||
| 2526 | var casted = operand; | |
| 2527 | if (casted.isBoolRes()) { | |
| 2528 | casted = try ZigTag.int_from_bool.create(t.arena, casted); | |
| 2529 | } else if (src_dest_order == .gt) { | |
| 2530 | // No C type is smaller than the 1 bit from @intFromBool | |
| 2531 | casted = try ZigTag.truncate.create(t.arena, casted); | |
| 2532 | } | |
| 2533 | if (needs_bitcast) { | |
| 2534 | if (src_dest_order != .eq) { | |
| 2535 | casted = try ZigTag.as.create(t.arena, .{ | |
| 2536 | .lhs = try t.transTypeIntWidthOf(dest_qt, t.signedness(src_qt) == .signed), | |
| 2537 | .rhs = casted, | |
| 2538 | }); | |
| 2539 | } | |
| 2540 | return ZigTag.bit_cast.create(t.arena, casted); | |
| 2541 | } | |
| 2542 | return casted; | |
| 2543 | } | |
| 2544 | ||
| 2545 | /// Same as `transExpr` but adds a `&` if the expression is an identifier referencing a function type. | |
| 2546 | fn transPointerCastExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { | |
| 2547 | const sub_expr_node = try t.transExpr(scope, expr, .used); | |
| 2548 | switch (expr.get(t.tree)) { | |
| 2549 | .cast => |cast| if (cast.kind == .function_to_pointer and sub_expr_node.tag() == .identifier) { | |
| 2550 | return ZigTag.address_of.create(t.arena, sub_expr_node); | |
| 2551 | }, | |
| 2552 | else => {}, | |
| 2553 | } | |
| 2554 | return sub_expr_node; | |
| 2555 | } | |
| 2556 | ||
| 2557 | fn transDeclRefExpr(t: *Translator, scope: *Scope, decl_ref: Node.DeclRef) TransError!ZigNode { | |
| 2558 | const name = t.tree.tokSlice(decl_ref.name_tok); | |
| 2559 | const maybe_alias = scope.getAlias(name); | |
| 2560 | const mangled_name = maybe_alias orelse name; | |
| 2561 | ||
| 2562 | switch (decl_ref.decl.get(t.tree)) { | |
| 2563 | .function => |function| if (function.definition == null and function.body == null) { | |
| 2564 | // Try translating the decl again in case of out of scope declaration. | |
| 2565 | try t.transFnDecl(scope, function); | |
| 2566 | }, | |
| 2567 | else => {}, | |
| 2568 | } | |
| 2569 | ||
| 2570 | const decl = decl_ref.decl.get(t.tree); | |
| 2571 | const ref_expr = blk: { | |
| 2572 | const identifier = try ZigTag.identifier.create(t.arena, mangled_name); | |
| 2573 | if (decl_ref.qt.is(t.comp, .func) and maybe_alias != null) { | |
| 2574 | break :blk try ZigTag.field_access.create(t.arena, .{ | |
| 2575 | .lhs = identifier, | |
| 2576 | .field_name = name, | |
| 2577 | }); | |
| 2578 | } | |
| 2579 | if (decl == .variable and maybe_alias != null) { | |
| 2580 | switch (decl.variable.storage_class) { | |
| 2581 | .@"extern", .static => { | |
| 2582 | break :blk try ZigTag.field_access.create(t.arena, .{ | |
| 2583 | .lhs = identifier, | |
| 2584 | .field_name = name, | |
| 2585 | }); | |
| 2586 | }, | |
| 2587 | else => {}, | |
| 2588 | } | |
| 2589 | } | |
| 2590 | break :blk identifier; | |
| 2591 | }; | |
| 2592 | ||
| 2593 | scope.skipVariableDiscard(mangled_name); | |
| 2594 | return ref_expr; | |
| 2595 | } | |
| 2596 | ||
| 2597 | fn transBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { | |
| 2598 | const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); | |
| 2599 | const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); | |
| 2600 | ||
| 2601 | const lhs = if (lhs_uncasted.isBoolRes()) | |
| 2602 | try ZigTag.int_from_bool.create(t.arena, lhs_uncasted) | |
| 2603 | else | |
| 2604 | lhs_uncasted; | |
| 2605 | ||
| 2606 | const rhs = if (rhs_uncasted.isBoolRes()) | |
| 2607 | try ZigTag.int_from_bool.create(t.arena, rhs_uncasted) | |
| 2608 | else | |
| 2609 | rhs_uncasted; | |
| 2610 | ||
| 2611 | return t.createBinOpNode(op_id, lhs, rhs); | |
| 2612 | } | |
| 2613 | ||
| 2614 | fn transBoolBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op: ZigTag) !ZigNode { | |
| 2615 | std.debug.assert(op == .@"and" or op == .@"or"); | |
| 2616 | ||
| 2617 | const lhs = try t.transBoolExpr(scope, bin.lhs); | |
| 2618 | const rhs = try t.transBoolExpr(scope, bin.rhs); | |
| 2619 | ||
| 2620 | return t.createBinOpNode(op, lhs, rhs); | |
| 2621 | } | |
| 2622 | ||
| 2623 | fn transShiftExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) !ZigNode { | |
| 2624 | std.debug.assert(op_id == .shl or op_id == .shr); | |
| 2625 | ||
| 2626 | // lhs >> @intCast(rh) | |
| 2627 | const lhs = try t.transExpr(scope, bin.lhs, .used); | |
| 2628 | ||
| 2629 | const rhs = try t.transExprCoercing(scope, bin.rhs, .used); | |
| 2630 | const rhs_casted = try ZigTag.int_cast.create(t.arena, rhs); | |
| 2631 | ||
| 2632 | return t.createBinOpNode(op_id, lhs, rhs_casted); | |
| 2633 | } | |
| 2634 | ||
| 2635 | fn transCondExpr( | |
| 2636 | t: *Translator, | |
| 2637 | scope: *Scope, | |
| 2638 | conditional: Node.Conditional, | |
| 2639 | used: ResultUsed, | |
| 2640 | ) TransError!ZigNode { | |
| 2641 | var cond_scope: Scope.Condition = .{ | |
| 2642 | .base = .{ | |
| 2643 | .parent = scope, | |
| 2644 | .id = .condition, | |
| 2645 | }, | |
| 2646 | }; | |
| 2647 | defer cond_scope.deinit(); | |
| 2648 | ||
| 2649 | const res_is_bool = conditional.qt.is(t.comp, .bool); | |
| 2650 | const cond = try t.transBoolExpr(&cond_scope.base, conditional.cond); | |
| 2651 | ||
| 2652 | var then_body = try t.transExpr(scope, conditional.then_expr, used); | |
| 2653 | if (!res_is_bool and then_body.isBoolRes()) { | |
| 2654 | then_body = try ZigTag.int_from_bool.create(t.arena, then_body); | |
| 2655 | } | |
| 2656 | ||
| 2657 | var else_body = try t.transExpr(scope, conditional.else_expr, used); | |
| 2658 | if (!res_is_bool and else_body.isBoolRes()) { | |
| 2659 | else_body = try ZigTag.int_from_bool.create(t.arena, else_body); | |
| 2660 | } | |
| 2661 | ||
| 2662 | // The `ResultUsed` is forwarded to both branches so no need to suppress the result here. | |
| 2663 | return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body }); | |
| 2664 | } | |
| 2665 | ||
| 2666 | fn transBinaryCondExpr( | |
| 2667 | t: *Translator, | |
| 2668 | scope: *Scope, | |
| 2669 | conditional: Node.Conditional, | |
| 2670 | used: ResultUsed, | |
| 2671 | ) TransError!ZigNode { | |
| 2672 | // GNU extension of the ternary operator where the middle expression is | |
| 2673 | // omitted, the condition itself is returned if it evaluates to true. | |
| 2674 | ||
| 2675 | if (used == .unused) { | |
| 2676 | // Result unused so this can be translated as | |
| 2677 | // if (condition) else_expr; | |
| 2678 | var cond_scope: Scope.Condition = .{ | |
| 2679 | .base = .{ | |
| 2680 | .parent = scope, | |
| 2681 | .id = .condition, | |
| 2682 | }, | |
| 2683 | }; | |
| 2684 | defer cond_scope.deinit(); | |
| 2685 | ||
| 2686 | return ZigTag.@"if".create(t.arena, .{ | |
| 2687 | .cond = try t.transBoolExpr(&cond_scope.base, conditional.cond), | |
| 2688 | .then = try t.transExpr(scope, conditional.else_expr, .unused), | |
| 2689 | .@"else" = null, | |
| 2690 | }); | |
| 2691 | } | |
| 2692 | ||
| 2693 | const res_is_bool = conditional.qt.is(t.comp, .bool); | |
| 2694 | // c: (condition)?:(else_expr) | |
| 2695 | // zig: (blk: { | |
| 2696 | // const _cond_temp = (condition); | |
| 2697 | // break :blk if (_cond_temp) _cond_temp else (else_expr); | |
| 2698 | // }) | |
| 2699 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 2700 | defer block_scope.deinit(); | |
| 2701 | ||
| 2702 | const cond_temp = try block_scope.reserveMangledName("cond_temp"); | |
| 2703 | const init_node = try t.transExpr(&block_scope.base, conditional.cond, .used); | |
| 2704 | const temp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = cond_temp, .init = init_node }); | |
| 2705 | try block_scope.statements.append(t.gpa, temp_decl); | |
| 2706 | ||
| 2707 | var cond_scope: Scope.Condition = .{ | |
| 2708 | .base = .{ | |
| 2709 | .parent = &block_scope.base, | |
| 2710 | .id = .condition, | |
| 2711 | }, | |
| 2712 | }; | |
| 2713 | defer cond_scope.deinit(); | |
| 2714 | ||
| 2715 | const cond_ident = try ZigTag.identifier.create(t.arena, cond_temp); | |
| 2716 | const cond_node = try t.finishBoolExpr(conditional.cond.qt(t.tree), cond_ident); | |
| 2717 | var then_body = cond_ident; | |
| 2718 | if (!res_is_bool and init_node.isBoolRes()) { | |
| 2719 | then_body = try ZigTag.int_from_bool.create(t.arena, then_body); | |
| 2720 | } | |
| 2721 | ||
| 2722 | var else_body = try t.transExpr(&block_scope.base, conditional.else_expr, .used); | |
| 2723 | if (!res_is_bool and else_body.isBoolRes()) { | |
| 2724 | else_body = try ZigTag.int_from_bool.create(t.arena, else_body); | |
| 2725 | } | |
| 2726 | const if_node = try ZigTag.@"if".create(t.arena, .{ | |
| 2727 | .cond = cond_node, | |
| 2728 | .then = then_body, | |
| 2729 | .@"else" = else_body, | |
| 2730 | }); | |
| 2731 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2732 | .label = block_scope.label, | |
| 2733 | .val = if_node, | |
| 2734 | }); | |
| 2735 | try block_scope.statements.append(t.gpa, break_node); | |
| 2736 | return block_scope.complete(); | |
| 2737 | } | |
| 2738 | ||
| 2739 | fn transCommaExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) TransError!ZigNode { | |
| 2740 | if (used == .unused) { | |
| 2741 | const lhs = try t.transExprCoercing(scope, bin.lhs, .unused); | |
| 2742 | try scope.appendNode(lhs); | |
| 2743 | const rhs = try t.transExprCoercing(scope, bin.rhs, .unused); | |
| 2744 | return rhs; | |
| 2745 | } | |
| 2746 | ||
| 2747 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 2748 | defer block_scope.deinit(); | |
| 2749 | ||
| 2750 | const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .unused); | |
| 2751 | try block_scope.statements.append(t.gpa, lhs); | |
| 2752 | ||
| 2753 | const rhs = try t.transExprCoercing(&block_scope.base, bin.rhs, .used); | |
| 2754 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2755 | .label = block_scope.label, | |
| 2756 | .val = rhs, | |
| 2757 | }); | |
| 2758 | try block_scope.statements.append(t.gpa, break_node); | |
| 2759 | ||
| 2760 | return try block_scope.complete(); | |
| 2761 | } | |
| 2762 | ||
| 2763 | fn transAssignExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) !ZigNode { | |
| 2764 | if (used == .unused) { | |
| 2765 | const lhs = try t.transExpr(scope, bin.lhs, .used); | |
| 2766 | var rhs = try t.transExprCoercing(scope, bin.rhs, .used); | |
| 2767 | ||
| 2768 | const lhs_qt = bin.lhs.qt(t.tree); | |
| 2769 | if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { | |
| 2770 | rhs = try ZigTag.int_from_bool.create(t.arena, rhs); | |
| 2771 | } | |
| 2772 | ||
| 2773 | return t.createBinOpNode(.assign, lhs, rhs); | |
| 2774 | } | |
| 2775 | ||
| 2776 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 2777 | defer block_scope.deinit(); | |
| 2778 | ||
| 2779 | const tmp = try block_scope.reserveMangledName("tmp"); | |
| 2780 | ||
| 2781 | var rhs = try t.transExpr(&block_scope.base, bin.rhs, .used); | |
| 2782 | const lhs_qt = bin.lhs.qt(t.tree); | |
| 2783 | if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { | |
| 2784 | rhs = try ZigTag.int_from_bool.create(t.arena, rhs); | |
| 2785 | } | |
| 2786 | ||
| 2787 | const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = rhs }); | |
| 2788 | try block_scope.statements.append(t.gpa, tmp_decl); | |
| 2789 | ||
| 2790 | const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .used); | |
| 2791 | const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); | |
| 2792 | ||
| 2793 | const assign = try t.createBinOpNode(.assign, lhs, tmp_ident); | |
| 2794 | try block_scope.statements.append(t.gpa, assign); | |
| 2795 | ||
| 2796 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2797 | .label = block_scope.label, | |
| 2798 | .val = tmp_ident, | |
| 2799 | }); | |
| 2800 | try block_scope.statements.append(t.gpa, break_node); | |
| 2801 | ||
| 2802 | return try block_scope.complete(); | |
| 2803 | } | |
| 2804 | ||
| 2805 | fn transCompoundAssign( | |
| 2806 | t: *Translator, | |
| 2807 | scope: *Scope, | |
| 2808 | assign: Node.Binary, | |
| 2809 | used: ResultUsed, | |
| 2810 | ) !ZigNode { | |
| 2811 | // If the result is unused we can try using the equivalent Zig operator | |
| 2812 | // without a block | |
| 2813 | if (used == .unused) { | |
| 2814 | if (try t.transCompoundAssignSimple(scope, null, assign)) |some| { | |
| 2815 | return some; | |
| 2816 | } | |
| 2817 | } | |
| 2818 | ||
| 2819 | // Otherwise we need to wrap the the compound assignment in a block. | |
| 2820 | var block_scope = try Scope.Block.init(t, scope, used == .used); | |
| 2821 | defer block_scope.deinit(); | |
| 2822 | const ref = try block_scope.reserveMangledName("ref"); | |
| 2823 | ||
| 2824 | const lhs_expr = try t.transExpr(&block_scope.base, assign.lhs, .used); | |
| 2825 | const addr_of = try ZigTag.address_of.create(t.arena, lhs_expr); | |
| 2826 | const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = addr_of }); | |
| 2827 | try block_scope.statements.append(t.gpa, ref_decl); | |
| 2828 | ||
| 2829 | const lhs_node = try ZigTag.identifier.create(t.arena, ref); | |
| 2830 | const ref_node = try ZigTag.deref.create(t.arena, lhs_node); | |
| 2831 | ||
| 2832 | // Use the equivalent Zig operator if possible. | |
| 2833 | if (try t.transCompoundAssignSimple(scope, ref_node, assign)) |some| { | |
| 2834 | try block_scope.statements.append(t.gpa, some); | |
| 2835 | } else { | |
| 2836 | const old_dummy = t.compound_assign_dummy; | |
| 2837 | defer t.compound_assign_dummy = old_dummy; | |
| 2838 | t.compound_assign_dummy = ref_node; | |
| 2839 | ||
| 2840 | // Otherwise do the operation and assignment separately. | |
| 2841 | const rhs_node = try t.transExprCoercing(&block_scope.base, assign.rhs, .used); | |
| 2842 | const assign_node = try t.createBinOpNode(.assign, ref_node, rhs_node); | |
| 2843 | try block_scope.statements.append(t.gpa, assign_node); | |
| 2844 | } | |
| 2845 | ||
| 2846 | if (used == .used) { | |
| 2847 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2848 | .label = block_scope.label, | |
| 2849 | .val = ref_node, | |
| 2850 | }); | |
| 2851 | try block_scope.statements.append(t.gpa, break_node); | |
| 2852 | } | |
| 2853 | return block_scope.complete(); | |
| 2854 | } | |
| 2855 | ||
| 2856 | /// Translates compound assignment using the equivalent Zig operator if possible. | |
| 2857 | fn transCompoundAssignSimple(t: *Translator, scope: *Scope, lhs_dummy_opt: ?ZigNode, assign: Node.Binary) TransError!?ZigNode { | |
| 2858 | const assign_rhs = assign.rhs.get(t.tree); | |
| 2859 | if (assign_rhs == .cast) return null; | |
| 2860 | ||
| 2861 | const is_signed = t.signedness(assign.qt) == .signed; | |
| 2862 | switch (assign_rhs) { | |
| 2863 | .div_expr, .mod_expr => if (is_signed) return null, | |
| 2864 | else => {}, | |
| 2865 | } | |
| 2866 | const lhs_ptr = assign.qt.isPointer(t.comp); | |
| 2867 | ||
| 2868 | const bin, const op: ZigTag, const cast: enum { none, shift, usize } = switch (assign_rhs) { | |
| 2869 | .add_expr => |bin| .{ | |
| 2870 | bin, | |
| 2871 | if (t.typeHasWrappingOverflow(bin.qt)) .add_wrap_assign else .add_assign, | |
| 2872 | if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, | |
| 2873 | }, | |
| 2874 | .sub_expr => |bin| .{ | |
| 2875 | bin, | |
| 2876 | if (t.typeHasWrappingOverflow(bin.qt)) .sub_wrap_assign else .sub_assign, | |
| 2877 | if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, | |
| 2878 | }, | |
| 2879 | .mul_expr => |bin| .{ | |
| 2880 | bin, | |
| 2881 | if (t.typeHasWrappingOverflow(bin.qt)) .mul_wrap_assign else .mul_assign, | |
| 2882 | .none, | |
| 2883 | }, | |
| 2884 | .mod_expr => |bin| .{ bin, .mod_assign, .none }, | |
| 2885 | .div_expr => |bin| .{ bin, .div_assign, .none }, | |
| 2886 | .shl_expr => |bin| .{ bin, .shl_assign, .shift }, | |
| 2887 | .shr_expr => |bin| .{ bin, .shr_assign, .shift }, | |
| 2888 | .bit_and_expr => |bin| .{ bin, .bit_and_assign, .none }, | |
| 2889 | .bit_xor_expr => |bin| .{ bin, .bit_xor_assign, .none }, | |
| 2890 | .bit_or_expr => |bin| .{ bin, .bit_or_assign, .none }, | |
| 2891 | else => unreachable, | |
| 2892 | }; | |
| 2893 | ||
| 2894 | const lhs_node = blk: { | |
| 2895 | const old_dummy = t.compound_assign_dummy; | |
| 2896 | defer t.compound_assign_dummy = old_dummy; | |
| 2897 | t.compound_assign_dummy = lhs_dummy_opt orelse try t.transExpr(scope, assign.lhs, .used); | |
| 2898 | ||
| 2899 | break :blk try t.transExpr(scope, bin.lhs, .used); | |
| 2900 | }; | |
| 2901 | ||
| 2902 | const rhs_node = try t.transExprCoercing(scope, bin.rhs, .used); | |
| 2903 | const casted_rhs = switch (cast) { | |
| 2904 | .none => rhs_node, | |
| 2905 | .shift => try ZigTag.int_cast.create(t.arena, rhs_node), | |
| 2906 | .usize => try t.usizeCastForWrappingPtrArithmetic(rhs_node), | |
| 2907 | }; | |
| 2908 | return try t.createBinOpNode(op, lhs_node, casted_rhs); | |
| 2909 | } | |
| 2910 | ||
| 2911 | fn transIncDecExpr( | |
| 2912 | t: *Translator, | |
| 2913 | scope: *Scope, | |
| 2914 | un: Node.Unary, | |
| 2915 | position: enum { pre, post }, | |
| 2916 | kind: enum { inc, dec }, | |
| 2917 | used: ResultUsed, | |
| 2918 | ) !ZigNode { | |
| 2919 | const is_wrapping = t.typeHasWrappingOverflow(un.qt); | |
| 2920 | const op: ZigTag = switch (kind) { | |
| 2921 | .inc => if (is_wrapping) .add_wrap_assign else .add_assign, | |
| 2922 | .dec => if (is_wrapping) .sub_wrap_assign else .sub_assign, | |
| 2923 | }; | |
| 2924 | ||
| 2925 | const one_literal = ZigTag.one_literal.init(); | |
| 2926 | if (used == .unused) { | |
| 2927 | const operand = try t.transExpr(scope, un.operand, .used); | |
| 2928 | return try t.createBinOpNode(op, operand, one_literal); | |
| 2929 | } | |
| 2930 | ||
| 2931 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 2932 | defer block_scope.deinit(); | |
| 2933 | ||
| 2934 | const ref = try block_scope.reserveMangledName("ref"); | |
| 2935 | const operand = try t.transExprCoercing(&block_scope.base, un.operand, .used); | |
| 2936 | const operand_ref = try ZigTag.address_of.create(t.arena, operand); | |
| 2937 | const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = operand_ref }); | |
| 2938 | try block_scope.statements.append(t.gpa, ref_decl); | |
| 2939 | ||
| 2940 | const ref_ident = try ZigTag.identifier.create(t.arena, ref); | |
| 2941 | const ref_deref = try ZigTag.deref.create(t.arena, ref_ident); | |
| 2942 | const effect = try t.createBinOpNode(op, ref_deref, one_literal); | |
| 2943 | ||
| 2944 | switch (position) { | |
| 2945 | .pre => { | |
| 2946 | try block_scope.statements.append(t.gpa, effect); | |
| 2947 | ||
| 2948 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2949 | .label = block_scope.label, | |
| 2950 | .val = ref_deref, | |
| 2951 | }); | |
| 2952 | try block_scope.statements.append(t.gpa, break_node); | |
| 2953 | }, | |
| 2954 | .post => { | |
| 2955 | const tmp = try block_scope.reserveMangledName("tmp"); | |
| 2956 | const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = ref_deref }); | |
| 2957 | try block_scope.statements.append(t.gpa, tmp_decl); | |
| 2958 | ||
| 2959 | try block_scope.statements.append(t.gpa, effect); | |
| 2960 | ||
| 2961 | const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); | |
| 2962 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 2963 | .label = block_scope.label, | |
| 2964 | .val = tmp_ident, | |
| 2965 | }); | |
| 2966 | try block_scope.statements.append(t.gpa, break_node); | |
| 2967 | }, | |
| 2968 | } | |
| 2969 | ||
| 2970 | return try block_scope.complete(); | |
| 2971 | } | |
| 2972 | ||
| 2973 | fn transPtrDiffExpr(t: *Translator, scope: *Scope, bin: Node.Binary) TransError!ZigNode { | |
| 2974 | const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); | |
| 2975 | const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); | |
| 2976 | ||
| 2977 | const lhs = try ZigTag.int_from_ptr.create(t.arena, lhs_uncasted); | |
| 2978 | const rhs = try ZigTag.int_from_ptr.create(t.arena, rhs_uncasted); | |
| 2979 | ||
| 2980 | const sub_res = try t.createBinOpNode(.sub_wrap, lhs, rhs); | |
| 2981 | ||
| 2982 | // @divExact(@as(<platform-ptrdiff_t>, @bitCast(@intFromPtr(lhs)) -% @intFromPtr(rhs)), @sizeOf(<lhs target type>)) | |
| 2983 | const ptrdiff_type = try t.transTypeIntWidthOf(bin.qt, true); | |
| 2984 | ||
| 2985 | const bitcast = try ZigTag.as.create(t.arena, .{ | |
| 2986 | .lhs = ptrdiff_type, | |
| 2987 | .rhs = try ZigTag.bit_cast.create(t.arena, sub_res), | |
| 2988 | }); | |
| 2989 | ||
| 2990 | // C standard requires that pointer subtraction operands are of the same type, | |
| 2991 | // otherwise it is undefined behavior. So we can assume the left and right | |
| 2992 | // sides are the same Type and arbitrarily choose left. | |
| 2993 | const lhs_ty = try t.transType(scope, bin.lhs.qt(t.tree), bin.lhs.tok(t.tree)); | |
| 2994 | const c_pointer = t.getContainer(lhs_ty).?; | |
| 2995 | ||
| 2996 | if (c_pointer.castTag(.c_pointer)) |c_pointer_payload| { | |
| 2997 | const sizeof = try ZigTag.sizeof.create(t.arena, c_pointer_payload.data.elem_type); | |
| 2998 | return ZigTag.div_exact.create(t.arena, .{ | |
| 2999 | .lhs = bitcast, | |
| 3000 | .rhs = sizeof, | |
| 3001 | }); | |
| 3002 | } else { | |
| 3003 | // This is an opaque/incomplete type. This subtraction exhibits Undefined Behavior by the C99 spec. | |
| 3004 | // However, allowing subtraction on `void *` and function pointers is a commonly used extension. | |
| 3005 | // So, just return the value in byte units, mirroring the behavior of this language extension as implemented by GCC and Clang. | |
| 3006 | return bitcast; | |
| 3007 | } | |
| 3008 | } | |
| 3009 | ||
| 3010 | /// Translate an arithmetic expression with a pointer operand and a signed-integer operand. | |
| 3011 | /// Zig requires a usize argument for pointer arithmetic, so we intCast to isize and then | |
| 3012 | /// bitcast to usize; pointer wraparound makes the math work. | |
| 3013 | /// Zig pointer addition is not commutative (unlike C); the pointer operand needs to be on the left. | |
| 3014 | /// The + operator in C is not a sequence point so it should be safe to switch the order if necessary. | |
| 3015 | fn transPointerArithmeticSignedOp(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { | |
| 3016 | std.debug.assert(op_id == .add or op_id == .sub); | |
| 3017 | ||
| 3018 | const lhs_qt = bin.lhs.qt(t.tree); | |
| 3019 | const swap_operands = op_id == .add and t.signedness(lhs_qt) == .signed; | |
| 3020 | ||
| 3021 | const swizzled_lhs = if (swap_operands) bin.rhs else bin.lhs; | |
| 3022 | const swizzled_rhs = if (swap_operands) bin.lhs else bin.rhs; | |
| 3023 | ||
| 3024 | const lhs_node = try t.transExpr(scope, swizzled_lhs, .used); | |
| 3025 | const rhs_node = try t.transExpr(scope, swizzled_rhs, .used); | |
| 3026 | ||
| 3027 | const bitcast_node = try t.usizeCastForWrappingPtrArithmetic(rhs_node); | |
| 3028 | ||
| 3029 | return t.createBinOpNode(op_id, lhs_node, bitcast_node); | |
| 3030 | } | |
| 3031 | ||
| 3032 | fn transMemberAccess( | |
| 3033 | t: *Translator, | |
| 3034 | scope: *Scope, | |
| 3035 | kind: enum { normal, ptr }, | |
| 3036 | member_access: Node.MemberAccess, | |
| 3037 | opt_base: ?ZigNode, | |
| 3038 | ) TransError!ZigNode { | |
| 3039 | const base_info = switch (kind) { | |
| 3040 | .normal => member_access.base.qt(t.tree), | |
| 3041 | .ptr => member_access.base.qt(t.tree).childType(t.comp), | |
| 3042 | }; | |
| 3043 | const record = base_info.getRecord(t.comp).?; | |
| 3044 | const field = record.fields[member_access.member_index]; | |
| 3045 | const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ | |
| 3046 | .parent = base_info.base(t.comp).qt, | |
| 3047 | .field = field.qt, | |
| 3048 | }).? else field.name.lookup(t.comp); | |
| 3049 | const base_node = opt_base orelse try t.transExpr(scope, member_access.base, .used); | |
| 3050 | const lhs = switch (kind) { | |
| 3051 | .normal => base_node, | |
| 3052 | .ptr => try ZigTag.deref.create(t.arena, base_node), | |
| 3053 | }; | |
| 3054 | const field_access = try ZigTag.field_access.create(t.arena, .{ | |
| 3055 | .lhs = lhs, | |
| 3056 | .field_name = field_name, | |
| 3057 | }); | |
| 3058 | ||
| 3059 | // Flexible array members are translated as member functions. | |
| 3060 | if (member_access.member_index == record.fields.len - 1 or base_info.base(t.comp).type == .@"union") { | |
| 3061 | if (field.qt.get(t.comp, .array)) |array_ty| { | |
| 3062 | if (array_ty.len == .incomplete or (array_ty.len == .fixed and array_ty.len.fixed == 0)) { | |
| 3063 | return ZigTag.call.create(t.arena, .{ .lhs = field_access, .args = &.{} }); | |
| 3064 | } | |
| 3065 | } | |
| 3066 | } | |
| 3067 | ||
| 3068 | return field_access; | |
| 3069 | } | |
| 3070 | ||
| 3071 | fn transArrayAccess(t: *Translator, scope: *Scope, array_access: Node.ArrayAccess, opt_base: ?ZigNode) TransError!ZigNode { | |
| 3072 | // Unwrap the base statement if it's an array decayed to a bare pointer type | |
| 3073 | // so that we index the array itself | |
| 3074 | const base = base: { | |
| 3075 | const base = array_access.base.get(t.tree); | |
| 3076 | if (base != .cast) break :base array_access.base; | |
| 3077 | if (base.cast.kind != .array_to_pointer) break :base array_access.base; | |
| 3078 | break :base base.cast.operand; | |
| 3079 | }; | |
| 3080 | ||
| 3081 | const base_node = opt_base orelse try t.transExpr(scope, base, .used); | |
| 3082 | const index = index: { | |
| 3083 | const index = try t.transExpr(scope, array_access.index, .used); | |
| 3084 | const index_qt = array_access.index.qt(t.tree); | |
| 3085 | const maybe_bigger_than_usize = switch (index_qt.base(t.comp).type) { | |
| 3086 | .bool => { | |
| 3087 | break :index try ZigTag.int_from_bool.create(t.arena, index); | |
| 3088 | }, | |
| 3089 | .int => |int| switch (int) { | |
| 3090 | .long_long, .ulong_long, .int128, .uint128 => true, | |
| 3091 | else => false, | |
| 3092 | }, | |
| 3093 | .bit_int => |bit_int| bit_int.bits > t.comp.target.ptrBitWidth(), | |
| 3094 | else => unreachable, | |
| 3095 | }; | |
| 3096 | ||
| 3097 | const is_nonnegative_int_literal = if (t.tree.value_map.get(array_access.index)) |val| | |
| 3098 | val.compare(.gte, .zero, t.comp) | |
| 3099 | else | |
| 3100 | false; | |
| 3101 | const is_signed = t.signedness(index_qt) == .signed; | |
| 3102 | ||
| 3103 | if (is_signed and !is_nonnegative_int_literal) { | |
| 3104 | // First cast to `isize` to get proper sign extension and | |
| 3105 | // then @bitCast to `usize` to satisfy the compiler. | |
| 3106 | const index_isize = try ZigTag.as.create(t.arena, .{ | |
| 3107 | .lhs = try ZigTag.type.create(t.arena, "isize"), | |
| 3108 | .rhs = try ZigTag.int_cast.create(t.arena, index), | |
| 3109 | }); | |
| 3110 | break :index try ZigTag.bit_cast.create(t.arena, index_isize); | |
| 3111 | } | |
| 3112 | ||
| 3113 | if (maybe_bigger_than_usize) { | |
| 3114 | break :index try ZigTag.int_cast.create(t.arena, index); | |
| 3115 | } | |
| 3116 | break :index index; | |
| 3117 | }; | |
| 3118 | ||
| 3119 | return ZigTag.array_access.create(t.arena, .{ | |
| 3120 | .lhs = base_node, | |
| 3121 | .rhs = index, | |
| 3122 | }); | |
| 3123 | } | |
| 3124 | ||
| 3125 | fn transOffsetof(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { | |
| 3126 | // Translate __builtin_offsetof(T, designator) as | |
| 3127 | // @intFromPtr(&(@as(*allowzero T, @ptrFromInt(0)).designator)) | |
| 3128 | const member = try t.transMemberDesignator(scope, arg); | |
| 3129 | const address = try ZigTag.address_of.create(t.arena, member); | |
| 3130 | return ZigTag.int_from_ptr.create(t.arena, address); | |
| 3131 | } | |
| 3132 | ||
| 3133 | fn transMemberDesignator(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { | |
| 3134 | switch (arg.get(t.tree)) { | |
| 3135 | .default_init_expr => |default| { | |
| 3136 | const elem_node = try t.transType(scope, default.qt, default.last_tok); | |
| 3137 | const ptr_ty = try ZigTag.single_pointer.create(t.arena, .{ | |
| 3138 | .elem_type = elem_node, | |
| 3139 | .is_allowzero = true, | |
| 3140 | .is_const = false, | |
| 3141 | .is_volatile = false, | |
| 3142 | }); | |
| 3143 | const zero = try ZigTag.ptr_from_int.create(t.arena, ZigTag.zero_literal.init()); | |
| 3144 | return ZigTag.as.create(t.arena, .{ .lhs = ptr_ty, .rhs = zero }); | |
| 3145 | }, | |
| 3146 | .array_access_expr => |access| { | |
| 3147 | const base = try t.transMemberDesignator(scope, access.base); | |
| 3148 | return t.transArrayAccess(scope, access, base); | |
| 3149 | }, | |
| 3150 | .member_access_expr => |access| { | |
| 3151 | const base = try t.transMemberDesignator(scope, access.base); | |
| 3152 | return t.transMemberAccess(scope, .normal, access, base); | |
| 3153 | }, | |
| 3154 | .cast => |cast| { | |
| 3155 | assert(cast.kind == .array_to_pointer); | |
| 3156 | return t.transMemberDesignator(scope, cast.operand); | |
| 3157 | }, | |
| 3158 | else => unreachable, | |
| 3159 | } | |
| 3160 | } | |
| 3161 | ||
| 3162 | fn transBuiltinCall( | |
| 3163 | t: *Translator, | |
| 3164 | scope: *Scope, | |
| 3165 | call: Node.BuiltinCall, | |
| 3166 | used: ResultUsed, | |
| 3167 | ) TransError!ZigNode { | |
| 3168 | const builtin_name = t.tree.tokSlice(call.builtin_tok); | |
| 3169 | if (std.mem.eql(u8, builtin_name, "__builtin_offsetof")) { | |
| 3170 | const res = try t.transOffsetof(scope, call.args[0]); | |
| 3171 | return t.maybeSuppressResult(used, res); | |
| 3172 | } | |
| 3173 | ||
| 3174 | const builtin = builtins.map.get(builtin_name) orelse | |
| 3175 | return t.fail(error.UnsupportedTranslation, call.builtin_tok, "TODO implement function '{s}' in std.zig.c_builtins", .{builtin_name}); | |
| 3176 | ||
| 3177 | if (builtin.tag) |tag| switch (tag) { | |
| 3178 | .byte_swap, .ceil, .cos, .sin, .exp, .exp2, .exp10, .abs, .log, .log2, .log10, .round, .sqrt, .trunc, .floor => { | |
| 3179 | assert(call.args.len == 1); | |
| 3180 | const arg = try t.transExprCoercing(scope, call.args[0], .used); | |
| 3181 | const arg_ty = try t.transType(scope, call.args[0].qt(t.tree), call.args[0].tok(t.tree)); | |
| 3182 | const coerced = try ZigTag.as.create(t.arena, .{ .lhs = arg_ty, .rhs = arg }); | |
| 3183 | ||
| 3184 | const ptr = try t.arena.create(ast.Payload.UnOp); | |
| 3185 | ptr.* = .{ .base = .{ .tag = tag }, .data = coerced }; | |
| 3186 | return t.maybeSuppressResult(used, ZigNode.initPayload(&ptr.base)); | |
| 3187 | }, | |
| 3188 | .@"unreachable" => return ZigTag.@"unreachable".init(), | |
| 3189 | else => unreachable, | |
| 3190 | }; | |
| 3191 | ||
| 3192 | const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); | |
| 3193 | for (call.args, arg_nodes) |c_arg, *zig_arg| { | |
| 3194 | zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); | |
| 3195 | } | |
| 3196 | ||
| 3197 | const builtin_identifier = try ZigTag.identifier.create(t.arena, "__builtin"); | |
| 3198 | const field_access = try ZigTag.field_access.create(t.arena, .{ | |
| 3199 | .lhs = builtin_identifier, | |
| 3200 | .field_name = builtin.name, | |
| 3201 | }); | |
| 3202 | ||
| 3203 | const res = try ZigTag.call.create(t.arena, .{ | |
| 3204 | .lhs = field_access, | |
| 3205 | .args = arg_nodes, | |
| 3206 | }); | |
| 3207 | if (call.qt.is(t.comp, .void)) return res; | |
| 3208 | return t.maybeSuppressResult(used, res); | |
| 3209 | } | |
| 3210 | ||
| 3211 | fn transCall( | |
| 3212 | t: *Translator, | |
| 3213 | scope: *Scope, | |
| 3214 | call: Node.Call, | |
| 3215 | used: ResultUsed, | |
| 3216 | ) TransError!ZigNode { | |
| 3217 | const raw_fn_expr = try t.transExpr(scope, call.callee, .used); | |
| 3218 | const fn_expr = blk: { | |
| 3219 | loop: switch (call.callee.get(t.tree)) { | |
| 3220 | .paren_expr => |paren_expr| { | |
| 3221 | continue :loop paren_expr.operand.get(t.tree); | |
| 3222 | }, | |
| 3223 | .decl_ref_expr => |decl_ref| { | |
| 3224 | if (decl_ref.qt.is(t.comp, .func)) break :blk raw_fn_expr; | |
| 3225 | }, | |
| 3226 | .cast => |cast| { | |
| 3227 | if (cast.kind == .function_to_pointer) { | |
| 3228 | continue :loop cast.operand.get(t.tree); | |
| 3229 | } | |
| 3230 | }, | |
| 3231 | .deref_expr, .addr_of_expr => |un| { | |
| 3232 | continue :loop un.operand.get(t.tree); | |
| 3233 | }, | |
| 3234 | .generic_expr => |generic| { | |
| 3235 | continue :loop generic.chosen.get(t.tree); | |
| 3236 | }, | |
| 3237 | .generic_association_expr => |generic| { | |
| 3238 | continue :loop generic.expr.get(t.tree); | |
| 3239 | }, | |
| 3240 | .generic_default_expr => |generic| { | |
| 3241 | continue :loop generic.expr.get(t.tree); | |
| 3242 | }, | |
| 3243 | else => {}, | |
| 3244 | } | |
| 3245 | break :blk try ZigTag.unwrap.create(t.arena, raw_fn_expr); | |
| 3246 | }; | |
| 3247 | ||
| 3248 | const callee_qt = call.callee.qt(t.tree); | |
| 3249 | const maybe_ptr_ty = callee_qt.get(t.comp, .pointer); | |
| 3250 | const func_qt = if (maybe_ptr_ty) |ptr| ptr.child else callee_qt; | |
| 3251 | const func_ty = func_qt.get(t.comp, .func).?; | |
| 3252 | ||
| 3253 | const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); | |
| 3254 | for (call.args, arg_nodes, 0..) |c_arg, *zig_arg, i| { | |
| 3255 | if (i < func_ty.params.len) { | |
| 3256 | zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); | |
| 3257 | ||
| 3258 | if (zig_arg.isBoolRes() and !func_ty.params[i].qt.is(t.comp, .bool)) { | |
| 3259 | // In C the result type of a boolean expression is int. If this result is passed as | |
| 3260 | // an argument to a function whose parameter is also int, there is no cast. Therefore | |
| 3261 | // in Zig we'll need to cast it from bool to u1 (which will safely coerce to c_int). | |
| 3262 | zig_arg.* = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); | |
| 3263 | } | |
| 3264 | } else { | |
| 3265 | zig_arg.* = try t.transExpr(scope, c_arg, .used); | |
| 3266 | ||
| 3267 | if (zig_arg.isBoolRes()) { | |
| 3268 | // Same as above but now we don't have a result type. | |
| 3269 | const u1_node = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); | |
| 3270 | const c_int_node = try ZigTag.type.create(t.arena, "c_int"); | |
| 3271 | zig_arg.* = try ZigTag.as.create(t.arena, .{ .lhs = c_int_node, .rhs = u1_node }); | |
| 3272 | } | |
| 3273 | } | |
| 3274 | } | |
| 3275 | ||
| 3276 | const res = try ZigTag.call.create(t.arena, .{ | |
| 3277 | .lhs = fn_expr, | |
| 3278 | .args = arg_nodes, | |
| 3279 | }); | |
| 3280 | if (call.qt.is(t.comp, .void)) return res; | |
| 3281 | return t.maybeSuppressResult(used, res); | |
| 3282 | } | |
| 3283 | ||
| 3284 | const SuppressCast = enum { with_as, no_as }; | |
| 3285 | ||
| 3286 | fn transIntLiteral( | |
| 3287 | t: *Translator, | |
| 3288 | scope: *Scope, | |
| 3289 | literal_index: Node.Index, | |
| 3290 | used: ResultUsed, | |
| 3291 | suppress_as: SuppressCast, | |
| 3292 | ) TransError!ZigNode { | |
| 3293 | const val = t.tree.value_map.get(literal_index).?; | |
| 3294 | const int_lit_node = try t.createIntNode(val); | |
| 3295 | if (suppress_as == .no_as) { | |
| 3296 | return t.maybeSuppressResult(used, int_lit_node); | |
| 3297 | } | |
| 3298 | ||
| 3299 | // Integer literals in C have types, and this can matter for several reasons. | |
| 3300 | // For example, this is valid C: | |
| 3301 | // unsigned char y = 256; | |
| 3302 | // How this gets evaluated is the 256 is an integer, which gets truncated to signed char, then bit-casted | |
| 3303 | // to unsigned char, resulting in 0. In order for this to work, we have to emit this zig code: | |
| 3304 | // var y = @as(u8, @bitCast(@as(i8, @truncate(@as(c_int, 256))))); | |
| 3305 | ||
| 3306 | // @as(T, x) | |
| 3307 | const ty_node = try t.transType(scope, literal_index.qt(t.tree), literal_index.tok(t.tree)); | |
| 3308 | const as = try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_lit_node }); | |
| 3309 | return t.maybeSuppressResult(used, as); | |
| 3310 | } | |
| 3311 | ||
| 3312 | fn transCharLiteral( | |
| 3313 | t: *Translator, | |
| 3314 | scope: *Scope, | |
| 3315 | literal_index: Node.Index, | |
| 3316 | used: ResultUsed, | |
| 3317 | suppress_as: SuppressCast, | |
| 3318 | ) TransError!ZigNode { | |
| 3319 | const val = t.tree.value_map.get(literal_index).?; | |
| 3320 | const char_literal = literal_index.get(t.tree).char_literal; | |
| 3321 | const narrow = char_literal.kind == .ascii or char_literal.kind == .utf8; | |
| 3322 | ||
| 3323 | // C has a somewhat obscure feature called multi-character character constant | |
| 3324 | // e.g. 'abcd' | |
| 3325 | const int_value = val.toInt(u32, t.comp).?; | |
| 3326 | const int_lit_node = if (char_literal.kind == .ascii and int_value > 255) | |
| 3327 | try t.createNumberNode(int_value, .int) | |
| 3328 | else | |
| 3329 | try t.createCharLiteralNode(narrow, int_value); | |
| 3330 | ||
| 3331 | if (suppress_as == .no_as) { | |
| 3332 | return t.maybeSuppressResult(used, int_lit_node); | |
| 3333 | } | |
| 3334 | ||
| 3335 | // See comment in `transIntLiteral` for why this code is here. | |
| 3336 | // @as(T, x) | |
| 3337 | const as_node = try ZigTag.as.create(t.arena, .{ | |
| 3338 | .lhs = try t.transType(scope, char_literal.qt, char_literal.literal_tok), | |
| 3339 | .rhs = int_lit_node, | |
| 3340 | }); | |
| 3341 | return t.maybeSuppressResult(used, as_node); | |
| 3342 | } | |
| 3343 | ||
| 3344 | fn transFloatLiteral( | |
| 3345 | t: *Translator, | |
| 3346 | scope: *Scope, | |
| 3347 | literal_index: Node.Index, | |
| 3348 | used: ResultUsed, | |
| 3349 | suppress_as: SuppressCast, | |
| 3350 | ) TransError!ZigNode { | |
| 3351 | const val = t.tree.value_map.get(literal_index).?; | |
| 3352 | const float_literal = literal_index.get(t.tree).float_literal; | |
| 3353 | ||
| 3354 | var allocating: std.Io.Writer.Allocating = .init(t.gpa); | |
| 3355 | defer allocating.deinit(); | |
| 3356 | _ = val.print(float_literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 3357 | ||
| 3358 | const float_lit_node = try ZigTag.float_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); | |
| 3359 | if (suppress_as == .no_as) { | |
| 3360 | return t.maybeSuppressResult(used, float_lit_node); | |
| 3361 | } | |
| 3362 | ||
| 3363 | const as_node = try ZigTag.as.create(t.arena, .{ | |
| 3364 | .lhs = try t.transType(scope, float_literal.qt, float_literal.literal_tok), | |
| 3365 | .rhs = float_lit_node, | |
| 3366 | }); | |
| 3367 | return t.maybeSuppressResult(used, as_node); | |
| 3368 | } | |
| 3369 | ||
| 3370 | fn transStringLiteral( | |
| 3371 | t: *Translator, | |
| 3372 | scope: *Scope, | |
| 3373 | expr: Node.Index, | |
| 3374 | literal: Node.CharLiteral, | |
| 3375 | ) TransError!ZigNode { | |
| 3376 | switch (literal.kind) { | |
| 3377 | .ascii, .utf8 => return t.transNarrowStringLiteral(expr, literal), | |
| 3378 | .utf16, .utf32, .wide => { | |
| 3379 | const name = try std.fmt.allocPrint(t.arena, "{s}_string_{d}", .{ @tagName(literal.kind), t.getMangle() }); | |
| 3380 | ||
| 3381 | const array_type = try t.transTypeInit(scope, literal.qt, expr, literal.literal_tok); | |
| 3382 | const lit_array = try t.transStringLiteralInitializer(expr, literal, array_type); | |
| 3383 | const decl = try ZigTag.var_simple.create(t.arena, .{ .name = name, .init = lit_array }); | |
| 3384 | try scope.appendNode(decl); | |
| 3385 | return ZigTag.identifier.create(t.arena, name); | |
| 3386 | }, | |
| 3387 | } | |
| 3388 | } | |
| 3389 | ||
| 3390 | fn transNarrowStringLiteral( | |
| 3391 | t: *Translator, | |
| 3392 | expr: Node.Index, | |
| 3393 | literal: Node.CharLiteral, | |
| 3394 | ) TransError!ZigNode { | |
| 3395 | const val = t.tree.value_map.get(expr).?; | |
| 3396 | ||
| 3397 | const bytes = t.comp.interner.get(val.ref()).bytes; | |
| 3398 | var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); | |
| 3399 | defer allocating.deinit(); | |
| 3400 | ||
| 3401 | aro.Value.printString(bytes, literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; | |
| 3402 | ||
| 3403 | return ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.getWritten())); | |
| 3404 | } | |
| 3405 | ||
| 3406 | /// Translate a string literal that is initializing an array. In general narrow string | |
| 3407 | /// literals become `"<string>".*` or `"<string>"[0..<size>].*` if they need truncation. | |
| 3408 | /// Wide string literals become an array of integers. zero-fillers pad out the array to | |
| 3409 | /// the appropriate length, if necessary. | |
| 3410 | fn transStringLiteralInitializer( | |
| 3411 | t: *Translator, | |
| 3412 | expr: Node.Index, | |
| 3413 | literal: Node.CharLiteral, | |
| 3414 | array_type: ZigNode, | |
| 3415 | ) TransError!ZigNode { | |
| 3416 | assert(array_type.tag() == .array_type or array_type.tag() == .null_sentinel_array_type); | |
| 3417 | ||
| 3418 | const is_narrow = literal.kind == .ascii or literal.kind == .utf8; | |
| 3419 | ||
| 3420 | // The length of the string literal excluding the sentinel. | |
| 3421 | const str_length = literal.qt.arrayLen(t.comp).? - 1; | |
| 3422 | ||
| 3423 | const payload = (array_type.castTag(.array_type) orelse array_type.castTag(.null_sentinel_array_type).?).data; | |
| 3424 | const array_size = payload.len; | |
| 3425 | const elem_type = payload.elem_type; | |
| 3426 | ||
| 3427 | if (array_size == 0) return ZigTag.empty_array.create(t.arena, array_type); | |
| 3428 | ||
| 3429 | const num_inits = @min(str_length, array_size); | |
| 3430 | if (num_inits == 0) { | |
| 3431 | return ZigTag.array_filler.create(t.arena, .{ | |
| 3432 | .type = elem_type, | |
| 3433 | .filler = ZigTag.zero_literal.init(), | |
| 3434 | .count = array_size, | |
| 3435 | }); | |
| 3436 | } | |
| 3437 | ||
| 3438 | const init_node = if (is_narrow) blk: { | |
| 3439 | // "string literal".* or string literal"[0..num_inits].* | |
| 3440 | var str = try t.transNarrowStringLiteral(expr, literal); | |
| 3441 | if (str_length != array_size) str = try ZigTag.string_slice.create(t.arena, .{ .string = str, .end = num_inits }); | |
| 3442 | break :blk try ZigTag.deref.create(t.arena, str); | |
| 3443 | } else blk: { | |
| 3444 | const size = literal.qt.childType(t.comp).sizeof(t.comp); | |
| 3445 | ||
| 3446 | const val = t.tree.value_map.get(expr).?; | |
| 3447 | const bytes = t.comp.interner.get(val.ref()).bytes; | |
| 3448 | ||
| 3449 | const init_list = try t.arena.alloc(ZigNode, @intCast(num_inits)); | |
| 3450 | for (init_list, 0..) |*item, i| { | |
| 3451 | const codepoint = switch (size) { | |
| 3452 | 2 => @as(*const u16, @alignCast(@ptrCast(bytes.ptr + i * 2))).*, | |
| 3453 | 4 => @as(*const u32, @alignCast(@ptrCast(bytes.ptr + i * 4))).*, | |
| 3454 | else => unreachable, | |
| 3455 | }; | |
| 3456 | item.* = try t.createCharLiteralNode(false, codepoint); | |
| 3457 | } | |
| 3458 | const init_args: ast.Payload.Array.ArrayTypeInfo = .{ .len = num_inits, .elem_type = elem_type }; | |
| 3459 | const init_array_type = if (array_type.tag() == .array_type) | |
| 3460 | try ZigTag.array_type.create(t.arena, init_args) | |
| 3461 | else | |
| 3462 | try ZigTag.null_sentinel_array_type.create(t.arena, init_args); | |
| 3463 | break :blk try ZigTag.array_init.create(t.arena, .{ | |
| 3464 | .cond = init_array_type, | |
| 3465 | .cases = init_list, | |
| 3466 | }); | |
| 3467 | }; | |
| 3468 | ||
| 3469 | if (num_inits == array_size) return init_node; | |
| 3470 | assert(array_size > str_length); // If array_size <= str_length, `num_inits == array_size` and we've already returned. | |
| 3471 | ||
| 3472 | const filler_node = try ZigTag.array_filler.create(t.arena, .{ | |
| 3473 | .type = elem_type, | |
| 3474 | .filler = ZigTag.zero_literal.init(), | |
| 3475 | .count = array_size - str_length, | |
| 3476 | }); | |
| 3477 | return ZigTag.array_cat.create(t.arena, .{ .lhs = init_node, .rhs = filler_node }); | |
| 3478 | } | |
| 3479 | ||
| 3480 | fn transCompoundLiteral( | |
| 3481 | t: *Translator, | |
| 3482 | scope: *Scope, | |
| 3483 | literal: Node.CompoundLiteral, | |
| 3484 | used: ResultUsed, | |
| 3485 | ) TransError!ZigNode { | |
| 3486 | if (used == .unused) { | |
| 3487 | return t.transExpr(scope, literal.initializer, .unused); | |
| 3488 | } | |
| 3489 | ||
| 3490 | // TODO taking a reference to a compound literal should result in a mutable | |
| 3491 | // pointer (unless the literal is const). | |
| 3492 | ||
| 3493 | const initializer = try t.transExprCoercing(scope, literal.initializer, .used); | |
| 3494 | const ty = try t.transType(scope, literal.qt, literal.l_paren_tok); | |
| 3495 | if (!literal.thread_local and literal.storage_class != .static) { | |
| 3496 | // In the simple case a compound literal can be translated | |
| 3497 | // simply as `@as(type, initializer)`. | |
| 3498 | return ZigTag.as.create(t.arena, .{ .lhs = ty, .rhs = initializer }); | |
| 3499 | } | |
| 3500 | ||
| 3501 | // Otherwise static or thread local compound literals are translated as | |
| 3502 | // a reference to a variable wrapped in a struct. | |
| 3503 | ||
| 3504 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 3505 | defer block_scope.deinit(); | |
| 3506 | ||
| 3507 | const tmp = try block_scope.reserveMangledName("tmp"); | |
| 3508 | const wrapped_name = "compound_literal"; | |
| 3509 | ||
| 3510 | // const tmp = struct { var compound_literal = initializer }; | |
| 3511 | const temp_decl = try ZigTag.var_decl.create(t.arena, .{ | |
| 3512 | .is_pub = false, | |
| 3513 | .is_const = literal.qt.@"const", | |
| 3514 | .is_extern = false, | |
| 3515 | .is_export = false, | |
| 3516 | .is_threadlocal = literal.thread_local, | |
| 3517 | .linksection_string = null, | |
| 3518 | .alignment = null, | |
| 3519 | .name = wrapped_name, | |
| 3520 | .type = ty, | |
| 3521 | .init = initializer, | |
| 3522 | }); | |
| 3523 | const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = tmp, .init = temp_decl }); | |
| 3524 | try block_scope.statements.append(t.gpa, wrapped); | |
| 3525 | ||
| 3526 | // break :blk tmp.compound_literal | |
| 3527 | const static_tmp_ident = try ZigTag.identifier.create(t.arena, tmp); | |
| 3528 | const field_access = try ZigTag.field_access.create(t.arena, .{ | |
| 3529 | .lhs = static_tmp_ident, | |
| 3530 | .field_name = wrapped_name, | |
| 3531 | }); | |
| 3532 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 3533 | .label = block_scope.label, | |
| 3534 | .val = field_access, | |
| 3535 | }); | |
| 3536 | try block_scope.statements.append(t.gpa, break_node); | |
| 3537 | ||
| 3538 | return block_scope.complete(); | |
| 3539 | } | |
| 3540 | ||
| 3541 | fn transDefaultInit( | |
| 3542 | t: *Translator, | |
| 3543 | scope: *Scope, | |
| 3544 | default_init: Node.DefaultInit, | |
| 3545 | used: ResultUsed, | |
| 3546 | suppress_as: SuppressCast, | |
| 3547 | ) TransError!ZigNode { | |
| 3548 | assert(used == .used); | |
| 3549 | const type_node = try t.transType(scope, default_init.qt, default_init.last_tok); | |
| 3550 | return try t.createZeroValueNode(default_init.qt, type_node, suppress_as); | |
| 3551 | } | |
| 3552 | ||
| 3553 | fn transArrayInit( | |
| 3554 | t: *Translator, | |
| 3555 | scope: *Scope, | |
| 3556 | array_init: Node.ContainerInit, | |
| 3557 | used: ResultUsed, | |
| 3558 | ) TransError!ZigNode { | |
| 3559 | assert(used == .used); | |
| 3560 | const array_item_qt = array_init.container_qt.childType(t.comp); | |
| 3561 | const array_item_type = try t.transType(scope, array_item_qt, array_init.l_brace_tok); | |
| 3562 | var maybe_lhs: ?ZigNode = null; | |
| 3563 | var val_list: std.ArrayListUnmanaged(ZigNode) = .empty; | |
| 3564 | defer val_list.deinit(t.gpa); | |
| 3565 | var i: usize = 0; | |
| 3566 | while (i < array_init.items.len) { | |
| 3567 | const rhs = switch (array_init.items[i].get(t.tree)) { | |
| 3568 | .array_filler_expr => |array_filler| blk: { | |
| 3569 | const node = try ZigTag.array_filler.create(t.arena, .{ | |
| 3570 | .type = array_item_type, | |
| 3571 | .filler = try t.createZeroValueNode(array_item_qt, array_item_type, .no_as), | |
| 3572 | .count = @intCast(array_filler.count), | |
| 3573 | }); | |
| 3574 | i += 1; | |
| 3575 | break :blk node; | |
| 3576 | }, | |
| 3577 | else => blk: { | |
| 3578 | defer val_list.clearRetainingCapacity(); | |
| 3579 | while (i < array_init.items.len) : (i += 1) { | |
| 3580 | if (array_init.items[i].get(t.tree) == .array_filler_expr) break; | |
| 3581 | const expr = try t.transExprCoercing(scope, array_init.items[i], .used); | |
| 3582 | try val_list.append(t.gpa, expr); | |
| 3583 | } | |
| 3584 | const array_type = try ZigTag.array_type.create(t.arena, .{ | |
| 3585 | .elem_type = array_item_type, | |
| 3586 | .len = val_list.items.len, | |
| 3587 | }); | |
| 3588 | const array_init_node = try ZigTag.array_init.create(t.arena, .{ | |
| 3589 | .cond = array_type, | |
| 3590 | .cases = try t.arena.dupe(ZigNode, val_list.items), | |
| 3591 | }); | |
| 3592 | break :blk array_init_node; | |
| 3593 | }, | |
| 3594 | }; | |
| 3595 | maybe_lhs = if (maybe_lhs) |lhs| blk: { | |
| 3596 | const cat = try ZigTag.array_cat.create(t.arena, .{ | |
| 3597 | .lhs = lhs, | |
| 3598 | .rhs = rhs, | |
| 3599 | }); | |
| 3600 | break :blk cat; | |
| 3601 | } else rhs; | |
| 3602 | } | |
| 3603 | return maybe_lhs orelse try ZigTag.container_init_dot.create(t.arena, &.{}); | |
| 3604 | } | |
| 3605 | ||
| 3606 | fn transUnionInit( | |
| 3607 | t: *Translator, | |
| 3608 | scope: *Scope, | |
| 3609 | union_init: Node.UnionInit, | |
| 3610 | used: ResultUsed, | |
| 3611 | ) TransError!ZigNode { | |
| 3612 | assert(used == .used); | |
| 3613 | const init_expr = union_init.initializer orelse | |
| 3614 | return ZigTag.undefined_literal.init(); | |
| 3615 | ||
| 3616 | if (init_expr.get(t.tree) == .default_init_expr) { | |
| 3617 | return try t.transExpr(scope, init_expr, used); | |
| 3618 | } | |
| 3619 | ||
| 3620 | const union_type = try t.transType(scope, union_init.union_qt, union_init.l_brace_tok); | |
| 3621 | ||
| 3622 | const union_base = union_init.union_qt.base(t.comp); | |
| 3623 | const field = union_base.type.@"union".fields[union_init.field_index]; | |
| 3624 | const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ | |
| 3625 | .parent = union_base.qt, | |
| 3626 | .field = field.qt, | |
| 3627 | }).? else field.name.lookup(t.comp); | |
| 3628 | ||
| 3629 | const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); | |
| 3630 | field_init.* = .{ | |
| 3631 | .name = field_name, | |
| 3632 | .value = try t.transExprCoercing(scope, init_expr, .used), | |
| 3633 | }; | |
| 3634 | const container_init = try ZigTag.container_init.create(t.arena, .{ | |
| 3635 | .lhs = union_type, | |
| 3636 | .inits = field_init[0..1], | |
| 3637 | }); | |
| 3638 | return container_init; | |
| 3639 | } | |
| 3640 | ||
| 3641 | fn transStructInit( | |
| 3642 | t: *Translator, | |
| 3643 | scope: *Scope, | |
| 3644 | struct_init: Node.ContainerInit, | |
| 3645 | used: ResultUsed, | |
| 3646 | ) TransError!ZigNode { | |
| 3647 | assert(used == .used); | |
| 3648 | const struct_type = try t.transType(scope, struct_init.container_qt, struct_init.l_brace_tok); | |
| 3649 | const field_inits = try t.arena.alloc(ast.Payload.ContainerInit.Initializer, struct_init.items.len); | |
| 3650 | ||
| 3651 | const struct_base = struct_init.container_qt.base(t.comp); | |
| 3652 | for ( | |
| 3653 | field_inits, | |
| 3654 | struct_init.items, | |
| 3655 | struct_base.type.@"struct".fields, | |
| 3656 | ) |*init, field_expr, field| { | |
| 3657 | const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ | |
| 3658 | .parent = struct_base.qt, | |
| 3659 | .field = field.qt, | |
| 3660 | }).? else field.name.lookup(t.comp); | |
| 3661 | init.* = .{ | |
| 3662 | .name = field_name, | |
| 3663 | .value = try t.transExprCoercing(scope, field_expr, .used), | |
| 3664 | }; | |
| 3665 | } | |
| 3666 | ||
| 3667 | const container_init = try ZigTag.container_init.create(t.arena, .{ | |
| 3668 | .lhs = struct_type, | |
| 3669 | .inits = field_inits, | |
| 3670 | }); | |
| 3671 | return container_init; | |
| 3672 | } | |
| 3673 | ||
| 3674 | fn transTypeInfo( | |
| 3675 | t: *Translator, | |
| 3676 | scope: *Scope, | |
| 3677 | op: ZigTag, | |
| 3678 | typeinfo: Node.TypeInfo, | |
| 3679 | ) TransError!ZigNode { | |
| 3680 | const operand = operand: { | |
| 3681 | if (typeinfo.expr) |expr| { | |
| 3682 | const operand = try t.transExpr(scope, expr, .used); | |
| 3683 | break :operand try ZigTag.typeof.create(t.arena, operand); | |
| 3684 | } | |
| 3685 | break :operand try t.transType(scope, typeinfo.operand_qt, typeinfo.op_tok); | |
| 3686 | }; | |
| 3687 | ||
| 3688 | const payload = try t.arena.create(ast.Payload.UnOp); | |
| 3689 | payload.* = .{ | |
| 3690 | .base = .{ .tag = op }, | |
| 3691 | .data = operand, | |
| 3692 | }; | |
| 3693 | return ZigNode.initPayload(&payload.base); | |
| 3694 | } | |
| 3695 | ||
| 3696 | fn transStmtExpr( | |
| 3697 | t: *Translator, | |
| 3698 | scope: *Scope, | |
| 3699 | stmt_expr: Node.Unary, | |
| 3700 | used: ResultUsed, | |
| 3701 | ) TransError!ZigNode { | |
| 3702 | const compound_stmt = stmt_expr.operand.get(t.tree).compound_stmt; | |
| 3703 | if (used == .unused) { | |
| 3704 | return t.transCompoundStmt(scope, compound_stmt); | |
| 3705 | } | |
| 3706 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 3707 | defer block_scope.deinit(); | |
| 3708 | ||
| 3709 | for (compound_stmt.body[0 .. compound_stmt.body.len - 1]) |stmt| { | |
| 3710 | const result = try t.transStmt(&block_scope.base, stmt); | |
| 3711 | switch (result.tag()) { | |
| 3712 | .declaration, .empty_block => {}, | |
| 3713 | else => try block_scope.statements.append(t.gpa, result), | |
| 3714 | } | |
| 3715 | } | |
| 3716 | ||
| 3717 | const last_result = try t.transExpr(&block_scope.base, compound_stmt.body[compound_stmt.body.len - 1], .used); | |
| 3718 | switch (last_result.tag()) { | |
| 3719 | .declaration, .empty_block => {}, | |
| 3720 | else => { | |
| 3721 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 3722 | .label = block_scope.label, | |
| 3723 | .val = last_result, | |
| 3724 | }); | |
| 3725 | try block_scope.statements.append(t.gpa, break_node); | |
| 3726 | }, | |
| 3727 | } | |
| 3728 | return block_scope.complete(); | |
| 3729 | } | |
| 3730 | ||
| 3731 | fn transConvertvectorExpr( | |
| 3732 | t: *Translator, | |
| 3733 | scope: *Scope, | |
| 3734 | convertvector: Node.Convertvector, | |
| 3735 | ) TransError!ZigNode { | |
| 3736 | var block_scope = try Scope.Block.init(t, scope, true); | |
| 3737 | defer block_scope.deinit(); | |
| 3738 | ||
| 3739 | const src_expr_node = try t.transExpr(&block_scope.base, convertvector.operand, .used); | |
| 3740 | const tmp = try block_scope.reserveMangledName("tmp"); | |
| 3741 | const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = src_expr_node }); | |
| 3742 | try block_scope.statements.append(t.gpa, tmp_decl); | |
| 3743 | const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); | |
| 3744 | ||
| 3745 | const dest_type_node = try t.transType(&block_scope.base, convertvector.dest_qt, convertvector.builtin_tok); | |
| 3746 | const dest_vec_ty = convertvector.dest_qt.get(t.comp, .vector).?; | |
| 3747 | const src_vec_ty = convertvector.operand.qt(t.tree).get(t.comp, .vector).?; | |
| 3748 | ||
| 3749 | const src_elem_sk = src_vec_ty.elem.scalarKind(t.comp); | |
| 3750 | const dest_elem_sk = convertvector.dest_qt.childType(t.comp).scalarKind(t.comp); | |
| 3751 | ||
| 3752 | const items = try t.arena.alloc(ZigNode, dest_vec_ty.len); | |
| 3753 | for (items, 0..dest_vec_ty.len) |*item, i| { | |
| 3754 | const value = try ZigTag.array_access.create(t.arena, .{ | |
| 3755 | .lhs = tmp_ident, | |
| 3756 | .rhs = try t.createNumberNode(i, .int), | |
| 3757 | }); | |
| 3758 | ||
| 3759 | if (src_elem_sk == .float and dest_elem_sk == .float) { | |
| 3760 | item.* = try ZigTag.float_cast.create(t.arena, value); | |
| 3761 | } else if (src_elem_sk == .float) { | |
| 3762 | item.* = try ZigTag.int_from_float.create(t.arena, value); | |
| 3763 | } else if (dest_elem_sk == .float) { | |
| 3764 | item.* = try ZigTag.float_from_int.create(t.arena, value); | |
| 3765 | } else { | |
| 3766 | item.* = try t.transIntCast(value, src_vec_ty.elem, dest_vec_ty.elem); | |
| 3767 | } | |
| 3768 | } | |
| 3769 | ||
| 3770 | const vec_init = try ZigTag.array_init.create(t.arena, .{ | |
| 3771 | .cond = dest_type_node, | |
| 3772 | .cases = items, | |
| 3773 | }); | |
| 3774 | const break_node = try ZigTag.break_val.create(t.arena, .{ | |
| 3775 | .label = block_scope.label, | |
| 3776 | .val = vec_init, | |
| 3777 | }); | |
| 3778 | try block_scope.statements.append(t.gpa, break_node); | |
| 3779 | ||
| 3780 | return block_scope.complete(); | |
| 3781 | } | |
| 3782 | ||
| 3783 | fn transShufflevectorExpr( | |
| 3784 | t: *Translator, | |
| 3785 | scope: *Scope, | |
| 3786 | shufflevector: Node.Shufflevector, | |
| 3787 | ) TransError!ZigNode { | |
| 3788 | if (shufflevector.indexes.len == 0) { | |
| 3789 | return t.fail(error.UnsupportedTranslation, shufflevector.builtin_tok, "@shuffle needs at least 1 index", .{}); | |
| 3790 | } | |
| 3791 | ||
| 3792 | const a = try t.transExpr(scope, shufflevector.lhs, .used); | |
| 3793 | const b = try t.transExpr(scope, shufflevector.rhs, .used); | |
| 3794 | ||
| 3795 | // First two arguments to __builtin_shufflevector must be the same type | |
| 3796 | const vector_child_type = try t.vectorTypeInfo(a, "child"); | |
| 3797 | const vector_len = try t.vectorTypeInfo(a, "len"); | |
| 3798 | const shuffle_mask = blk: { | |
| 3799 | const mask_len = shufflevector.indexes.len; | |
| 3800 | ||
| 3801 | const mask_type = try ZigTag.vector.create(t.arena, .{ | |
| 3802 | .lhs = try t.createNumberNode(mask_len, .int), | |
| 3803 | .rhs = try ZigTag.type.create(t.arena, "i32"), | |
| 3804 | }); | |
| 3805 | ||
| 3806 | const init_list = try t.arena.alloc(ZigNode, mask_len); | |
| 3807 | for (init_list, shufflevector.indexes) |*init, index| { | |
| 3808 | const index_expr = try t.transExprCoercing(scope, index, .used); | |
| 3809 | const converted_index = try t.createHelperCallNode(.shuffleVectorIndex, &.{ index_expr, vector_len }); | |
| 3810 | init.* = converted_index; | |
| 3811 | } | |
| 3812 | ||
| 3813 | break :blk try ZigTag.array_init.create(t.arena, .{ | |
| 3814 | .cond = mask_type, | |
| 3815 | .cases = init_list, | |
| 3816 | }); | |
| 3817 | }; | |
| 3818 | ||
| 3819 | return ZigTag.shuffle.create(t.arena, .{ | |
| 3820 | .element_type = vector_child_type, | |
| 3821 | .a = a, | |
| 3822 | .b = b, | |
| 3823 | .mask_vector = shuffle_mask, | |
| 3824 | }); | |
| 3825 | } | |
| 3826 | ||
| 3827 | // ===================== | |
| 3828 | // Node creation helpers | |
| 3829 | // ===================== | |
| 3830 | ||
| 3831 | fn createZeroValueNode( | |
| 3832 | t: *Translator, | |
| 3833 | qt: QualType, | |
| 3834 | type_node: ZigNode, | |
| 3835 | suppress_as: SuppressCast, | |
| 3836 | ) !ZigNode { | |
| 3837 | switch (qt.base(t.comp).type) { | |
| 3838 | .bool => return ZigTag.false_literal.init(), | |
| 3839 | .int, .bit_int, .float => { | |
| 3840 | const zero_literal = ZigTag.zero_literal.init(); | |
| 3841 | return switch (suppress_as) { | |
| 3842 | .with_as => try t.createBinOpNode(.as, type_node, zero_literal), | |
| 3843 | .no_as => zero_literal, | |
| 3844 | }; | |
| 3845 | }, | |
| 3846 | .pointer => { | |
| 3847 | const null_literal = ZigTag.null_literal.init(); | |
| 3848 | return switch (suppress_as) { | |
| 3849 | .with_as => try t.createBinOpNode(.as, type_node, null_literal), | |
| 3850 | .no_as => null_literal, | |
| 3851 | }; | |
| 3852 | }, | |
| 3853 | else => {}, | |
| 3854 | } | |
| 3855 | return try ZigTag.std_mem_zeroes.create(t.arena, type_node); | |
| 3856 | } | |
| 3857 | ||
| 3858 | fn createIntNode(t: *Translator, int: aro.Value) !ZigNode { | |
| 3859 | var space: aro.Interner.Tag.Int.BigIntSpace = undefined; | |
| 3860 | var big = t.comp.interner.get(int.ref()).toBigInt(&space); | |
| 3861 | const is_negative = !big.positive; | |
| 3862 | big.positive = true; | |
| 3863 | ||
| 3864 | const str = big.toStringAlloc(t.arena, 10, .lower) catch |err| switch (err) { | |
| 3865 | error.OutOfMemory => return error.OutOfMemory, | |
| 3866 | }; | |
| 3867 | const res = try ZigTag.integer_literal.create(t.arena, str); | |
| 3868 | if (is_negative) return ZigTag.negate.create(t.arena, res); | |
| 3869 | return res; | |
| 3870 | } | |
| 3871 | ||
| 3872 | fn createNumberNode(t: *Translator, num: anytype, num_kind: enum { int, float }) !ZigNode { | |
| 3873 | const fmt_s = switch (@typeInfo(@TypeOf(num))) { | |
| 3874 | .int, .comptime_int => "{d}", | |
| 3875 | else => "{s}", | |
| 3876 | }; | |
| 3877 | const str = try std.fmt.allocPrint(t.arena, fmt_s, .{num}); | |
| 3878 | if (num_kind == .float) | |
| 3879 | return ZigTag.float_literal.create(t.arena, str) | |
| 3880 | else | |
| 3881 | return ZigTag.integer_literal.create(t.arena, str); | |
| 3882 | } | |
| 3883 | ||
| 3884 | fn createCharLiteralNode(t: *Translator, narrow: bool, val: u32) TransError!ZigNode { | |
| 3885 | return ZigTag.char_literal.create(t.arena, if (narrow) | |
| 3886 | try std.fmt.allocPrint(t.arena, "'{f}'", .{std.zig.fmtChar(&.{@as(u8, @intCast(val))})}) | |
| 3887 | else | |
| 3888 | try std.fmt.allocPrint(t.arena, "'\\u{{{x}}}'", .{val})); | |
| 3889 | } | |
| 3890 | ||
| 3891 | fn createBinOpNode( | |
| 3892 | t: *Translator, | |
| 3893 | op: ZigTag, | |
| 3894 | lhs: ZigNode, | |
| 3895 | rhs: ZigNode, | |
| 3896 | ) !ZigNode { | |
| 3897 | const payload = try t.arena.create(ast.Payload.BinOp); | |
| 3898 | payload.* = .{ | |
| 3899 | .base = .{ .tag = op }, | |
| 3900 | .data = .{ | |
| 3901 | .lhs = lhs, | |
| 3902 | .rhs = rhs, | |
| 3903 | }, | |
| 3904 | }; | |
| 3905 | return ZigNode.initPayload(&payload.base); | |
| 3906 | } | |
| 3907 | ||
| 3908 | pub fn createHelperCallNode(t: *Translator, name: std.meta.DeclEnum(@import("helpers")), args_opt: ?[]const ZigNode) !ZigNode { | |
| 3909 | if (args_opt) |args| { | |
| 3910 | return ZigTag.helper_call.create(t.arena, .{ | |
| 3911 | .name = @tagName(name), | |
| 3912 | .args = try t.arena.dupe(ZigNode, args), | |
| 3913 | }); | |
| 3914 | } else { | |
| 3915 | return ZigTag.helper_ref.create(t.arena, @tagName(name)); | |
| 3916 | } | |
| 3917 | } | |
| 3918 | ||
| 3919 | /// Cast a signed integer node to a usize, for use in pointer arithmetic. Negative numbers | |
| 3920 | /// will become very large positive numbers but that is ok since we only use this in | |
| 3921 | /// pointer arithmetic expressions, where wraparound will ensure we get the correct value. | |
| 3922 | /// node -> @as(usize, @bitCast(@as(isize, @intCast(node)))) | |
| 3923 | fn usizeCastForWrappingPtrArithmetic(t: *Translator, node: ZigNode) TransError!ZigNode { | |
| 3924 | const intcast_node = try ZigTag.as.create(t.arena, .{ | |
| 3925 | .lhs = try ZigTag.type.create(t.arena, "isize"), | |
| 3926 | .rhs = try ZigTag.int_cast.create(t.arena, node), | |
| 3927 | }); | |
| 3928 | ||
| 3929 | return ZigTag.as.create(t.arena, .{ | |
| 3930 | .lhs = try ZigTag.type.create(t.arena, "usize"), | |
| 3931 | .rhs = try ZigTag.bit_cast.create(t.arena, intcast_node), | |
| 3932 | }); | |
| 3933 | } | |
| 3934 | ||
| 3935 | /// @typeInfo(@TypeOf(vec_node)).vector.<field> | |
| 3936 | fn vectorTypeInfo(t: *Translator, vec_node: ZigNode, field: []const u8) TransError!ZigNode { | |
| 3937 | const typeof_call = try ZigTag.typeof.create(t.arena, vec_node); | |
| 3938 | const typeinfo_call = try ZigTag.typeinfo.create(t.arena, typeof_call); | |
| 3939 | const vector_type_info = try ZigTag.field_access.create(t.arena, .{ .lhs = typeinfo_call, .field_name = "vector" }); | |
| 3940 | return ZigTag.field_access.create(t.arena, .{ .lhs = vector_type_info, .field_name = field }); | |
| 3941 | } | |
| 3942 | ||
| 3943 | /// Build a getter function for a flexible array field in a C record | |
| 3944 | /// e.g. `T items[]` or `T items[0]`. The generated function returns a [*c] pointer | |
| 3945 | /// to the flexible array with the correct const and volatile qualifiers | |
| 3946 | fn createFlexibleMemberFn( | |
| 3947 | t: *Translator, | |
| 3948 | member_name: []const u8, | |
| 3949 | field_name: []const u8, | |
| 3950 | ) Error!ZigNode { | |
| 3951 | const self_param_name = "self"; | |
| 3952 | const self_param = try ZigTag.identifier.create(t.arena, self_param_name); | |
| 3953 | const self_type = try ZigTag.typeof.create(t.arena, self_param); | |
| 3954 | ||
| 3955 | const fn_params = try t.arena.alloc(ast.Payload.Param, 1); | |
| 3956 | fn_params[0] = .{ | |
| 3957 | .name = self_param_name, | |
| 3958 | .type = ZigTag.@"anytype".init(), | |
| 3959 | .is_noalias = false, | |
| 3960 | }; | |
| 3961 | ||
| 3962 | // @typeInfo(@TypeOf(self.*.<field_name>)).pointer.child | |
| 3963 | const dereffed = try ZigTag.deref.create(t.arena, self_param); | |
| 3964 | const field_access = try ZigTag.field_access.create(t.arena, .{ .lhs = dereffed, .field_name = field_name }); | |
| 3965 | const type_of = try ZigTag.typeof.create(t.arena, field_access); | |
| 3966 | const type_info = try ZigTag.typeinfo.create(t.arena, type_of); | |
| 3967 | const array_info = try ZigTag.field_access.create(t.arena, .{ .lhs = type_info, .field_name = "array" }); | |
| 3968 | const child_info = try ZigTag.field_access.create(t.arena, .{ .lhs = array_info, .field_name = "child" }); | |
| 3969 | ||
| 3970 | const return_type = try t.createHelperCallNode(.FlexibleArrayType, &.{ self_type, child_info }); | |
| 3971 | ||
| 3972 | // return @ptrCast(&self.*.<field_name>); | |
| 3973 | const address_of = try ZigTag.address_of.create(t.arena, field_access); | |
| 3974 | const casted = try ZigTag.ptr_cast.create(t.arena, address_of); | |
| 3975 | const return_stmt = try ZigTag.@"return".create(t.arena, casted); | |
| 3976 | const body = try ZigTag.block_single.create(t.arena, return_stmt); | |
| 3977 | ||
| 3978 | return ZigTag.func.create(t.arena, .{ | |
| 3979 | .is_pub = true, | |
| 3980 | .is_extern = false, | |
| 3981 | .is_export = false, | |
| 3982 | .is_inline = false, | |
| 3983 | .is_var_args = false, | |
| 3984 | .name = member_name, | |
| 3985 | .linksection_string = null, | |
| 3986 | .explicit_callconv = null, | |
| 3987 | .params = fn_params, | |
| 3988 | .return_type = return_type, | |
| 3989 | .body = body, | |
| 3990 | .alignment = null, | |
| 3991 | }); | |
| 3992 | } | |
| 3993 | ||
| 3994 | // ================= | |
| 3995 | // Macro translation | |
| 3996 | // ================= | |
| 3997 | ||
| 3998 | fn transMacros(t: *Translator) !void { | |
| 3999 | var tok_list = std.ArrayList(CToken).init(t.gpa); | |
| 4000 | defer tok_list.deinit(); | |
| 4001 | ||
| 4002 | var pattern_list = try PatternList.init(t.gpa); | |
| 4003 | defer pattern_list.deinit(t.gpa); | |
| 4004 | ||
| 4005 | for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { | |
| 4006 | if (macro.is_builtin) continue; | |
| 4007 | if (t.global_scope.containsNow(name)) { | |
| 4008 | continue; | |
| 4009 | } | |
| 4010 | ||
| 4011 | tok_list.items.len = 0; | |
| 4012 | try tok_list.ensureUnusedCapacity(macro.tokens.len); | |
| 4013 | for (macro.tokens) |tok| { | |
| 4014 | switch (tok.id) { | |
| 4015 | .invalid => continue, | |
| 4016 | .whitespace => continue, | |
| 4017 | .comment => continue, | |
| 4018 | .macro_ws => continue, | |
| 4019 | else => {}, | |
| 4020 | } | |
| 4021 | tok_list.appendAssumeCapacity(tok); | |
| 4022 | } | |
| 4023 | ||
| 4024 | if (macro.is_func) { | |
| 4025 | const ms: PatternList.MacroSlicer = .{ | |
| 4026 | .tokens = tok_list.items, | |
| 4027 | .source = t.comp.getSource(macro.loc.id).buf, | |
| 4028 | .params = @intCast(macro.params.len), | |
| 4029 | }; | |
| 4030 | if (try pattern_list.match(ms)) |impl| { | |
| 4031 | const decl = try ZigTag.pub_var_simple.create(t.arena, .{ | |
| 4032 | .name = name, | |
| 4033 | .init = try t.createHelperCallNode(impl, null), | |
| 4034 | }); | |
| 4035 | try t.addTopLevelDecl(name, decl); | |
| 4036 | continue; | |
| 4037 | } | |
| 4038 | } | |
| 4039 | ||
| 4040 | if (t.checkTranslatableMacro(tok_list.items, macro.params)) |err| { | |
| 4041 | switch (err) { | |
| 4042 | .undefined_identifier => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: undefined identifier `{s}`", .{ident}), | |
| 4043 | .invalid_arg_usage => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: untranslatable usage of arg `{s}`", .{ident}), | |
| 4044 | } | |
| 4045 | continue; | |
| 4046 | } | |
| 4047 | ||
| 4048 | var macro_translator: MacroTranslator = .{ | |
| 4049 | .t = t, | |
| 4050 | .tokens = tok_list.items, | |
| 4051 | .source = t.comp.getSource(macro.loc.id).buf, | |
| 4052 | .name = name, | |
| 4053 | .macro = macro, | |
| 4054 | }; | |
| 4055 | ||
| 4056 | const res = if (macro.is_func) | |
| 4057 | macro_translator.transFnMacro() | |
| 4058 | else | |
| 4059 | macro_translator.transMacro(); | |
| 4060 | res catch |err| switch (err) { | |
| 4061 | error.ParseError => continue, | |
| 4062 | error.OutOfMemory => |e| return e, | |
| 4063 | }; | |
| 4064 | } | |
| 4065 | } | |
| 4066 | ||
| 4067 | const MacroTranslateError = union(enum) { | |
| 4068 | undefined_identifier: []const u8, | |
| 4069 | invalid_arg_usage: []const u8, | |
| 4070 | }; | |
| 4071 | ||
| 4072 | fn checkTranslatableMacro(t: *Translator, tokens: []const CToken, params: []const []const u8) ?MacroTranslateError { | |
| 4073 | var last_is_type_kw = false; | |
| 4074 | var i: usize = 0; | |
| 4075 | while (i < tokens.len) : (i += 1) { | |
| 4076 | const token = tokens[i]; | |
| 4077 | switch (token.id) { | |
| 4078 | .period, .arrow => i += 1, // skip next token since field identifiers can be unknown | |
| 4079 | .keyword_struct, .keyword_union, .keyword_enum => if (!last_is_type_kw) { | |
| 4080 | last_is_type_kw = true; | |
| 4081 | continue; | |
| 4082 | }, | |
| 4083 | .macro_param, .macro_param_no_expand => { | |
| 4084 | if (last_is_type_kw) { | |
| 4085 | return .{ .invalid_arg_usage = params[token.end] }; | |
| 4086 | } | |
| 4087 | }, | |
| 4088 | .identifier, .extended_identifier => { | |
| 4089 | const identifier = t.pp.tokSlice(token); | |
| 4090 | if (!t.global_scope.contains(identifier) and !builtins.map.has(identifier)) { | |
| 4091 | return .{ .undefined_identifier = identifier }; | |
| 4092 | } | |
| 4093 | }, | |
| 4094 | else => {}, | |
| 4095 | } | |
| 4096 | last_is_type_kw = false; | |
| 4097 | } | |
| 4098 | return null; | |
| 4099 | } | |
| 4100 | ||
| 4101 | fn getContainer(t: *Translator, node: ZigNode) ?ZigNode { | |
| 4102 | switch (node.tag()) { | |
| 4103 | .@"union", | |
| 4104 | .@"struct", | |
| 4105 | .address_of, | |
| 4106 | .bit_not, | |
| 4107 | .not, | |
| 4108 | .optional_type, | |
| 4109 | .negate, | |
| 4110 | .negate_wrap, | |
| 4111 | .array_type, | |
| 4112 | .c_pointer, | |
| 4113 | .single_pointer, | |
| 4114 | => return node, | |
| 4115 | ||
| 4116 | .identifier => { | |
| 4117 | const ident = node.castTag(.identifier).?; | |
| 4118 | if (t.global_scope.sym_table.get(ident.data)) |value| { | |
| 4119 | if (value.castTag(.var_decl)) |var_decl| | |
| 4120 | return t.getContainer(var_decl.data.init.?); | |
| 4121 | if (value.castTag(.var_simple) orelse value.castTag(.pub_var_simple)) |var_decl| | |
| 4122 | return t.getContainer(var_decl.data.init); | |
| 4123 | } | |
| 4124 | }, | |
| 4125 | ||
| 4126 | .field_access => { | |
| 4127 | const field_access = node.castTag(.field_access).?; | |
| 4128 | ||
| 4129 | if (t.getContainerTypeOf(field_access.data.lhs)) |ty_node| { | |
| 4130 | if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| { | |
| 4131 | for (container.data.fields) |field| { | |
| 4132 | if (mem.eql(u8, field.name, field_access.data.field_name)) { | |
| 4133 | return t.getContainer(field.type); | |
| 4134 | } | |
| 4135 | } | |
| 4136 | } | |
| 4137 | } | |
| 4138 | }, | |
| 4139 | ||
| 4140 | else => {}, | |
| 4141 | } | |
| 4142 | return null; | |
| 4143 | } | |
| 4144 | ||
| 4145 | fn getContainerTypeOf(t: *Translator, ref: ZigNode) ?ZigNode { | |
| 4146 | if (ref.castTag(.identifier)) |ident| { | |
| 4147 | if (t.global_scope.sym_table.get(ident.data)) |value| { | |
| 4148 | if (value.castTag(.var_decl)) |var_decl| { | |
| 4149 | return t.getContainer(var_decl.data.type); | |
| 4150 | } | |
| 4151 | } | |
| 4152 | } else if (ref.castTag(.field_access)) |field_access| { | |
| 4153 | if (t.getContainerTypeOf(field_access.data.lhs)) |ty_node| { | |
| 4154 | if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| { | |
| 4155 | for (container.data.fields) |field| { | |
| 4156 | if (mem.eql(u8, field.name, field_access.data.field_name)) { | |
| 4157 | return t.getContainer(field.type); | |
| 4158 | } | |
| 4159 | } | |
| 4160 | } else return ty_node; | |
| 4161 | } | |
| 4162 | } | |
| 4163 | return null; | |
| 4164 | } | |
| 4165 | ||
| 4166 | pub fn getFnProto(t: *Translator, ref: ZigNode) ?*ast.Payload.Func { | |
| 4167 | const init = if (ref.castTag(.var_decl)) |v| | |
| 4168 | v.data.init orelse return null | |
| 4169 | else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v| | |
| 4170 | v.data.init | |
| 4171 | else | |
| 4172 | return null; | |
| 4173 | if (t.getContainerTypeOf(init)) |ty_node| { | |
| 4174 | if (ty_node.castTag(.optional_type)) |prefix| { | |
| 4175 | if (prefix.data.castTag(.single_pointer)) |sp| { | |
| 4176 | if (sp.data.elem_type.castTag(.func)) |fn_proto| { | |
| 4177 | return fn_proto; | |
| 4178 | } | |
| 4179 | } | |
| 4180 | } | |
| 4181 | } | |
| 4182 | return null; | |
| 4183 | } |
lib/compiler/translate-c/src/ast.zig deleted-3063| ... | ... | @@ -1,3063 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Allocator = std.mem.Allocator; | |
| 3 | ||
| 4 | pub const Node = extern union { | |
| 5 | /// If the tag value is less than Tag.no_payload_count, then no pointer | |
| 6 | /// dereference is needed. | |
| 7 | tag_if_small_enough: usize, | |
| 8 | ptr_otherwise: *Payload, | |
| 9 | ||
| 10 | pub const Tag = enum { | |
| 11 | /// Declarations add themselves to the correct scopes and should not be emitted as this tag. | |
| 12 | declaration, | |
| 13 | null_literal, | |
| 14 | undefined_literal, | |
| 15 | /// opaque {} | |
| 16 | opaque_literal, | |
| 17 | true_literal, | |
| 18 | false_literal, | |
| 19 | empty_block, | |
| 20 | return_void, | |
| 21 | zero_literal, | |
| 22 | one_literal, | |
| 23 | @"unreachable", | |
| 24 | void_type, | |
| 25 | noreturn_type, | |
| 26 | @"anytype", | |
| 27 | @"continue", | |
| 28 | @"break", | |
| 29 | // After this, the tag requires a payload. | |
| 30 | ||
| 31 | integer_literal, | |
| 32 | float_literal, | |
| 33 | string_literal, | |
| 34 | char_literal, | |
| 35 | enum_literal, | |
| 36 | /// "string"[0..end] | |
| 37 | string_slice, | |
| 38 | identifier, | |
| 39 | @"if", | |
| 40 | /// if (!operand) break; | |
| 41 | if_not_break, | |
| 42 | @"while", | |
| 43 | /// while (true) operand | |
| 44 | while_true, | |
| 45 | @"switch", | |
| 46 | /// else => operand, | |
| 47 | switch_else, | |
| 48 | /// items => body, | |
| 49 | switch_prong, | |
| 50 | break_val, | |
| 51 | @"return", | |
| 52 | field_access, | |
| 53 | array_access, | |
| 54 | call, | |
| 55 | var_decl, | |
| 56 | /// const name = struct { init } | |
| 57 | wrapped_local, | |
| 58 | /// var name = init.* | |
| 59 | mut_str, | |
| 60 | func, | |
| 61 | warning, | |
| 62 | @"struct", | |
| 63 | @"union", | |
| 64 | @"opaque", | |
| 65 | @"comptime", | |
| 66 | @"defer", | |
| 67 | array_init, | |
| 68 | tuple, | |
| 69 | container_init, | |
| 70 | container_init_dot, | |
| 71 | /// _ = operand; | |
| 72 | discard, | |
| 73 | ||
| 74 | // a + b | |
| 75 | add, | |
| 76 | // a = b | |
| 77 | add_assign, | |
| 78 | // c = (a = b) | |
| 79 | add_wrap, | |
| 80 | add_wrap_assign, | |
| 81 | sub, | |
| 82 | sub_assign, | |
| 83 | sub_wrap, | |
| 84 | sub_wrap_assign, | |
| 85 | mul, | |
| 86 | mul_assign, | |
| 87 | mul_wrap, | |
| 88 | mul_wrap_assign, | |
| 89 | div, | |
| 90 | div_assign, | |
| 91 | shl, | |
| 92 | shl_assign, | |
| 93 | shr, | |
| 94 | shr_assign, | |
| 95 | mod, | |
| 96 | mod_assign, | |
| 97 | @"and", | |
| 98 | @"or", | |
| 99 | less_than, | |
| 100 | less_than_equal, | |
| 101 | greater_than, | |
| 102 | greater_than_equal, | |
| 103 | equal, | |
| 104 | not_equal, | |
| 105 | bit_and, | |
| 106 | bit_and_assign, | |
| 107 | bit_or, | |
| 108 | bit_or_assign, | |
| 109 | bit_xor, | |
| 110 | bit_xor_assign, | |
| 111 | array_cat, | |
| 112 | ellipsis3, | |
| 113 | assign, | |
| 114 | ||
| 115 | /// @intCast(operand) | |
| 116 | int_cast, | |
| 117 | /// @constCast(operand) | |
| 118 | const_cast, | |
| 119 | /// @volatileCast(operand) | |
| 120 | volatile_cast, | |
| 121 | /// @divTrunc(lhs, rhs) | |
| 122 | div_trunc, | |
| 123 | /// @intFromBool(operand) | |
| 124 | int_from_bool, | |
| 125 | /// @as(lhs, rhs) | |
| 126 | as, | |
| 127 | /// @truncate(operand) | |
| 128 | truncate, | |
| 129 | /// @bitCast(operand) | |
| 130 | bit_cast, | |
| 131 | /// @floatCast(operand) | |
| 132 | float_cast, | |
| 133 | /// @intFromFloat(operand) | |
| 134 | int_from_float, | |
| 135 | /// @floatFromInt(operand) | |
| 136 | float_from_int, | |
| 137 | /// @ptrFromInt(operand) | |
| 138 | ptr_from_int, | |
| 139 | /// @intFromPtr(operand) | |
| 140 | int_from_ptr, | |
| 141 | /// @alignCast(operand) | |
| 142 | align_cast, | |
| 143 | /// @ptrCast(operand) | |
| 144 | ptr_cast, | |
| 145 | /// @divExact(lhs, rhs) | |
| 146 | div_exact, | |
| 147 | /// @offsetOf(lhs, rhs) | |
| 148 | offset_of, | |
| 149 | /// @splat(operand) | |
| 150 | vector_zero_init, | |
| 151 | /// @shuffle(type, a, b, mask) | |
| 152 | shuffle, | |
| 153 | /// @extern(ty, .{ .name = n }) | |
| 154 | builtin_extern, | |
| 155 | ||
| 156 | /// @byteSwap(operand) | |
| 157 | byte_swap, | |
| 158 | /// @ceil(operand) | |
| 159 | ceil, | |
| 160 | /// @cos(operand) | |
| 161 | cos, | |
| 162 | /// @sin(operand) | |
| 163 | sin, | |
| 164 | /// @exp(operand) | |
| 165 | exp, | |
| 166 | /// @exp2(operand) | |
| 167 | exp2, | |
| 168 | /// @exp10(operand) | |
| 169 | exp10, | |
| 170 | /// @abs(operand) | |
| 171 | abs, | |
| 172 | /// @log(operand) | |
| 173 | log, | |
| 174 | /// @log2(operand) | |
| 175 | log2, | |
| 176 | /// @log10(operand) | |
| 177 | log10, | |
| 178 | /// @round(operand) | |
| 179 | round, | |
| 180 | /// @sqrt(operand) | |
| 181 | sqrt, | |
| 182 | /// @trunc(operand) | |
| 183 | trunc, | |
| 184 | /// @floor(operand) | |
| 185 | floor, | |
| 186 | ||
| 187 | /// __helpers.<name>(argshelper_call) | |
| 188 | helper_call, | |
| 189 | /// __helpers.<name> | |
| 190 | helper_ref, | |
| 191 | ||
| 192 | asm_simple, | |
| 193 | ||
| 194 | negate, | |
| 195 | negate_wrap, | |
| 196 | bit_not, | |
| 197 | not, | |
| 198 | address_of, | |
| 199 | /// .? | |
| 200 | unwrap, | |
| 201 | /// .* | |
| 202 | deref, | |
| 203 | ||
| 204 | block, | |
| 205 | /// { operand } | |
| 206 | block_single, | |
| 207 | ||
| 208 | sizeof, | |
| 209 | alignof, | |
| 210 | typeof, | |
| 211 | typeinfo, | |
| 212 | type, | |
| 213 | ||
| 214 | optional_type, | |
| 215 | c_pointer, | |
| 216 | single_pointer, | |
| 217 | array_type, | |
| 218 | null_sentinel_array_type, | |
| 219 | ||
| 220 | /// @Vector(lhs, rhs) | |
| 221 | vector, | |
| 222 | /// @import("std").mem.zeroes(operand) | |
| 223 | std_mem_zeroes, | |
| 224 | /// @import("std").mem.zeroInit(lhs, rhs) | |
| 225 | std_mem_zeroinit, | |
| 226 | // pub const name = @compileError(msg); | |
| 227 | fail_decl, | |
| 228 | // var actual = mangled; | |
| 229 | arg_redecl, | |
| 230 | /// pub const alias = actual; | |
| 231 | alias, | |
| 232 | /// const name = init; | |
| 233 | var_simple, | |
| 234 | /// pub const name = init; | |
| 235 | pub_var_simple, | |
| 236 | /// pub? const name (: type)? = value | |
| 237 | enum_constant, | |
| 238 | ||
| 239 | /// pub inline fn name(params) return_type body | |
| 240 | pub_inline_fn, | |
| 241 | ||
| 242 | /// array_type{} | |
| 243 | empty_array, | |
| 244 | /// [1]type{val} ** count | |
| 245 | array_filler, | |
| 246 | ||
| 247 | /// comptime { if (!(lhs)) @compileError(rhs); } | |
| 248 | static_assert, | |
| 249 | ||
| 250 | pub const last_no_payload_tag = Tag.@"break"; | |
| 251 | pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1; | |
| 252 | ||
| 253 | pub fn Type(comptime t: Tag) type { | |
| 254 | return switch (t) { | |
| 255 | .declaration, | |
| 256 | .null_literal, | |
| 257 | .undefined_literal, | |
| 258 | .opaque_literal, | |
| 259 | .true_literal, | |
| 260 | .false_literal, | |
| 261 | .empty_block, | |
| 262 | .return_void, | |
| 263 | .zero_literal, | |
| 264 | .one_literal, | |
| 265 | .void_type, | |
| 266 | .noreturn_type, | |
| 267 | .@"anytype", | |
| 268 | .@"continue", | |
| 269 | .@"break", | |
| 270 | .@"unreachable", | |
| 271 | => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), | |
| 272 | ||
| 273 | .std_mem_zeroes, | |
| 274 | .@"return", | |
| 275 | .@"comptime", | |
| 276 | .@"defer", | |
| 277 | .asm_simple, | |
| 278 | .negate, | |
| 279 | .negate_wrap, | |
| 280 | .bit_not, | |
| 281 | .not, | |
| 282 | .optional_type, | |
| 283 | .address_of, | |
| 284 | .unwrap, | |
| 285 | .deref, | |
| 286 | .int_from_ptr, | |
| 287 | .empty_array, | |
| 288 | .while_true, | |
| 289 | .if_not_break, | |
| 290 | .switch_else, | |
| 291 | .block_single, | |
| 292 | .int_from_bool, | |
| 293 | .sizeof, | |
| 294 | .alignof, | |
| 295 | .typeof, | |
| 296 | .typeinfo, | |
| 297 | .align_cast, | |
| 298 | .truncate, | |
| 299 | .bit_cast, | |
| 300 | .float_cast, | |
| 301 | .int_from_float, | |
| 302 | .float_from_int, | |
| 303 | .ptr_from_int, | |
| 304 | .ptr_cast, | |
| 305 | .int_cast, | |
| 306 | .const_cast, | |
| 307 | .volatile_cast, | |
| 308 | .vector_zero_init, | |
| 309 | .byte_swap, | |
| 310 | .ceil, | |
| 311 | .cos, | |
| 312 | .sin, | |
| 313 | .exp, | |
| 314 | .exp2, | |
| 315 | .exp10, | |
| 316 | .abs, | |
| 317 | .log, | |
| 318 | .log2, | |
| 319 | .log10, | |
| 320 | .round, | |
| 321 | .sqrt, | |
| 322 | .trunc, | |
| 323 | .floor, | |
| 324 | => Payload.UnOp, | |
| 325 | ||
| 326 | .add, | |
| 327 | .add_assign, | |
| 328 | .add_wrap, | |
| 329 | .add_wrap_assign, | |
| 330 | .sub, | |
| 331 | .sub_assign, | |
| 332 | .sub_wrap, | |
| 333 | .sub_wrap_assign, | |
| 334 | .mul, | |
| 335 | .mul_assign, | |
| 336 | .mul_wrap, | |
| 337 | .mul_wrap_assign, | |
| 338 | .div, | |
| 339 | .div_assign, | |
| 340 | .shl, | |
| 341 | .shl_assign, | |
| 342 | .shr, | |
| 343 | .shr_assign, | |
| 344 | .mod, | |
| 345 | .mod_assign, | |
| 346 | .@"and", | |
| 347 | .@"or", | |
| 348 | .less_than, | |
| 349 | .less_than_equal, | |
| 350 | .greater_than, | |
| 351 | .greater_than_equal, | |
| 352 | .equal, | |
| 353 | .not_equal, | |
| 354 | .bit_and, | |
| 355 | .bit_and_assign, | |
| 356 | .bit_or, | |
| 357 | .bit_or_assign, | |
| 358 | .bit_xor, | |
| 359 | .bit_xor_assign, | |
| 360 | .div_trunc, | |
| 361 | .as, | |
| 362 | .array_cat, | |
| 363 | .ellipsis3, | |
| 364 | .assign, | |
| 365 | .array_access, | |
| 366 | .std_mem_zeroinit, | |
| 367 | .vector, | |
| 368 | .div_exact, | |
| 369 | .offset_of, | |
| 370 | .static_assert, | |
| 371 | => Payload.BinOp, | |
| 372 | ||
| 373 | .integer_literal, | |
| 374 | .float_literal, | |
| 375 | .string_literal, | |
| 376 | .char_literal, | |
| 377 | .enum_literal, | |
| 378 | .identifier, | |
| 379 | .warning, | |
| 380 | .type, | |
| 381 | => Payload.Value, | |
| 382 | .discard => Payload.Discard, | |
| 383 | .@"if" => Payload.If, | |
| 384 | .@"while" => Payload.While, | |
| 385 | .@"switch", .array_init, .switch_prong => Payload.Switch, | |
| 386 | .break_val => Payload.BreakVal, | |
| 387 | .call => Payload.Call, | |
| 388 | .var_decl => Payload.VarDecl, | |
| 389 | .func => Payload.Func, | |
| 390 | .@"struct", .@"union", .@"opaque" => Payload.Container, | |
| 391 | .tuple => Payload.TupleInit, | |
| 392 | .container_init => Payload.ContainerInit, | |
| 393 | .container_init_dot => Payload.ContainerInitDot, | |
| 394 | .block => Payload.Block, | |
| 395 | .c_pointer, .single_pointer => Payload.Pointer, | |
| 396 | .array_type, .null_sentinel_array_type => Payload.Array, | |
| 397 | .arg_redecl, .alias, .fail_decl => Payload.ArgRedecl, | |
| 398 | .var_simple, .pub_var_simple, .wrapped_local, .mut_str => Payload.SimpleVarDecl, | |
| 399 | .enum_constant => Payload.EnumConstant, | |
| 400 | .array_filler => Payload.ArrayFiller, | |
| 401 | .pub_inline_fn => Payload.PubInlineFn, | |
| 402 | .field_access => Payload.FieldAccess, | |
| 403 | .string_slice => Payload.StringSlice, | |
| 404 | .shuffle => Payload.Shuffle, | |
| 405 | .builtin_extern => Payload.Extern, | |
| 406 | .helper_call => Payload.HelperCall, | |
| 407 | .helper_ref => Payload.HelperRef, | |
| 408 | }; | |
| 409 | } | |
| 410 | ||
| 411 | pub fn init(comptime t: Tag) Node { | |
| 412 | comptime std.debug.assert(@intFromEnum(t) < Tag.no_payload_count); | |
| 413 | return .{ .tag_if_small_enough = @intFromEnum(t) }; | |
| 414 | } | |
| 415 | ||
| 416 | pub fn create(comptime t: Tag, ally: Allocator, data: Data(t)) error{OutOfMemory}!Node { | |
| 417 | const ptr = try ally.create(t.Type()); | |
| 418 | ptr.* = .{ | |
| 419 | .base = .{ .tag = t }, | |
| 420 | .data = data, | |
| 421 | }; | |
| 422 | return Node{ .ptr_otherwise = &ptr.base }; | |
| 423 | } | |
| 424 | ||
| 425 | pub fn Data(comptime t: Tag) type { | |
| 426 | return std.meta.fieldInfo(t.Type(), .data).type; | |
| 427 | } | |
| 428 | }; | |
| 429 | ||
| 430 | pub fn tag(self: Node) Tag { | |
| 431 | if (self.tag_if_small_enough < Tag.no_payload_count) { | |
| 432 | return @enumFromInt(@as(std.meta.Tag(Tag), @intCast(self.tag_if_small_enough))); | |
| 433 | } else { | |
| 434 | return self.ptr_otherwise.tag; | |
| 435 | } | |
| 436 | } | |
| 437 | ||
| 438 | pub fn castTag(self: Node, comptime t: Tag) ?*t.Type() { | |
| 439 | if (self.tag_if_small_enough < Tag.no_payload_count) | |
| 440 | return null; | |
| 441 | ||
| 442 | if (self.ptr_otherwise.tag == t) | |
| 443 | return @alignCast(@fieldParentPtr("base", self.ptr_otherwise)); | |
| 444 | ||
| 445 | return null; | |
| 446 | } | |
| 447 | ||
| 448 | pub fn initPayload(payload: *Payload) Node { | |
| 449 | std.debug.assert(@intFromEnum(payload.tag) >= Tag.no_payload_count); | |
| 450 | return .{ .ptr_otherwise = payload }; | |
| 451 | } | |
| 452 | ||
| 453 | pub fn isNoreturn(node: Node, break_counts: bool) bool { | |
| 454 | switch (node.tag()) { | |
| 455 | .block => { | |
| 456 | const block_node = node.castTag(.block).?; | |
| 457 | if (block_node.data.stmts.len == 0) return false; | |
| 458 | ||
| 459 | const last = block_node.data.stmts[block_node.data.stmts.len - 1]; | |
| 460 | return last.isNoreturn(break_counts); | |
| 461 | }, | |
| 462 | .@"switch" => { | |
| 463 | const switch_node = node.castTag(.@"switch").?; | |
| 464 | ||
| 465 | for (switch_node.data.cases) |case| { | |
| 466 | const body = if (case.castTag(.switch_else)) |some| | |
| 467 | some.data | |
| 468 | else if (case.castTag(.switch_prong)) |some| | |
| 469 | some.data.cond | |
| 470 | else | |
| 471 | unreachable; | |
| 472 | ||
| 473 | if (!body.isNoreturn(break_counts)) return false; | |
| 474 | } | |
| 475 | return true; | |
| 476 | }, | |
| 477 | .@"return", .return_void => return true, | |
| 478 | .@"break" => if (break_counts) return true, | |
| 479 | else => {}, | |
| 480 | } | |
| 481 | return false; | |
| 482 | } | |
| 483 | ||
| 484 | pub fn isBoolRes(res: Node) bool { | |
| 485 | switch (res.tag()) { | |
| 486 | .@"or", | |
| 487 | .@"and", | |
| 488 | .equal, | |
| 489 | .not_equal, | |
| 490 | .less_than, | |
| 491 | .less_than_equal, | |
| 492 | .greater_than, | |
| 493 | .greater_than_equal, | |
| 494 | .not, | |
| 495 | .false_literal, | |
| 496 | .true_literal, | |
| 497 | => return true, | |
| 498 | else => return false, | |
| 499 | } | |
| 500 | } | |
| 501 | }; | |
| 502 | ||
| 503 | pub const Payload = struct { | |
| 504 | tag: Node.Tag, | |
| 505 | ||
| 506 | pub const Value = struct { | |
| 507 | base: Payload, | |
| 508 | data: []const u8, | |
| 509 | }; | |
| 510 | ||
| 511 | pub const UnOp = struct { | |
| 512 | base: Payload, | |
| 513 | data: Node, | |
| 514 | }; | |
| 515 | ||
| 516 | pub const BinOp = struct { | |
| 517 | base: Payload, | |
| 518 | data: struct { | |
| 519 | lhs: Node, | |
| 520 | rhs: Node, | |
| 521 | }, | |
| 522 | }; | |
| 523 | ||
| 524 | pub const Discard = struct { | |
| 525 | base: Payload, | |
| 526 | data: struct { | |
| 527 | should_skip: bool, | |
| 528 | value: Node, | |
| 529 | }, | |
| 530 | }; | |
| 531 | ||
| 532 | pub const If = struct { | |
| 533 | base: Payload, | |
| 534 | data: struct { | |
| 535 | cond: Node, | |
| 536 | then: Node, | |
| 537 | @"else": ?Node, | |
| 538 | }, | |
| 539 | }; | |
| 540 | ||
| 541 | pub const While = struct { | |
| 542 | base: Payload, | |
| 543 | data: struct { | |
| 544 | cond: Node, | |
| 545 | body: Node, | |
| 546 | cont_expr: ?Node, | |
| 547 | }, | |
| 548 | }; | |
| 549 | ||
| 550 | pub const Switch = struct { | |
| 551 | base: Payload, | |
| 552 | data: struct { | |
| 553 | cond: Node, | |
| 554 | cases: []Node, | |
| 555 | }, | |
| 556 | }; | |
| 557 | ||
| 558 | pub const BreakVal = struct { | |
| 559 | base: Payload, | |
| 560 | data: struct { | |
| 561 | label: ?[]const u8, | |
| 562 | val: Node, | |
| 563 | }, | |
| 564 | }; | |
| 565 | ||
| 566 | pub const Call = struct { | |
| 567 | base: Payload, | |
| 568 | data: struct { | |
| 569 | lhs: Node, | |
| 570 | args: []Node, | |
| 571 | }, | |
| 572 | }; | |
| 573 | ||
| 574 | pub const VarDecl = struct { | |
| 575 | base: Payload, | |
| 576 | data: struct { | |
| 577 | is_pub: bool, | |
| 578 | is_const: bool, | |
| 579 | is_extern: bool, | |
| 580 | is_export: bool, | |
| 581 | is_threadlocal: bool, | |
| 582 | alignment: ?c_uint, | |
| 583 | linksection_string: ?[]const u8, | |
| 584 | name: []const u8, | |
| 585 | type: Node, | |
| 586 | init: ?Node, | |
| 587 | }, | |
| 588 | }; | |
| 589 | ||
| 590 | pub const Func = struct { | |
| 591 | base: Payload, | |
| 592 | data: struct { | |
| 593 | is_pub: bool, | |
| 594 | is_extern: bool, | |
| 595 | is_export: bool, | |
| 596 | is_inline: bool, | |
| 597 | is_var_args: bool, | |
| 598 | name: ?[]const u8, | |
| 599 | linksection_string: ?[]const u8, | |
| 600 | explicit_callconv: ?CallingConvention, | |
| 601 | params: []Param, | |
| 602 | return_type: Node, | |
| 603 | body: ?Node, | |
| 604 | alignment: ?c_uint, | |
| 605 | }, | |
| 606 | ||
| 607 | pub const CallingConvention = enum { | |
| 608 | c, | |
| 609 | x86_64_sysv, | |
| 610 | x86_64_win, | |
| 611 | x86_stdcall, | |
| 612 | x86_fastcall, | |
| 613 | x86_thiscall, | |
| 614 | x86_vectorcall, | |
| 615 | x86_regcall, | |
| 616 | aarch64_vfabi, | |
| 617 | aarch64_sve_pcs, | |
| 618 | arm_aapcs, | |
| 619 | arm_aapcs_vfp, | |
| 620 | m68k_rtd, | |
| 621 | riscv_vector, | |
| 622 | }; | |
| 623 | }; | |
| 624 | ||
| 625 | pub const Param = struct { | |
| 626 | is_noalias: bool, | |
| 627 | name: ?[]const u8, | |
| 628 | type: Node, | |
| 629 | }; | |
| 630 | ||
| 631 | pub const Container = struct { | |
| 632 | base: Payload, | |
| 633 | data: struct { | |
| 634 | layout: enum { @"packed", @"extern", none }, | |
| 635 | fields: []Field, | |
| 636 | decls: []Node, | |
| 637 | }, | |
| 638 | ||
| 639 | pub const Field = struct { | |
| 640 | name: []const u8, | |
| 641 | type: Node, | |
| 642 | alignment: ?c_uint, | |
| 643 | default_value: ?Node, | |
| 644 | }; | |
| 645 | }; | |
| 646 | ||
| 647 | pub const TupleInit = struct { | |
| 648 | base: Payload, | |
| 649 | data: []Node, | |
| 650 | }; | |
| 651 | ||
| 652 | pub const ContainerInit = struct { | |
| 653 | base: Payload, | |
| 654 | data: struct { | |
| 655 | lhs: Node, | |
| 656 | inits: []Initializer, | |
| 657 | }, | |
| 658 | ||
| 659 | pub const Initializer = struct { | |
| 660 | name: []const u8, | |
| 661 | value: Node, | |
| 662 | }; | |
| 663 | }; | |
| 664 | ||
| 665 | pub const ContainerInitDot = struct { | |
| 666 | base: Payload, | |
| 667 | data: []Initializer, | |
| 668 | ||
| 669 | pub const Initializer = struct { | |
| 670 | name: []const u8, | |
| 671 | value: Node, | |
| 672 | }; | |
| 673 | }; | |
| 674 | ||
| 675 | pub const Block = struct { | |
| 676 | base: Payload, | |
| 677 | data: struct { | |
| 678 | label: ?[]const u8, | |
| 679 | stmts: []Node, | |
| 680 | }, | |
| 681 | }; | |
| 682 | ||
| 683 | pub const Array = struct { | |
| 684 | base: Payload, | |
| 685 | data: ArrayTypeInfo, | |
| 686 | ||
| 687 | pub const ArrayTypeInfo = struct { | |
| 688 | elem_type: Node, | |
| 689 | len: u64, | |
| 690 | }; | |
| 691 | }; | |
| 692 | ||
| 693 | pub const Pointer = struct { | |
| 694 | base: Payload, | |
| 695 | data: struct { | |
| 696 | elem_type: Node, | |
| 697 | is_const: bool, | |
| 698 | is_volatile: bool, | |
| 699 | is_allowzero: bool, | |
| 700 | }, | |
| 701 | }; | |
| 702 | ||
| 703 | pub const ArgRedecl = struct { | |
| 704 | base: Payload, | |
| 705 | data: struct { | |
| 706 | actual: []const u8, | |
| 707 | mangled: []const u8, | |
| 708 | }, | |
| 709 | }; | |
| 710 | ||
| 711 | pub const SimpleVarDecl = struct { | |
| 712 | base: Payload, | |
| 713 | data: struct { | |
| 714 | name: []const u8, | |
| 715 | init: Node, | |
| 716 | }, | |
| 717 | }; | |
| 718 | ||
| 719 | pub const EnumConstant = struct { | |
| 720 | base: Payload, | |
| 721 | data: struct { | |
| 722 | name: []const u8, | |
| 723 | is_public: bool, | |
| 724 | type: ?Node, | |
| 725 | value: Node, | |
| 726 | }, | |
| 727 | }; | |
| 728 | ||
| 729 | pub const ArrayFiller = struct { | |
| 730 | base: Payload, | |
| 731 | data: struct { | |
| 732 | type: Node, | |
| 733 | filler: Node, | |
| 734 | count: u64, | |
| 735 | }, | |
| 736 | }; | |
| 737 | ||
| 738 | pub const PubInlineFn = struct { | |
| 739 | base: Payload, | |
| 740 | data: struct { | |
| 741 | name: []const u8, | |
| 742 | params: []Param, | |
| 743 | return_type: Node, | |
| 744 | body: Node, | |
| 745 | }, | |
| 746 | }; | |
| 747 | ||
| 748 | pub const FieldAccess = struct { | |
| 749 | base: Payload, | |
| 750 | data: struct { | |
| 751 | lhs: Node, | |
| 752 | field_name: []const u8, | |
| 753 | }, | |
| 754 | }; | |
| 755 | ||
| 756 | pub const StringSlice = struct { | |
| 757 | base: Payload, | |
| 758 | data: struct { | |
| 759 | string: Node, | |
| 760 | end: u64, | |
| 761 | }, | |
| 762 | }; | |
| 763 | ||
| 764 | pub const Shuffle = struct { | |
| 765 | base: Payload, | |
| 766 | data: struct { | |
| 767 | element_type: Node, | |
| 768 | a: Node, | |
| 769 | b: Node, | |
| 770 | mask_vector: Node, | |
| 771 | }, | |
| 772 | }; | |
| 773 | ||
| 774 | pub const Extern = struct { | |
| 775 | base: Payload, | |
| 776 | data: struct { | |
| 777 | type: Node, | |
| 778 | name: Node, | |
| 779 | }, | |
| 780 | }; | |
| 781 | ||
| 782 | pub const HelperCall = struct { | |
| 783 | base: Payload, | |
| 784 | data: struct { | |
| 785 | name: []const u8, | |
| 786 | args: []const Node, | |
| 787 | }, | |
| 788 | }; | |
| 789 | ||
| 790 | pub const HelperRef = struct { | |
| 791 | base: Payload, | |
| 792 | data: []const u8, | |
| 793 | }; | |
| 794 | }; | |
| 795 | ||
| 796 | /// Converts the nodes into a Zig Ast. | |
| 797 | /// Caller must free the source slice. | |
| 798 | pub fn render(gpa: Allocator, nodes: []const Node) !std.zig.Ast { | |
| 799 | var ctx: Context = .{ | |
| 800 | .gpa = gpa, | |
| 801 | .buf = std.array_list.Managed(u8).init(gpa), | |
| 802 | }; | |
| 803 | defer ctx.buf.deinit(); | |
| 804 | defer ctx.nodes.deinit(gpa); | |
| 805 | defer ctx.extra_data.deinit(gpa); | |
| 806 | defer ctx.tokens.deinit(gpa); | |
| 807 | ||
| 808 | // Estimate that each top level node has 10 child nodes. | |
| 809 | const estimated_node_count = nodes.len * 10 + 1; // +1 for the .root node | |
| 810 | try ctx.nodes.ensureTotalCapacity(gpa, estimated_node_count); | |
| 811 | // Estimate that each each node has 2 tokens. | |
| 812 | const estimated_tokens_count = estimated_node_count * 2; | |
| 813 | try ctx.tokens.ensureTotalCapacity(gpa, estimated_tokens_count); | |
| 814 | // Estimate that each each token is 3 bytes long. | |
| 815 | const estimated_buf_len = estimated_tokens_count * 3; | |
| 816 | try ctx.buf.ensureTotalCapacity(estimated_buf_len); | |
| 817 | ||
| 818 | ctx.nodes.appendAssumeCapacity(.{ | |
| 819 | .tag = .root, | |
| 820 | .main_token = 0, | |
| 821 | .data = undefined, | |
| 822 | }); | |
| 823 | ||
| 824 | const root_members = blk: { | |
| 825 | var result = std.array_list.Managed(NodeIndex).init(gpa); | |
| 826 | defer result.deinit(); | |
| 827 | ||
| 828 | for (nodes) |node| { | |
| 829 | const res = (try renderNodeOpt(&ctx, node)) orelse continue; | |
| 830 | try result.append(res); | |
| 831 | } | |
| 832 | break :blk try ctx.listToSpan(result.items); | |
| 833 | }; | |
| 834 | ||
| 835 | ctx.nodes.items(.data)[0] = .{ .extra_range = .{ | |
| 836 | .start = root_members.start, | |
| 837 | .end = root_members.end, | |
| 838 | } }; | |
| 839 | ||
| 840 | try ctx.tokens.append(gpa, .{ | |
| 841 | .tag = .eof, | |
| 842 | .start = @as(u32, @intCast(ctx.buf.items.len)), | |
| 843 | }); | |
| 844 | ||
| 845 | return .{ | |
| 846 | .source = try ctx.buf.toOwnedSliceSentinel(0), | |
| 847 | .tokens = ctx.tokens.toOwnedSlice(), | |
| 848 | .nodes = ctx.nodes.toOwnedSlice(), | |
| 849 | .extra_data = try ctx.extra_data.toOwnedSlice(gpa), | |
| 850 | .errors = &.{}, | |
| 851 | .mode = .zig, | |
| 852 | }; | |
| 853 | } | |
| 854 | ||
| 855 | const NodeIndex = std.zig.Ast.Node.Index; | |
| 856 | const NodeSubRange = std.zig.Ast.Node.SubRange; | |
| 857 | const TokenIndex = std.zig.Ast.TokenIndex; | |
| 858 | const TokenTag = std.zig.Token.Tag; | |
| 859 | ||
| 860 | const Context = struct { | |
| 861 | gpa: Allocator, | |
| 862 | buf: std.array_list.Managed(u8), | |
| 863 | nodes: std.zig.Ast.NodeList = .{}, | |
| 864 | extra_data: std.ArrayListUnmanaged(u32) = .empty, | |
| 865 | tokens: std.zig.Ast.TokenList = .{}, | |
| 866 | ||
| 867 | fn addTokenFmt(c: *Context, tag: TokenTag, comptime format: []const u8, args: anytype) Allocator.Error!TokenIndex { | |
| 868 | const start_index = c.buf.items.len; | |
| 869 | try c.buf.print(format ++ " ", args); | |
| 870 | ||
| 871 | try c.tokens.append(c.gpa, .{ | |
| 872 | .tag = tag, | |
| 873 | .start = @intCast(start_index), | |
| 874 | }); | |
| 875 | ||
| 876 | return @intCast(c.tokens.len - 1); | |
| 877 | } | |
| 878 | ||
| 879 | fn addToken(c: *Context, tag: TokenTag, bytes: []const u8) Allocator.Error!TokenIndex { | |
| 880 | return c.addTokenFmt(tag, "{s}", .{bytes}); | |
| 881 | } | |
| 882 | ||
| 883 | fn addIdentifier(c: *Context, bytes: []const u8) Allocator.Error!TokenIndex { | |
| 884 | if (std.zig.primitives.isPrimitive(bytes)) | |
| 885 | return c.addTokenFmt(.identifier, "@\"{s}\"", .{bytes}); | |
| 886 | return c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtId(bytes)}); | |
| 887 | } | |
| 888 | ||
| 889 | fn listToSpan(c: *Context, list: []const NodeIndex) Allocator.Error!NodeSubRange { | |
| 890 | try c.extra_data.appendSlice(c.gpa, @ptrCast(list)); | |
| 891 | return .{ | |
| 892 | .start = @enumFromInt(c.extra_data.items.len - list.len), | |
| 893 | .end = @enumFromInt(c.extra_data.items.len), | |
| 894 | }; | |
| 895 | } | |
| 896 | ||
| 897 | fn addNode(c: *Context, elem: std.zig.Ast.Node) Allocator.Error!NodeIndex { | |
| 898 | const result: NodeIndex = @enumFromInt(c.nodes.len); | |
| 899 | try c.nodes.append(c.gpa, elem); | |
| 900 | return result; | |
| 901 | } | |
| 902 | ||
| 903 | fn addExtra(c: *Context, extra: anytype) Allocator.Error!std.zig.Ast.ExtraIndex { | |
| 904 | const fields = std.meta.fields(@TypeOf(extra)); | |
| 905 | try c.extra_data.ensureUnusedCapacity(c.gpa, fields.len); | |
| 906 | const result: std.zig.Ast.ExtraIndex = @enumFromInt(c.extra_data.items.len); | |
| 907 | inline for (fields) |field| { | |
| 908 | const data: u32 = switch (field.type) { | |
| 909 | NodeIndex, | |
| 910 | std.zig.Ast.Node.OptionalIndex, | |
| 911 | std.zig.Ast.OptionalTokenIndex, | |
| 912 | std.zig.Ast.ExtraIndex, | |
| 913 | => @intFromEnum(@field(extra, field.name)), | |
| 914 | TokenIndex, | |
| 915 | => @field(extra, field.name), | |
| 916 | else => @compileError("unexpected field type"), | |
| 917 | }; | |
| 918 | c.extra_data.appendAssumeCapacity(data); | |
| 919 | } | |
| 920 | return result; | |
| 921 | } | |
| 922 | }; | |
| 923 | ||
| 924 | fn renderNodeOpt(c: *Context, node: Node) Allocator.Error!?NodeIndex { | |
| 925 | switch (node.tag()) { | |
| 926 | .warning => { | |
| 927 | const payload = node.castTag(.warning).?.data; | |
| 928 | try c.buf.appendSlice(payload); | |
| 929 | try c.buf.append('\n'); | |
| 930 | return null; | |
| 931 | }, | |
| 932 | .discard => { | |
| 933 | const payload = node.castTag(.discard).?.data; | |
| 934 | if (payload.should_skip) return null; | |
| 935 | ||
| 936 | return try renderNode(c, node); | |
| 937 | }, | |
| 938 | else => return try renderNode(c, node), | |
| 939 | } | |
| 940 | } | |
| 941 | ||
| 942 | fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { | |
| 943 | switch (node.tag()) { | |
| 944 | .declaration => unreachable, | |
| 945 | .warning => unreachable, | |
| 946 | .discard => { | |
| 947 | const payload = node.castTag(.discard).?.data; | |
| 948 | std.debug.assert(!payload.should_skip); | |
| 949 | ||
| 950 | const lhs = try c.addNode(.{ | |
| 951 | .tag = .identifier, | |
| 952 | .main_token = try c.addToken(.identifier, "_"), | |
| 953 | .data = undefined, | |
| 954 | }); | |
| 955 | const main_token = try c.addToken(.equal, "="); | |
| 956 | if (payload.value.tag() == .identifier) { | |
| 957 | // Render as `_ = &foo;` to avoid tripping "pointless discard" and "local variable never mutated" errors. | |
| 958 | var addr_of_pl: Payload.UnOp = .{ | |
| 959 | .base = .{ .tag = .address_of }, | |
| 960 | .data = payload.value, | |
| 961 | }; | |
| 962 | const addr_of: Node = .{ .ptr_otherwise = &addr_of_pl.base }; | |
| 963 | return try c.addNode(.{ | |
| 964 | .tag = .assign, | |
| 965 | .main_token = main_token, | |
| 966 | .data = .{ .node_and_node = .{ | |
| 967 | lhs, try renderNode(c, addr_of), | |
| 968 | } }, | |
| 969 | }); | |
| 970 | } else { | |
| 971 | return try c.addNode(.{ | |
| 972 | .tag = .assign, | |
| 973 | .main_token = main_token, | |
| 974 | .data = .{ .node_and_node = .{ | |
| 975 | lhs, try renderNode(c, payload.value), | |
| 976 | } }, | |
| 977 | }); | |
| 978 | } | |
| 979 | }, | |
| 980 | .std_mem_zeroes => { | |
| 981 | const payload = node.castTag(.std_mem_zeroes).?.data; | |
| 982 | const import_node = try renderStdImport(c, &.{ "mem", "zeroes" }); | |
| 983 | return renderCall(c, import_node, &.{payload}); | |
| 984 | }, | |
| 985 | .std_mem_zeroinit => { | |
| 986 | const payload = node.castTag(.std_mem_zeroinit).?.data; | |
| 987 | const import_node = try renderStdImport(c, &.{ "mem", "zeroInit" }); | |
| 988 | return renderCall(c, import_node, &.{ payload.lhs, payload.rhs }); | |
| 989 | }, | |
| 990 | .vector => { | |
| 991 | const payload = node.castTag(.vector).?.data; | |
| 992 | return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs }); | |
| 993 | }, | |
| 994 | .call => { | |
| 995 | const payload = node.castTag(.call).?.data; | |
| 996 | const lhs = try renderNodeGrouped(c, payload.lhs); | |
| 997 | return renderCall(c, lhs, payload.args); | |
| 998 | }, | |
| 999 | .null_literal => return c.addNode(.{ | |
| 1000 | .tag = .identifier, | |
| 1001 | .main_token = try c.addToken(.identifier, "null"), | |
| 1002 | .data = undefined, | |
| 1003 | }), | |
| 1004 | .undefined_literal => return c.addNode(.{ | |
| 1005 | .tag = .identifier, | |
| 1006 | .main_token = try c.addToken(.identifier, "undefined"), | |
| 1007 | .data = undefined, | |
| 1008 | }), | |
| 1009 | .true_literal => return c.addNode(.{ | |
| 1010 | .tag = .identifier, | |
| 1011 | .main_token = try c.addToken(.identifier, "true"), | |
| 1012 | .data = undefined, | |
| 1013 | }), | |
| 1014 | .false_literal => return c.addNode(.{ | |
| 1015 | .tag = .identifier, | |
| 1016 | .main_token = try c.addToken(.identifier, "false"), | |
| 1017 | .data = undefined, | |
| 1018 | }), | |
| 1019 | .zero_literal => return c.addNode(.{ | |
| 1020 | .tag = .number_literal, | |
| 1021 | .main_token = try c.addToken(.number_literal, "0"), | |
| 1022 | .data = undefined, | |
| 1023 | }), | |
| 1024 | .one_literal => return c.addNode(.{ | |
| 1025 | .tag = .number_literal, | |
| 1026 | .main_token = try c.addToken(.number_literal, "1"), | |
| 1027 | .data = undefined, | |
| 1028 | }), | |
| 1029 | .@"unreachable" => return c.addNode(.{ | |
| 1030 | .tag = .unreachable_literal, | |
| 1031 | .main_token = try c.addToken(.keyword_unreachable, "unreachable"), | |
| 1032 | .data = undefined, | |
| 1033 | }), | |
| 1034 | .void_type => return c.addNode(.{ | |
| 1035 | .tag = .identifier, | |
| 1036 | .main_token = try c.addToken(.identifier, "void"), | |
| 1037 | .data = undefined, | |
| 1038 | }), | |
| 1039 | .noreturn_type => return c.addNode(.{ | |
| 1040 | .tag = .identifier, | |
| 1041 | .main_token = try c.addToken(.identifier, "noreturn"), | |
| 1042 | .data = undefined, | |
| 1043 | }), | |
| 1044 | .@"continue" => return c.addNode(.{ | |
| 1045 | .tag = .@"continue", | |
| 1046 | .main_token = try c.addToken(.keyword_continue, "continue"), | |
| 1047 | .data = .{ .opt_token_and_opt_node = .{ | |
| 1048 | .none, .none, | |
| 1049 | } }, | |
| 1050 | }), | |
| 1051 | .return_void => return c.addNode(.{ | |
| 1052 | .tag = .@"return", | |
| 1053 | .main_token = try c.addToken(.keyword_return, "return"), | |
| 1054 | .data = .{ .opt_node = .none }, | |
| 1055 | }), | |
| 1056 | .@"break" => return c.addNode(.{ | |
| 1057 | .tag = .@"break", | |
| 1058 | .main_token = try c.addToken(.keyword_break, "break"), | |
| 1059 | .data = .{ .opt_token_and_opt_node = .{ | |
| 1060 | .none, .none, | |
| 1061 | } }, | |
| 1062 | }), | |
| 1063 | .break_val => { | |
| 1064 | const payload = node.castTag(.break_val).?.data; | |
| 1065 | const tok = try c.addToken(.keyword_break, "break"); | |
| 1066 | const break_label = if (payload.label) |some| blk: { | |
| 1067 | _ = try c.addToken(.colon, ":"); | |
| 1068 | break :blk try c.addIdentifier(some); | |
| 1069 | } else 0; | |
| 1070 | return c.addNode(.{ | |
| 1071 | .tag = .@"break", | |
| 1072 | .main_token = tok, | |
| 1073 | .data = .{ .opt_token_and_opt_node = .{ | |
| 1074 | .fromToken(break_label), (try renderNode(c, payload.val)).toOptional(), | |
| 1075 | } }, | |
| 1076 | }); | |
| 1077 | }, | |
| 1078 | .@"return" => { | |
| 1079 | const payload = node.castTag(.@"return").?.data; | |
| 1080 | return c.addNode(.{ | |
| 1081 | .tag = .@"return", | |
| 1082 | .main_token = try c.addToken(.keyword_return, "return"), | |
| 1083 | .data = .{ .opt_node = (try renderNode(c, payload)).toOptional() }, | |
| 1084 | }); | |
| 1085 | }, | |
| 1086 | .@"comptime" => { | |
| 1087 | const payload = node.castTag(.@"comptime").?.data; | |
| 1088 | return c.addNode(.{ | |
| 1089 | .tag = .@"comptime", | |
| 1090 | .main_token = try c.addToken(.keyword_comptime, "comptime"), | |
| 1091 | .data = .{ | |
| 1092 | .node = try renderNode(c, payload), | |
| 1093 | }, | |
| 1094 | }); | |
| 1095 | }, | |
| 1096 | .@"defer" => { | |
| 1097 | const payload = node.castTag(.@"defer").?.data; | |
| 1098 | return c.addNode(.{ | |
| 1099 | .tag = .@"defer", | |
| 1100 | .main_token = try c.addToken(.keyword_defer, "defer"), | |
| 1101 | .data = .{ | |
| 1102 | .node = try renderNode(c, payload), | |
| 1103 | }, | |
| 1104 | }); | |
| 1105 | }, | |
| 1106 | .asm_simple => { | |
| 1107 | const payload = node.castTag(.asm_simple).?.data; | |
| 1108 | const asm_token = try c.addToken(.keyword_asm, "asm"); | |
| 1109 | _ = try c.addToken(.l_paren, "("); | |
| 1110 | return c.addNode(.{ | |
| 1111 | .tag = .asm_simple, | |
| 1112 | .main_token = asm_token, | |
| 1113 | .data = .{ .node_and_token = .{ | |
| 1114 | try renderNode(c, payload), | |
| 1115 | try c.addToken(.r_paren, ")"), | |
| 1116 | } }, | |
| 1117 | }); | |
| 1118 | }, | |
| 1119 | .type => { | |
| 1120 | const payload = node.castTag(.type).?.data; | |
| 1121 | return c.addNode(.{ | |
| 1122 | .tag = .identifier, | |
| 1123 | .main_token = try c.addToken(.identifier, payload), | |
| 1124 | .data = undefined, | |
| 1125 | }); | |
| 1126 | }, | |
| 1127 | .identifier => { | |
| 1128 | const payload = node.castTag(.identifier).?.data; | |
| 1129 | return c.addNode(.{ | |
| 1130 | .tag = .identifier, | |
| 1131 | .main_token = try c.addIdentifier(payload), | |
| 1132 | .data = undefined, | |
| 1133 | }); | |
| 1134 | }, | |
| 1135 | .float_literal => { | |
| 1136 | const payload = node.castTag(.float_literal).?.data; | |
| 1137 | return c.addNode(.{ | |
| 1138 | .tag = .number_literal, | |
| 1139 | .main_token = try c.addToken(.number_literal, payload), | |
| 1140 | .data = undefined, | |
| 1141 | }); | |
| 1142 | }, | |
| 1143 | .integer_literal => { | |
| 1144 | const payload = node.castTag(.integer_literal).?.data; | |
| 1145 | return c.addNode(.{ | |
| 1146 | .tag = .number_literal, | |
| 1147 | .main_token = try c.addToken(.number_literal, payload), | |
| 1148 | .data = undefined, | |
| 1149 | }); | |
| 1150 | }, | |
| 1151 | .string_literal => { | |
| 1152 | const payload = node.castTag(.string_literal).?.data; | |
| 1153 | return c.addNode(.{ | |
| 1154 | .tag = .string_literal, | |
| 1155 | .main_token = try c.addToken(.string_literal, payload), | |
| 1156 | .data = undefined, | |
| 1157 | }); | |
| 1158 | }, | |
| 1159 | .char_literal => { | |
| 1160 | const payload = node.castTag(.char_literal).?.data; | |
| 1161 | return c.addNode(.{ | |
| 1162 | .tag = .char_literal, | |
| 1163 | .main_token = try c.addToken(.char_literal, payload), | |
| 1164 | .data = undefined, | |
| 1165 | }); | |
| 1166 | }, | |
| 1167 | .enum_literal => { | |
| 1168 | const payload = node.castTag(.enum_literal).?.data; | |
| 1169 | _ = try c.addToken(.period, "."); | |
| 1170 | return c.addNode(.{ | |
| 1171 | .tag = .enum_literal, | |
| 1172 | .main_token = try c.addToken(.identifier, payload), | |
| 1173 | .data = undefined, | |
| 1174 | }); | |
| 1175 | }, | |
| 1176 | .string_slice => { | |
| 1177 | const payload = node.castTag(.string_slice).?.data; | |
| 1178 | ||
| 1179 | const string = try renderNode(c, payload.string); | |
| 1180 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 1181 | const start = try c.addNode(.{ | |
| 1182 | .tag = .number_literal, | |
| 1183 | .main_token = try c.addToken(.number_literal, "0"), | |
| 1184 | .data = undefined, | |
| 1185 | }); | |
| 1186 | _ = try c.addToken(.ellipsis2, ".."); | |
| 1187 | const end = try c.addNode(.{ | |
| 1188 | .tag = .number_literal, | |
| 1189 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.end}), | |
| 1190 | .data = undefined, | |
| 1191 | }); | |
| 1192 | _ = try c.addToken(.r_bracket, "]"); | |
| 1193 | ||
| 1194 | return c.addNode(.{ | |
| 1195 | .tag = .slice, | |
| 1196 | .main_token = l_bracket, | |
| 1197 | .data = .{ .node_and_extra = .{ | |
| 1198 | string, try c.addExtra(std.zig.Ast.Node.Slice{ | |
| 1199 | .start = start, | |
| 1200 | .end = end, | |
| 1201 | }), | |
| 1202 | } }, | |
| 1203 | }); | |
| 1204 | }, | |
| 1205 | .fail_decl => { | |
| 1206 | const payload = node.castTag(.fail_decl).?.data; | |
| 1207 | // pub const name = @compileError(msg); | |
| 1208 | _ = try c.addToken(.keyword_pub, "pub"); | |
| 1209 | const const_tok = try c.addToken(.keyword_const, "const"); | |
| 1210 | _ = try c.addIdentifier(payload.actual); | |
| 1211 | _ = try c.addToken(.equal, "="); | |
| 1212 | ||
| 1213 | const compile_error_tok = try c.addToken(.builtin, "@compileError"); | |
| 1214 | _ = try c.addToken(.l_paren, "("); | |
| 1215 | const err_msg_tok = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(payload.mangled)}); | |
| 1216 | const err_msg = try c.addNode(.{ | |
| 1217 | .tag = .string_literal, | |
| 1218 | .main_token = err_msg_tok, | |
| 1219 | .data = undefined, | |
| 1220 | }); | |
| 1221 | _ = try c.addToken(.r_paren, ")"); | |
| 1222 | const compile_error = try c.addNode(.{ | |
| 1223 | .tag = .builtin_call_two, | |
| 1224 | .main_token = compile_error_tok, | |
| 1225 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1226 | err_msg.toOptional(), .none, | |
| 1227 | } }, | |
| 1228 | }); | |
| 1229 | _ = try c.addToken(.semicolon, ";"); | |
| 1230 | ||
| 1231 | return c.addNode(.{ | |
| 1232 | .tag = .simple_var_decl, | |
| 1233 | .main_token = const_tok, | |
| 1234 | .data = .{ | |
| 1235 | .opt_node_and_opt_node = .{ | |
| 1236 | .none, // Type expression | |
| 1237 | compile_error.toOptional(), // Init expression | |
| 1238 | }, | |
| 1239 | }, | |
| 1240 | }); | |
| 1241 | }, | |
| 1242 | .pub_var_simple, .var_simple => { | |
| 1243 | const payload = @as(*Payload.SimpleVarDecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 1244 | if (node.tag() == .pub_var_simple) _ = try c.addToken(.keyword_pub, "pub"); | |
| 1245 | const const_tok = try c.addToken(.keyword_const, "const"); | |
| 1246 | _ = try c.addIdentifier(payload.name); | |
| 1247 | _ = try c.addToken(.equal, "="); | |
| 1248 | ||
| 1249 | const init = try renderNode(c, payload.init); | |
| 1250 | _ = try c.addToken(.semicolon, ";"); | |
| 1251 | ||
| 1252 | return c.addNode(.{ | |
| 1253 | .tag = .simple_var_decl, | |
| 1254 | .main_token = const_tok, | |
| 1255 | .data = .{ | |
| 1256 | .opt_node_and_opt_node = .{ | |
| 1257 | .none, // Type expression | |
| 1258 | init.toOptional(), // Init expression | |
| 1259 | }, | |
| 1260 | }, | |
| 1261 | }); | |
| 1262 | }, | |
| 1263 | .wrapped_local => { | |
| 1264 | const payload = node.castTag(.wrapped_local).?.data; | |
| 1265 | ||
| 1266 | const const_tok = try c.addToken(.keyword_const, "const"); | |
| 1267 | _ = try c.addIdentifier(payload.name); | |
| 1268 | _ = try c.addToken(.equal, "="); | |
| 1269 | ||
| 1270 | const kind_tok = try c.addToken(.keyword_struct, "struct"); | |
| 1271 | _ = try c.addToken(.l_brace, "{"); | |
| 1272 | ||
| 1273 | const container_def = try c.addNode(.{ | |
| 1274 | .tag = .container_decl_two_trailing, | |
| 1275 | .main_token = kind_tok, | |
| 1276 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1277 | (try renderNode(c, payload.init)).toOptional(), .none, | |
| 1278 | } }, | |
| 1279 | }); | |
| 1280 | _ = try c.addToken(.r_brace, "}"); | |
| 1281 | _ = try c.addToken(.semicolon, ";"); | |
| 1282 | ||
| 1283 | return c.addNode(.{ | |
| 1284 | .tag = .simple_var_decl, | |
| 1285 | .main_token = const_tok, | |
| 1286 | .data = .{ | |
| 1287 | .opt_node_and_opt_node = .{ | |
| 1288 | .none, // Type expression | |
| 1289 | container_def.toOptional(), // Init expression | |
| 1290 | }, | |
| 1291 | }, | |
| 1292 | }); | |
| 1293 | }, | |
| 1294 | .mut_str => { | |
| 1295 | const payload = node.castTag(.mut_str).?.data; | |
| 1296 | ||
| 1297 | const var_tok = try c.addToken(.keyword_var, "var"); | |
| 1298 | _ = try c.addIdentifier(payload.name); | |
| 1299 | _ = try c.addToken(.equal, "="); | |
| 1300 | ||
| 1301 | const deref = try c.addNode(.{ | |
| 1302 | .tag = .deref, | |
| 1303 | .data = .{ | |
| 1304 | .node = try renderNodeGrouped(c, payload.init), | |
| 1305 | }, | |
| 1306 | .main_token = try c.addToken(.period_asterisk, ".*"), | |
| 1307 | }); | |
| 1308 | _ = try c.addToken(.semicolon, ";"); | |
| 1309 | ||
| 1310 | return c.addNode(.{ | |
| 1311 | .tag = .simple_var_decl, | |
| 1312 | .main_token = var_tok, | |
| 1313 | .data = .{ | |
| 1314 | .opt_node_and_opt_node = .{ | |
| 1315 | .none, // Type expression | |
| 1316 | deref.toOptional(), // Init expression | |
| 1317 | }, | |
| 1318 | }, | |
| 1319 | }); | |
| 1320 | }, | |
| 1321 | .var_decl => return renderVar(c, node), | |
| 1322 | .arg_redecl, .alias => { | |
| 1323 | const payload = @as(*Payload.ArgRedecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 1324 | if (node.tag() == .alias) _ = try c.addToken(.keyword_pub, "pub"); | |
| 1325 | const mut_tok = if (node.tag() == .alias) | |
| 1326 | try c.addToken(.keyword_const, "const") | |
| 1327 | else | |
| 1328 | try c.addToken(.keyword_var, "var"); | |
| 1329 | _ = try c.addIdentifier(payload.actual); | |
| 1330 | _ = try c.addToken(.equal, "="); | |
| 1331 | ||
| 1332 | const init = try c.addNode(.{ | |
| 1333 | .tag = .identifier, | |
| 1334 | .main_token = try c.addIdentifier(payload.mangled), | |
| 1335 | .data = undefined, | |
| 1336 | }); | |
| 1337 | _ = try c.addToken(.semicolon, ";"); | |
| 1338 | ||
| 1339 | return c.addNode(.{ | |
| 1340 | .tag = .simple_var_decl, | |
| 1341 | .main_token = mut_tok, | |
| 1342 | .data = .{ | |
| 1343 | .opt_node_and_opt_node = .{ | |
| 1344 | .none, // Type expression | |
| 1345 | init.toOptional(), // Init expression | |
| 1346 | }, | |
| 1347 | }, | |
| 1348 | }); | |
| 1349 | }, | |
| 1350 | .int_cast => { | |
| 1351 | const payload = node.castTag(.int_cast).?.data; | |
| 1352 | return renderBuiltinCall(c, "@intCast", &.{payload}); | |
| 1353 | }, | |
| 1354 | .const_cast => { | |
| 1355 | const payload = node.castTag(.const_cast).?.data; | |
| 1356 | return renderBuiltinCall(c, "@constCast", &.{payload}); | |
| 1357 | }, | |
| 1358 | .volatile_cast => { | |
| 1359 | const payload = node.castTag(.volatile_cast).?.data; | |
| 1360 | return renderBuiltinCall(c, "@volatileCast", &.{payload}); | |
| 1361 | }, | |
| 1362 | .div_trunc => { | |
| 1363 | const payload = node.castTag(.div_trunc).?.data; | |
| 1364 | return renderBuiltinCall(c, "@divTrunc", &.{ payload.lhs, payload.rhs }); | |
| 1365 | }, | |
| 1366 | .int_from_bool => { | |
| 1367 | const payload = node.castTag(.int_from_bool).?.data; | |
| 1368 | return renderBuiltinCall(c, "@intFromBool", &.{payload}); | |
| 1369 | }, | |
| 1370 | .as => { | |
| 1371 | const payload = node.castTag(.as).?.data; | |
| 1372 | return renderBuiltinCall(c, "@as", &.{ payload.lhs, payload.rhs }); | |
| 1373 | }, | |
| 1374 | .truncate => { | |
| 1375 | const payload = node.castTag(.truncate).?.data; | |
| 1376 | return renderBuiltinCall(c, "@truncate", &.{payload}); | |
| 1377 | }, | |
| 1378 | .bit_cast => { | |
| 1379 | const payload = node.castTag(.bit_cast).?.data; | |
| 1380 | return renderBuiltinCall(c, "@bitCast", &.{payload}); | |
| 1381 | }, | |
| 1382 | .float_cast => { | |
| 1383 | const payload = node.castTag(.float_cast).?.data; | |
| 1384 | return renderBuiltinCall(c, "@floatCast", &.{payload}); | |
| 1385 | }, | |
| 1386 | .int_from_float => { | |
| 1387 | const payload = node.castTag(.int_from_float).?.data; | |
| 1388 | return renderBuiltinCall(c, "@intFromFloat", &.{payload}); | |
| 1389 | }, | |
| 1390 | .float_from_int => { | |
| 1391 | const payload = node.castTag(.float_from_int).?.data; | |
| 1392 | return renderBuiltinCall(c, "@floatFromInt", &.{payload}); | |
| 1393 | }, | |
| 1394 | .ptr_from_int => { | |
| 1395 | const payload = node.castTag(.ptr_from_int).?.data; | |
| 1396 | return renderBuiltinCall(c, "@ptrFromInt", &.{payload}); | |
| 1397 | }, | |
| 1398 | .int_from_ptr => { | |
| 1399 | const payload = node.castTag(.int_from_ptr).?.data; | |
| 1400 | return renderBuiltinCall(c, "@intFromPtr", &.{payload}); | |
| 1401 | }, | |
| 1402 | .align_cast => { | |
| 1403 | const payload = node.castTag(.align_cast).?.data; | |
| 1404 | return renderBuiltinCall(c, "@alignCast", &.{payload}); | |
| 1405 | }, | |
| 1406 | .ptr_cast => { | |
| 1407 | const payload = node.castTag(.ptr_cast).?.data; | |
| 1408 | return renderBuiltinCall(c, "@ptrCast", &.{payload}); | |
| 1409 | }, | |
| 1410 | .div_exact => { | |
| 1411 | const payload = node.castTag(.div_exact).?.data; | |
| 1412 | return renderBuiltinCall(c, "@divExact", &.{ payload.lhs, payload.rhs }); | |
| 1413 | }, | |
| 1414 | .offset_of => { | |
| 1415 | const payload = node.castTag(.offset_of).?.data; | |
| 1416 | return renderBuiltinCall(c, "@offsetOf", &.{ payload.lhs, payload.rhs }); | |
| 1417 | }, | |
| 1418 | .sizeof => { | |
| 1419 | const payload = node.castTag(.sizeof).?.data; | |
| 1420 | return renderBuiltinCall(c, "@sizeOf", &.{payload}); | |
| 1421 | }, | |
| 1422 | .shuffle => { | |
| 1423 | const payload = node.castTag(.shuffle).?.data; | |
| 1424 | return renderBuiltinCall(c, "@shuffle", &.{ | |
| 1425 | payload.element_type, | |
| 1426 | payload.a, | |
| 1427 | payload.b, | |
| 1428 | payload.mask_vector, | |
| 1429 | }); | |
| 1430 | }, | |
| 1431 | .builtin_extern => { | |
| 1432 | const payload = node.castTag(.builtin_extern).?.data; | |
| 1433 | ||
| 1434 | var info_inits: [1]Payload.ContainerInitDot.Initializer = .{ | |
| 1435 | .{ .name = "name", .value = payload.name }, | |
| 1436 | }; | |
| 1437 | var info_payload: Payload.ContainerInitDot = .{ | |
| 1438 | .base = .{ .tag = .container_init_dot }, | |
| 1439 | .data = &info_inits, | |
| 1440 | }; | |
| 1441 | ||
| 1442 | return renderBuiltinCall(c, "@extern", &.{ | |
| 1443 | payload.type, | |
| 1444 | .{ .ptr_otherwise = &info_payload.base }, | |
| 1445 | }); | |
| 1446 | }, | |
| 1447 | .helper_call => { | |
| 1448 | const payload = node.castTag(.helper_call).?.data; | |
| 1449 | const helpers_tok = try c.addNode(.{ | |
| 1450 | .tag = .identifier, | |
| 1451 | .main_token = try c.addIdentifier("__helpers"), | |
| 1452 | .data = undefined, | |
| 1453 | }); | |
| 1454 | const func = try renderFieldAccess(c, helpers_tok, payload.name); | |
| 1455 | return renderCall(c, func, payload.args); | |
| 1456 | }, | |
| 1457 | .helper_ref => { | |
| 1458 | const payload = node.castTag(.helper_ref).?.data; | |
| 1459 | const helpers_tok = try c.addNode(.{ | |
| 1460 | .tag = .identifier, | |
| 1461 | .main_token = try c.addIdentifier("__helpers"), | |
| 1462 | .data = undefined, | |
| 1463 | }); | |
| 1464 | return renderFieldAccess(c, helpers_tok, payload); | |
| 1465 | }, | |
| 1466 | .alignof => { | |
| 1467 | const payload = node.castTag(.alignof).?.data; | |
| 1468 | return renderBuiltinCall(c, "@alignOf", &.{payload}); | |
| 1469 | }, | |
| 1470 | .typeof => { | |
| 1471 | const payload = node.castTag(.typeof).?.data; | |
| 1472 | return renderBuiltinCall(c, "@TypeOf", &.{payload}); | |
| 1473 | }, | |
| 1474 | .typeinfo => { | |
| 1475 | const payload = node.castTag(.typeinfo).?.data; | |
| 1476 | return renderBuiltinCall(c, "@typeInfo", &.{payload}); | |
| 1477 | }, | |
| 1478 | .byte_swap => { | |
| 1479 | const payload = node.castTag(.byte_swap).?.data; | |
| 1480 | return renderBuiltinCall(c, "@byteSwap", &.{payload}); | |
| 1481 | }, | |
| 1482 | .ceil => { | |
| 1483 | const payload = node.castTag(.ceil).?.data; | |
| 1484 | return renderBuiltinCall(c, "@ceil", &.{payload}); | |
| 1485 | }, | |
| 1486 | .cos => { | |
| 1487 | const payload = node.castTag(.cos).?.data; | |
| 1488 | return renderBuiltinCall(c, "@cos", &.{payload}); | |
| 1489 | }, | |
| 1490 | .sin => { | |
| 1491 | const payload = node.castTag(.sin).?.data; | |
| 1492 | return renderBuiltinCall(c, "@sin", &.{payload}); | |
| 1493 | }, | |
| 1494 | .exp => { | |
| 1495 | const payload = node.castTag(.exp).?.data; | |
| 1496 | return renderBuiltinCall(c, "@exp", &.{payload}); | |
| 1497 | }, | |
| 1498 | .exp2 => { | |
| 1499 | const payload = node.castTag(.exp2).?.data; | |
| 1500 | return renderBuiltinCall(c, "@exp2", &.{payload}); | |
| 1501 | }, | |
| 1502 | .exp10 => { | |
| 1503 | const payload = node.castTag(.exp10).?.data; | |
| 1504 | return renderBuiltinCall(c, "@exp10", &.{payload}); | |
| 1505 | }, | |
| 1506 | .abs => { | |
| 1507 | const payload = node.castTag(.abs).?.data; | |
| 1508 | return renderBuiltinCall(c, "@abs", &.{payload}); | |
| 1509 | }, | |
| 1510 | .log => { | |
| 1511 | const payload = node.castTag(.log).?.data; | |
| 1512 | return renderBuiltinCall(c, "@log", &.{payload}); | |
| 1513 | }, | |
| 1514 | .log2 => { | |
| 1515 | const payload = node.castTag(.log2).?.data; | |
| 1516 | return renderBuiltinCall(c, "@log2", &.{payload}); | |
| 1517 | }, | |
| 1518 | .log10 => { | |
| 1519 | const payload = node.castTag(.log10).?.data; | |
| 1520 | return renderBuiltinCall(c, "@log10", &.{payload}); | |
| 1521 | }, | |
| 1522 | .round => { | |
| 1523 | const payload = node.castTag(.round).?.data; | |
| 1524 | return renderBuiltinCall(c, "@round", &.{payload}); | |
| 1525 | }, | |
| 1526 | .sqrt => { | |
| 1527 | const payload = node.castTag(.sqrt).?.data; | |
| 1528 | return renderBuiltinCall(c, "@sqrt", &.{payload}); | |
| 1529 | }, | |
| 1530 | .trunc => { | |
| 1531 | const payload = node.castTag(.trunc).?.data; | |
| 1532 | return renderBuiltinCall(c, "@trunc", &.{payload}); | |
| 1533 | }, | |
| 1534 | .floor => { | |
| 1535 | const payload = node.castTag(.floor).?.data; | |
| 1536 | return renderBuiltinCall(c, "@floor", &.{payload}); | |
| 1537 | }, | |
| 1538 | .negate => return renderPrefixOp(c, node, .negation, .minus, "-"), | |
| 1539 | .negate_wrap => return renderPrefixOp(c, node, .negation_wrap, .minus_percent, "-%"), | |
| 1540 | .bit_not => return renderPrefixOp(c, node, .bit_not, .tilde, "~"), | |
| 1541 | .not => return renderPrefixOp(c, node, .bool_not, .bang, "!"), | |
| 1542 | .optional_type => return renderPrefixOp(c, node, .optional_type, .question_mark, "?"), | |
| 1543 | .address_of => { | |
| 1544 | const payload = node.castTag(.address_of).?.data; | |
| 1545 | ||
| 1546 | const ampersand = try c.addToken(.ampersand, "&"); | |
| 1547 | const base = try renderNodeGrouped(c, payload); | |
| 1548 | return c.addNode(.{ | |
| 1549 | .tag = .address_of, | |
| 1550 | .main_token = ampersand, | |
| 1551 | .data = .{ | |
| 1552 | .node = base, | |
| 1553 | }, | |
| 1554 | }); | |
| 1555 | }, | |
| 1556 | .deref => { | |
| 1557 | const payload = node.castTag(.deref).?.data; | |
| 1558 | const operand = try renderNodeGrouped(c, payload); | |
| 1559 | const deref_tok = try c.addToken(.period_asterisk, ".*"); | |
| 1560 | return c.addNode(.{ | |
| 1561 | .tag = .deref, | |
| 1562 | .main_token = deref_tok, | |
| 1563 | .data = .{ | |
| 1564 | .node = operand, | |
| 1565 | }, | |
| 1566 | }); | |
| 1567 | }, | |
| 1568 | .unwrap => { | |
| 1569 | const payload = node.castTag(.unwrap).?.data; | |
| 1570 | const operand = try renderNodeGrouped(c, payload); | |
| 1571 | const period = try c.addToken(.period, "."); | |
| 1572 | const question_mark = try c.addToken(.question_mark, "?"); | |
| 1573 | return c.addNode(.{ | |
| 1574 | .tag = .unwrap_optional, | |
| 1575 | .main_token = period, | |
| 1576 | .data = .{ .node_and_token = .{ | |
| 1577 | operand, question_mark, | |
| 1578 | } }, | |
| 1579 | }); | |
| 1580 | }, | |
| 1581 | .c_pointer, .single_pointer => { | |
| 1582 | const payload = @as(*Payload.Pointer, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 1583 | ||
| 1584 | const main_token = if (node.tag() == .single_pointer) | |
| 1585 | try c.addToken(.asterisk, "*") | |
| 1586 | else blk: { | |
| 1587 | const res = try c.addToken(.l_bracket, "["); | |
| 1588 | _ = try c.addToken(.asterisk, "*"); | |
| 1589 | _ = try c.addIdentifier("c"); | |
| 1590 | _ = try c.addToken(.r_bracket, "]"); | |
| 1591 | break :blk res; | |
| 1592 | }; | |
| 1593 | if (payload.is_const) _ = try c.addToken(.keyword_const, "const"); | |
| 1594 | if (payload.is_volatile) _ = try c.addToken(.keyword_volatile, "volatile"); | |
| 1595 | if (payload.is_allowzero) _ = try c.addToken(.keyword_allowzero, "allowzero"); | |
| 1596 | const elem_type = try renderNodeGrouped(c, payload.elem_type); | |
| 1597 | ||
| 1598 | return c.addNode(.{ | |
| 1599 | .tag = .ptr_type_aligned, | |
| 1600 | .main_token = main_token, | |
| 1601 | .data = .{ | |
| 1602 | .opt_node_and_node = .{ | |
| 1603 | .none, // Align node | |
| 1604 | elem_type, | |
| 1605 | }, | |
| 1606 | }, | |
| 1607 | }); | |
| 1608 | }, | |
| 1609 | .add => return renderBinOpGrouped(c, node, .add, .plus, "+"), | |
| 1610 | .add_assign => return renderBinOp(c, node, .assign_add, .plus_equal, "+="), | |
| 1611 | .add_wrap => return renderBinOpGrouped(c, node, .add_wrap, .plus_percent, "+%"), | |
| 1612 | .add_wrap_assign => return renderBinOp(c, node, .assign_add_wrap, .plus_percent_equal, "+%="), | |
| 1613 | .sub => return renderBinOpGrouped(c, node, .sub, .minus, "-"), | |
| 1614 | .sub_assign => return renderBinOp(c, node, .assign_sub, .minus_equal, "-="), | |
| 1615 | .sub_wrap => return renderBinOpGrouped(c, node, .sub_wrap, .minus_percent, "-%"), | |
| 1616 | .sub_wrap_assign => return renderBinOp(c, node, .assign_sub_wrap, .minus_percent_equal, "-%="), | |
| 1617 | .mul => return renderBinOpGrouped(c, node, .mul, .asterisk, "*"), | |
| 1618 | .mul_assign => return renderBinOp(c, node, .assign_mul, .asterisk_equal, "*="), | |
| 1619 | .mul_wrap => return renderBinOpGrouped(c, node, .mul_wrap, .asterisk_percent, "*%"), | |
| 1620 | .mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="), | |
| 1621 | .div => return renderBinOpGrouped(c, node, .div, .slash, "/"), | |
| 1622 | .div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="), | |
| 1623 | .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"), | |
| 1624 | .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="), | |
| 1625 | .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"), | |
| 1626 | .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="), | |
| 1627 | .mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"), | |
| 1628 | .mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="), | |
| 1629 | .@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"), | |
| 1630 | .@"or" => return renderBinOpGrouped(c, node, .bool_or, .keyword_or, "or"), | |
| 1631 | .less_than => return renderBinOpGrouped(c, node, .less_than, .angle_bracket_left, "<"), | |
| 1632 | .less_than_equal => return renderBinOpGrouped(c, node, .less_or_equal, .angle_bracket_left_equal, "<="), | |
| 1633 | .greater_than => return renderBinOpGrouped(c, node, .greater_than, .angle_bracket_right, ">="), | |
| 1634 | .greater_than_equal => return renderBinOpGrouped(c, node, .greater_or_equal, .angle_bracket_right_equal, ">="), | |
| 1635 | .equal => return renderBinOpGrouped(c, node, .equal_equal, .equal_equal, "=="), | |
| 1636 | .not_equal => return renderBinOpGrouped(c, node, .bang_equal, .bang_equal, "!="), | |
| 1637 | .bit_and => return renderBinOpGrouped(c, node, .bit_and, .ampersand, "&"), | |
| 1638 | .bit_and_assign => return renderBinOp(c, node, .assign_bit_and, .ampersand_equal, "&="), | |
| 1639 | .bit_or => return renderBinOpGrouped(c, node, .bit_or, .pipe, "|"), | |
| 1640 | .bit_or_assign => return renderBinOp(c, node, .assign_bit_or, .pipe_equal, "|="), | |
| 1641 | .bit_xor => return renderBinOpGrouped(c, node, .bit_xor, .caret, "^"), | |
| 1642 | .bit_xor_assign => return renderBinOp(c, node, .assign_bit_xor, .caret_equal, "^="), | |
| 1643 | .array_cat => return renderBinOp(c, node, .array_cat, .plus_plus, "++"), | |
| 1644 | .ellipsis3 => return renderBinOpGrouped(c, node, .switch_range, .ellipsis3, "..."), | |
| 1645 | .assign => return renderBinOp(c, node, .assign, .equal, "="), | |
| 1646 | .empty_block => { | |
| 1647 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 1648 | _ = try c.addToken(.r_brace, "}"); | |
| 1649 | return c.addNode(.{ | |
| 1650 | .tag = .block_two, | |
| 1651 | .main_token = l_brace, | |
| 1652 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1653 | .none, .none, | |
| 1654 | } }, | |
| 1655 | }); | |
| 1656 | }, | |
| 1657 | .block_single => { | |
| 1658 | const payload = node.castTag(.block_single).?.data; | |
| 1659 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 1660 | ||
| 1661 | const stmt = (try renderNodeOpt(c, payload)) orelse { | |
| 1662 | _ = try c.addToken(.r_brace, "}"); | |
| 1663 | return c.addNode(.{ | |
| 1664 | .tag = .block_two, | |
| 1665 | .main_token = l_brace, | |
| 1666 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1667 | .none, .none, | |
| 1668 | } }, | |
| 1669 | }); | |
| 1670 | }; | |
| 1671 | try addSemicolonIfNeeded(c, payload); | |
| 1672 | ||
| 1673 | _ = try c.addToken(.r_brace, "}"); | |
| 1674 | return c.addNode(.{ | |
| 1675 | .tag = .block_two_semicolon, | |
| 1676 | .main_token = l_brace, | |
| 1677 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1678 | stmt.toOptional(), .none, | |
| 1679 | } }, | |
| 1680 | }); | |
| 1681 | }, | |
| 1682 | .block => { | |
| 1683 | const payload = node.castTag(.block).?.data; | |
| 1684 | if (payload.label) |some| { | |
| 1685 | _ = try c.addIdentifier(some); | |
| 1686 | _ = try c.addToken(.colon, ":"); | |
| 1687 | } | |
| 1688 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 1689 | ||
| 1690 | var stmts = std.array_list.Managed(NodeIndex).init(c.gpa); | |
| 1691 | defer stmts.deinit(); | |
| 1692 | for (payload.stmts) |stmt| { | |
| 1693 | const res = (try renderNodeOpt(c, stmt)) orelse continue; | |
| 1694 | try addSemicolonIfNeeded(c, stmt); | |
| 1695 | try stmts.append(res); | |
| 1696 | } | |
| 1697 | const span = try c.listToSpan(stmts.items); | |
| 1698 | _ = try c.addToken(.r_brace, "}"); | |
| 1699 | ||
| 1700 | const semicolon = c.tokens.items(.tag)[c.tokens.len - 2] == .semicolon; | |
| 1701 | return c.addNode(.{ | |
| 1702 | .tag = if (semicolon) .block_semicolon else .block, | |
| 1703 | .main_token = l_brace, | |
| 1704 | .data = .{ .extra_range = span }, | |
| 1705 | }); | |
| 1706 | }, | |
| 1707 | .func => return renderFunc(c, node), | |
| 1708 | .pub_inline_fn => return renderMacroFunc(c, node), | |
| 1709 | .@"while" => { | |
| 1710 | const payload = node.castTag(.@"while").?.data; | |
| 1711 | const while_tok = try c.addToken(.keyword_while, "while"); | |
| 1712 | _ = try c.addToken(.l_paren, "("); | |
| 1713 | const cond = try renderNode(c, payload.cond); | |
| 1714 | _ = try c.addToken(.r_paren, ")"); | |
| 1715 | ||
| 1716 | const cont_expr_opt = if (payload.cont_expr) |some| blk: { | |
| 1717 | _ = try c.addToken(.colon, ":"); | |
| 1718 | _ = try c.addToken(.l_paren, "("); | |
| 1719 | const res = try renderNode(c, some); | |
| 1720 | _ = try c.addToken(.r_paren, ")"); | |
| 1721 | break :blk res; | |
| 1722 | } else null; | |
| 1723 | const body = try renderNode(c, payload.body); | |
| 1724 | ||
| 1725 | if (cont_expr_opt) |cont_expr| { | |
| 1726 | return c.addNode(.{ | |
| 1727 | .tag = .while_cont, | |
| 1728 | .main_token = while_tok, | |
| 1729 | .data = .{ .node_and_extra = .{ | |
| 1730 | cond, | |
| 1731 | try c.addExtra(std.zig.Ast.Node.WhileCont{ | |
| 1732 | .cont_expr = cont_expr, | |
| 1733 | .then_expr = body, | |
| 1734 | }), | |
| 1735 | } }, | |
| 1736 | }); | |
| 1737 | } else { | |
| 1738 | return c.addNode(.{ | |
| 1739 | .tag = .while_simple, | |
| 1740 | .main_token = while_tok, | |
| 1741 | .data = .{ .node_and_node = .{ | |
| 1742 | cond, body, | |
| 1743 | } }, | |
| 1744 | }); | |
| 1745 | } | |
| 1746 | }, | |
| 1747 | .while_true => { | |
| 1748 | const payload = node.castTag(.while_true).?.data; | |
| 1749 | const while_tok = try c.addToken(.keyword_while, "while"); | |
| 1750 | _ = try c.addToken(.l_paren, "("); | |
| 1751 | const cond = try c.addNode(.{ | |
| 1752 | .tag = .identifier, | |
| 1753 | .main_token = try c.addToken(.identifier, "true"), | |
| 1754 | .data = undefined, | |
| 1755 | }); | |
| 1756 | _ = try c.addToken(.r_paren, ")"); | |
| 1757 | const body = try renderNode(c, payload); | |
| 1758 | ||
| 1759 | return c.addNode(.{ | |
| 1760 | .tag = .while_simple, | |
| 1761 | .main_token = while_tok, | |
| 1762 | .data = .{ .node_and_node = .{ | |
| 1763 | cond, body, | |
| 1764 | } }, | |
| 1765 | }); | |
| 1766 | }, | |
| 1767 | .@"if" => { | |
| 1768 | const payload = node.castTag(.@"if").?.data; | |
| 1769 | const if_tok = try c.addToken(.keyword_if, "if"); | |
| 1770 | _ = try c.addToken(.l_paren, "("); | |
| 1771 | const cond = try renderNode(c, payload.cond); | |
| 1772 | _ = try c.addToken(.r_paren, ")"); | |
| 1773 | ||
| 1774 | const then_expr = try renderNode(c, payload.then); | |
| 1775 | const else_node = payload.@"else" orelse return c.addNode(.{ | |
| 1776 | .tag = .if_simple, | |
| 1777 | .main_token = if_tok, | |
| 1778 | .data = .{ .node_and_node = .{ | |
| 1779 | cond, then_expr, | |
| 1780 | } }, | |
| 1781 | }); | |
| 1782 | _ = try c.addToken(.keyword_else, "else"); | |
| 1783 | const else_expr = try renderNode(c, else_node); | |
| 1784 | ||
| 1785 | return c.addNode(.{ | |
| 1786 | .tag = .@"if", | |
| 1787 | .main_token = if_tok, | |
| 1788 | .data = .{ .node_and_extra = .{ | |
| 1789 | cond, | |
| 1790 | try c.addExtra(std.zig.Ast.Node.If{ | |
| 1791 | .then_expr = then_expr, | |
| 1792 | .else_expr = else_expr, | |
| 1793 | }), | |
| 1794 | } }, | |
| 1795 | }); | |
| 1796 | }, | |
| 1797 | .if_not_break => { | |
| 1798 | const payload = node.castTag(.if_not_break).?.data; | |
| 1799 | const if_tok = try c.addToken(.keyword_if, "if"); | |
| 1800 | _ = try c.addToken(.l_paren, "("); | |
| 1801 | const cond = try c.addNode(.{ | |
| 1802 | .tag = .bool_not, | |
| 1803 | .main_token = try c.addToken(.bang, "!"), | |
| 1804 | .data = .{ | |
| 1805 | .node = try renderNodeGrouped(c, payload), | |
| 1806 | }, | |
| 1807 | }); | |
| 1808 | _ = try c.addToken(.r_paren, ")"); | |
| 1809 | const then_expr = try c.addNode(.{ | |
| 1810 | .tag = .@"break", | |
| 1811 | .main_token = try c.addToken(.keyword_break, "break"), | |
| 1812 | .data = .{ .opt_token_and_opt_node = .{ | |
| 1813 | .none, .none, | |
| 1814 | } }, | |
| 1815 | }); | |
| 1816 | ||
| 1817 | return c.addNode(.{ | |
| 1818 | .tag = .if_simple, | |
| 1819 | .main_token = if_tok, | |
| 1820 | .data = .{ .node_and_node = .{ | |
| 1821 | cond, then_expr, | |
| 1822 | } }, | |
| 1823 | }); | |
| 1824 | }, | |
| 1825 | .@"switch" => { | |
| 1826 | const payload = node.castTag(.@"switch").?.data; | |
| 1827 | const switch_tok = try c.addToken(.keyword_switch, "switch"); | |
| 1828 | _ = try c.addToken(.l_paren, "("); | |
| 1829 | const cond = try renderNode(c, payload.cond); | |
| 1830 | _ = try c.addToken(.r_paren, ")"); | |
| 1831 | ||
| 1832 | _ = try c.addToken(.l_brace, "{"); | |
| 1833 | var cases = try c.gpa.alloc(NodeIndex, payload.cases.len); | |
| 1834 | defer c.gpa.free(cases); | |
| 1835 | for (payload.cases, 0..) |case, i| { | |
| 1836 | cases[i] = try renderNode(c, case); | |
| 1837 | _ = try c.addToken(.comma, ","); | |
| 1838 | } | |
| 1839 | const span = try c.listToSpan(cases); | |
| 1840 | _ = try c.addToken(.r_brace, "}"); | |
| 1841 | return c.addNode(.{ | |
| 1842 | .tag = .switch_comma, | |
| 1843 | .main_token = switch_tok, | |
| 1844 | .data = .{ .node_and_extra = .{ | |
| 1845 | cond, | |
| 1846 | try c.addExtra(NodeSubRange{ | |
| 1847 | .start = span.start, | |
| 1848 | .end = span.end, | |
| 1849 | }), | |
| 1850 | } }, | |
| 1851 | }); | |
| 1852 | }, | |
| 1853 | .switch_else => { | |
| 1854 | const payload = node.castTag(.switch_else).?.data; | |
| 1855 | _ = try c.addToken(.keyword_else, "else"); | |
| 1856 | return c.addNode(.{ | |
| 1857 | .tag = .switch_case_one, | |
| 1858 | .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), | |
| 1859 | .data = .{ .opt_node_and_node = .{ | |
| 1860 | .none, try renderNode(c, payload), | |
| 1861 | } }, | |
| 1862 | }); | |
| 1863 | }, | |
| 1864 | .switch_prong => { | |
| 1865 | const payload = node.castTag(.switch_prong).?.data; | |
| 1866 | var items = try c.gpa.alloc(NodeIndex, payload.cases.len); | |
| 1867 | defer c.gpa.free(items); | |
| 1868 | ||
| 1869 | for (payload.cases, 0..) |item, i| { | |
| 1870 | if (i != 0) _ = try c.addToken(.comma, ","); | |
| 1871 | items[i] = try renderNode(c, item); | |
| 1872 | } | |
| 1873 | _ = try c.addToken(.r_brace, "}"); | |
| 1874 | if (items.len < 2) { | |
| 1875 | return c.addNode(.{ | |
| 1876 | .tag = .switch_case_one, | |
| 1877 | .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), | |
| 1878 | .data = .{ .opt_node_and_node = .{ | |
| 1879 | if (payload.cases.len == 1) items[0].toOptional() else .none, | |
| 1880 | try renderNode(c, payload.cond), | |
| 1881 | } }, | |
| 1882 | }); | |
| 1883 | } else { | |
| 1884 | return c.addNode(.{ | |
| 1885 | .tag = .switch_case, | |
| 1886 | .main_token = try c.addToken(.equal_angle_bracket_right, "=>"), | |
| 1887 | .data = .{ .extra_and_node = .{ | |
| 1888 | try c.addExtra(try c.listToSpan(items)), | |
| 1889 | try renderNode(c, payload.cond), | |
| 1890 | } }, | |
| 1891 | }); | |
| 1892 | } | |
| 1893 | }, | |
| 1894 | .opaque_literal => { | |
| 1895 | const opaque_tok = try c.addToken(.keyword_opaque, "opaque"); | |
| 1896 | _ = try c.addToken(.l_brace, "{"); | |
| 1897 | _ = try c.addToken(.r_brace, "}"); | |
| 1898 | ||
| 1899 | return c.addNode(.{ | |
| 1900 | .tag = .container_decl_two, | |
| 1901 | .main_token = opaque_tok, | |
| 1902 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1903 | .none, .none, | |
| 1904 | } }, | |
| 1905 | }); | |
| 1906 | }, | |
| 1907 | .array_access => { | |
| 1908 | const payload = node.castTag(.array_access).?.data; | |
| 1909 | const lhs = try renderNodeGrouped(c, payload.lhs); | |
| 1910 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 1911 | const index_expr = try renderNode(c, payload.rhs); | |
| 1912 | _ = try c.addToken(.r_bracket, "]"); | |
| 1913 | return c.addNode(.{ | |
| 1914 | .tag = .array_access, | |
| 1915 | .main_token = l_bracket, | |
| 1916 | .data = .{ .node_and_node = .{ | |
| 1917 | lhs, index_expr, | |
| 1918 | } }, | |
| 1919 | }); | |
| 1920 | }, | |
| 1921 | .array_type => { | |
| 1922 | const payload = node.castTag(.array_type).?.data; | |
| 1923 | return renderArrayType(c, payload.len, payload.elem_type); | |
| 1924 | }, | |
| 1925 | .null_sentinel_array_type => { | |
| 1926 | const payload = node.castTag(.null_sentinel_array_type).?.data; | |
| 1927 | return renderNullSentinelArrayType(c, payload.len, payload.elem_type); | |
| 1928 | }, | |
| 1929 | .array_filler => { | |
| 1930 | const payload = node.castTag(.array_filler).?.data; | |
| 1931 | ||
| 1932 | const type_expr = try renderArrayType(c, 1, payload.type); | |
| 1933 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 1934 | const val = try renderNode(c, payload.filler); | |
| 1935 | _ = try c.addToken(.r_brace, "}"); | |
| 1936 | ||
| 1937 | const init = try c.addNode(.{ | |
| 1938 | .tag = .array_init_one, | |
| 1939 | .main_token = l_brace, | |
| 1940 | .data = .{ .node_and_node = .{ | |
| 1941 | type_expr, val, | |
| 1942 | } }, | |
| 1943 | }); | |
| 1944 | return c.addNode(.{ | |
| 1945 | .tag = .array_cat, | |
| 1946 | .main_token = try c.addToken(.asterisk_asterisk, "**"), | |
| 1947 | .data = .{ .node_and_node = .{ | |
| 1948 | init, | |
| 1949 | try c.addNode(.{ | |
| 1950 | .tag = .number_literal, | |
| 1951 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.count}), | |
| 1952 | .data = undefined, | |
| 1953 | }), | |
| 1954 | } }, | |
| 1955 | }); | |
| 1956 | }, | |
| 1957 | .empty_array => { | |
| 1958 | const payload = node.castTag(.empty_array).?.data; | |
| 1959 | ||
| 1960 | const type_expr = try renderNode(c, payload); | |
| 1961 | return renderArrayInit(c, type_expr, &.{}); | |
| 1962 | }, | |
| 1963 | .array_init => { | |
| 1964 | const payload = node.castTag(.array_init).?.data; | |
| 1965 | const type_expr = try renderNode(c, payload.cond); | |
| 1966 | return renderArrayInit(c, type_expr, payload.cases); | |
| 1967 | }, | |
| 1968 | .vector_zero_init => { | |
| 1969 | const payload = node.castTag(.vector_zero_init).?.data; | |
| 1970 | return renderBuiltinCall(c, "@splat", &.{payload}); | |
| 1971 | }, | |
| 1972 | .field_access => { | |
| 1973 | const payload = node.castTag(.field_access).?.data; | |
| 1974 | const lhs = try renderNodeGrouped(c, payload.lhs); | |
| 1975 | return renderFieldAccess(c, lhs, payload.field_name); | |
| 1976 | }, | |
| 1977 | .@"struct", .@"union", .@"opaque" => return renderContainer(c, node), | |
| 1978 | .enum_constant => { | |
| 1979 | const payload = node.castTag(.enum_constant).?.data; | |
| 1980 | ||
| 1981 | if (payload.is_public) _ = try c.addToken(.keyword_pub, "pub"); | |
| 1982 | const const_tok = try c.addToken(.keyword_const, "const"); | |
| 1983 | _ = try c.addIdentifier(payload.name); | |
| 1984 | ||
| 1985 | const type_node_opt = if (payload.type) |enum_const_type| blk: { | |
| 1986 | _ = try c.addToken(.colon, ":"); | |
| 1987 | break :blk try renderNode(c, enum_const_type); | |
| 1988 | } else null; | |
| 1989 | ||
| 1990 | _ = try c.addToken(.equal, "="); | |
| 1991 | ||
| 1992 | const init_node = try renderNode(c, payload.value); | |
| 1993 | _ = try c.addToken(.semicolon, ";"); | |
| 1994 | ||
| 1995 | return c.addNode(.{ | |
| 1996 | .tag = .simple_var_decl, | |
| 1997 | .main_token = const_tok, | |
| 1998 | .data = .{ .opt_node_and_opt_node = .{ | |
| 1999 | .fromOptional(type_node_opt), | |
| 2000 | init_node.toOptional(), | |
| 2001 | } }, | |
| 2002 | }); | |
| 2003 | }, | |
| 2004 | .tuple => { | |
| 2005 | const payload = node.castTag(.tuple).?.data; | |
| 2006 | _ = try c.addToken(.period, "."); | |
| 2007 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2008 | var inits = try c.gpa.alloc(NodeIndex, payload.len); | |
| 2009 | defer c.gpa.free(inits); | |
| 2010 | ||
| 2011 | for (payload, 0..) |init, i| { | |
| 2012 | if (i != 0) _ = try c.addToken(.comma, ","); | |
| 2013 | inits[i] = try renderNode(c, init); | |
| 2014 | } | |
| 2015 | _ = try c.addToken(.r_brace, "}"); | |
| 2016 | if (payload.len < 3) { | |
| 2017 | return c.addNode(.{ | |
| 2018 | .tag = .array_init_dot_two, | |
| 2019 | .main_token = l_brace, | |
| 2020 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2021 | if (inits.len >= 1) inits[0].toOptional() else .none, | |
| 2022 | if (inits.len >= 2) inits[1].toOptional() else .none, | |
| 2023 | } }, | |
| 2024 | }); | |
| 2025 | } else { | |
| 2026 | return c.addNode(.{ | |
| 2027 | .tag = .array_init_dot, | |
| 2028 | .main_token = l_brace, | |
| 2029 | .data = .{ .extra_range = try c.listToSpan(inits) }, | |
| 2030 | }); | |
| 2031 | } | |
| 2032 | }, | |
| 2033 | .container_init_dot => { | |
| 2034 | const payload = node.castTag(.container_init_dot).?.data; | |
| 2035 | _ = try c.addToken(.period, "."); | |
| 2036 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2037 | var inits = try c.gpa.alloc(NodeIndex, payload.len); | |
| 2038 | defer c.gpa.free(inits); | |
| 2039 | ||
| 2040 | for (payload, 0..) |init, i| { | |
| 2041 | _ = try c.addToken(.period, "."); | |
| 2042 | _ = try c.addIdentifier(init.name); | |
| 2043 | _ = try c.addToken(.equal, "="); | |
| 2044 | inits[i] = try renderNode(c, init.value); | |
| 2045 | _ = try c.addToken(.comma, ","); | |
| 2046 | } | |
| 2047 | _ = try c.addToken(.r_brace, "}"); | |
| 2048 | ||
| 2049 | if (payload.len < 3) { | |
| 2050 | return c.addNode(.{ | |
| 2051 | .tag = .struct_init_dot_two_comma, | |
| 2052 | .main_token = l_brace, | |
| 2053 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2054 | if (inits.len >= 1) inits[0].toOptional() else .none, | |
| 2055 | if (inits.len >= 2) inits[1].toOptional() else .none, | |
| 2056 | } }, | |
| 2057 | }); | |
| 2058 | } else { | |
| 2059 | return c.addNode(.{ | |
| 2060 | .tag = .struct_init_dot_comma, | |
| 2061 | .main_token = l_brace, | |
| 2062 | .data = .{ .extra_range = try c.listToSpan(inits) }, | |
| 2063 | }); | |
| 2064 | } | |
| 2065 | }, | |
| 2066 | .container_init => { | |
| 2067 | const payload = node.castTag(.container_init).?.data; | |
| 2068 | const lhs = try renderNode(c, payload.lhs); | |
| 2069 | ||
| 2070 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2071 | var inits = try c.gpa.alloc(NodeIndex, payload.inits.len); | |
| 2072 | defer c.gpa.free(inits); | |
| 2073 | ||
| 2074 | for (payload.inits, 0..) |init, i| { | |
| 2075 | _ = try c.addToken(.period, "."); | |
| 2076 | _ = try c.addIdentifier(init.name); | |
| 2077 | _ = try c.addToken(.equal, "="); | |
| 2078 | inits[i] = try renderNode(c, init.value); | |
| 2079 | _ = try c.addToken(.comma, ","); | |
| 2080 | } | |
| 2081 | _ = try c.addToken(.r_brace, "}"); | |
| 2082 | ||
| 2083 | switch (inits.len) { | |
| 2084 | 0 => return c.addNode(.{ | |
| 2085 | .tag = .struct_init_one, | |
| 2086 | .main_token = l_brace, | |
| 2087 | .data = .{ .node_and_opt_node = .{ | |
| 2088 | lhs, .none, | |
| 2089 | } }, | |
| 2090 | }), | |
| 2091 | 1 => return c.addNode(.{ | |
| 2092 | .tag = .struct_init_one_comma, | |
| 2093 | .main_token = l_brace, | |
| 2094 | .data = .{ .node_and_opt_node = .{ | |
| 2095 | lhs, inits[0].toOptional(), | |
| 2096 | } }, | |
| 2097 | }), | |
| 2098 | else => return c.addNode(.{ | |
| 2099 | .tag = .struct_init_comma, | |
| 2100 | .main_token = l_brace, | |
| 2101 | .data = .{ .node_and_extra = .{ | |
| 2102 | lhs, | |
| 2103 | try c.addExtra(try c.listToSpan(inits)), | |
| 2104 | } }, | |
| 2105 | }), | |
| 2106 | } | |
| 2107 | }, | |
| 2108 | .static_assert => { | |
| 2109 | const payload = node.castTag(.static_assert).?.data; | |
| 2110 | const comptime_tok = try c.addToken(.keyword_comptime, "comptime"); | |
| 2111 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2112 | ||
| 2113 | const if_tok = try c.addToken(.keyword_if, "if"); | |
| 2114 | _ = try c.addToken(.l_paren, "("); | |
| 2115 | const cond = try c.addNode(.{ | |
| 2116 | .tag = .bool_not, | |
| 2117 | .main_token = try c.addToken(.bang, "!"), | |
| 2118 | .data = .{ | |
| 2119 | .node = try renderNodeGrouped(c, payload.lhs), | |
| 2120 | }, | |
| 2121 | }); | |
| 2122 | _ = try c.addToken(.r_paren, ")"); | |
| 2123 | ||
| 2124 | const compile_error_tok = try c.addToken(.builtin, "@compileError"); | |
| 2125 | _ = try c.addToken(.l_paren, "("); | |
| 2126 | const err_msg = try renderNode(c, payload.rhs); | |
| 2127 | _ = try c.addToken(.r_paren, ")"); | |
| 2128 | const compile_error = try c.addNode(.{ | |
| 2129 | .tag = .builtin_call_two, | |
| 2130 | .main_token = compile_error_tok, | |
| 2131 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2132 | err_msg.toOptional(), .none, | |
| 2133 | } }, | |
| 2134 | }); | |
| 2135 | ||
| 2136 | const if_node = try c.addNode(.{ | |
| 2137 | .tag = .if_simple, | |
| 2138 | .main_token = if_tok, | |
| 2139 | .data = .{ .node_and_node = .{ | |
| 2140 | cond, compile_error, | |
| 2141 | } }, | |
| 2142 | }); | |
| 2143 | _ = try c.addToken(.semicolon, ";"); | |
| 2144 | _ = try c.addToken(.r_brace, "}"); | |
| 2145 | const block_node = try c.addNode(.{ | |
| 2146 | .tag = .block_two_semicolon, | |
| 2147 | .main_token = l_brace, | |
| 2148 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2149 | if_node.toOptional(), .none, | |
| 2150 | } }, | |
| 2151 | }); | |
| 2152 | ||
| 2153 | return c.addNode(.{ | |
| 2154 | .tag = .@"comptime", | |
| 2155 | .main_token = comptime_tok, | |
| 2156 | .data = .{ | |
| 2157 | .node = block_node, | |
| 2158 | }, | |
| 2159 | }); | |
| 2160 | }, | |
| 2161 | .@"anytype" => unreachable, // Handled in renderParams | |
| 2162 | } | |
| 2163 | } | |
| 2164 | ||
| 2165 | fn renderContainer(c: *Context, node: Node) !NodeIndex { | |
| 2166 | const payload = @as(*Payload.Container, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 2167 | if (payload.layout == .@"packed") | |
| 2168 | _ = try c.addToken(.keyword_packed, "packed") | |
| 2169 | else if (payload.layout == .@"extern") | |
| 2170 | _ = try c.addToken(.keyword_extern, "extern"); | |
| 2171 | const kind_tok = if (node.tag() == .@"struct") | |
| 2172 | try c.addToken(.keyword_struct, "struct") | |
| 2173 | else if (node.tag() == .@"union") | |
| 2174 | try c.addToken(.keyword_union, "union") | |
| 2175 | else if (node.tag() == .@"opaque") | |
| 2176 | try c.addToken(.keyword_opaque, "opaque") | |
| 2177 | else | |
| 2178 | unreachable; | |
| 2179 | ||
| 2180 | _ = try c.addToken(.l_brace, "{"); | |
| 2181 | ||
| 2182 | const num_decls = payload.decls.len; | |
| 2183 | const total_members = payload.fields.len + num_decls; | |
| 2184 | const members = try c.gpa.alloc(NodeIndex, total_members); | |
| 2185 | defer c.gpa.free(members); | |
| 2186 | ||
| 2187 | for (payload.fields, 0..) |field, i| { | |
| 2188 | const name_tok = try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field.name, .{ .allow_primitive = true })}); | |
| 2189 | _ = try c.addToken(.colon, ":"); | |
| 2190 | const type_expr = try renderNode(c, field.type); | |
| 2191 | ||
| 2192 | const align_expr_opt = if (field.alignment) |alignment| blk: { | |
| 2193 | _ = try c.addToken(.keyword_align, "align"); | |
| 2194 | _ = try c.addToken(.l_paren, "("); | |
| 2195 | const align_expr = try c.addNode(.{ | |
| 2196 | .tag = .number_literal, | |
| 2197 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{alignment}), | |
| 2198 | .data = undefined, | |
| 2199 | }); | |
| 2200 | _ = try c.addToken(.r_paren, ")"); | |
| 2201 | break :blk align_expr; | |
| 2202 | } else null; | |
| 2203 | ||
| 2204 | const value_expr_opt = if (field.default_value) |value| blk: { | |
| 2205 | _ = try c.addToken(.equal, "="); | |
| 2206 | break :blk try renderNode(c, value); | |
| 2207 | } else null; | |
| 2208 | ||
| 2209 | if (align_expr_opt) |align_expr| { | |
| 2210 | if (value_expr_opt) |value_expr| { | |
| 2211 | members[i] = try c.addNode(.{ | |
| 2212 | .tag = .container_field, | |
| 2213 | .main_token = name_tok, | |
| 2214 | .data = .{ .node_and_extra = .{ | |
| 2215 | type_expr, | |
| 2216 | try c.addExtra(std.zig.Ast.Node.ContainerField{ | |
| 2217 | .align_expr = align_expr, | |
| 2218 | .value_expr = value_expr, | |
| 2219 | }), | |
| 2220 | } }, | |
| 2221 | }); | |
| 2222 | } else { | |
| 2223 | members[i] = try c.addNode(.{ | |
| 2224 | .tag = .container_field_align, | |
| 2225 | .main_token = name_tok, | |
| 2226 | .data = .{ .node_and_node = .{ | |
| 2227 | type_expr, | |
| 2228 | align_expr, | |
| 2229 | } }, | |
| 2230 | }); | |
| 2231 | } | |
| 2232 | } else { | |
| 2233 | members[i] = try c.addNode(.{ | |
| 2234 | .tag = .container_field_init, | |
| 2235 | .main_token = name_tok, | |
| 2236 | .data = .{ .node_and_opt_node = .{ | |
| 2237 | type_expr, | |
| 2238 | .fromOptional(value_expr_opt), | |
| 2239 | } }, | |
| 2240 | }); | |
| 2241 | } | |
| 2242 | _ = try c.addToken(.comma, ","); | |
| 2243 | } | |
| 2244 | for (members[payload.fields.len..], payload.decls) |*member, decl| { | |
| 2245 | member.* = try renderNode(c, decl); | |
| 2246 | } | |
| 2247 | const trailing = switch (c.tokens.items(.tag)[c.tokens.len - 1]) { | |
| 2248 | .comma, .semicolon => true, | |
| 2249 | else => false, | |
| 2250 | }; | |
| 2251 | _ = try c.addToken(.r_brace, "}"); | |
| 2252 | ||
| 2253 | if (total_members == 0) { | |
| 2254 | return c.addNode(.{ | |
| 2255 | .tag = .container_decl_two, | |
| 2256 | .main_token = kind_tok, | |
| 2257 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2258 | .none, .none, | |
| 2259 | } }, | |
| 2260 | }); | |
| 2261 | } else if (total_members <= 2) { | |
| 2262 | return c.addNode(.{ | |
| 2263 | .tag = if (trailing) .container_decl_two_trailing else .container_decl_two, | |
| 2264 | .main_token = kind_tok, | |
| 2265 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2266 | if (members.len >= 1) members[0].toOptional() else .none, | |
| 2267 | if (members.len >= 2) members[1].toOptional() else .none, | |
| 2268 | } }, | |
| 2269 | }); | |
| 2270 | } else { | |
| 2271 | const span = try c.listToSpan(members); | |
| 2272 | return c.addNode(.{ | |
| 2273 | .tag = if (trailing) .container_decl_trailing else .container_decl, | |
| 2274 | .main_token = kind_tok, | |
| 2275 | .data = .{ .extra_range = span }, | |
| 2276 | }); | |
| 2277 | } | |
| 2278 | } | |
| 2279 | ||
| 2280 | fn renderFieldAccess(c: *Context, lhs: NodeIndex, field_name: []const u8) !NodeIndex { | |
| 2281 | return c.addNode(.{ | |
| 2282 | .tag = .field_access, | |
| 2283 | .main_token = try c.addToken(.period, "."), | |
| 2284 | .data = .{ .node_and_token = .{ | |
| 2285 | lhs, try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field_name, .{ .allow_primitive = true })}), | |
| 2286 | } }, | |
| 2287 | }); | |
| 2288 | } | |
| 2289 | ||
| 2290 | fn renderArrayInit(c: *Context, lhs: NodeIndex, inits: []const Node) !NodeIndex { | |
| 2291 | const l_brace = try c.addToken(.l_brace, "{"); | |
| 2292 | var rendered = try c.gpa.alloc(NodeIndex, inits.len); | |
| 2293 | defer c.gpa.free(rendered); | |
| 2294 | ||
| 2295 | for (inits, 0..) |init, i| { | |
| 2296 | rendered[i] = try renderNode(c, init); | |
| 2297 | _ = try c.addToken(.comma, ","); | |
| 2298 | } | |
| 2299 | _ = try c.addToken(.r_brace, "}"); | |
| 2300 | switch (inits.len) { | |
| 2301 | 0 => return c.addNode(.{ | |
| 2302 | .tag = .struct_init_one, | |
| 2303 | .main_token = l_brace, | |
| 2304 | .data = .{ .node_and_opt_node = .{ | |
| 2305 | lhs, .none, | |
| 2306 | } }, | |
| 2307 | }), | |
| 2308 | 1 => return c.addNode(.{ | |
| 2309 | .tag = .array_init_one_comma, | |
| 2310 | .main_token = l_brace, | |
| 2311 | .data = .{ .node_and_node = .{ | |
| 2312 | lhs, rendered[0], | |
| 2313 | } }, | |
| 2314 | }), | |
| 2315 | else => return c.addNode(.{ | |
| 2316 | .tag = .array_init_comma, | |
| 2317 | .main_token = l_brace, | |
| 2318 | .data = .{ .node_and_extra = .{ | |
| 2319 | lhs, | |
| 2320 | try c.addExtra(try c.listToSpan(rendered)), | |
| 2321 | } }, | |
| 2322 | }), | |
| 2323 | } | |
| 2324 | } | |
| 2325 | ||
| 2326 | fn renderArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex { | |
| 2327 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 2328 | const len_expr = try c.addNode(.{ | |
| 2329 | .tag = .number_literal, | |
| 2330 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}), | |
| 2331 | .data = undefined, | |
| 2332 | }); | |
| 2333 | _ = try c.addToken(.r_bracket, "]"); | |
| 2334 | const elem_type_expr = try renderNode(c, elem_type); | |
| 2335 | return c.addNode(.{ | |
| 2336 | .tag = .array_type, | |
| 2337 | .main_token = l_bracket, | |
| 2338 | .data = .{ .node_and_node = .{ | |
| 2339 | len_expr, elem_type_expr, | |
| 2340 | } }, | |
| 2341 | }); | |
| 2342 | } | |
| 2343 | ||
| 2344 | fn renderNullSentinelArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex { | |
| 2345 | const l_bracket = try c.addToken(.l_bracket, "["); | |
| 2346 | const len_expr = try c.addNode(.{ | |
| 2347 | .tag = .number_literal, | |
| 2348 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}), | |
| 2349 | .data = undefined, | |
| 2350 | }); | |
| 2351 | _ = try c.addToken(.colon, ":"); | |
| 2352 | ||
| 2353 | const sentinel_expr = try c.addNode(.{ | |
| 2354 | .tag = .number_literal, | |
| 2355 | .main_token = try c.addToken(.number_literal, "0"), | |
| 2356 | .data = undefined, | |
| 2357 | }); | |
| 2358 | ||
| 2359 | _ = try c.addToken(.r_bracket, "]"); | |
| 2360 | const elem_type_expr = try renderNode(c, elem_type); | |
| 2361 | return c.addNode(.{ | |
| 2362 | .tag = .array_type_sentinel, | |
| 2363 | .main_token = l_bracket, | |
| 2364 | .data = .{ .node_and_extra = .{ | |
| 2365 | len_expr, | |
| 2366 | try c.addExtra(std.zig.Ast.Node.ArrayTypeSentinel{ | |
| 2367 | .sentinel = sentinel_expr, | |
| 2368 | .elem_type = elem_type_expr, | |
| 2369 | }), | |
| 2370 | } }, | |
| 2371 | }); | |
| 2372 | } | |
| 2373 | ||
| 2374 | fn addSemicolonIfNeeded(c: *Context, node: Node) !void { | |
| 2375 | switch (node.tag()) { | |
| 2376 | .warning => unreachable, | |
| 2377 | .var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .wrapped_local, .mut_str => {}, | |
| 2378 | .while_true => { | |
| 2379 | const payload = node.castTag(.while_true).?.data; | |
| 2380 | return addSemicolonIfNotBlock(c, payload); | |
| 2381 | }, | |
| 2382 | .@"while" => { | |
| 2383 | const payload = node.castTag(.@"while").?.data; | |
| 2384 | return addSemicolonIfNotBlock(c, payload.body); | |
| 2385 | }, | |
| 2386 | .@"if" => { | |
| 2387 | const payload = node.castTag(.@"if").?.data; | |
| 2388 | if (payload.@"else") |some| | |
| 2389 | return addSemicolonIfNeeded(c, some); | |
| 2390 | return addSemicolonIfNotBlock(c, payload.then); | |
| 2391 | }, | |
| 2392 | else => _ = try c.addToken(.semicolon, ";"), | |
| 2393 | } | |
| 2394 | } | |
| 2395 | ||
| 2396 | fn addSemicolonIfNotBlock(c: *Context, node: Node) !void { | |
| 2397 | switch (node.tag()) { | |
| 2398 | .block, .empty_block, .block_single => {}, | |
| 2399 | else => _ = try c.addToken(.semicolon, ";"), | |
| 2400 | } | |
| 2401 | } | |
| 2402 | ||
| 2403 | fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { | |
| 2404 | switch (node.tag()) { | |
| 2405 | .declaration => unreachable, | |
| 2406 | .null_literal, | |
| 2407 | .undefined_literal, | |
| 2408 | .true_literal, | |
| 2409 | .false_literal, | |
| 2410 | .return_void, | |
| 2411 | .zero_literal, | |
| 2412 | .one_literal, | |
| 2413 | .void_type, | |
| 2414 | .noreturn_type, | |
| 2415 | .@"anytype", | |
| 2416 | .div_trunc, | |
| 2417 | .int_cast, | |
| 2418 | .const_cast, | |
| 2419 | .volatile_cast, | |
| 2420 | .as, | |
| 2421 | .truncate, | |
| 2422 | .bit_cast, | |
| 2423 | .float_cast, | |
| 2424 | .int_from_float, | |
| 2425 | .float_from_int, | |
| 2426 | .ptr_from_int, | |
| 2427 | .std_mem_zeroes, | |
| 2428 | .int_from_ptr, | |
| 2429 | .sizeof, | |
| 2430 | .alignof, | |
| 2431 | .typeof, | |
| 2432 | .typeinfo, | |
| 2433 | .vector, | |
| 2434 | .std_mem_zeroinit, | |
| 2435 | .integer_literal, | |
| 2436 | .float_literal, | |
| 2437 | .string_literal, | |
| 2438 | .string_slice, | |
| 2439 | .char_literal, | |
| 2440 | .enum_literal, | |
| 2441 | .identifier, | |
| 2442 | .field_access, | |
| 2443 | .ptr_cast, | |
| 2444 | .type, | |
| 2445 | .array_access, | |
| 2446 | .align_cast, | |
| 2447 | .optional_type, | |
| 2448 | .c_pointer, | |
| 2449 | .single_pointer, | |
| 2450 | .unwrap, | |
| 2451 | .deref, | |
| 2452 | .not, | |
| 2453 | .negate, | |
| 2454 | .negate_wrap, | |
| 2455 | .bit_not, | |
| 2456 | .func, | |
| 2457 | .call, | |
| 2458 | .array_type, | |
| 2459 | .null_sentinel_array_type, | |
| 2460 | .int_from_bool, | |
| 2461 | .div_exact, | |
| 2462 | .offset_of, | |
| 2463 | .shuffle, | |
| 2464 | .builtin_extern, | |
| 2465 | .wrapped_local, | |
| 2466 | .mut_str, | |
| 2467 | .helper_call, | |
| 2468 | .helper_ref, | |
| 2469 | .byte_swap, | |
| 2470 | .ceil, | |
| 2471 | .cos, | |
| 2472 | .sin, | |
| 2473 | .exp, | |
| 2474 | .exp2, | |
| 2475 | .exp10, | |
| 2476 | .abs, | |
| 2477 | .log, | |
| 2478 | .log2, | |
| 2479 | .log10, | |
| 2480 | .round, | |
| 2481 | .sqrt, | |
| 2482 | .trunc, | |
| 2483 | .floor, | |
| 2484 | => { | |
| 2485 | // no grouping needed | |
| 2486 | return renderNode(c, node); | |
| 2487 | }, | |
| 2488 | ||
| 2489 | .opaque_literal, | |
| 2490 | .@"opaque", | |
| 2491 | .empty_array, | |
| 2492 | .block_single, | |
| 2493 | .add, | |
| 2494 | .add_wrap, | |
| 2495 | .sub, | |
| 2496 | .sub_wrap, | |
| 2497 | .mul, | |
| 2498 | .mul_wrap, | |
| 2499 | .div, | |
| 2500 | .shl, | |
| 2501 | .shr, | |
| 2502 | .mod, | |
| 2503 | .@"and", | |
| 2504 | .@"or", | |
| 2505 | .less_than, | |
| 2506 | .less_than_equal, | |
| 2507 | .greater_than, | |
| 2508 | .greater_than_equal, | |
| 2509 | .equal, | |
| 2510 | .not_equal, | |
| 2511 | .bit_and, | |
| 2512 | .bit_or, | |
| 2513 | .bit_xor, | |
| 2514 | .empty_block, | |
| 2515 | .array_cat, | |
| 2516 | .array_filler, | |
| 2517 | .@"if", | |
| 2518 | .@"struct", | |
| 2519 | .@"union", | |
| 2520 | .array_init, | |
| 2521 | .vector_zero_init, | |
| 2522 | .tuple, | |
| 2523 | .container_init, | |
| 2524 | .container_init_dot, | |
| 2525 | .block, | |
| 2526 | .address_of, | |
| 2527 | => return c.addNode(.{ | |
| 2528 | .tag = .grouped_expression, | |
| 2529 | .main_token = try c.addToken(.l_paren, "("), | |
| 2530 | .data = .{ .node_and_token = .{ | |
| 2531 | try renderNode(c, node), | |
| 2532 | try c.addToken(.r_paren, ")"), | |
| 2533 | } }, | |
| 2534 | }), | |
| 2535 | .ellipsis3, | |
| 2536 | .switch_prong, | |
| 2537 | .warning, | |
| 2538 | .var_decl, | |
| 2539 | .fail_decl, | |
| 2540 | .arg_redecl, | |
| 2541 | .alias, | |
| 2542 | .var_simple, | |
| 2543 | .pub_var_simple, | |
| 2544 | .enum_constant, | |
| 2545 | .@"while", | |
| 2546 | .@"switch", | |
| 2547 | .@"break", | |
| 2548 | .break_val, | |
| 2549 | .pub_inline_fn, | |
| 2550 | .discard, | |
| 2551 | .@"continue", | |
| 2552 | .@"return", | |
| 2553 | .@"comptime", | |
| 2554 | .@"defer", | |
| 2555 | .asm_simple, | |
| 2556 | .while_true, | |
| 2557 | .if_not_break, | |
| 2558 | .switch_else, | |
| 2559 | .add_assign, | |
| 2560 | .add_wrap_assign, | |
| 2561 | .sub_assign, | |
| 2562 | .sub_wrap_assign, | |
| 2563 | .mul_assign, | |
| 2564 | .mul_wrap_assign, | |
| 2565 | .div_assign, | |
| 2566 | .shl_assign, | |
| 2567 | .shr_assign, | |
| 2568 | .mod_assign, | |
| 2569 | .bit_and_assign, | |
| 2570 | .bit_or_assign, | |
| 2571 | .bit_xor_assign, | |
| 2572 | .assign, | |
| 2573 | .static_assert, | |
| 2574 | .@"unreachable", | |
| 2575 | => { | |
| 2576 | // these should never appear in places where grouping might be needed. | |
| 2577 | unreachable; | |
| 2578 | }, | |
| 2579 | } | |
| 2580 | } | |
| 2581 | ||
| 2582 | fn renderPrefixOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { | |
| 2583 | const payload = @as(*Payload.UnOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 2584 | return c.addNode(.{ | |
| 2585 | .tag = tag, | |
| 2586 | .main_token = try c.addToken(tok_tag, bytes), | |
| 2587 | .data = .{ | |
| 2588 | .node = try renderNodeGrouped(c, payload), | |
| 2589 | }, | |
| 2590 | }); | |
| 2591 | } | |
| 2592 | ||
| 2593 | fn renderBinOpGrouped(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { | |
| 2594 | const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 2595 | const lhs = try renderNodeGrouped(c, payload.lhs); | |
| 2596 | return c.addNode(.{ | |
| 2597 | .tag = tag, | |
| 2598 | .main_token = try c.addToken(tok_tag, bytes), | |
| 2599 | .data = .{ .node_and_node = .{ | |
| 2600 | lhs, try renderNodeGrouped(c, payload.rhs), | |
| 2601 | } }, | |
| 2602 | }); | |
| 2603 | } | |
| 2604 | ||
| 2605 | fn renderBinOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex { | |
| 2606 | const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data; | |
| 2607 | const lhs = try renderNode(c, payload.lhs); | |
| 2608 | return c.addNode(.{ | |
| 2609 | .tag = tag, | |
| 2610 | .main_token = try c.addToken(tok_tag, bytes), | |
| 2611 | .data = .{ .node_and_node = .{ | |
| 2612 | lhs, try renderNode(c, payload.rhs), | |
| 2613 | } }, | |
| 2614 | }); | |
| 2615 | } | |
| 2616 | ||
| 2617 | fn renderStdImport(c: *Context, parts: []const []const u8) !NodeIndex { | |
| 2618 | const import_tok = try c.addToken(.builtin, "@import"); | |
| 2619 | _ = try c.addToken(.l_paren, "("); | |
| 2620 | const std_tok = try c.addToken(.string_literal, "\"std\""); | |
| 2621 | const std_node = try c.addNode(.{ | |
| 2622 | .tag = .string_literal, | |
| 2623 | .main_token = std_tok, | |
| 2624 | .data = undefined, | |
| 2625 | }); | |
| 2626 | _ = try c.addToken(.r_paren, ")"); | |
| 2627 | ||
| 2628 | const import_node = try c.addNode(.{ | |
| 2629 | .tag = .builtin_call_two, | |
| 2630 | .main_token = import_tok, | |
| 2631 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2632 | std_node.toOptional(), .none, | |
| 2633 | } }, | |
| 2634 | }); | |
| 2635 | ||
| 2636 | var access_chain = import_node; | |
| 2637 | for (parts) |part| { | |
| 2638 | access_chain = try renderFieldAccess(c, access_chain, part); | |
| 2639 | } | |
| 2640 | return access_chain; | |
| 2641 | } | |
| 2642 | ||
| 2643 | fn renderCall(c: *Context, lhs: NodeIndex, args: []const Node) !NodeIndex { | |
| 2644 | const lparen = try c.addToken(.l_paren, "("); | |
| 2645 | const res = switch (args.len) { | |
| 2646 | 0 => try c.addNode(.{ | |
| 2647 | .tag = .call_one, | |
| 2648 | .main_token = lparen, | |
| 2649 | .data = .{ .node_and_opt_node = .{ | |
| 2650 | lhs, .none, | |
| 2651 | } }, | |
| 2652 | }), | |
| 2653 | 1 => try c.addNode(.{ | |
| 2654 | .tag = .call_one, | |
| 2655 | .main_token = lparen, | |
| 2656 | .data = .{ .node_and_opt_node = .{ | |
| 2657 | lhs, (try renderNode(c, args[0])).toOptional(), | |
| 2658 | } }, | |
| 2659 | }), | |
| 2660 | else => blk: { | |
| 2661 | var rendered = try c.gpa.alloc(NodeIndex, args.len); | |
| 2662 | defer c.gpa.free(rendered); | |
| 2663 | ||
| 2664 | for (args, 0..) |arg, i| { | |
| 2665 | if (i != 0) _ = try c.addToken(.comma, ","); | |
| 2666 | rendered[i] = try renderNode(c, arg); | |
| 2667 | } | |
| 2668 | const span = try c.listToSpan(rendered); | |
| 2669 | break :blk try c.addNode(.{ | |
| 2670 | .tag = .call, | |
| 2671 | .main_token = lparen, | |
| 2672 | .data = .{ .node_and_extra = .{ | |
| 2673 | lhs, try c.addExtra(NodeSubRange{ | |
| 2674 | .start = span.start, | |
| 2675 | .end = span.end, | |
| 2676 | }), | |
| 2677 | } }, | |
| 2678 | }); | |
| 2679 | }, | |
| 2680 | }; | |
| 2681 | _ = try c.addToken(.r_paren, ")"); | |
| 2682 | return res; | |
| 2683 | } | |
| 2684 | ||
| 2685 | fn renderBuiltinCall(c: *Context, builtin: []const u8, args: []const Node) !NodeIndex { | |
| 2686 | const builtin_tok = try c.addToken(.builtin, builtin); | |
| 2687 | _ = try c.addToken(.l_paren, "("); | |
| 2688 | var arg_1: ?NodeIndex = null; | |
| 2689 | var arg_2: ?NodeIndex = null; | |
| 2690 | var arg_3: ?NodeIndex = null; | |
| 2691 | var arg_4: ?NodeIndex = null; | |
| 2692 | switch (args.len) { | |
| 2693 | 0 => {}, | |
| 2694 | 1 => { | |
| 2695 | arg_1 = try renderNode(c, args[0]); | |
| 2696 | }, | |
| 2697 | 2 => { | |
| 2698 | arg_1 = try renderNode(c, args[0]); | |
| 2699 | _ = try c.addToken(.comma, ","); | |
| 2700 | arg_2 = try renderNode(c, args[1]); | |
| 2701 | }, | |
| 2702 | 4 => { | |
| 2703 | arg_1 = try renderNode(c, args[0]); | |
| 2704 | _ = try c.addToken(.comma, ","); | |
| 2705 | arg_2 = try renderNode(c, args[1]); | |
| 2706 | _ = try c.addToken(.comma, ","); | |
| 2707 | arg_3 = try renderNode(c, args[2]); | |
| 2708 | _ = try c.addToken(.comma, ","); | |
| 2709 | arg_4 = try renderNode(c, args[3]); | |
| 2710 | }, | |
| 2711 | else => unreachable, // expand this function as needed. | |
| 2712 | } | |
| 2713 | ||
| 2714 | _ = try c.addToken(.r_paren, ")"); | |
| 2715 | if (args.len <= 2) { | |
| 2716 | return c.addNode(.{ | |
| 2717 | .tag = .builtin_call_two, | |
| 2718 | .main_token = builtin_tok, | |
| 2719 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2720 | .fromOptional(arg_1), .fromOptional(arg_2), | |
| 2721 | } }, | |
| 2722 | }); | |
| 2723 | } else { | |
| 2724 | std.debug.assert(args.len == 4); | |
| 2725 | ||
| 2726 | const params = try c.listToSpan(&.{ arg_1.?, arg_2.?, arg_3.?, arg_4.? }); | |
| 2727 | return c.addNode(.{ | |
| 2728 | .tag = .builtin_call, | |
| 2729 | .main_token = builtin_tok, | |
| 2730 | .data = .{ .extra_range = .{ | |
| 2731 | .start = params.start, | |
| 2732 | .end = params.end, | |
| 2733 | } }, | |
| 2734 | }); | |
| 2735 | } | |
| 2736 | } | |
| 2737 | ||
| 2738 | fn renderVar(c: *Context, node: Node) !NodeIndex { | |
| 2739 | const payload = node.castTag(.var_decl).?.data; | |
| 2740 | if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub"); | |
| 2741 | if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern"); | |
| 2742 | if (payload.is_export) _ = try c.addToken(.keyword_export, "export"); | |
| 2743 | if (payload.is_threadlocal) _ = try c.addToken(.keyword_threadlocal, "threadlocal"); | |
| 2744 | const mut_tok = if (payload.is_const) | |
| 2745 | try c.addToken(.keyword_const, "const") | |
| 2746 | else | |
| 2747 | try c.addToken(.keyword_var, "var"); | |
| 2748 | _ = try c.addIdentifier(payload.name); | |
| 2749 | _ = try c.addToken(.colon, ":"); | |
| 2750 | const type_node = try renderNode(c, payload.type); | |
| 2751 | ||
| 2752 | const align_node_opt = if (payload.alignment) |some| blk: { | |
| 2753 | _ = try c.addToken(.keyword_align, "align"); | |
| 2754 | _ = try c.addToken(.l_paren, "("); | |
| 2755 | const res = try c.addNode(.{ | |
| 2756 | .tag = .number_literal, | |
| 2757 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}), | |
| 2758 | .data = undefined, | |
| 2759 | }); | |
| 2760 | _ = try c.addToken(.r_paren, ")"); | |
| 2761 | break :blk res; | |
| 2762 | } else null; | |
| 2763 | ||
| 2764 | const section_node_opt = if (payload.linksection_string) |some| blk: { | |
| 2765 | _ = try c.addToken(.keyword_linksection, "linksection"); | |
| 2766 | _ = try c.addToken(.l_paren, "("); | |
| 2767 | const res = try c.addNode(.{ | |
| 2768 | .tag = .string_literal, | |
| 2769 | .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}), | |
| 2770 | .data = undefined, | |
| 2771 | }); | |
| 2772 | _ = try c.addToken(.r_paren, ")"); | |
| 2773 | break :blk res; | |
| 2774 | } else null; | |
| 2775 | ||
| 2776 | const init_node_opt = if (payload.init) |some| blk: { | |
| 2777 | _ = try c.addToken(.equal, "="); | |
| 2778 | break :blk try renderNode(c, some); | |
| 2779 | } else null; | |
| 2780 | _ = try c.addToken(.semicolon, ";"); | |
| 2781 | ||
| 2782 | if (section_node_opt) |section_node| { | |
| 2783 | return c.addNode(.{ | |
| 2784 | .tag = .global_var_decl, | |
| 2785 | .main_token = mut_tok, | |
| 2786 | .data = .{ .extra_and_opt_node = .{ | |
| 2787 | try c.addExtra(std.zig.Ast.Node.GlobalVarDecl{ | |
| 2788 | .type_node = type_node.toOptional(), | |
| 2789 | .align_node = .fromOptional(align_node_opt), | |
| 2790 | .section_node = section_node.toOptional(), | |
| 2791 | .addrspace_node = .none, | |
| 2792 | }), | |
| 2793 | .fromOptional(init_node_opt), | |
| 2794 | } }, | |
| 2795 | }); | |
| 2796 | } else { | |
| 2797 | if (align_node_opt) |align_node| { | |
| 2798 | return c.addNode(.{ | |
| 2799 | .tag = .local_var_decl, | |
| 2800 | .main_token = mut_tok, | |
| 2801 | .data = .{ .extra_and_opt_node = .{ | |
| 2802 | try c.addExtra(std.zig.Ast.Node.LocalVarDecl{ | |
| 2803 | .type_node = type_node, | |
| 2804 | .align_node = align_node, | |
| 2805 | }), | |
| 2806 | .fromOptional(init_node_opt), | |
| 2807 | } }, | |
| 2808 | }); | |
| 2809 | } else { | |
| 2810 | return c.addNode(.{ | |
| 2811 | .tag = .simple_var_decl, | |
| 2812 | .main_token = mut_tok, | |
| 2813 | .data = .{ | |
| 2814 | .opt_node_and_opt_node = .{ | |
| 2815 | type_node.toOptional(), // Type expression | |
| 2816 | .fromOptional(init_node_opt), // Init expression | |
| 2817 | }, | |
| 2818 | }, | |
| 2819 | }); | |
| 2820 | } | |
| 2821 | } | |
| 2822 | } | |
| 2823 | ||
| 2824 | fn renderFunc(c: *Context, node: Node) !NodeIndex { | |
| 2825 | const payload = node.castTag(.func).?.data; | |
| 2826 | if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub"); | |
| 2827 | if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern"); | |
| 2828 | if (payload.is_export) _ = try c.addToken(.keyword_export, "export"); | |
| 2829 | if (payload.is_inline) _ = try c.addToken(.keyword_inline, "inline"); | |
| 2830 | const fn_token = try c.addToken(.keyword_fn, "fn"); | |
| 2831 | if (payload.name) |some| _ = try c.addIdentifier(some); | |
| 2832 | ||
| 2833 | const params = try renderParams(c, payload.params, payload.is_var_args); | |
| 2834 | defer params.deinit(); | |
| 2835 | var span: NodeSubRange = undefined; | |
| 2836 | if (params.items.len > 1) span = try c.listToSpan(params.items); | |
| 2837 | ||
| 2838 | const align_expr_opt = if (payload.alignment) |some| blk: { | |
| 2839 | _ = try c.addToken(.keyword_align, "align"); | |
| 2840 | _ = try c.addToken(.l_paren, "("); | |
| 2841 | const res = try c.addNode(.{ | |
| 2842 | .tag = .number_literal, | |
| 2843 | .main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}), | |
| 2844 | .data = undefined, | |
| 2845 | }); | |
| 2846 | _ = try c.addToken(.r_paren, ")"); | |
| 2847 | break :blk res; | |
| 2848 | } else null; | |
| 2849 | ||
| 2850 | const section_expr_opt = if (payload.linksection_string) |some| blk: { | |
| 2851 | _ = try c.addToken(.keyword_linksection, "linksection"); | |
| 2852 | _ = try c.addToken(.l_paren, "("); | |
| 2853 | const res = try c.addNode(.{ | |
| 2854 | .tag = .string_literal, | |
| 2855 | .main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}), | |
| 2856 | .data = undefined, | |
| 2857 | }); | |
| 2858 | _ = try c.addToken(.r_paren, ")"); | |
| 2859 | break :blk res; | |
| 2860 | } else null; | |
| 2861 | ||
| 2862 | const callconv_expr_opt = if (payload.explicit_callconv) |some| blk: { | |
| 2863 | _ = try c.addToken(.keyword_callconv, "callconv"); | |
| 2864 | _ = try c.addToken(.l_paren, "("); | |
| 2865 | const cc_node = switch (some) { | |
| 2866 | .c => cc_node: { | |
| 2867 | _ = try c.addToken(.period, "."); | |
| 2868 | break :cc_node try c.addNode(.{ | |
| 2869 | .tag = .enum_literal, | |
| 2870 | .main_token = try c.addToken(.identifier, "c"), | |
| 2871 | .data = undefined, | |
| 2872 | }); | |
| 2873 | }, | |
| 2874 | .x86_64_sysv, | |
| 2875 | .x86_64_win, | |
| 2876 | .x86_stdcall, | |
| 2877 | .x86_fastcall, | |
| 2878 | .x86_thiscall, | |
| 2879 | .x86_vectorcall, | |
| 2880 | .x86_regcall, | |
| 2881 | .aarch64_vfabi, | |
| 2882 | .aarch64_sve_pcs, | |
| 2883 | .arm_aapcs, | |
| 2884 | .arm_aapcs_vfp, | |
| 2885 | .m68k_rtd, | |
| 2886 | .riscv_vector, | |
| 2887 | => cc_node: { | |
| 2888 | // .{ .foo = .{} } | |
| 2889 | _ = try c.addToken(.period, "."); | |
| 2890 | const outer_lbrace = try c.addToken(.l_brace, "{"); | |
| 2891 | _ = try c.addToken(.period, "."); | |
| 2892 | _ = try c.addToken(.identifier, @tagName(some)); | |
| 2893 | _ = try c.addToken(.equal, "="); | |
| 2894 | _ = try c.addToken(.period, "."); | |
| 2895 | const inner_lbrace = try c.addToken(.l_brace, "{"); | |
| 2896 | _ = try c.addToken(.r_brace, "}"); | |
| 2897 | _ = try c.addToken(.r_brace, "}"); | |
| 2898 | break :cc_node try c.addNode(.{ | |
| 2899 | .tag = .struct_init_dot_two, | |
| 2900 | .main_token = outer_lbrace, | |
| 2901 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2902 | (try c.addNode(.{ | |
| 2903 | .tag = .struct_init_dot_two, | |
| 2904 | .main_token = inner_lbrace, | |
| 2905 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2906 | .none, .none, | |
| 2907 | } }, | |
| 2908 | })).toOptional(), | |
| 2909 | .none, | |
| 2910 | } }, | |
| 2911 | }); | |
| 2912 | }, | |
| 2913 | }; | |
| 2914 | _ = try c.addToken(.r_paren, ")"); | |
| 2915 | break :blk cc_node; | |
| 2916 | } else null; | |
| 2917 | ||
| 2918 | const return_type_expr = try renderNode(c, payload.return_type); | |
| 2919 | ||
| 2920 | const fn_proto = try blk: { | |
| 2921 | if (align_expr_opt == null and section_expr_opt == null and callconv_expr_opt == null) { | |
| 2922 | if (params.items.len < 2) | |
| 2923 | break :blk c.addNode(.{ | |
| 2924 | .tag = .fn_proto_simple, | |
| 2925 | .main_token = fn_token, | |
| 2926 | .data = .{ .opt_node_and_opt_node = .{ | |
| 2927 | if (params.items.len == 1) params.items[0].toOptional() else .none, | |
| 2928 | return_type_expr.toOptional(), | |
| 2929 | } }, | |
| 2930 | }) | |
| 2931 | else | |
| 2932 | break :blk c.addNode(.{ | |
| 2933 | .tag = .fn_proto_multi, | |
| 2934 | .main_token = fn_token, | |
| 2935 | .data = .{ .extra_and_opt_node = .{ | |
| 2936 | try c.addExtra(span), | |
| 2937 | return_type_expr.toOptional(), | |
| 2938 | } }, | |
| 2939 | }); | |
| 2940 | } | |
| 2941 | if (params.items.len < 2) | |
| 2942 | break :blk c.addNode(.{ | |
| 2943 | .tag = .fn_proto_one, | |
| 2944 | .main_token = fn_token, | |
| 2945 | .data = .{ | |
| 2946 | .extra_and_opt_node = .{ | |
| 2947 | try c.addExtra(std.zig.Ast.Node.FnProtoOne{ | |
| 2948 | .param = if (params.items.len == 1) params.items[0].toOptional() else .none, | |
| 2949 | .align_expr = .fromOptional(align_expr_opt), | |
| 2950 | .addrspace_expr = .none, // TODO | |
| 2951 | .section_expr = .fromOptional(section_expr_opt), | |
| 2952 | .callconv_expr = .fromOptional(callconv_expr_opt), | |
| 2953 | }), | |
| 2954 | return_type_expr.toOptional(), | |
| 2955 | }, | |
| 2956 | }, | |
| 2957 | }) | |
| 2958 | else | |
| 2959 | break :blk c.addNode(.{ | |
| 2960 | .tag = .fn_proto, | |
| 2961 | .main_token = fn_token, | |
| 2962 | .data = .{ | |
| 2963 | .extra_and_opt_node = .{ | |
| 2964 | try c.addExtra(std.zig.Ast.Node.FnProto{ | |
| 2965 | .params_start = span.start, | |
| 2966 | .params_end = span.end, | |
| 2967 | .align_expr = .fromOptional(align_expr_opt), | |
| 2968 | .addrspace_expr = .none, // TODO | |
| 2969 | .section_expr = .fromOptional(section_expr_opt), | |
| 2970 | .callconv_expr = .fromOptional(callconv_expr_opt), | |
| 2971 | }), | |
| 2972 | return_type_expr.toOptional(), | |
| 2973 | }, | |
| 2974 | }, | |
| 2975 | }); | |
| 2976 | }; | |
| 2977 | ||
| 2978 | const payload_body = payload.body orelse { | |
| 2979 | if (payload.is_extern) { | |
| 2980 | _ = try c.addToken(.semicolon, ";"); | |
| 2981 | } | |
| 2982 | return fn_proto; | |
| 2983 | }; | |
| 2984 | const body = try renderNode(c, payload_body); | |
| 2985 | return c.addNode(.{ | |
| 2986 | .tag = .fn_decl, | |
| 2987 | .main_token = fn_token, | |
| 2988 | .data = .{ .node_and_node = .{ | |
| 2989 | fn_proto, body, | |
| 2990 | } }, | |
| 2991 | }); | |
| 2992 | } | |
| 2993 | ||
| 2994 | fn renderMacroFunc(c: *Context, node: Node) !NodeIndex { | |
| 2995 | const payload = node.castTag(.pub_inline_fn).?.data; | |
| 2996 | _ = try c.addToken(.keyword_pub, "pub"); | |
| 2997 | _ = try c.addToken(.keyword_inline, "inline"); | |
| 2998 | const fn_token = try c.addToken(.keyword_fn, "fn"); | |
| 2999 | _ = try c.addIdentifier(payload.name); | |
| 3000 | ||
| 3001 | const params = try renderParams(c, payload.params, false); | |
| 3002 | defer params.deinit(); | |
| 3003 | var span: NodeSubRange = undefined; | |
| 3004 | if (params.items.len > 1) span = try c.listToSpan(params.items); | |
| 3005 | ||
| 3006 | const return_type_expr = try renderNodeGrouped(c, payload.return_type); | |
| 3007 | ||
| 3008 | const fn_proto = blk: { | |
| 3009 | if (params.items.len < 2) { | |
| 3010 | break :blk try c.addNode(.{ | |
| 3011 | .tag = .fn_proto_simple, | |
| 3012 | .main_token = fn_token, | |
| 3013 | .data = .{ .opt_node_and_opt_node = .{ | |
| 3014 | if (params.items.len == 1) params.items[0].toOptional() else .none, | |
| 3015 | return_type_expr.toOptional(), | |
| 3016 | } }, | |
| 3017 | }); | |
| 3018 | } else { | |
| 3019 | break :blk try c.addNode(.{ | |
| 3020 | .tag = .fn_proto_multi, | |
| 3021 | .main_token = fn_token, | |
| 3022 | .data = .{ .extra_and_opt_node = .{ | |
| 3023 | try c.addExtra(span), | |
| 3024 | return_type_expr.toOptional(), | |
| 3025 | } }, | |
| 3026 | }); | |
| 3027 | } | |
| 3028 | }; | |
| 3029 | return c.addNode(.{ | |
| 3030 | .tag = .fn_decl, | |
| 3031 | .main_token = fn_token, | |
| 3032 | .data = .{ .node_and_node = .{ | |
| 3033 | fn_proto, try renderNode(c, payload.body), | |
| 3034 | } }, | |
| 3035 | }); | |
| 3036 | } | |
| 3037 | ||
| 3038 | fn renderParams(c: *Context, params: []Payload.Param, is_var_args: bool) !std.array_list.Managed(NodeIndex) { | |
| 3039 | _ = try c.addToken(.l_paren, "("); | |
| 3040 | var rendered = try std.array_list.Managed(NodeIndex).initCapacity(c.gpa, @max(params.len, 1)); | |
| 3041 | errdefer rendered.deinit(); | |
| 3042 | ||
| 3043 | for (params, 0..) |param, i| { | |
| 3044 | if (i != 0) _ = try c.addToken(.comma, ","); | |
| 3045 | if (param.is_noalias) _ = try c.addToken(.keyword_noalias, "noalias"); | |
| 3046 | if (param.name) |some| { | |
| 3047 | _ = try c.addIdentifier(some); | |
| 3048 | _ = try c.addToken(.colon, ":"); | |
| 3049 | } | |
| 3050 | if (param.type.tag() == .@"anytype") { | |
| 3051 | _ = try c.addToken(.keyword_anytype, "anytype"); | |
| 3052 | continue; | |
| 3053 | } | |
| 3054 | rendered.appendAssumeCapacity(try renderNode(c, param.type)); | |
| 3055 | } | |
| 3056 | if (is_var_args) { | |
| 3057 | if (params.len != 0) _ = try c.addToken(.comma, ","); | |
| 3058 | _ = try c.addToken(.ellipsis3, "..."); | |
| 3059 | } | |
| 3060 | _ = try c.addToken(.r_paren, ")"); | |
| 3061 | ||
| 3062 | return rendered; | |
| 3063 | } |
lib/compiler/translate-c/src/builtins.zig deleted-76| ... | ... | @@ -1,76 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | const ast = @import("ast.zig"); | |
| 4 | ||
| 5 | /// All builtins need to have a source so that macros can reference them | |
| 6 | /// but for some it is possible to directly call an equivalent Zig builtin | |
| 7 | /// which is preferrable. | |
| 8 | pub const Builtin = struct { | |
| 9 | /// The name of the builtin in `c_builtins.zig`. | |
| 10 | name: []const u8, | |
| 11 | tag: ?ast.Node.Tag = null, | |
| 12 | }; | |
| 13 | ||
| 14 | pub const map = std.StaticStringMap(Builtin).initComptime([_]struct { []const u8, Builtin }{ | |
| 15 | .{ "__builtin_abs", .{ .name = "abs" } }, | |
| 16 | .{ "__builtin_assume", .{ .name = "assume" } }, | |
| 17 | .{ "__builtin_bswap16", .{ .name = "bswap16", .tag = .byte_swap } }, | |
| 18 | .{ "__builtin_bswap32", .{ .name = "bswap32", .tag = .byte_swap } }, | |
| 19 | .{ "__builtin_bswap64", .{ .name = "bswap64", .tag = .byte_swap } }, | |
| 20 | .{ "__builtin_ceilf", .{ .name = "ceilf", .tag = .ceil } }, | |
| 21 | .{ "__builtin_ceil", .{ .name = "ceil", .tag = .ceil } }, | |
| 22 | .{ "__builtin_clz", .{ .name = "clz" } }, | |
| 23 | .{ "__builtin_constant_p", .{ .name = "constant_p" } }, | |
| 24 | .{ "__builtin_cosf", .{ .name = "cosf", .tag = .cos } }, | |
| 25 | .{ "__builtin_cos", .{ .name = "cos", .tag = .cos } }, | |
| 26 | .{ "__builtin_ctz", .{ .name = "ctz" } }, | |
| 27 | .{ "__builtin_exp2f", .{ .name = "exp2f", .tag = .exp2 } }, | |
| 28 | .{ "__builtin_exp2", .{ .name = "exp2", .tag = .exp2 } }, | |
| 29 | .{ "__builtin_expf", .{ .name = "expf", .tag = .exp } }, | |
| 30 | .{ "__builtin_exp", .{ .name = "exp", .tag = .exp } }, | |
| 31 | .{ "__builtin_expect", .{ .name = "expect" } }, | |
| 32 | .{ "__builtin_fabsf", .{ .name = "fabsf", .tag = .abs } }, | |
| 33 | .{ "__builtin_fabs", .{ .name = "fabs", .tag = .abs } }, | |
| 34 | .{ "__builtin_floorf", .{ .name = "floorf", .tag = .floor } }, | |
| 35 | .{ "__builtin_floor", .{ .name = "floor", .tag = .floor } }, | |
| 36 | .{ "__builtin_huge_valf", .{ .name = "huge_valf" } }, | |
| 37 | .{ "__builtin_inff", .{ .name = "inff" } }, | |
| 38 | .{ "__builtin_isinf_sign", .{ .name = "isinf_sign" } }, | |
| 39 | .{ "__builtin_isinf", .{ .name = "isinf" } }, | |
| 40 | .{ "__builtin_isnan", .{ .name = "isnan" } }, | |
| 41 | .{ "__builtin_labs", .{ .name = "labs" } }, | |
| 42 | .{ "__builtin_llabs", .{ .name = "llabs" } }, | |
| 43 | .{ "__builtin_log10f", .{ .name = "log10f", .tag = .log10 } }, | |
| 44 | .{ "__builtin_log10", .{ .name = "log10", .tag = .log10 } }, | |
| 45 | .{ "__builtin_log2f", .{ .name = "log2f", .tag = .log2 } }, | |
| 46 | .{ "__builtin_log2", .{ .name = "log2", .tag = .log2 } }, | |
| 47 | .{ "__builtin_logf", .{ .name = "logf", .tag = .log } }, | |
| 48 | .{ "__builtin_log", .{ .name = "log", .tag = .log } }, | |
| 49 | .{ "__builtin___memcpy_chk", .{ .name = "memcpy_chk" } }, | |
| 50 | .{ "__builtin_memcpy", .{ .name = "memcpy" } }, | |
| 51 | .{ "__builtin___memset_chk", .{ .name = "memset_chk" } }, | |
| 52 | .{ "__builtin_memset", .{ .name = "memset" } }, | |
| 53 | .{ "__builtin_mul_overflow", .{ .name = "mul_overflow" } }, | |
| 54 | .{ "__builtin_nanf", .{ .name = "nanf" } }, | |
| 55 | .{ "__builtin_object_size", .{ .name = "object_size" } }, | |
| 56 | .{ "__builtin_popcount", .{ .name = "popcount" } }, | |
| 57 | .{ "__builtin_roundf", .{ .name = "roundf", .tag = .round } }, | |
| 58 | .{ "__builtin_round", .{ .name = "round", .tag = .round } }, | |
| 59 | .{ "__builtin_signbitf", .{ .name = "signbitf" } }, | |
| 60 | .{ "__builtin_signbit", .{ .name = "signbit" } }, | |
| 61 | .{ "__builtin_sinf", .{ .name = "sinf", .tag = .sin } }, | |
| 62 | .{ "__builtin_sin", .{ .name = "sin", .tag = .sin } }, | |
| 63 | .{ "__builtin_sqrtf", .{ .name = "sqrtf", .tag = .sqrt } }, | |
| 64 | .{ "__builtin_sqrt", .{ .name = "sqrt", .tag = .sqrt } }, | |
| 65 | .{ "__builtin_strcmp", .{ .name = "strcmp" } }, | |
| 66 | .{ "__builtin_strlen", .{ .name = "strlen" } }, | |
| 67 | .{ "__builtin_truncf", .{ .name = "truncf", .tag = .trunc } }, | |
| 68 | .{ "__builtin_trunc", .{ .name = "trunc", .tag = .trunc } }, | |
| 69 | .{ "__builtin_unreachable", .{ .name = "unreachable", .tag = .@"unreachable" } }, | |
| 70 | .{ "__has_builtin", .{ .name = "has_builtin" } }, | |
| 71 | ||
| 72 | // __builtin_alloca_with_align is not currently implemented. | |
| 73 | // It is used in a run and a translate test to ensure that non-implemented | |
| 74 | // builtins are correctly demoted. If you implement __builtin_alloca_with_align, | |
| 75 | // please update the tests to use a different non-implemented builtin. | |
| 76 | }); |
lib/compiler/translate-c/src/helpers.zig deleted-327| ... | ... | @@ -1,327 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const testing = std.testing; | |
| 4 | const math = std.math; | |
| 5 | ||
| 6 | const helpers = @import("helpers"); | |
| 7 | ||
| 8 | const cast = helpers.cast; | |
| 9 | ||
| 10 | test cast { | |
| 11 | var i = @as(i64, 10); | |
| 12 | ||
| 13 | try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16))); | |
| 14 | try testing.expect(cast(*u64, &i).* == @as(u64, 10)); | |
| 15 | try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i); | |
| 16 | ||
| 17 | try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2))); | |
| 18 | try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i); | |
| 19 | try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i); | |
| 20 | ||
| 21 | try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4)))); | |
| 22 | try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4)))); | |
| 23 | try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); | |
| 24 | ||
| 25 | try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000))); | |
| 26 | ||
| 27 | try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2)))); | |
| 28 | try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2)))); | |
| 29 | ||
| 30 | try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2)))); | |
| 31 | ||
| 32 | var foo: c_int = -1; | |
| 33 | _ = &foo; | |
| 34 | try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 35 | try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 36 | try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 37 | try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 38 | ||
| 39 | const FnPtr = ?*align(1) const fn (*anyopaque) void; | |
| 40 | try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0)))); | |
| 41 | try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 42 | ||
| 43 | const complexFunction = struct { | |
| 44 | fn f(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize { | |
| 45 | return 0; | |
| 46 | } | |
| 47 | }.f; | |
| 48 | ||
| 49 | const SDL_FunctionPointer = ?*const fn () callconv(.c) void; | |
| 50 | const fn_ptr = cast(SDL_FunctionPointer, complexFunction); | |
| 51 | try testing.expect(fn_ptr != null); | |
| 52 | } | |
| 53 | ||
| 54 | const sizeof = helpers.sizeof; | |
| 55 | ||
| 56 | test sizeof { | |
| 57 | const S = extern struct { a: u32 }; | |
| 58 | ||
| 59 | const ptr_size = @sizeOf(*anyopaque); | |
| 60 | ||
| 61 | try testing.expect(sizeof(u32) == 4); | |
| 62 | try testing.expect(sizeof(@as(u32, 2)) == 4); | |
| 63 | try testing.expect(sizeof(2) == @sizeOf(c_int)); | |
| 64 | ||
| 65 | try testing.expect(sizeof(2.0) == @sizeOf(f64)); | |
| 66 | ||
| 67 | try testing.expect(sizeof(S) == 4); | |
| 68 | ||
| 69 | try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12); | |
| 70 | try testing.expect(sizeof([3]u32) == 12); | |
| 71 | try testing.expect(sizeof([3:0]u32) == 16); | |
| 72 | try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size); | |
| 73 | ||
| 74 | try testing.expect(sizeof(*u32) == ptr_size); | |
| 75 | try testing.expect(sizeof([*]u32) == ptr_size); | |
| 76 | try testing.expect(sizeof([*c]u32) == ptr_size); | |
| 77 | try testing.expect(sizeof(?*u32) == ptr_size); | |
| 78 | try testing.expect(sizeof(?[*]u32) == ptr_size); | |
| 79 | try testing.expect(sizeof(*anyopaque) == ptr_size); | |
| 80 | try testing.expect(sizeof(*void) == ptr_size); | |
| 81 | try testing.expect(sizeof(null) == ptr_size); | |
| 82 | ||
| 83 | try testing.expect(sizeof("foobar") == 7); | |
| 84 | try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14); | |
| 85 | try testing.expect(sizeof(*const [4:0]u8) == 5); | |
| 86 | try testing.expect(sizeof(*[4:0]u8) == ptr_size); | |
| 87 | try testing.expect(sizeof([*]const [4:0]u8) == ptr_size); | |
| 88 | try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size); | |
| 89 | try testing.expect(sizeof(*const [4]u8) == ptr_size); | |
| 90 | ||
| 91 | if (false) { // TODO | |
| 92 | try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof))); | |
| 93 | try testing.expect(sizeof(sizeof) == 1); | |
| 94 | } | |
| 95 | ||
| 96 | try testing.expect(sizeof(void) == 1); | |
| 97 | try testing.expect(sizeof(anyopaque) == 1); | |
| 98 | } | |
| 99 | ||
| 100 | const promoteIntLiteral = helpers.promoteIntLiteral; | |
| 101 | ||
| 102 | test promoteIntLiteral { | |
| 103 | const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex); | |
| 104 | try testing.expectEqual(c_uint, @TypeOf(signed_hex)); | |
| 105 | ||
| 106 | if (math.maxInt(c_longlong) == math.maxInt(c_int)) return; | |
| 107 | ||
| 108 | const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal); | |
| 109 | const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex); | |
| 110 | ||
| 111 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 112 | try testing.expectEqual(c_long, @TypeOf(signed_decimal)); | |
| 113 | try testing.expectEqual(c_ulong, @TypeOf(unsigned)); | |
| 114 | } else { | |
| 115 | try testing.expectEqual(c_longlong, @TypeOf(signed_decimal)); | |
| 116 | try testing.expectEqual(c_ulonglong, @TypeOf(unsigned)); | |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | const shuffleVectorIndex = helpers.shuffleVectorIndex; | |
| 121 | ||
| 122 | test shuffleVectorIndex { | |
| 123 | const vector_len: usize = 4; | |
| 124 | ||
| 125 | _ = shuffleVectorIndex(-1, vector_len); | |
| 126 | ||
| 127 | try testing.expect(shuffleVectorIndex(0, vector_len) == 0); | |
| 128 | try testing.expect(shuffleVectorIndex(1, vector_len) == 1); | |
| 129 | try testing.expect(shuffleVectorIndex(2, vector_len) == 2); | |
| 130 | try testing.expect(shuffleVectorIndex(3, vector_len) == 3); | |
| 131 | ||
| 132 | try testing.expect(shuffleVectorIndex(4, vector_len) == -1); | |
| 133 | try testing.expect(shuffleVectorIndex(5, vector_len) == -2); | |
| 134 | try testing.expect(shuffleVectorIndex(6, vector_len) == -3); | |
| 135 | try testing.expect(shuffleVectorIndex(7, vector_len) == -4); | |
| 136 | } | |
| 137 | ||
| 138 | const FlexibleArrayType = helpers.FlexibleArrayType; | |
| 139 | ||
| 140 | test FlexibleArrayType { | |
| 141 | const Container = extern struct { | |
| 142 | size: usize, | |
| 143 | }; | |
| 144 | ||
| 145 | try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int); | |
| 146 | try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int); | |
| 147 | try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int); | |
| 148 | try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int); | |
| 149 | } | |
| 150 | ||
| 151 | const signedRemainder = helpers.signedRemainder; | |
| 152 | ||
| 153 | test signedRemainder { | |
| 154 | // TODO add test | |
| 155 | return error.SkipZigTest; | |
| 156 | } | |
| 157 | ||
| 158 | const ArithmeticConversion = helpers.ArithmeticConversion; | |
| 159 | ||
| 160 | test ArithmeticConversion { | |
| 161 | // Promotions not necessarily the same for other platforms | |
| 162 | if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest; | |
| 163 | ||
| 164 | const Test = struct { | |
| 165 | /// Order of operands should not matter for arithmetic conversions | |
| 166 | fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void { | |
| 167 | try std.testing.expect(ArithmeticConversion(A, B) == Expected); | |
| 168 | try std.testing.expect(ArithmeticConversion(B, A) == Expected); | |
| 169 | } | |
| 170 | }; | |
| 171 | ||
| 172 | try Test.checkPromotion(c_longdouble, c_int, c_longdouble); | |
| 173 | try Test.checkPromotion(c_int, f64, f64); | |
| 174 | try Test.checkPromotion(f32, bool, f32); | |
| 175 | ||
| 176 | try Test.checkPromotion(bool, c_short, c_int); | |
| 177 | try Test.checkPromotion(c_int, c_int, c_int); | |
| 178 | try Test.checkPromotion(c_short, c_int, c_int); | |
| 179 | ||
| 180 | try Test.checkPromotion(c_int, c_long, c_long); | |
| 181 | ||
| 182 | try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong); | |
| 183 | ||
| 184 | try Test.checkPromotion(c_uint, c_int, c_uint); | |
| 185 | ||
| 186 | try Test.checkPromotion(c_uint, c_long, c_long); | |
| 187 | ||
| 188 | try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong); | |
| 189 | ||
| 190 | // stdint.h | |
| 191 | try Test.checkPromotion(u8, i8, c_int); | |
| 192 | try Test.checkPromotion(u16, i16, c_int); | |
| 193 | try Test.checkPromotion(i32, c_int, c_int); | |
| 194 | try Test.checkPromotion(u32, c_int, c_uint); | |
| 195 | try Test.checkPromotion(i64, c_int, c_long); | |
| 196 | try Test.checkPromotion(u64, c_int, c_ulong); | |
| 197 | try Test.checkPromotion(isize, c_int, c_long); | |
| 198 | try Test.checkPromotion(usize, c_int, c_ulong); | |
| 199 | } | |
| 200 | ||
| 201 | const F_SUFFIX = helpers.F_SUFFIX; | |
| 202 | ||
| 203 | test F_SUFFIX { | |
| 204 | try testing.expect(@TypeOf(F_SUFFIX(1)) == f32); | |
| 205 | } | |
| 206 | ||
| 207 | const U_SUFFIX = helpers.U_SUFFIX; | |
| 208 | ||
| 209 | test U_SUFFIX { | |
| 210 | try testing.expect(@TypeOf(U_SUFFIX(1)) == c_uint); | |
| 211 | if (math.maxInt(c_ulong) > math.maxInt(c_uint)) { | |
| 212 | try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong); | |
| 213 | } | |
| 214 | if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { | |
| 215 | try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); | |
| 216 | } | |
| 217 | } | |
| 218 | ||
| 219 | const L_SUFFIX = helpers.L_SUFFIX; | |
| 220 | ||
| 221 | test L_SUFFIX { | |
| 222 | try testing.expect(@TypeOf(L_SUFFIX(1)) == c_long); | |
| 223 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 224 | try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_int) + 1)) == c_long); | |
| 225 | } | |
| 226 | if (math.maxInt(c_longlong) > math.maxInt(c_long)) { | |
| 227 | try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); | |
| 228 | } | |
| 229 | } | |
| 230 | const UL_SUFFIX = helpers.UL_SUFFIX; | |
| 231 | ||
| 232 | test UL_SUFFIX { | |
| 233 | try testing.expect(@TypeOf(UL_SUFFIX(1)) == c_ulong); | |
| 234 | if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { | |
| 235 | try testing.expect(@TypeOf(UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); | |
| 236 | } | |
| 237 | } | |
| 238 | const LL_SUFFIX = helpers.LL_SUFFIX; | |
| 239 | ||
| 240 | test LL_SUFFIX { | |
| 241 | try testing.expect(@TypeOf(LL_SUFFIX(1)) == c_longlong); | |
| 242 | } | |
| 243 | const ULL_SUFFIX = helpers.ULL_SUFFIX; | |
| 244 | ||
| 245 | test ULL_SUFFIX { | |
| 246 | try testing.expect(@TypeOf(ULL_SUFFIX(1)) == c_ulonglong); | |
| 247 | } | |
| 248 | ||
| 249 | test "Extended C ABI casting" { | |
| 250 | if (math.maxInt(c_long) > math.maxInt(c_char)) { | |
| 251 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char | |
| 252 | } | |
| 253 | if (math.maxInt(c_long) > math.maxInt(c_short)) { | |
| 254 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short | |
| 255 | } | |
| 256 | ||
| 257 | if (math.maxInt(c_long) > math.maxInt(c_ushort)) { | |
| 258 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort | |
| 259 | } | |
| 260 | ||
| 261 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 262 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int | |
| 263 | } | |
| 264 | ||
| 265 | if (math.maxInt(c_long) > math.maxInt(c_uint)) { | |
| 266 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint | |
| 267 | try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long | |
| 268 | } | |
| 269 | ||
| 270 | if (math.maxInt(c_longlong) > math.maxInt(c_long)) { | |
| 271 | try testing.expect(@TypeOf(L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long | |
| 272 | try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong | |
| 273 | } | |
| 274 | } | |
| 275 | ||
| 276 | const WL_CONTAINER_OF = helpers.WL_CONTAINER_OF; | |
| 277 | ||
| 278 | test WL_CONTAINER_OF { | |
| 279 | const S = struct { | |
| 280 | a: u32 = 0, | |
| 281 | b: u32 = 0, | |
| 282 | }; | |
| 283 | const x = S{}; | |
| 284 | const y = S{}; | |
| 285 | const ptr = WL_CONTAINER_OF(&x.b, &y, "b"); | |
| 286 | try testing.expectEqual(&x, ptr); | |
| 287 | } | |
| 288 | ||
| 289 | const CAST_OR_CALL = helpers.CAST_OR_CALL; | |
| 290 | ||
| 291 | test "CAST_OR_CALL casting" { | |
| 292 | const arg: c_int = 1000; | |
| 293 | const casted = CAST_OR_CALL(u8, arg); | |
| 294 | try testing.expectEqual(cast(u8, arg), casted); | |
| 295 | ||
| 296 | const S = struct { | |
| 297 | x: u32 = 0, | |
| 298 | }; | |
| 299 | var s: S = .{}; | |
| 300 | const casted_ptr = CAST_OR_CALL(*u8, &s); | |
| 301 | try testing.expectEqual(cast(*u8, &s), casted_ptr); | |
| 302 | } | |
| 303 | ||
| 304 | test "CAST_OR_CALL calling" { | |
| 305 | const Helper = struct { | |
| 306 | var last_val: bool = false; | |
| 307 | fn returnsVoid(val: bool) void { | |
| 308 | last_val = val; | |
| 309 | } | |
| 310 | fn returnsBool(f: f32) bool { | |
| 311 | return f > 0; | |
| 312 | } | |
| 313 | fn identity(self: c_uint) c_uint { | |
| 314 | return self; | |
| 315 | } | |
| 316 | }; | |
| 317 | ||
| 318 | CAST_OR_CALL(Helper.returnsVoid, true); | |
| 319 | try testing.expectEqual(true, Helper.last_val); | |
| 320 | CAST_OR_CALL(Helper.returnsVoid, false); | |
| 321 | try testing.expectEqual(false, Helper.last_val); | |
| 322 | ||
| 323 | try testing.expectEqual(Helper.returnsBool(1), CAST_OR_CALL(Helper.returnsBool, @as(f32, 1))); | |
| 324 | try testing.expectEqual(Helper.returnsBool(-1), CAST_OR_CALL(Helper.returnsBool, @as(f32, -1))); | |
| 325 | ||
| 326 | try testing.expectEqual(Helper.identity(@as(c_uint, 100)), CAST_OR_CALL(Helper.identity, @as(c_uint, 100))); | |
| 327 | } |
lib/compiler/translate-c/src/main.zig deleted-251| ... | ... | @@ -1,251 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const mem = std.mem; | |
| 4 | const process = std.process; | |
| 5 | const aro = @import("aro"); | |
| 6 | const Translator = @import("Translator.zig"); | |
| 7 | ||
| 8 | const fast_exit = @import("builtin").mode != .Debug; | |
| 9 | ||
| 10 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; | |
| 11 | ||
| 12 | pub fn main() u8 { | |
| 13 | const gpa = general_purpose_allocator.allocator(); | |
| 14 | defer _ = general_purpose_allocator.deinit(); | |
| 15 | ||
| 16 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); | |
| 17 | defer arena_instance.deinit(); | |
| 18 | const arena = arena_instance.allocator(); | |
| 19 | ||
| 20 | const args = process.argsAlloc(arena) catch { | |
| 21 | std.debug.print("ran out of memory allocating arguments\n", .{}); | |
| 22 | if (fast_exit) process.exit(1); | |
| 23 | return 1; | |
| 24 | }; | |
| 25 | ||
| 26 | var stderr_buf: [1024]u8 = undefined; | |
| 27 | var stderr = std.fs.File.stderr().writer(&stderr_buf); | |
| 28 | var diagnostics: aro.Diagnostics = .{ | |
| 29 | .output = .{ .to_writer = .{ | |
| 30 | .color = .detect(stderr.file), | |
| 31 | .writer = &stderr.interface, | |
| 32 | } }, | |
| 33 | }; | |
| 34 | ||
| 35 | var comp = aro.Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |err| switch (err) { | |
| 36 | error.OutOfMemory => { | |
| 37 | std.debug.print("ran out of memory initializing C compilation\n", .{}); | |
| 38 | if (fast_exit) process.exit(1); | |
| 39 | return 1; | |
| 40 | }, | |
| 41 | }; | |
| 42 | defer comp.deinit(); | |
| 43 | ||
| 44 | const exe_name = std.fs.selfExePathAlloc(gpa) catch { | |
| 45 | std.debug.print("unable to find translate-c executable path\n", .{}); | |
| 46 | if (fast_exit) process.exit(1); | |
| 47 | return 1; | |
| 48 | }; | |
| 49 | defer gpa.free(exe_name); | |
| 50 | ||
| 51 | var driver: aro.Driver = .{ .comp = &comp, .diagnostics = &diagnostics, .aro_name = exe_name }; | |
| 52 | defer driver.deinit(); | |
| 53 | ||
| 54 | var toolchain: aro.Toolchain = .{ .driver = &driver, .filesystem = .{ .real = comp.cwd } }; | |
| 55 | defer toolchain.deinit(); | |
| 56 | ||
| 57 | translate(&driver, &toolchain, args) catch |err| switch (err) { | |
| 58 | error.OutOfMemory => { | |
| 59 | std.debug.print("ran out of memory translating\n", .{}); | |
| 60 | if (fast_exit) process.exit(1); | |
| 61 | return 1; | |
| 62 | }, | |
| 63 | error.FatalError => { | |
| 64 | if (fast_exit) process.exit(1); | |
| 65 | return 1; | |
| 66 | }, | |
| 67 | error.WriteFailed => { | |
| 68 | std.debug.print("unable to write to stdout\n", .{}); | |
| 69 | if (fast_exit) process.exit(1); | |
| 70 | return 1; | |
| 71 | }, | |
| 72 | }; | |
| 73 | if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0)); | |
| 74 | return @intFromBool(comp.diagnostics.errors != 0); | |
| 75 | } | |
| 76 | ||
| 77 | pub const usage = | |
| 78 | \\Usage {s}: [options] file [CC options] | |
| 79 | \\ | |
| 80 | \\Options: | |
| 81 | \\ --help Print this message | |
| 82 | \\ --version Print translate-c version | |
| 83 | \\ -fmodule-libs Import libraries as modules | |
| 84 | \\ -fno-module-libs (default) Install libraries next to output file | |
| 85 | \\ | |
| 86 | \\ | |
| 87 | ; | |
| 88 | ||
| 89 | fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void { | |
| 90 | const gpa = d.comp.gpa; | |
| 91 | ||
| 92 | var module_libs = false; | |
| 93 | ||
| 94 | const aro_args = args: { | |
| 95 | var i: usize = 0; | |
| 96 | for (args) |arg| { | |
| 97 | args[i] = arg; | |
| 98 | if (mem.eql(u8, arg, "--help")) { | |
| 99 | var stdout_buf: [512]u8 = undefined; | |
| 100 | var stdout = std.fs.File.stdout().writer(&stdout_buf); | |
| 101 | try stdout.interface.print(usage, .{args[0]}); | |
| 102 | try stdout.interface.flush(); | |
| 103 | return; | |
| 104 | } else if (mem.eql(u8, arg, "--version")) { | |
| 105 | var stdout_buf: [512]u8 = undefined; | |
| 106 | var stdout = std.fs.File.stdout().writer(&stdout_buf); | |
| 107 | // TODO add version | |
| 108 | try stdout.interface.writeAll("0.0.0-dev\n"); | |
| 109 | try stdout.interface.flush(); | |
| 110 | return; | |
| 111 | } else if (mem.eql(u8, arg, "-fmodule-libs")) { | |
| 112 | module_libs = true; | |
| 113 | } else if (mem.eql(u8, arg, "-fno-module-libs")) { | |
| 114 | module_libs = false; | |
| 115 | } else { | |
| 116 | i += 1; | |
| 117 | } | |
| 118 | } | |
| 119 | break :args args[0..i]; | |
| 120 | }; | |
| 121 | const user_macros = macros: { | |
| 122 | var macro_buf: std.ArrayListUnmanaged(u8) = .empty; | |
| 123 | defer macro_buf.deinit(gpa); | |
| 124 | ||
| 125 | try macro_buf.appendSlice(gpa, "#define __TRANSLATE_C__ 1\n"); | |
| 126 | ||
| 127 | var discard_buf: [256]u8 = undefined; | |
| 128 | var discarding: std.io.Writer.Discarding = .init(&discard_buf); | |
| 129 | assert(!try d.parseArgs(&discarding.writer, &macro_buf, aro_args)); | |
| 130 | if (macro_buf.items.len > std.math.maxInt(u32)) { | |
| 131 | return d.fatal("user provided macro source exceeded max size", .{}); | |
| 132 | } | |
| 133 | ||
| 134 | const content = try macro_buf.toOwnedSlice(gpa); | |
| 135 | errdefer gpa.free(content); | |
| 136 | ||
| 137 | break :macros try d.comp.addSourceFromOwnedBuffer("<command line>", content, .user); | |
| 138 | }; | |
| 139 | ||
| 140 | if (d.inputs.items.len != 1) { | |
| 141 | return d.fatal("expected exactly one input file", .{}); | |
| 142 | } | |
| 143 | const source = d.inputs.items[0]; | |
| 144 | ||
| 145 | tc.discover() catch |er| switch (er) { | |
| 146 | error.OutOfMemory => return error.OutOfMemory, | |
| 147 | error.TooManyMultilibs => return d.fatal("found more than one multilib with the same priority", .{}), | |
| 148 | }; | |
| 149 | tc.defineSystemIncludes() catch |er| switch (er) { | |
| 150 | error.OutOfMemory => return error.OutOfMemory, | |
| 151 | error.AroIncludeNotFound => return d.fatal("unable to find Aro builtin headers", .{}), | |
| 152 | }; | |
| 153 | ||
| 154 | const builtin_macros = d.comp.generateBuiltinMacros(.include_system_defines) catch |err| switch (err) { | |
| 155 | error.FileTooBig => return d.fatal("builtin macro source exceeded max size", .{}), | |
| 156 | else => |e| return e, | |
| 157 | }; | |
| 158 | ||
| 159 | var pp = try aro.Preprocessor.initDefault(d.comp); | |
| 160 | defer pp.deinit(); | |
| 161 | ||
| 162 | try pp.preprocessSources(&.{ source, builtin_macros, user_macros }); | |
| 163 | ||
| 164 | var c_tree = try pp.parse(); | |
| 165 | defer c_tree.deinit(); | |
| 166 | ||
| 167 | if (d.diagnostics.errors != 0) { | |
| 168 | if (fast_exit) process.exit(1); | |
| 169 | return error.FatalError; | |
| 170 | } | |
| 171 | ||
| 172 | const rendered_zig = try Translator.translate(.{ | |
| 173 | .gpa = gpa, | |
| 174 | .comp = d.comp, | |
| 175 | .pp = &pp, | |
| 176 | .tree = &c_tree, | |
| 177 | .module_libs = module_libs, | |
| 178 | }); | |
| 179 | defer gpa.free(rendered_zig); | |
| 180 | ||
| 181 | var close_out_file = false; | |
| 182 | var out_file_path: []const u8 = "<stdout>"; | |
| 183 | var out_file: std.fs.File = .stdout(); | |
| 184 | defer if (close_out_file) out_file.close(); | |
| 185 | ||
| 186 | if (d.output_name) |path| blk: { | |
| 187 | if (std.mem.eql(u8, path, "-")) break :blk; | |
| 188 | if (std.fs.path.dirname(path)) |dirname| { | |
| 189 | std.fs.cwd().makePath(dirname) catch |err| | |
| 190 | return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); | |
| 191 | } | |
| 192 | out_file = std.fs.cwd().createFile(path, .{}) catch |err| { | |
| 193 | return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) }); | |
| 194 | }; | |
| 195 | close_out_file = true; | |
| 196 | out_file_path = path; | |
| 197 | } | |
| 198 | ||
| 199 | var out_buf: [4096]u8 = undefined; | |
| 200 | var out_writer = out_file.writer(&out_buf); | |
| 201 | out_writer.interface.writeAll(rendered_zig) catch | |
| 202 | return d.fatal("failed to write result to '{s}': {s}", .{ out_file_path, aro.Driver.errorDescription(out_writer.err.?) }); | |
| 203 | ||
| 204 | if (!module_libs) { | |
| 205 | const dest_path = if (d.output_name) |path| std.fs.path.dirname(path) else null; | |
| 206 | installLibs(d, dest_path) catch |err| | |
| 207 | return d.fatal("failed to install library files: {s}", .{aro.Driver.errorDescription(err)}); | |
| 208 | } | |
| 209 | ||
| 210 | if (fast_exit) process.exit(0); | |
| 211 | } | |
| 212 | ||
| 213 | fn installLibs(d: *aro.Driver, dest_path: ?[]const u8) !void { | |
| 214 | const gpa = d.comp.gpa; | |
| 215 | const cwd = std.fs.cwd(); | |
| 216 | ||
| 217 | const self_exe_path = try std.fs.selfExePathAlloc(gpa); | |
| 218 | defer gpa.free(self_exe_path); | |
| 219 | ||
| 220 | var cur_dir: []const u8 = self_exe_path; | |
| 221 | while (std.fs.path.dirname(cur_dir)) |dirname| : (cur_dir = dirname) { | |
| 222 | var base_dir = cwd.openDir(dirname, .{}) catch continue; | |
| 223 | defer base_dir.close(); | |
| 224 | ||
| 225 | var lib_dir = base_dir.openDir("lib", .{}) catch continue; | |
| 226 | defer lib_dir.close(); | |
| 227 | ||
| 228 | lib_dir.access("c_builtins.zig", .{}) catch continue; | |
| 229 | ||
| 230 | { | |
| 231 | const install_path = try std.fs.path.join(gpa, &.{ dest_path orelse "", "c_builtins.zig" }); | |
| 232 | defer gpa.free(install_path); | |
| 233 | try lib_dir.copyFile("c_builtins.zig", cwd, install_path, .{}); | |
| 234 | } | |
| 235 | { | |
| 236 | const install_path = try std.fs.path.join(gpa, &.{ dest_path orelse "", "helpers.zig" }); | |
| 237 | defer gpa.free(install_path); | |
| 238 | try lib_dir.copyFile("helpers.zig", cwd, install_path, .{}); | |
| 239 | } | |
| 240 | return; | |
| 241 | } | |
| 242 | return error.FileNotFound; | |
| 243 | } | |
| 244 | ||
| 245 | comptime { | |
| 246 | if (@import("builtin").is_test) { | |
| 247 | _ = Translator; | |
| 248 | _ = @import("helpers.zig"); | |
| 249 | _ = @import("PatternList.zig"); | |
| 250 | } | |
| 251 | } |
lib/std/zig.zig+4-3| ... | ... | @@ -36,9 +36,10 @@ pub const ParsedCharLiteral = string_literal.ParsedCharLiteral; |
| 36 | 36 | pub const parseCharLiteral = string_literal.parseCharLiteral; |
| 37 | 37 | pub const parseNumberLiteral = number_literal.parseNumberLiteral; |
| 38 | 38 | |
| 39 | // Files needed by translate-c. | |
| 40 | pub const c_builtins = @import("zig/c_builtins.zig"); | |
| 41 | pub const c_translation = @import("zig/c_translation.zig"); | |
| 39 | pub const c_translation = struct { | |
| 40 | pub const builtins = @import("zig/c_translation/builtins.zig"); | |
| 41 | pub const helpers = @import("zig/c_translation/helpers.zig"); | |
| 42 | }; | |
| 42 | 43 | |
| 43 | 44 | pub const SrcHasher = std.crypto.hash.Blake3; |
| 44 | 45 | pub const SrcHash = [16]u8; |
lib/std/zig/c_builtins.zig deleted-268| ... | ... | @@ -1,268 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | pub inline fn __builtin_bswap16(val: u16) u16 { | |
| 4 | return @byteSwap(val); | |
| 5 | } | |
| 6 | pub inline fn __builtin_bswap32(val: u32) u32 { | |
| 7 | return @byteSwap(val); | |
| 8 | } | |
| 9 | pub inline fn __builtin_bswap64(val: u64) u64 { | |
| 10 | return @byteSwap(val); | |
| 11 | } | |
| 12 | ||
| 13 | pub inline fn __builtin_signbit(val: f64) c_int { | |
| 14 | return @intFromBool(std.math.signbit(val)); | |
| 15 | } | |
| 16 | pub inline fn __builtin_signbitf(val: f32) c_int { | |
| 17 | return @intFromBool(std.math.signbit(val)); | |
| 18 | } | |
| 19 | ||
| 20 | pub inline fn __builtin_popcount(val: c_uint) c_int { | |
| 21 | // popcount of a c_uint will never exceed the capacity of a c_int | |
| 22 | @setRuntimeSafety(false); | |
| 23 | return @as(c_int, @bitCast(@as(c_uint, @popCount(val)))); | |
| 24 | } | |
| 25 | pub inline fn __builtin_ctz(val: c_uint) c_int { | |
| 26 | // Returns the number of trailing 0-bits in val, starting at the least significant bit position. | |
| 27 | // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint | |
| 28 | @setRuntimeSafety(false); | |
| 29 | return @as(c_int, @bitCast(@as(c_uint, @ctz(val)))); | |
| 30 | } | |
| 31 | pub inline fn __builtin_clz(val: c_uint) c_int { | |
| 32 | // Returns the number of leading 0-bits in x, starting at the most significant bit position. | |
| 33 | // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint | |
| 34 | @setRuntimeSafety(false); | |
| 35 | return @as(c_int, @bitCast(@as(c_uint, @clz(val)))); | |
| 36 | } | |
| 37 | ||
| 38 | pub inline fn __builtin_sqrt(val: f64) f64 { | |
| 39 | return @sqrt(val); | |
| 40 | } | |
| 41 | pub inline fn __builtin_sqrtf(val: f32) f32 { | |
| 42 | return @sqrt(val); | |
| 43 | } | |
| 44 | ||
| 45 | pub inline fn __builtin_sin(val: f64) f64 { | |
| 46 | return @sin(val); | |
| 47 | } | |
| 48 | pub inline fn __builtin_sinf(val: f32) f32 { | |
| 49 | return @sin(val); | |
| 50 | } | |
| 51 | pub inline fn __builtin_cos(val: f64) f64 { | |
| 52 | return @cos(val); | |
| 53 | } | |
| 54 | pub inline fn __builtin_cosf(val: f32) f32 { | |
| 55 | return @cos(val); | |
| 56 | } | |
| 57 | ||
| 58 | pub inline fn __builtin_exp(val: f64) f64 { | |
| 59 | return @exp(val); | |
| 60 | } | |
| 61 | pub inline fn __builtin_expf(val: f32) f32 { | |
| 62 | return @exp(val); | |
| 63 | } | |
| 64 | pub inline fn __builtin_exp2(val: f64) f64 { | |
| 65 | return @exp2(val); | |
| 66 | } | |
| 67 | pub inline fn __builtin_exp2f(val: f32) f32 { | |
| 68 | return @exp2(val); | |
| 69 | } | |
| 70 | pub inline fn __builtin_log(val: f64) f64 { | |
| 71 | return @log(val); | |
| 72 | } | |
| 73 | pub inline fn __builtin_logf(val: f32) f32 { | |
| 74 | return @log(val); | |
| 75 | } | |
| 76 | pub inline fn __builtin_log2(val: f64) f64 { | |
| 77 | return @log2(val); | |
| 78 | } | |
| 79 | pub inline fn __builtin_log2f(val: f32) f32 { | |
| 80 | return @log2(val); | |
| 81 | } | |
| 82 | pub inline fn __builtin_log10(val: f64) f64 { | |
| 83 | return @log10(val); | |
| 84 | } | |
| 85 | pub inline fn __builtin_log10f(val: f32) f32 { | |
| 86 | return @log10(val); | |
| 87 | } | |
| 88 | ||
| 89 | // Standard C Library bug: The absolute value of the most negative integer remains negative. | |
| 90 | pub inline fn __builtin_abs(val: c_int) c_int { | |
| 91 | return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); | |
| 92 | } | |
| 93 | pub inline fn __builtin_labs(val: c_long) c_long { | |
| 94 | return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val)); | |
| 95 | } | |
| 96 | pub inline fn __builtin_llabs(val: c_longlong) c_longlong { | |
| 97 | return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val)); | |
| 98 | } | |
| 99 | pub inline fn __builtin_fabs(val: f64) f64 { | |
| 100 | return @abs(val); | |
| 101 | } | |
| 102 | pub inline fn __builtin_fabsf(val: f32) f32 { | |
| 103 | return @abs(val); | |
| 104 | } | |
| 105 | ||
| 106 | pub inline fn __builtin_floor(val: f64) f64 { | |
| 107 | return @floor(val); | |
| 108 | } | |
| 109 | pub inline fn __builtin_floorf(val: f32) f32 { | |
| 110 | return @floor(val); | |
| 111 | } | |
| 112 | pub inline fn __builtin_ceil(val: f64) f64 { | |
| 113 | return @ceil(val); | |
| 114 | } | |
| 115 | pub inline fn __builtin_ceilf(val: f32) f32 { | |
| 116 | return @ceil(val); | |
| 117 | } | |
| 118 | pub inline fn __builtin_trunc(val: f64) f64 { | |
| 119 | return @trunc(val); | |
| 120 | } | |
| 121 | pub inline fn __builtin_truncf(val: f32) f32 { | |
| 122 | return @trunc(val); | |
| 123 | } | |
| 124 | pub inline fn __builtin_round(val: f64) f64 { | |
| 125 | return @round(val); | |
| 126 | } | |
| 127 | pub inline fn __builtin_roundf(val: f32) f32 { | |
| 128 | return @round(val); | |
| 129 | } | |
| 130 | ||
| 131 | pub inline fn __builtin_strlen(s: [*c]const u8) usize { | |
| 132 | return std.mem.sliceTo(s, 0).len; | |
| 133 | } | |
| 134 | pub inline fn __builtin_strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int { | |
| 135 | return switch (std.mem.orderZ(u8, s1, s2)) { | |
| 136 | .lt => -1, | |
| 137 | .eq => 0, | |
| 138 | .gt => 1, | |
| 139 | }; | |
| 140 | } | |
| 141 | ||
| 142 | pub inline fn __builtin_object_size(ptr: ?*const anyopaque, ty: c_int) usize { | |
| 143 | _ = ptr; | |
| 144 | // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html | |
| 145 | // If it is not possible to determine which objects ptr points to at compile time, | |
| 146 | // __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 | |
| 147 | // for type 2 or 3. | |
| 148 | if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1))); | |
| 149 | if (ty == 2 or ty == 3) return 0; | |
| 150 | unreachable; | |
| 151 | } | |
| 152 | ||
| 153 | pub inline fn __builtin___memset_chk( | |
| 154 | dst: ?*anyopaque, | |
| 155 | val: c_int, | |
| 156 | len: usize, | |
| 157 | remaining: usize, | |
| 158 | ) ?*anyopaque { | |
| 159 | if (len > remaining) @panic("std.c.builtins.memset_chk called with len > remaining"); | |
| 160 | return __builtin_memset(dst, val, len); | |
| 161 | } | |
| 162 | ||
| 163 | pub inline fn __builtin_memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { | |
| 164 | const dst_cast = @as([*c]u8, @ptrCast(dst)); | |
| 165 | @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); | |
| 166 | return dst; | |
| 167 | } | |
| 168 | ||
| 169 | pub inline fn __builtin___memcpy_chk( | |
| 170 | noalias dst: ?*anyopaque, | |
| 171 | noalias src: ?*const anyopaque, | |
| 172 | len: usize, | |
| 173 | remaining: usize, | |
| 174 | ) ?*anyopaque { | |
| 175 | if (len > remaining) @panic("std.c.builtins.memcpy_chk called with len > remaining"); | |
| 176 | return __builtin_memcpy(dst, src, len); | |
| 177 | } | |
| 178 | ||
| 179 | pub inline fn __builtin_memcpy( | |
| 180 | noalias dst: ?*anyopaque, | |
| 181 | noalias src: ?*const anyopaque, | |
| 182 | len: usize, | |
| 183 | ) ?*anyopaque { | |
| 184 | if (len > 0) @memcpy( | |
| 185 | @as([*]u8, @ptrCast(dst.?))[0..len], | |
| 186 | @as([*]const u8, @ptrCast(src.?)), | |
| 187 | ); | |
| 188 | return dst; | |
| 189 | } | |
| 190 | ||
| 191 | /// The return value of __builtin_expect is `expr`. `c` is the expected value | |
| 192 | /// of `expr` and is used as a hint to the compiler in C. Here it is unused. | |
| 193 | pub inline fn __builtin_expect(expr: c_long, c: c_long) c_long { | |
| 194 | _ = c; | |
| 195 | return expr; | |
| 196 | } | |
| 197 | ||
| 198 | /// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an | |
| 199 | /// implementation-defined way. | |
| 200 | /// This implementation is based on the description for __builtin_nan provided in the GCC docs at | |
| 201 | /// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan | |
| 202 | /// Comment is reproduced below: | |
| 203 | /// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description | |
| 204 | /// of the parsing is in order. | |
| 205 | /// The string is parsed as by strtol; that is, the base is recognized by leading ‘0’ or ‘0x’ prefixes. | |
| 206 | /// The number parsed is placed in the significand such that the least significant bit of the number is | |
| 207 | /// at the least significant bit of the significand. | |
| 208 | /// The number is truncated to fit the significand field provided. | |
| 209 | /// The significand is forced to be a quiet NaN. | |
| 210 | /// | |
| 211 | /// If tagp contains any non-numeric characters, the function returns a NaN whose significand is zero. | |
| 212 | /// If tagp is empty, the function returns a NaN whose significand is zero. | |
| 213 | pub inline fn __builtin_nanf(tagp: []const u8) f32 { | |
| 214 | const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0; | |
| 215 | const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits | |
| 216 | return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32)))); | |
| 217 | } | |
| 218 | ||
| 219 | pub inline fn __builtin_huge_valf() f32 { | |
| 220 | return std.math.inf(f32); | |
| 221 | } | |
| 222 | ||
| 223 | pub inline fn __builtin_inff() f32 { | |
| 224 | return std.math.inf(f32); | |
| 225 | } | |
| 226 | ||
| 227 | pub inline fn __builtin_isnan(x: anytype) c_int { | |
| 228 | return @intFromBool(std.math.isNan(x)); | |
| 229 | } | |
| 230 | ||
| 231 | pub inline fn __builtin_isinf(x: anytype) c_int { | |
| 232 | return @intFromBool(std.math.isInf(x)); | |
| 233 | } | |
| 234 | ||
| 235 | /// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf. | |
| 236 | pub inline fn __builtin_isinf_sign(x: anytype) c_int { | |
| 237 | if (!std.math.isInf(x)) return 0; | |
| 238 | return if (std.math.isPositiveInf(x)) 1 else -1; | |
| 239 | } | |
| 240 | ||
| 241 | pub inline fn __has_builtin(func: anytype) c_int { | |
| 242 | _ = func; | |
| 243 | return @intFromBool(true); | |
| 244 | } | |
| 245 | ||
| 246 | pub inline fn __builtin_assume(cond: bool) void { | |
| 247 | if (!cond) unreachable; | |
| 248 | } | |
| 249 | ||
| 250 | pub inline fn __builtin_unreachable() noreturn { | |
| 251 | unreachable; | |
| 252 | } | |
| 253 | ||
| 254 | pub inline fn __builtin_constant_p(expr: anytype) c_int { | |
| 255 | _ = expr; | |
| 256 | return @intFromBool(false); | |
| 257 | } | |
| 258 | pub fn __builtin_mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int { | |
| 259 | const res = @mulWithOverflow(a, b); | |
| 260 | result.* = res[0]; | |
| 261 | return res[1]; | |
| 262 | } | |
| 263 | ||
| 264 | // __builtin_alloca_with_align is not currently implemented. | |
| 265 | // It is used in a run-translated-c test and a test-translate-c test to ensure that non-implemented | |
| 266 | // builtins are correctly demoted. If you implement __builtin_alloca_with_align, please update the | |
| 267 | // run-translated-c test and the test-translate-c test to use a different non-implemented builtin. | |
| 268 | // pub inline fn __builtin_alloca_with_align(size: usize, alignment: usize) *anyopaque {} |
lib/std/zig/c_translation.zig deleted-699| ... | ... | @@ -1,699 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const testing = std.testing; | |
| 4 | const math = std.math; | |
| 5 | const mem = std.mem; | |
| 6 | ||
| 7 | /// Given a type and value, cast the value to the type as c would. | |
| 8 | pub fn cast(comptime DestType: type, target: anytype) DestType { | |
| 9 | // this function should behave like transCCast in translate-c, except it's for macros | |
| 10 | const SourceType = @TypeOf(target); | |
| 11 | switch (@typeInfo(DestType)) { | |
| 12 | .@"fn" => return castToPtr(*const DestType, SourceType, target), | |
| 13 | .pointer => return castToPtr(DestType, SourceType, target), | |
| 14 | .optional => |dest_opt| { | |
| 15 | if (@typeInfo(dest_opt.child) == .pointer) { | |
| 16 | return castToPtr(DestType, SourceType, target); | |
| 17 | } else if (@typeInfo(dest_opt.child) == .@"fn") { | |
| 18 | return castToPtr(?*const dest_opt.child, SourceType, target); | |
| 19 | } | |
| 20 | }, | |
| 21 | .int => { | |
| 22 | switch (@typeInfo(SourceType)) { | |
| 23 | .pointer => { | |
| 24 | return castInt(DestType, @intFromPtr(target)); | |
| 25 | }, | |
| 26 | .optional => |opt| { | |
| 27 | if (@typeInfo(opt.child) == .pointer) { | |
| 28 | return castInt(DestType, @intFromPtr(target)); | |
| 29 | } | |
| 30 | }, | |
| 31 | .int => { | |
| 32 | return castInt(DestType, target); | |
| 33 | }, | |
| 34 | .@"fn" => { | |
| 35 | return castInt(DestType, @intFromPtr(&target)); | |
| 36 | }, | |
| 37 | .bool => { | |
| 38 | return @intFromBool(target); | |
| 39 | }, | |
| 40 | else => {}, | |
| 41 | } | |
| 42 | }, | |
| 43 | .float => { | |
| 44 | switch (@typeInfo(SourceType)) { | |
| 45 | .int => return @as(DestType, @floatFromInt(target)), | |
| 46 | .float => return @as(DestType, @floatCast(target)), | |
| 47 | .bool => return @as(DestType, @floatFromInt(@intFromBool(target))), | |
| 48 | else => {}, | |
| 49 | } | |
| 50 | }, | |
| 51 | .@"union" => |info| { | |
| 52 | inline for (info.fields) |field| { | |
| 53 | if (field.type == SourceType) return @unionInit(DestType, field.name, target); | |
| 54 | } | |
| 55 | @compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union"); | |
| 56 | }, | |
| 57 | .bool => return cast(usize, target) != 0, | |
| 58 | else => {}, | |
| 59 | } | |
| 60 | return @as(DestType, target); | |
| 61 | } | |
| 62 | ||
| 63 | fn castInt(comptime DestType: type, target: anytype) DestType { | |
| 64 | const dest = @typeInfo(DestType).int; | |
| 65 | const source = @typeInfo(@TypeOf(target)).int; | |
| 66 | ||
| 67 | if (dest.bits < source.bits) | |
| 68 | return @as(DestType, @bitCast(@as(std.meta.Int(source.signedness, dest.bits), @truncate(target)))) | |
| 69 | else | |
| 70 | return @as(DestType, @bitCast(@as(std.meta.Int(source.signedness, dest.bits), target))); | |
| 71 | } | |
| 72 | ||
| 73 | fn castPtr(comptime DestType: type, target: anytype) DestType { | |
| 74 | return @ptrCast(@alignCast(@constCast(@volatileCast(target)))); | |
| 75 | } | |
| 76 | ||
| 77 | fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType { | |
| 78 | switch (@typeInfo(SourceType)) { | |
| 79 | .int => { | |
| 80 | return @as(DestType, @ptrFromInt(castInt(usize, target))); | |
| 81 | }, | |
| 82 | .comptime_int => { | |
| 83 | if (target < 0) | |
| 84 | return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target)))))) | |
| 85 | else | |
| 86 | return @as(DestType, @ptrFromInt(@as(usize, @intCast(target)))); | |
| 87 | }, | |
| 88 | .pointer => { | |
| 89 | return castPtr(DestType, target); | |
| 90 | }, | |
| 91 | .@"fn" => { | |
| 92 | return castPtr(DestType, &target); | |
| 93 | }, | |
| 94 | .optional => |target_opt| { | |
| 95 | if (@typeInfo(target_opt.child) == .pointer) { | |
| 96 | return castPtr(DestType, target); | |
| 97 | } | |
| 98 | }, | |
| 99 | else => {}, | |
| 100 | } | |
| 101 | return @as(DestType, target); | |
| 102 | } | |
| 103 | ||
| 104 | fn ptrInfo(comptime PtrType: type) std.builtin.Type.Pointer { | |
| 105 | return switch (@typeInfo(PtrType)) { | |
| 106 | .optional => |opt_info| @typeInfo(opt_info.child).pointer, | |
| 107 | .pointer => |ptr_info| ptr_info, | |
| 108 | else => unreachable, | |
| 109 | }; | |
| 110 | } | |
| 111 | ||
| 112 | test "cast" { | |
| 113 | var i = @as(i64, 10); | |
| 114 | ||
| 115 | try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16))); | |
| 116 | try testing.expect(cast(*u64, &i).* == @as(u64, 10)); | |
| 117 | try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i); | |
| 118 | ||
| 119 | try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2))); | |
| 120 | try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i); | |
| 121 | try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i); | |
| 122 | ||
| 123 | try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4)))); | |
| 124 | try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4)))); | |
| 125 | try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); | |
| 126 | ||
| 127 | try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000))); | |
| 128 | ||
| 129 | try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2)))); | |
| 130 | try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2)))); | |
| 131 | ||
| 132 | try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2)))); | |
| 133 | ||
| 134 | var foo: c_int = -1; | |
| 135 | _ = &foo; | |
| 136 | try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 137 | try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 138 | try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 139 | try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 140 | ||
| 141 | const FnPtr = ?*align(1) const fn (*anyopaque) void; | |
| 142 | try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0)))); | |
| 143 | try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); | |
| 144 | } | |
| 145 | ||
| 146 | /// Given a value returns its size as C's sizeof operator would. | |
| 147 | pub fn sizeof(target: anytype) usize { | |
| 148 | const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); | |
| 149 | switch (@typeInfo(T)) { | |
| 150 | .float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T), | |
| 151 | .@"fn" => { | |
| 152 | // sizeof(main) in C returns 1 | |
| 153 | return 1; | |
| 154 | }, | |
| 155 | .null => return @sizeOf(*anyopaque), | |
| 156 | .void => { | |
| 157 | // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. | |
| 158 | return 1; | |
| 159 | }, | |
| 160 | .@"opaque" => { | |
| 161 | if (T == anyopaque) { | |
| 162 | // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. | |
| 163 | return 1; | |
| 164 | } else { | |
| 165 | @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); | |
| 166 | } | |
| 167 | }, | |
| 168 | .optional => |opt| { | |
| 169 | if (@typeInfo(opt.child) == .pointer) { | |
| 170 | return sizeof(opt.child); | |
| 171 | } else { | |
| 172 | @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); | |
| 173 | } | |
| 174 | }, | |
| 175 | .pointer => |ptr| { | |
| 176 | if (ptr.size == .slice) { | |
| 177 | @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); | |
| 178 | } | |
| 179 | // for strings, sizeof("a") returns 2. | |
| 180 | // normal pointer decay scenarios from C are handled | |
| 181 | // in the .array case above, but strings remain literals | |
| 182 | // and are therefore always pointers, so they need to be | |
| 183 | // specially handled here. | |
| 184 | if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) { | |
| 185 | const array_info = @typeInfo(ptr.child).array; | |
| 186 | if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) { | |
| 187 | // length of the string plus one for the null terminator. | |
| 188 | return (array_info.len + 1) * @sizeOf(array_info.child); | |
| 189 | } | |
| 190 | } | |
| 191 | // When zero sized pointers are removed, this case will no | |
| 192 | // longer be reachable and can be deleted. | |
| 193 | if (@sizeOf(T) == 0) { | |
| 194 | return @sizeOf(*anyopaque); | |
| 195 | } | |
| 196 | return @sizeOf(T); | |
| 197 | }, | |
| 198 | .comptime_float => return @sizeOf(f64), // TODO c_double #3999 | |
| 199 | .comptime_int => { | |
| 200 | // TODO to get the correct result we have to translate | |
| 201 | // `1073741824 * 4` as `int(1073741824) *% int(4)` since | |
| 202 | // sizeof(1073741824 * 4) != sizeof(4294967296). | |
| 203 | ||
| 204 | // TODO test if target fits in int, long or long long | |
| 205 | return @sizeOf(c_int); | |
| 206 | }, | |
| 207 | else => @compileError("std.meta.sizeof does not support type " ++ @typeName(T)), | |
| 208 | } | |
| 209 | } | |
| 210 | ||
| 211 | test "sizeof" { | |
| 212 | const S = extern struct { a: u32 }; | |
| 213 | ||
| 214 | const ptr_size = @sizeOf(*anyopaque); | |
| 215 | ||
| 216 | try testing.expect(sizeof(u32) == 4); | |
| 217 | try testing.expect(sizeof(@as(u32, 2)) == 4); | |
| 218 | try testing.expect(sizeof(2) == @sizeOf(c_int)); | |
| 219 | ||
| 220 | try testing.expect(sizeof(2.0) == @sizeOf(f64)); | |
| 221 | ||
| 222 | try testing.expect(sizeof(S) == 4); | |
| 223 | ||
| 224 | try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12); | |
| 225 | try testing.expect(sizeof([3]u32) == 12); | |
| 226 | try testing.expect(sizeof([3:0]u32) == 16); | |
| 227 | try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size); | |
| 228 | ||
| 229 | try testing.expect(sizeof(*u32) == ptr_size); | |
| 230 | try testing.expect(sizeof([*]u32) == ptr_size); | |
| 231 | try testing.expect(sizeof([*c]u32) == ptr_size); | |
| 232 | try testing.expect(sizeof(?*u32) == ptr_size); | |
| 233 | try testing.expect(sizeof(?[*]u32) == ptr_size); | |
| 234 | try testing.expect(sizeof(*anyopaque) == ptr_size); | |
| 235 | try testing.expect(sizeof(*void) == ptr_size); | |
| 236 | try testing.expect(sizeof(null) == ptr_size); | |
| 237 | ||
| 238 | try testing.expect(sizeof("foobar") == 7); | |
| 239 | try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14); | |
| 240 | try testing.expect(sizeof(*const [4:0]u8) == 5); | |
| 241 | try testing.expect(sizeof(*[4:0]u8) == ptr_size); | |
| 242 | try testing.expect(sizeof([*]const [4:0]u8) == ptr_size); | |
| 243 | try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size); | |
| 244 | try testing.expect(sizeof(*const [4]u8) == ptr_size); | |
| 245 | ||
| 246 | if (false) { // TODO | |
| 247 | try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof))); | |
| 248 | try testing.expect(sizeof(sizeof) == 1); | |
| 249 | } | |
| 250 | ||
| 251 | try testing.expect(sizeof(void) == 1); | |
| 252 | try testing.expect(sizeof(anyopaque) == 1); | |
| 253 | } | |
| 254 | ||
| 255 | pub const CIntLiteralBase = enum { decimal, octal, hex }; | |
| 256 | ||
| 257 | fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type { | |
| 258 | const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong }; | |
| 259 | const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; | |
| 260 | const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; | |
| 261 | ||
| 262 | const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned) | |
| 263 | &unsigned | |
| 264 | else if (base == .decimal) | |
| 265 | &signed_decimal | |
| 266 | else | |
| 267 | &signed_oct_hex; | |
| 268 | ||
| 269 | var pos = mem.indexOfScalar(type, list, SuffixType).?; | |
| 270 | ||
| 271 | while (pos < list.len) : (pos += 1) { | |
| 272 | if (number >= math.minInt(list[pos]) and number <= math.maxInt(list[pos])) { | |
| 273 | return list[pos]; | |
| 274 | } | |
| 275 | } | |
| 276 | @compileError("Integer literal is too large"); | |
| 277 | } | |
| 278 | ||
| 279 | /// Promote the type of an integer literal until it fits as C would. | |
| 280 | pub fn promoteIntLiteral( | |
| 281 | comptime SuffixType: type, | |
| 282 | comptime number: comptime_int, | |
| 283 | comptime base: CIntLiteralBase, | |
| 284 | ) PromoteIntLiteralReturnType(SuffixType, number, base) { | |
| 285 | return number; | |
| 286 | } | |
| 287 | ||
| 288 | test "promoteIntLiteral" { | |
| 289 | const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex); | |
| 290 | try testing.expectEqual(c_uint, @TypeOf(signed_hex)); | |
| 291 | ||
| 292 | if (math.maxInt(c_longlong) == math.maxInt(c_int)) return; | |
| 293 | ||
| 294 | const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal); | |
| 295 | const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex); | |
| 296 | ||
| 297 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 298 | try testing.expectEqual(c_long, @TypeOf(signed_decimal)); | |
| 299 | try testing.expectEqual(c_ulong, @TypeOf(unsigned)); | |
| 300 | } else { | |
| 301 | try testing.expectEqual(c_longlong, @TypeOf(signed_decimal)); | |
| 302 | try testing.expectEqual(c_ulonglong, @TypeOf(unsigned)); | |
| 303 | } | |
| 304 | } | |
| 305 | ||
| 306 | /// Convert from clang __builtin_shufflevector index to Zig @shuffle index | |
| 307 | /// clang requires __builtin_shufflevector index arguments to be integer constants. | |
| 308 | /// negative values for `this_index` indicate "don't care". | |
| 309 | /// clang enforces that `this_index` is less than the total number of vector elements | |
| 310 | /// See https://ziglang.org/documentation/master/#shuffle | |
| 311 | /// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector | |
| 312 | pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 { | |
| 313 | const positive_index = std.math.cast(usize, this_index) orelse return undefined; | |
| 314 | if (positive_index < source_vector_len) return @as(i32, @intCast(this_index)); | |
| 315 | const b_index = positive_index - source_vector_len; | |
| 316 | return ~@as(i32, @intCast(b_index)); | |
| 317 | } | |
| 318 | ||
| 319 | test "shuffleVectorIndex" { | |
| 320 | const vector_len: usize = 4; | |
| 321 | ||
| 322 | _ = shuffleVectorIndex(-1, vector_len); | |
| 323 | ||
| 324 | try testing.expect(shuffleVectorIndex(0, vector_len) == 0); | |
| 325 | try testing.expect(shuffleVectorIndex(1, vector_len) == 1); | |
| 326 | try testing.expect(shuffleVectorIndex(2, vector_len) == 2); | |
| 327 | try testing.expect(shuffleVectorIndex(3, vector_len) == 3); | |
| 328 | ||
| 329 | try testing.expect(shuffleVectorIndex(4, vector_len) == -1); | |
| 330 | try testing.expect(shuffleVectorIndex(5, vector_len) == -2); | |
| 331 | try testing.expect(shuffleVectorIndex(6, vector_len) == -3); | |
| 332 | try testing.expect(shuffleVectorIndex(7, vector_len) == -4); | |
| 333 | } | |
| 334 | ||
| 335 | /// Constructs a [*c] pointer with the const and volatile annotations | |
| 336 | /// from SelfType for pointing to a C flexible array of ElementType. | |
| 337 | pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type { | |
| 338 | switch (@typeInfo(SelfType)) { | |
| 339 | .pointer => |ptr| { | |
| 340 | return @Type(.{ .pointer = .{ | |
| 341 | .size = .c, | |
| 342 | .is_const = ptr.is_const, | |
| 343 | .is_volatile = ptr.is_volatile, | |
| 344 | .alignment = @alignOf(ElementType), | |
| 345 | .address_space = .generic, | |
| 346 | .child = ElementType, | |
| 347 | .is_allowzero = true, | |
| 348 | .sentinel_ptr = null, | |
| 349 | } }); | |
| 350 | }, | |
| 351 | else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)), | |
| 352 | } | |
| 353 | } | |
| 354 | ||
| 355 | test "Flexible Array Type" { | |
| 356 | const Container = extern struct { | |
| 357 | size: usize, | |
| 358 | }; | |
| 359 | ||
| 360 | try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int); | |
| 361 | try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int); | |
| 362 | try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int); | |
| 363 | try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int); | |
| 364 | } | |
| 365 | ||
| 366 | /// C `%` operator for signed integers | |
| 367 | /// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a" | |
| 368 | /// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for | |
| 369 | /// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety | |
| 370 | /// checked undefined behavior | |
| 371 | pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) { | |
| 372 | std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed); | |
| 373 | if (denominator > 0) return @rem(numerator, denominator); | |
| 374 | return numerator - @divTrunc(numerator, denominator) * denominator; | |
| 375 | } | |
| 376 | ||
| 377 | pub const Macros = struct { | |
| 378 | pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { | |
| 379 | return promoteIntLiteral(c_uint, n, .decimal); | |
| 380 | } | |
| 381 | ||
| 382 | fn L_SUFFIX_ReturnType(comptime number: anytype) type { | |
| 383 | switch (@typeInfo(@TypeOf(number))) { | |
| 384 | .int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), | |
| 385 | .float, .comptime_float => return c_longdouble, | |
| 386 | else => @compileError("Invalid value for L suffix"), | |
| 387 | } | |
| 388 | } | |
| 389 | pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { | |
| 390 | switch (@typeInfo(@TypeOf(number))) { | |
| 391 | .int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal), | |
| 392 | .float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), | |
| 393 | else => @compileError("Invalid value for L suffix"), | |
| 394 | } | |
| 395 | } | |
| 396 | ||
| 397 | pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { | |
| 398 | return promoteIntLiteral(c_ulong, n, .decimal); | |
| 399 | } | |
| 400 | ||
| 401 | pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { | |
| 402 | return promoteIntLiteral(c_longlong, n, .decimal); | |
| 403 | } | |
| 404 | ||
| 405 | pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { | |
| 406 | return promoteIntLiteral(c_ulonglong, n, .decimal); | |
| 407 | } | |
| 408 | ||
| 409 | pub fn F_SUFFIX(comptime f: comptime_float) f32 { | |
| 410 | return @as(f32, f); | |
| 411 | } | |
| 412 | ||
| 413 | pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { | |
| 414 | return @fieldParentPtr(member, ptr); | |
| 415 | } | |
| 416 | ||
| 417 | /// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) | |
| 418 | /// could be either: cast B to A, or call A with the value B. | |
| 419 | pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { | |
| 420 | .type => a, | |
| 421 | .@"fn" => |fn_info| fn_info.return_type orelse void, | |
| 422 | else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), | |
| 423 | } { | |
| 424 | switch (@typeInfo(@TypeOf(a))) { | |
| 425 | .type => return cast(a, b), | |
| 426 | .@"fn" => return a(b), | |
| 427 | else => unreachable, // return type will be a compile error otherwise | |
| 428 | } | |
| 429 | } | |
| 430 | ||
| 431 | pub inline fn DISCARD(x: anytype) void { | |
| 432 | _ = x; | |
| 433 | } | |
| 434 | }; | |
| 435 | ||
| 436 | /// Integer promotion described in C11 6.3.1.1.2 | |
| 437 | fn PromotedIntType(comptime T: type) type { | |
| 438 | return switch (T) { | |
| 439 | bool, c_short => c_int, | |
| 440 | c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int, | |
| 441 | c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T, | |
| 442 | else => switch (@typeInfo(T)) { | |
| 443 | .comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"), | |
| 444 | // promote to c_int if it can represent all values of T | |
| 445 | .int => |int_info| if (int_info.bits < @bitSizeOf(c_int)) | |
| 446 | c_int | |
| 447 | // otherwise, restore the original C type | |
| 448 | else if (int_info.bits == @bitSizeOf(c_int)) | |
| 449 | if (int_info.signedness == .unsigned) c_uint else c_int | |
| 450 | else if (int_info.bits <= @bitSizeOf(c_long)) | |
| 451 | if (int_info.signedness == .unsigned) c_ulong else c_long | |
| 452 | else if (int_info.bits <= @bitSizeOf(c_longlong)) | |
| 453 | if (int_info.signedness == .unsigned) c_ulonglong else c_longlong | |
| 454 | else | |
| 455 | @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required"), | |
| 456 | else => @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`"), | |
| 457 | }, | |
| 458 | }; | |
| 459 | } | |
| 460 | ||
| 461 | /// C11 6.3.1.1.1 | |
| 462 | fn integerRank(comptime T: type) u8 { | |
| 463 | return switch (T) { | |
| 464 | bool => 0, | |
| 465 | u8, i8 => 1, | |
| 466 | c_short, c_ushort => 2, | |
| 467 | c_int, c_uint => 3, | |
| 468 | c_long, c_ulong => 4, | |
| 469 | c_longlong, c_ulonglong => 5, | |
| 470 | else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"), | |
| 471 | }; | |
| 472 | } | |
| 473 | ||
| 474 | fn ToUnsigned(comptime T: type) type { | |
| 475 | return switch (T) { | |
| 476 | c_int => c_uint, | |
| 477 | c_long => c_ulong, | |
| 478 | c_longlong => c_ulonglong, | |
| 479 | else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"), | |
| 480 | }; | |
| 481 | } | |
| 482 | ||
| 483 | /// "Usual arithmetic conversions" from C11 standard 6.3.1.8 | |
| 484 | fn ArithmeticConversion(comptime A: type, comptime B: type) type { | |
| 485 | if (A == c_longdouble or B == c_longdouble) return c_longdouble; | |
| 486 | if (A == f80 or B == f80) return f80; | |
| 487 | if (A == f64 or B == f64) return f64; | |
| 488 | if (A == f32 or B == f32) return f32; | |
| 489 | ||
| 490 | const A_Promoted = PromotedIntType(A); | |
| 491 | const B_Promoted = PromotedIntType(B); | |
| 492 | comptime { | |
| 493 | std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int)); | |
| 494 | std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int)); | |
| 495 | } | |
| 496 | ||
| 497 | if (A_Promoted == B_Promoted) return A_Promoted; | |
| 498 | ||
| 499 | const a_signed = @typeInfo(A_Promoted).int.signedness == .signed; | |
| 500 | const b_signed = @typeInfo(B_Promoted).int.signedness == .signed; | |
| 501 | ||
| 502 | if (a_signed == b_signed) { | |
| 503 | return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted; | |
| 504 | } | |
| 505 | ||
| 506 | const SignedType = if (a_signed) A_Promoted else B_Promoted; | |
| 507 | const UnsignedType = if (!a_signed) A_Promoted else B_Promoted; | |
| 508 | ||
| 509 | if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType; | |
| 510 | ||
| 511 | if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType; | |
| 512 | ||
| 513 | return ToUnsigned(SignedType); | |
| 514 | } | |
| 515 | ||
| 516 | test "ArithmeticConversion" { | |
| 517 | // Promotions not necessarily the same for other platforms | |
| 518 | if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest; | |
| 519 | ||
| 520 | const Test = struct { | |
| 521 | /// Order of operands should not matter for arithmetic conversions | |
| 522 | fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void { | |
| 523 | try std.testing.expect(ArithmeticConversion(A, B) == Expected); | |
| 524 | try std.testing.expect(ArithmeticConversion(B, A) == Expected); | |
| 525 | } | |
| 526 | }; | |
| 527 | ||
| 528 | try Test.checkPromotion(c_longdouble, c_int, c_longdouble); | |
| 529 | try Test.checkPromotion(c_int, f64, f64); | |
| 530 | try Test.checkPromotion(f32, bool, f32); | |
| 531 | ||
| 532 | try Test.checkPromotion(bool, c_short, c_int); | |
| 533 | try Test.checkPromotion(c_int, c_int, c_int); | |
| 534 | try Test.checkPromotion(c_short, c_int, c_int); | |
| 535 | ||
| 536 | try Test.checkPromotion(c_int, c_long, c_long); | |
| 537 | ||
| 538 | try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong); | |
| 539 | ||
| 540 | try Test.checkPromotion(c_uint, c_int, c_uint); | |
| 541 | ||
| 542 | try Test.checkPromotion(c_uint, c_long, c_long); | |
| 543 | ||
| 544 | try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong); | |
| 545 | ||
| 546 | // stdint.h | |
| 547 | try Test.checkPromotion(u8, i8, c_int); | |
| 548 | try Test.checkPromotion(u16, i16, c_int); | |
| 549 | try Test.checkPromotion(i32, c_int, c_int); | |
| 550 | try Test.checkPromotion(u32, c_int, c_uint); | |
| 551 | try Test.checkPromotion(i64, c_int, c_long); | |
| 552 | try Test.checkPromotion(u64, c_int, c_ulong); | |
| 553 | try Test.checkPromotion(isize, c_int, c_long); | |
| 554 | try Test.checkPromotion(usize, c_int, c_ulong); | |
| 555 | } | |
| 556 | ||
| 557 | pub const MacroArithmetic = struct { | |
| 558 | pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { | |
| 559 | const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); | |
| 560 | const a_casted = cast(ResType, a); | |
| 561 | const b_casted = cast(ResType, b); | |
| 562 | switch (@typeInfo(ResType)) { | |
| 563 | .float => return a_casted / b_casted, | |
| 564 | .int => return @divTrunc(a_casted, b_casted), | |
| 565 | else => unreachable, | |
| 566 | } | |
| 567 | } | |
| 568 | ||
| 569 | pub fn rem(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { | |
| 570 | const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); | |
| 571 | const a_casted = cast(ResType, a); | |
| 572 | const b_casted = cast(ResType, b); | |
| 573 | switch (@typeInfo(ResType)) { | |
| 574 | .int => { | |
| 575 | if (@typeInfo(ResType).int.signedness == .signed) { | |
| 576 | return signedRemainder(a_casted, b_casted); | |
| 577 | } else { | |
| 578 | return a_casted % b_casted; | |
| 579 | } | |
| 580 | }, | |
| 581 | else => unreachable, | |
| 582 | } | |
| 583 | } | |
| 584 | }; | |
| 585 | ||
| 586 | test "Macro suffix functions" { | |
| 587 | try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32); | |
| 588 | ||
| 589 | try testing.expect(@TypeOf(Macros.U_SUFFIX(1)) == c_uint); | |
| 590 | if (math.maxInt(c_ulong) > math.maxInt(c_uint)) { | |
| 591 | try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong); | |
| 592 | } | |
| 593 | if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { | |
| 594 | try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); | |
| 595 | } | |
| 596 | ||
| 597 | try testing.expect(@TypeOf(Macros.L_SUFFIX(1)) == c_long); | |
| 598 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 599 | try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_int) + 1)) == c_long); | |
| 600 | } | |
| 601 | if (math.maxInt(c_longlong) > math.maxInt(c_long)) { | |
| 602 | try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); | |
| 603 | } | |
| 604 | ||
| 605 | try testing.expect(@TypeOf(Macros.UL_SUFFIX(1)) == c_ulong); | |
| 606 | if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { | |
| 607 | try testing.expect(@TypeOf(Macros.UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); | |
| 608 | } | |
| 609 | ||
| 610 | try testing.expect(@TypeOf(Macros.LL_SUFFIX(1)) == c_longlong); | |
| 611 | try testing.expect(@TypeOf(Macros.ULL_SUFFIX(1)) == c_ulonglong); | |
| 612 | } | |
| 613 | ||
| 614 | test "WL_CONTAINER_OF" { | |
| 615 | const S = struct { | |
| 616 | a: u32 = 0, | |
| 617 | b: u32 = 0, | |
| 618 | }; | |
| 619 | const x = S{}; | |
| 620 | const y = S{}; | |
| 621 | const ptr = Macros.WL_CONTAINER_OF(&x.b, &y, "b"); | |
| 622 | try testing.expectEqual(&x, ptr); | |
| 623 | } | |
| 624 | ||
| 625 | test "CAST_OR_CALL casting" { | |
| 626 | const arg: c_int = 1000; | |
| 627 | const casted = Macros.CAST_OR_CALL(u8, arg); | |
| 628 | try testing.expectEqual(cast(u8, arg), casted); | |
| 629 | ||
| 630 | const S = struct { | |
| 631 | x: u32 = 0, | |
| 632 | }; | |
| 633 | var s: S = .{}; | |
| 634 | const casted_ptr = Macros.CAST_OR_CALL(*u8, &s); | |
| 635 | try testing.expectEqual(cast(*u8, &s), casted_ptr); | |
| 636 | } | |
| 637 | ||
| 638 | test "CAST_OR_CALL calling" { | |
| 639 | const Helper = struct { | |
| 640 | var last_val: bool = false; | |
| 641 | fn returnsVoid(val: bool) void { | |
| 642 | last_val = val; | |
| 643 | } | |
| 644 | fn returnsBool(f: f32) bool { | |
| 645 | return f > 0; | |
| 646 | } | |
| 647 | fn identity(self: c_uint) c_uint { | |
| 648 | return self; | |
| 649 | } | |
| 650 | }; | |
| 651 | ||
| 652 | Macros.CAST_OR_CALL(Helper.returnsVoid, true); | |
| 653 | try testing.expectEqual(true, Helper.last_val); | |
| 654 | Macros.CAST_OR_CALL(Helper.returnsVoid, false); | |
| 655 | try testing.expectEqual(false, Helper.last_val); | |
| 656 | ||
| 657 | try testing.expectEqual(Helper.returnsBool(1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, 1))); | |
| 658 | try testing.expectEqual(Helper.returnsBool(-1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, -1))); | |
| 659 | ||
| 660 | try testing.expectEqual(Helper.identity(@as(c_uint, 100)), Macros.CAST_OR_CALL(Helper.identity, @as(c_uint, 100))); | |
| 661 | } | |
| 662 | ||
| 663 | test "Extended C ABI casting" { | |
| 664 | if (math.maxInt(c_long) > math.maxInt(c_char)) { | |
| 665 | try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char | |
| 666 | } | |
| 667 | if (math.maxInt(c_long) > math.maxInt(c_short)) { | |
| 668 | try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short | |
| 669 | } | |
| 670 | ||
| 671 | if (math.maxInt(c_long) > math.maxInt(c_ushort)) { | |
| 672 | try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort | |
| 673 | } | |
| 674 | ||
| 675 | if (math.maxInt(c_long) > math.maxInt(c_int)) { | |
| 676 | try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int | |
| 677 | } | |
| 678 | ||
| 679 | if (math.maxInt(c_long) > math.maxInt(c_uint)) { | |
| 680 | try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint | |
| 681 | try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long | |
| 682 | } | |
| 683 | ||
| 684 | if (math.maxInt(c_longlong) > math.maxInt(c_long)) { | |
| 685 | try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long | |
| 686 | try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong | |
| 687 | } | |
| 688 | } | |
| 689 | ||
| 690 | // Function with complex signature for testing the SDL case | |
| 691 | fn complexFunction(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize { | |
| 692 | return 0; | |
| 693 | } | |
| 694 | ||
| 695 | test "function pointer casting" { | |
| 696 | const SDL_FunctionPointer = ?*const fn () callconv(.c) void; | |
| 697 | const fn_ptr = cast(SDL_FunctionPointer, complexFunction); | |
| 698 | try testing.expect(fn_ptr != null); | |
| 699 | } |
lib/std/zig/c_translation/builtins.zig created+301| ... | ... | @@ -0,0 +1,301 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | /// Standard C Library bug: The absolute value of the most negative integer remains negative. | |
| 4 | pub inline fn abs(val: c_int) c_int { | |
| 5 | return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); | |
| 6 | } | |
| 7 | ||
| 8 | pub inline fn assume(cond: bool) void { | |
| 9 | if (!cond) unreachable; | |
| 10 | } | |
| 11 | ||
| 12 | pub inline fn bswap16(val: u16) u16 { | |
| 13 | return @byteSwap(val); | |
| 14 | } | |
| 15 | ||
| 16 | pub inline fn bswap32(val: u32) u32 { | |
| 17 | return @byteSwap(val); | |
| 18 | } | |
| 19 | ||
| 20 | pub inline fn bswap64(val: u64) u64 { | |
| 21 | return @byteSwap(val); | |
| 22 | } | |
| 23 | ||
| 24 | pub inline fn ceilf(val: f32) f32 { | |
| 25 | return @ceil(val); | |
| 26 | } | |
| 27 | ||
| 28 | pub inline fn ceil(val: f64) f64 { | |
| 29 | return @ceil(val); | |
| 30 | } | |
| 31 | ||
| 32 | /// Returns the number of leading 0-bits in x, starting at the most significant bit position. | |
| 33 | /// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint | |
| 34 | pub inline fn clz(val: c_uint) c_int { | |
| 35 | @setRuntimeSafety(false); | |
| 36 | return @as(c_int, @bitCast(@as(c_uint, @clz(val)))); | |
| 37 | } | |
| 38 | ||
| 39 | pub inline fn constant_p(expr: anytype) c_int { | |
| 40 | _ = expr; | |
| 41 | return @intFromBool(false); | |
| 42 | } | |
| 43 | ||
| 44 | pub inline fn cosf(val: f32) f32 { | |
| 45 | return @cos(val); | |
| 46 | } | |
| 47 | ||
| 48 | pub inline fn cos(val: f64) f64 { | |
| 49 | return @cos(val); | |
| 50 | } | |
| 51 | ||
| 52 | /// Returns the number of trailing 0-bits in val, starting at the least significant bit position. | |
| 53 | /// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint | |
| 54 | pub inline fn ctz(val: c_uint) c_int { | |
| 55 | @setRuntimeSafety(false); | |
| 56 | return @as(c_int, @bitCast(@as(c_uint, @ctz(val)))); | |
| 57 | } | |
| 58 | ||
| 59 | pub inline fn exp2f(val: f32) f32 { | |
| 60 | return @exp2(val); | |
| 61 | } | |
| 62 | ||
| 63 | pub inline fn exp2(val: f64) f64 { | |
| 64 | return @exp2(val); | |
| 65 | } | |
| 66 | ||
| 67 | pub inline fn expf(val: f32) f32 { | |
| 68 | return @exp(val); | |
| 69 | } | |
| 70 | ||
| 71 | pub inline fn exp(val: f64) f64 { | |
| 72 | return @exp(val); | |
| 73 | } | |
| 74 | ||
| 75 | /// The return value of __builtin_expect is `expr`. `c` is the expected value | |
| 76 | /// of `expr` and is used as a hint to the compiler in C. Here it is unused. | |
| 77 | pub inline fn expect(expr: c_long, c: c_long) c_long { | |
| 78 | _ = c; | |
| 79 | return expr; | |
| 80 | } | |
| 81 | ||
| 82 | pub inline fn fabsf(val: f32) f32 { | |
| 83 | return @abs(val); | |
| 84 | } | |
| 85 | ||
| 86 | pub inline fn fabs(val: f64) f64 { | |
| 87 | return @abs(val); | |
| 88 | } | |
| 89 | ||
| 90 | pub inline fn floorf(val: f32) f32 { | |
| 91 | return @floor(val); | |
| 92 | } | |
| 93 | ||
| 94 | pub inline fn floor(val: f64) f64 { | |
| 95 | return @floor(val); | |
| 96 | } | |
| 97 | ||
| 98 | pub inline fn has_builtin(func: anytype) c_int { | |
| 99 | _ = func; | |
| 100 | return @intFromBool(true); | |
| 101 | } | |
| 102 | ||
| 103 | pub inline fn huge_valf() f32 { | |
| 104 | return std.math.inf(f32); | |
| 105 | } | |
| 106 | ||
| 107 | pub inline fn inff() f32 { | |
| 108 | return std.math.inf(f32); | |
| 109 | } | |
| 110 | ||
| 111 | /// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf. | |
| 112 | pub inline fn isinf_sign(x: anytype) c_int { | |
| 113 | if (!std.math.isInf(x)) return 0; | |
| 114 | return if (std.math.isPositiveInf(x)) 1 else -1; | |
| 115 | } | |
| 116 | ||
| 117 | pub inline fn isinf(x: anytype) c_int { | |
| 118 | return @intFromBool(std.math.isInf(x)); | |
| 119 | } | |
| 120 | ||
| 121 | pub inline fn isnan(x: anytype) c_int { | |
| 122 | return @intFromBool(std.math.isNan(x)); | |
| 123 | } | |
| 124 | ||
| 125 | /// Standard C Library bug: The absolute value of the most negative integer remains negative. | |
| 126 | pub inline fn labs(val: c_long) c_long { | |
| 127 | return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val)); | |
| 128 | } | |
| 129 | ||
| 130 | /// Standard C Library bug: The absolute value of the most negative integer remains negative. | |
| 131 | pub inline fn llabs(val: c_longlong) c_longlong { | |
| 132 | return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val)); | |
| 133 | } | |
| 134 | ||
| 135 | pub inline fn log10f(val: f32) f32 { | |
| 136 | return @log10(val); | |
| 137 | } | |
| 138 | ||
| 139 | pub inline fn log10(val: f64) f64 { | |
| 140 | return @log10(val); | |
| 141 | } | |
| 142 | ||
| 143 | pub inline fn log2f(val: f32) f32 { | |
| 144 | return @log2(val); | |
| 145 | } | |
| 146 | ||
| 147 | pub inline fn log2(val: f64) f64 { | |
| 148 | return @log2(val); | |
| 149 | } | |
| 150 | ||
| 151 | pub inline fn logf(val: f32) f32 { | |
| 152 | return @log(val); | |
| 153 | } | |
| 154 | ||
| 155 | pub inline fn log(val: f64) f64 { | |
| 156 | return @log(val); | |
| 157 | } | |
| 158 | ||
| 159 | pub inline fn memcpy_chk( | |
| 160 | noalias dst: ?*anyopaque, | |
| 161 | noalias src: ?*const anyopaque, | |
| 162 | len: usize, | |
| 163 | remaining: usize, | |
| 164 | ) ?*anyopaque { | |
| 165 | if (len > remaining) @panic("__builtin___memcpy_chk called with len > remaining"); | |
| 166 | if (len > 0) @memcpy( | |
| 167 | @as([*]u8, @ptrCast(dst.?))[0..len], | |
| 168 | @as([*]const u8, @ptrCast(src.?)), | |
| 169 | ); | |
| 170 | return dst; | |
| 171 | } | |
| 172 | ||
| 173 | pub inline fn memcpy( | |
| 174 | noalias dst: ?*anyopaque, | |
| 175 | noalias src: ?*const anyopaque, | |
| 176 | len: usize, | |
| 177 | ) ?*anyopaque { | |
| 178 | if (len > 0) @memcpy( | |
| 179 | @as([*]u8, @ptrCast(dst.?))[0..len], | |
| 180 | @as([*]const u8, @ptrCast(src.?)), | |
| 181 | ); | |
| 182 | return dst; | |
| 183 | } | |
| 184 | ||
| 185 | pub inline fn memset_chk( | |
| 186 | dst: ?*anyopaque, | |
| 187 | val: c_int, | |
| 188 | len: usize, | |
| 189 | remaining: usize, | |
| 190 | ) ?*anyopaque { | |
| 191 | if (len > remaining) @panic("__builtin___memset_chk called with len > remaining"); | |
| 192 | const dst_cast = @as([*c]u8, @ptrCast(dst)); | |
| 193 | @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); | |
| 194 | return dst; | |
| 195 | } | |
| 196 | ||
| 197 | pub inline fn memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { | |
| 198 | const dst_cast = @as([*c]u8, @ptrCast(dst)); | |
| 199 | @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); | |
| 200 | return dst; | |
| 201 | } | |
| 202 | ||
| 203 | pub fn mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int { | |
| 204 | const res = @mulWithOverflow(a, b); | |
| 205 | result.* = res[0]; | |
| 206 | return res[1]; | |
| 207 | } | |
| 208 | ||
| 209 | /// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an | |
| 210 | /// implementation-defined way. | |
| 211 | /// This implementation is based on the description for nan provided in the GCC docs at | |
| 212 | /// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan | |
| 213 | /// Comment is reproduced below: | |
| 214 | /// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description | |
| 215 | /// of the parsing is in order. | |
| 216 | /// The string is parsed as by strtol; that is, the base is recognized by leading ‘0’ or ‘0x’ prefixes. | |
| 217 | /// The number parsed is placed in the significand such that the least significant bit of the number is | |
| 218 | /// at the least significant bit of the significand. | |
| 219 | /// The number is truncated to fit the significand field provided. | |
| 220 | /// The significand is forced to be a quiet NaN. | |
| 221 | /// | |
| 222 | /// If tagp contains any non-numeric characters, the function returns a NaN whose significand is zero. | |
| 223 | /// If tagp is empty, the function returns a NaN whose significand is zero. | |
| 224 | pub inline fn nanf(tagp: []const u8) f32 { | |
| 225 | const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0; | |
| 226 | const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits | |
| 227 | return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32)))); | |
| 228 | } | |
| 229 | ||
| 230 | pub inline fn object_size(ptr: ?*const anyopaque, ty: c_int) usize { | |
| 231 | _ = ptr; | |
| 232 | // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html | |
| 233 | // If it is not possible to determine which objects ptr points to at compile time, | |
| 234 | // object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 | |
| 235 | // for type 2 or 3. | |
| 236 | if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1))); | |
| 237 | if (ty == 2 or ty == 3) return 0; | |
| 238 | unreachable; | |
| 239 | } | |
| 240 | ||
| 241 | /// popcount of a c_uint will never exceed the capacity of a c_int | |
| 242 | pub inline fn popcount(val: c_uint) c_int { | |
| 243 | @setRuntimeSafety(false); | |
| 244 | return @as(c_int, @bitCast(@as(c_uint, @popCount(val)))); | |
| 245 | } | |
| 246 | ||
| 247 | pub inline fn roundf(val: f32) f32 { | |
| 248 | return @round(val); | |
| 249 | } | |
| 250 | ||
| 251 | pub inline fn round(val: f64) f64 { | |
| 252 | return @round(val); | |
| 253 | } | |
| 254 | ||
| 255 | pub inline fn signbitf(val: f32) c_int { | |
| 256 | return @intFromBool(std.math.signbit(val)); | |
| 257 | } | |
| 258 | ||
| 259 | pub inline fn signbit(val: f64) c_int { | |
| 260 | return @intFromBool(std.math.signbit(val)); | |
| 261 | } | |
| 262 | ||
| 263 | pub inline fn sinf(val: f32) f32 { | |
| 264 | return @sin(val); | |
| 265 | } | |
| 266 | ||
| 267 | pub inline fn sin(val: f64) f64 { | |
| 268 | return @sin(val); | |
| 269 | } | |
| 270 | ||
| 271 | pub inline fn sqrtf(val: f32) f32 { | |
| 272 | return @sqrt(val); | |
| 273 | } | |
| 274 | ||
| 275 | pub inline fn sqrt(val: f64) f64 { | |
| 276 | return @sqrt(val); | |
| 277 | } | |
| 278 | ||
| 279 | pub inline fn strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int { | |
| 280 | return switch (std.mem.orderZ(u8, s1, s2)) { | |
| 281 | .lt => -1, | |
| 282 | .eq => 0, | |
| 283 | .gt => 1, | |
| 284 | }; | |
| 285 | } | |
| 286 | ||
| 287 | pub inline fn strlen(s: [*c]const u8) usize { | |
| 288 | return std.mem.sliceTo(s, 0).len; | |
| 289 | } | |
| 290 | ||
| 291 | pub inline fn truncf(val: f32) f32 { | |
| 292 | return @trunc(val); | |
| 293 | } | |
| 294 | ||
| 295 | pub inline fn trunc(val: f64) f64 { | |
| 296 | return @trunc(val); | |
| 297 | } | |
| 298 | ||
| 299 | pub inline fn @"unreachable"() noreturn { | |
| 300 | unreachable; | |
| 301 | } |
lib/std/zig/c_translation/helpers.zig created+413| ... | ... | @@ -0,0 +1,413 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | /// "Usual arithmetic conversions" from C11 standard 6.3.1.8 | |
| 4 | pub fn ArithmeticConversion(comptime A: type, comptime B: type) type { | |
| 5 | if (A == c_longdouble or B == c_longdouble) return c_longdouble; | |
| 6 | if (A == f80 or B == f80) return f80; | |
| 7 | if (A == f64 or B == f64) return f64; | |
| 8 | if (A == f32 or B == f32) return f32; | |
| 9 | ||
| 10 | const A_Promoted = PromotedIntType(A); | |
| 11 | const B_Promoted = PromotedIntType(B); | |
| 12 | comptime { | |
| 13 | std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int)); | |
| 14 | std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int)); | |
| 15 | } | |
| 16 | ||
| 17 | if (A_Promoted == B_Promoted) return A_Promoted; | |
| 18 | ||
| 19 | const a_signed = @typeInfo(A_Promoted).int.signedness == .signed; | |
| 20 | const b_signed = @typeInfo(B_Promoted).int.signedness == .signed; | |
| 21 | ||
| 22 | if (a_signed == b_signed) { | |
| 23 | return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted; | |
| 24 | } | |
| 25 | ||
| 26 | const SignedType = if (a_signed) A_Promoted else B_Promoted; | |
| 27 | const UnsignedType = if (!a_signed) A_Promoted else B_Promoted; | |
| 28 | ||
| 29 | if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType; | |
| 30 | ||
| 31 | if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType; | |
| 32 | ||
| 33 | return ToUnsigned(SignedType); | |
| 34 | } | |
| 35 | ||
| 36 | /// Integer promotion described in C11 6.3.1.1.2 | |
| 37 | fn PromotedIntType(comptime T: type) type { | |
| 38 | return switch (T) { | |
| 39 | bool, c_short => c_int, | |
| 40 | c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int, | |
| 41 | c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T, | |
| 42 | else => switch (@typeInfo(T)) { | |
| 43 | .comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"), | |
| 44 | // promote to c_int if it can represent all values of T | |
| 45 | .int => |int_info| if (int_info.bits < @bitSizeOf(c_int)) | |
| 46 | c_int | |
| 47 | // otherwise, restore the original C type | |
| 48 | else if (int_info.bits == @bitSizeOf(c_int)) | |
| 49 | if (int_info.signedness == .unsigned) c_uint else c_int | |
| 50 | else if (int_info.bits <= @bitSizeOf(c_long)) | |
| 51 | if (int_info.signedness == .unsigned) c_ulong else c_long | |
| 52 | else if (int_info.bits <= @bitSizeOf(c_longlong)) | |
| 53 | if (int_info.signedness == .unsigned) c_ulonglong else c_longlong | |
| 54 | else | |
| 55 | @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required"), | |
| 56 | else => @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`"), | |
| 57 | }, | |
| 58 | }; | |
| 59 | } | |
| 60 | ||
| 61 | /// C11 6.3.1.1.1 | |
| 62 | fn integerRank(comptime T: type) u8 { | |
| 63 | return switch (T) { | |
| 64 | bool => 0, | |
| 65 | u8, i8 => 1, | |
| 66 | c_short, c_ushort => 2, | |
| 67 | c_int, c_uint => 3, | |
| 68 | c_long, c_ulong => 4, | |
| 69 | c_longlong, c_ulonglong => 5, | |
| 70 | else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"), | |
| 71 | }; | |
| 72 | } | |
| 73 | ||
| 74 | fn ToUnsigned(comptime T: type) type { | |
| 75 | return switch (T) { | |
| 76 | c_int => c_uint, | |
| 77 | c_long => c_ulong, | |
| 78 | c_longlong => c_ulonglong, | |
| 79 | else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"), | |
| 80 | }; | |
| 81 | } | |
| 82 | ||
| 83 | /// Constructs a [*c] pointer with the const and volatile annotations | |
| 84 | /// from SelfType for pointing to a C flexible array of ElementType. | |
| 85 | pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type { | |
| 86 | switch (@typeInfo(SelfType)) { | |
| 87 | .pointer => |ptr| { | |
| 88 | return @Type(.{ .pointer = .{ | |
| 89 | .size = .c, | |
| 90 | .is_const = ptr.is_const, | |
| 91 | .is_volatile = ptr.is_volatile, | |
| 92 | .alignment = @alignOf(ElementType), | |
| 93 | .address_space = .generic, | |
| 94 | .child = ElementType, | |
| 95 | .is_allowzero = true, | |
| 96 | .sentinel_ptr = null, | |
| 97 | } }); | |
| 98 | }, | |
| 99 | else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)), | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | /// Promote the type of an integer literal until it fits as C would. | |
| 104 | pub fn promoteIntLiteral( | |
| 105 | comptime SuffixType: type, | |
| 106 | comptime number: comptime_int, | |
| 107 | comptime base: CIntLiteralBase, | |
| 108 | ) PromoteIntLiteralReturnType(SuffixType, number, base) { | |
| 109 | return number; | |
| 110 | } | |
| 111 | ||
| 112 | const CIntLiteralBase = enum { decimal, octal, hex }; | |
| 113 | ||
| 114 | fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type { | |
| 115 | const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong }; | |
| 116 | const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; | |
| 117 | const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; | |
| 118 | ||
| 119 | const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned) | |
| 120 | &unsigned | |
| 121 | else if (base == .decimal) | |
| 122 | &signed_decimal | |
| 123 | else | |
| 124 | &signed_oct_hex; | |
| 125 | ||
| 126 | var pos = std.mem.indexOfScalar(type, list, SuffixType).?; | |
| 127 | while (pos < list.len) : (pos += 1) { | |
| 128 | if (number >= std.math.minInt(list[pos]) and number <= std.math.maxInt(list[pos])) { | |
| 129 | return list[pos]; | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 133 | @compileError("Integer literal is too large"); | |
| 134 | } | |
| 135 | ||
| 136 | /// Convert from clang __builtin_shufflevector index to Zig @shuffle index | |
| 137 | /// clang requires __builtin_shufflevector index arguments to be integer constants. | |
| 138 | /// negative values for `this_index` indicate "don't care". | |
| 139 | /// clang enforces that `this_index` is less than the total number of vector elements | |
| 140 | /// See https://ziglang.org/documentation/master/#shuffle | |
| 141 | /// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector | |
| 142 | pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 { | |
| 143 | const positive_index = std.math.cast(usize, this_index) orelse return undefined; | |
| 144 | if (positive_index < source_vector_len) return @as(i32, @intCast(this_index)); | |
| 145 | const b_index = positive_index - source_vector_len; | |
| 146 | return ~@as(i32, @intCast(b_index)); | |
| 147 | } | |
| 148 | ||
| 149 | /// C `%` operator for signed integers | |
| 150 | /// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a" | |
| 151 | /// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for | |
| 152 | /// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety | |
| 153 | /// checked undefined behavior | |
| 154 | pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) { | |
| 155 | std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed); | |
| 156 | if (denominator > 0) return @rem(numerator, denominator); | |
| 157 | return numerator - @divTrunc(numerator, denominator) * denominator; | |
| 158 | } | |
| 159 | ||
| 160 | /// Given a type and value, cast the value to the type as c would. | |
| 161 | pub fn cast(comptime DestType: type, target: anytype) DestType { | |
| 162 | // this function should behave like transCCast in translate-c, except it's for macros | |
| 163 | const SourceType = @TypeOf(target); | |
| 164 | switch (@typeInfo(DestType)) { | |
| 165 | .@"fn" => return castToPtr(*const DestType, SourceType, target), | |
| 166 | .pointer => return castToPtr(DestType, SourceType, target), | |
| 167 | .optional => |dest_opt| { | |
| 168 | if (@typeInfo(dest_opt.child) == .pointer) { | |
| 169 | return castToPtr(DestType, SourceType, target); | |
| 170 | } else if (@typeInfo(dest_opt.child) == .@"fn") { | |
| 171 | return castToPtr(?*const dest_opt.child, SourceType, target); | |
| 172 | } | |
| 173 | }, | |
| 174 | .int => { | |
| 175 | switch (@typeInfo(SourceType)) { | |
| 176 | .pointer => { | |
| 177 | return castInt(DestType, @intFromPtr(target)); | |
| 178 | }, | |
| 179 | .optional => |opt| { | |
| 180 | if (@typeInfo(opt.child) == .pointer) { | |
| 181 | return castInt(DestType, @intFromPtr(target)); | |
| 182 | } | |
| 183 | }, | |
| 184 | .int => { | |
| 185 | return castInt(DestType, target); | |
| 186 | }, | |
| 187 | .@"fn" => { | |
| 188 | return castInt(DestType, @intFromPtr(&target)); | |
| 189 | }, | |
| 190 | .bool => { | |
| 191 | return @intFromBool(target); | |
| 192 | }, | |
| 193 | else => {}, | |
| 194 | } | |
| 195 | }, | |
| 196 | .float => { | |
| 197 | switch (@typeInfo(SourceType)) { | |
| 198 | .int => return @as(DestType, @floatFromInt(target)), | |
| 199 | .float => return @as(DestType, @floatCast(target)), | |
| 200 | .bool => return @as(DestType, @floatFromInt(@intFromBool(target))), | |
| 201 | else => {}, | |
| 202 | } | |
| 203 | }, | |
| 204 | .@"union" => |info| { | |
| 205 | inline for (info.fields) |field| { | |
| 206 | if (field.type == SourceType) return @unionInit(DestType, field.name, target); | |
| 207 | } | |
| 208 | ||
| 209 | @compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union"); | |
| 210 | }, | |
| 211 | .bool => return cast(usize, target) != 0, | |
| 212 | else => {}, | |
| 213 | } | |
| 214 | ||
| 215 | return @as(DestType, target); | |
| 216 | } | |
| 217 | ||
| 218 | fn castInt(comptime DestType: type, target: anytype) DestType { | |
| 219 | const dest = @typeInfo(DestType).int; | |
| 220 | const source = @typeInfo(@TypeOf(target)).int; | |
| 221 | ||
| 222 | const Int = @Type(.{ .int = .{ .bits = dest.bits, .signedness = source.signedness } }); | |
| 223 | ||
| 224 | if (dest.bits < source.bits) | |
| 225 | return @as(DestType, @bitCast(@as(Int, @truncate(target)))) | |
| 226 | else | |
| 227 | return @as(DestType, @bitCast(@as(Int, target))); | |
| 228 | } | |
| 229 | ||
| 230 | fn castPtr(comptime DestType: type, target: anytype) DestType { | |
| 231 | return @constCast(@volatileCast(@alignCast(@ptrCast(target)))); | |
| 232 | } | |
| 233 | ||
| 234 | fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType { | |
| 235 | switch (@typeInfo(SourceType)) { | |
| 236 | .int => { | |
| 237 | return @as(DestType, @ptrFromInt(castInt(usize, target))); | |
| 238 | }, | |
| 239 | .comptime_int => { | |
| 240 | if (target < 0) | |
| 241 | return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target)))))) | |
| 242 | else | |
| 243 | return @as(DestType, @ptrFromInt(@as(usize, @intCast(target)))); | |
| 244 | }, | |
| 245 | .pointer => { | |
| 246 | return castPtr(DestType, target); | |
| 247 | }, | |
| 248 | .@"fn" => { | |
| 249 | return castPtr(DestType, &target); | |
| 250 | }, | |
| 251 | .optional => |target_opt| { | |
| 252 | if (@typeInfo(target_opt.child) == .pointer) { | |
| 253 | return castPtr(DestType, target); | |
| 254 | } | |
| 255 | }, | |
| 256 | else => {}, | |
| 257 | } | |
| 258 | ||
| 259 | return @as(DestType, target); | |
| 260 | } | |
| 261 | ||
| 262 | /// Given a value returns its size as C's sizeof operator would. | |
| 263 | pub fn sizeof(target: anytype) usize { | |
| 264 | const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); | |
| 265 | switch (@typeInfo(T)) { | |
| 266 | .float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T), | |
| 267 | .@"fn" => { | |
| 268 | // sizeof(main) in C returns 1 | |
| 269 | return 1; | |
| 270 | }, | |
| 271 | .null => return @sizeOf(*anyopaque), | |
| 272 | .void => { | |
| 273 | // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. | |
| 274 | return 1; | |
| 275 | }, | |
| 276 | .@"opaque" => { | |
| 277 | if (T == anyopaque) { | |
| 278 | // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. | |
| 279 | return 1; | |
| 280 | } else { | |
| 281 | @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); | |
| 282 | } | |
| 283 | }, | |
| 284 | .optional => |opt| { | |
| 285 | if (@typeInfo(opt.child) == .pointer) { | |
| 286 | return sizeof(opt.child); | |
| 287 | } else { | |
| 288 | @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); | |
| 289 | } | |
| 290 | }, | |
| 291 | .pointer => |ptr| { | |
| 292 | if (ptr.size == .slice) { | |
| 293 | @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); | |
| 294 | } | |
| 295 | ||
| 296 | // for strings, sizeof("a") returns 2. | |
| 297 | // normal pointer decay scenarios from C are handled | |
| 298 | // in the .array case above, but strings remain literals | |
| 299 | // and are therefore always pointers, so they need to be | |
| 300 | // specially handled here. | |
| 301 | if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) { | |
| 302 | const array_info = @typeInfo(ptr.child).array; | |
| 303 | if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) { | |
| 304 | // length of the string plus one for the null terminator. | |
| 305 | return (array_info.len + 1) * @sizeOf(array_info.child); | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | // When zero sized pointers are removed, this case will no | |
| 310 | // longer be reachable and can be deleted. | |
| 311 | if (@sizeOf(T) == 0) { | |
| 312 | return @sizeOf(*anyopaque); | |
| 313 | } | |
| 314 | ||
| 315 | return @sizeOf(T); | |
| 316 | }, | |
| 317 | .comptime_float => return @sizeOf(f64), // TODO c_double #3999 | |
| 318 | .comptime_int => { | |
| 319 | // TODO to get the correct result we have to translate | |
| 320 | // `1073741824 * 4` as `int(1073741824) *% int(4)` since | |
| 321 | // sizeof(1073741824 * 4) != sizeof(4294967296). | |
| 322 | ||
| 323 | // TODO test if target fits in int, long or long long | |
| 324 | return @sizeOf(c_int); | |
| 325 | }, | |
| 326 | else => @compileError("__helpers.sizeof does not support type " ++ @typeName(T)), | |
| 327 | } | |
| 328 | } | |
| 329 | ||
| 330 | pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { | |
| 331 | const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); | |
| 332 | const a_casted = cast(ResType, a); | |
| 333 | const b_casted = cast(ResType, b); | |
| 334 | switch (@typeInfo(ResType)) { | |
| 335 | .float => return a_casted / b_casted, | |
| 336 | .int => return @divTrunc(a_casted, b_casted), | |
| 337 | else => unreachable, | |
| 338 | } | |
| 339 | } | |
| 340 | ||
| 341 | pub fn rem(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) { | |
| 342 | const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b)); | |
| 343 | const a_casted = cast(ResType, a); | |
| 344 | const b_casted = cast(ResType, b); | |
| 345 | switch (@typeInfo(ResType)) { | |
| 346 | .int => { | |
| 347 | if (@typeInfo(ResType).int.signedness == .signed) { | |
| 348 | return signedRemainder(a_casted, b_casted); | |
| 349 | } else { | |
| 350 | return a_casted % b_casted; | |
| 351 | } | |
| 352 | }, | |
| 353 | else => unreachable, | |
| 354 | } | |
| 355 | } | |
| 356 | ||
| 357 | /// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) | |
| 358 | /// could be either: cast B to A, or call A with the value B. | |
| 359 | pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { | |
| 360 | .type => a, | |
| 361 | .@"fn" => |fn_info| fn_info.return_type orelse void, | |
| 362 | else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), | |
| 363 | } { | |
| 364 | switch (@typeInfo(@TypeOf(a))) { | |
| 365 | .type => return cast(a, b), | |
| 366 | .@"fn" => return a(b), | |
| 367 | else => unreachable, // return type will be a compile error otherwise | |
| 368 | } | |
| 369 | } | |
| 370 | ||
| 371 | pub inline fn DISCARD(x: anytype) void { | |
| 372 | _ = x; | |
| 373 | } | |
| 374 | ||
| 375 | pub fn F_SUFFIX(comptime f: comptime_float) f32 { | |
| 376 | return @as(f32, f); | |
| 377 | } | |
| 378 | ||
| 379 | fn L_SUFFIX_ReturnType(comptime number: anytype) type { | |
| 380 | switch (@typeInfo(@TypeOf(number))) { | |
| 381 | .int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), | |
| 382 | .float, .comptime_float => return c_longdouble, | |
| 383 | else => @compileError("Invalid value for L suffix"), | |
| 384 | } | |
| 385 | } | |
| 386 | ||
| 387 | pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { | |
| 388 | switch (@typeInfo(@TypeOf(number))) { | |
| 389 | .int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal), | |
| 390 | .float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), | |
| 391 | else => @compileError("Invalid value for L suffix"), | |
| 392 | } | |
| 393 | } | |
| 394 | ||
| 395 | pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { | |
| 396 | return promoteIntLiteral(c_longlong, n, .decimal); | |
| 397 | } | |
| 398 | ||
| 399 | pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { | |
| 400 | return promoteIntLiteral(c_uint, n, .decimal); | |
| 401 | } | |
| 402 | ||
| 403 | pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { | |
| 404 | return promoteIntLiteral(c_ulong, n, .decimal); | |
| 405 | } | |
| 406 | ||
| 407 | pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { | |
| 408 | return promoteIntLiteral(c_ulonglong, n, .decimal); | |
| 409 | } | |
| 410 | ||
| 411 | pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { | |
| 412 | return @fieldParentPtr(member, ptr); | |
| 413 | } |