zig0

my attempts at zig bootstrapping in C
Log | Files | Refs | README | LICENSE

astgen_test.zig (29707B) - Raw


      1 const std = @import("std");
      2 const Ast = std.zig.Ast;
      3 const Zir = std.zig.Zir;
      4 const AstGen = std.zig.AstGen;
      5 const Allocator = std.mem.Allocator;
      6 
      7 const c = @cImport({
      8     @cInclude("astgen.h");
      9 });
     10 
     11 fn refZir(gpa: Allocator, source: [:0]const u8) !Zir {
     12     var tree = try Ast.parse(gpa, source, .zig);
     13     defer tree.deinit(gpa);
     14     return try AstGen.generate(gpa, tree);
     15 }
     16 
     17 test "astgen dump: simple cases" {
     18     const gpa = std.testing.allocator;
     19 
     20     const cases = .{
     21         .{ "empty", "" },
     22         .{ "comptime {}", "comptime {}" },
     23         .{ "const x = 0;", "const x = 0;" },
     24         .{ "const x = 1;", "const x = 1;" },
     25         .{ "const x = 0; const y = 0;", "const x = 0; const y = 0;" },
     26         .{ "test \"t\" {}", "test \"t\" {}" },
     27         .{ "const std = @import(\"std\");", "const std = @import(\"std\");" },
     28         .{ "test_all.zig", @embedFile("test_all.zig") },
     29     };
     30 
     31     inline for (cases) |case| {
     32         // std.debug.print("--- {s} ---\n", .{case[0]});
     33         const source: [:0]const u8 = case[1];
     34         var zir = try refZir(gpa, source);
     35         zir.deinit(gpa);
     36     }
     37 }
     38 
     39 /// Build a mask of extra[] indices that contain hash data (src_hash or
     40 /// fields_hash). These are zero-filled in the C output but contain real
     41 /// Blake3 hashes in the Zig reference. We skip these positions during
     42 /// comparison.
     43 fn buildHashSkipMask(gpa: Allocator, ref: Zir) ![]bool {
     44     const ref_extra_len: u32 = @intCast(ref.extra.len);
     45     const skip = try gpa.alloc(bool, ref_extra_len);
     46     @memset(skip, false);
     47 
     48     const ref_len: u32 = @intCast(ref.instructions.len);
     49     const ref_tags = ref.instructions.items(.tag);
     50     const ref_datas = ref.instructions.items(.data);
     51     for (0..ref_len) |i| {
     52         switch (ref_tags[i]) {
     53             .extended => {
     54                 const ext = ref_datas[i].extended;
     55                 if (ext.opcode == .struct_decl or ext.opcode == .enum_decl) {
     56                     // StructDecl/EnumDecl starts with fields_hash[4].
     57                     const pi = ext.operand;
     58                     for (0..4) |j| skip[pi + j] = true;
     59                 }
     60             },
     61             .declaration => {
     62                 // Declaration starts with src_hash[4].
     63                 const pi = ref_datas[i].declaration.payload_index;
     64                 for (0..4) |j| skip[pi + j] = true;
     65             },
     66             .func, .func_inferred => {
     67                 // Func payload: ret_ty(1) + param_block(1) + body_len(1)
     68                 // + trailing ret_ty + body + SrcLocs(3) + proto_hash(4).
     69                 const pi = ref_datas[i].pl_node.payload_index;
     70                 const ret_ty_raw: u32 = ref.extra[pi];
     71                 const ret_body_len: u32 = ret_ty_raw & 0x7FFFFFFF;
     72                 const body_len: u32 = ref.extra[pi + 2];
     73                 // ret_ty trailing: if body_len > 1, it's a body; if == 1, it's a ref; if 0, void.
     74                 const ret_trailing: u32 = if (ret_body_len > 1) ret_body_len else if (ret_body_len == 1) 1 else 0;
     75                 // proto_hash is at: pi + 3 + ret_trailing + body_len + 3
     76                 if (body_len > 0) {
     77                     const hash_start = pi + 3 + ret_trailing + body_len + 3;
     78                     for (0..4) |j| {
     79                         if (hash_start + j < ref_extra_len)
     80                             skip[hash_start + j] = true;
     81                     }
     82                 }
     83             },
     84             else => {},
     85         }
     86     }
     87     return skip;
     88 }
     89 
     90 test "astgen: empty source" {
     91     const gpa = std.testing.allocator;
     92     const source: [:0]const u8 = "";
     93 
     94     var ref_zir = try refZir(gpa, source);
     95     defer ref_zir.deinit(gpa);
     96 
     97     var c_ast = c.astParse(source.ptr, @intCast(source.len));
     98     defer c.astDeinit(&c_ast);
     99     var c_zir = c.astGen(&c_ast);
    100     defer c.zirDeinit(&c_zir);
    101 
    102     try expectEqualZir(gpa, ref_zir, c_zir);
    103 }
    104 
    105 test "astgen: comptime {}" {
    106     const gpa = std.testing.allocator;
    107     const source: [:0]const u8 = "comptime {}";
    108 
    109     var ref_zir = try refZir(gpa, source);
    110     defer ref_zir.deinit(gpa);
    111 
    112     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    113     defer c.astDeinit(&c_ast);
    114     var c_zir = c.astGen(&c_ast);
    115     defer c.zirDeinit(&c_zir);
    116 
    117     try expectEqualZir(gpa, ref_zir, c_zir);
    118 }
    119 
    120 test "astgen: const x = 0;" {
    121     const gpa = std.testing.allocator;
    122     const source: [:0]const u8 = "const x = 0;";
    123 
    124     var ref_zir = try refZir(gpa, source);
    125     defer ref_zir.deinit(gpa);
    126 
    127     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    128     defer c.astDeinit(&c_ast);
    129     var c_zir = c.astGen(&c_ast);
    130     defer c.zirDeinit(&c_zir);
    131 
    132     try expectEqualZir(gpa, ref_zir, c_zir);
    133 }
    134 
    135 test "astgen: const x = 1;" {
    136     const gpa = std.testing.allocator;
    137     const source: [:0]const u8 = "const x = 1;";
    138 
    139     var ref_zir = try refZir(gpa, source);
    140     defer ref_zir.deinit(gpa);
    141 
    142     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    143     defer c.astDeinit(&c_ast);
    144     var c_zir = c.astGen(&c_ast);
    145     defer c.zirDeinit(&c_zir);
    146 
    147     try expectEqualZir(gpa, ref_zir, c_zir);
    148 }
    149 
    150 test "astgen: const x = 0; const y = 0;" {
    151     const gpa = std.testing.allocator;
    152     const source: [:0]const u8 = "const x = 0; const y = 0;";
    153 
    154     var ref_zir = try refZir(gpa, source);
    155     defer ref_zir.deinit(gpa);
    156 
    157     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    158     defer c.astDeinit(&c_ast);
    159     var c_zir = c.astGen(&c_ast);
    160     defer c.zirDeinit(&c_zir);
    161 
    162     try expectEqualZir(gpa, ref_zir, c_zir);
    163 }
    164 
    165 test "astgen: field_access" {
    166     const gpa = std.testing.allocator;
    167     const source: [:0]const u8 = "const std = @import(\"std\");\nconst mem = std.mem;";
    168 
    169     var ref_zir = try refZir(gpa, source);
    170     defer ref_zir.deinit(gpa);
    171 
    172     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    173     defer c.astDeinit(&c_ast);
    174     var c_zir = c.astGen(&c_ast);
    175     defer c.zirDeinit(&c_zir);
    176 
    177     try expectEqualZir(gpa, ref_zir, c_zir);
    178 }
    179 
    180 test "astgen: addr array init" {
    181     const gpa = std.testing.allocator;
    182     const source: [:0]const u8 = "const x = &[_][]const u8{\"a\",\"b\"};";
    183 
    184     var ref_zir = try refZir(gpa, source);
    185     defer ref_zir.deinit(gpa);
    186 
    187     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    188     defer c.astDeinit(&c_ast);
    189     var c_zir = c.astGen(&c_ast);
    190     defer c.zirDeinit(&c_zir);
    191 
    192     try expectEqualZir(gpa, ref_zir, c_zir);
    193 }
    194 
    195 test "astgen: test empty body" {
    196     const gpa = std.testing.allocator;
    197     const source: [:0]const u8 = "test \"t\" {}";
    198 
    199     var ref_zir = try refZir(gpa, source);
    200     defer ref_zir.deinit(gpa);
    201 
    202     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    203     defer c.astDeinit(&c_ast);
    204     var c_zir = c.astGen(&c_ast);
    205     defer c.zirDeinit(&c_zir);
    206 
    207     try expectEqualZir(gpa, ref_zir, c_zir);
    208 }
    209 
    210 test "astgen: test_all.zig" {
    211     const gpa = std.testing.allocator;
    212     const source: [:0]const u8 = @embedFile("test_all.zig");
    213 
    214     var ref_zir = try refZir(gpa, source);
    215     defer ref_zir.deinit(gpa);
    216 
    217     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    218     defer c.astDeinit(&c_ast);
    219     var c_zir = c.astGen(&c_ast);
    220     defer c.zirDeinit(&c_zir);
    221 
    222     try expectEqualZir(gpa, ref_zir, c_zir);
    223 }
    224 
    225 test "astgen: @import" {
    226     const gpa = std.testing.allocator;
    227     const source: [:0]const u8 = "const std = @import(\"std\");";
    228 
    229     var ref_zir = try refZir(gpa, source);
    230     defer ref_zir.deinit(gpa);
    231 
    232     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    233     defer c.astDeinit(&c_ast);
    234     var c_zir = c.astGen(&c_ast);
    235     defer c.zirDeinit(&c_zir);
    236 
    237     try expectEqualZir(gpa, ref_zir, c_zir);
    238 }
    239 
    240 fn expectEqualZir(gpa: Allocator, ref: Zir, got: c.Zir) !void {
    241     const ref_len: u32 = @intCast(ref.instructions.len);
    242     const ref_tags = ref.instructions.items(.tag);
    243     const ref_datas = ref.instructions.items(.data);
    244 
    245     // 1. Compare lengths.
    246     try std.testing.expectEqual(ref_len, got.inst_len);
    247 
    248     // 2. Compare instruction tags.
    249     for (0..ref_len) |i| {
    250         const ref_tag: u8 = @intFromEnum(ref_tags[i]);
    251         const got_tag: u8 = @intCast(got.inst_tags[i]);
    252         if (ref_tag != got_tag) {
    253             std.debug.print(
    254                 "inst_tags[{d}] mismatch: ref={d} got={d}\n",
    255                 .{ i, ref_tag, got_tag },
    256             );
    257             return error.TestExpectedEqual;
    258         }
    259     }
    260 
    261     // 3. Compare instruction data field-by-field.
    262     for (0..ref_len) |i| {
    263         try expectEqualData(i, ref_tags[i], ref_datas[i], got.inst_datas[i]);
    264     }
    265     // 4. Compare string bytes.
    266     const ref_sb_len: u32 = @intCast(ref.string_bytes.len);
    267     try std.testing.expectEqual(ref_sb_len, got.string_bytes_len);
    268     for (0..ref_sb_len) |i| {
    269         if (ref.string_bytes[i] != got.string_bytes[i]) {
    270             std.debug.print(
    271                 "string_bytes[{d}] mismatch: ref=0x{x:0>2} got=0x{x:0>2}\n",
    272                 .{ i, ref.string_bytes[i], got.string_bytes[i] },
    273             );
    274             return error.TestExpectedEqual;
    275         }
    276     }
    277 
    278     // 5. Compare extra data (skipping hash positions).
    279     const skip = try buildHashSkipMask(gpa, ref);
    280     defer gpa.free(skip);
    281     const ref_extra_len: u32 = @intCast(ref.extra.len);
    282     try std.testing.expectEqual(ref_extra_len, got.extra_len);
    283     for (0..ref_extra_len) |i| {
    284         if (skip[i]) continue;
    285         if (ref.extra[i] != got.extra[i]) {
    286             // Show first 10 extra diffs.
    287             var count: u32 = 0;
    288             for (0..ref_extra_len) |j| {
    289                 if (!skip[j] and ref.extra[j] != got.extra[j]) {
    290                     std.debug.print(
    291                         "extra[{d}] mismatch: ref={d} got={d}\n",
    292                         .{ j, ref.extra[j], got.extra[j] },
    293                     );
    294                     count += 1;
    295                     if (count >= 10) break;
    296                 }
    297             }
    298             return error.TestExpectedEqual;
    299         }
    300     }
    301 }
    302 
    303 /// Compare a single instruction's data, dispatching by tag.
    304 /// Zig's Data union has no guaranteed in-memory layout, so we
    305 /// compare each variant's fields individually.
    306 fn expectEqualData(
    307     idx: usize,
    308     tag: Zir.Inst.Tag,
    309     ref: Zir.Inst.Data,
    310     got: c.ZirInstData,
    311 ) !void {
    312     switch (tag) {
    313         .extended => {
    314             const r = ref.extended;
    315             const g = got.extended;
    316             // Some extended opcodes have undefined/unused small+operand.
    317             const skip_data = switch (r.opcode) {
    318                 .dbg_empty_stmt, .astgen_error => true,
    319                 else => false,
    320             };
    321             const skip_small = switch (r.opcode) {
    322                 .add_with_overflow,
    323                 .sub_with_overflow,
    324                 .mul_with_overflow,
    325                 .shl_with_overflow,
    326                 .restore_err_ret_index,
    327                 .branch_hint,
    328                 => true,
    329                 else => false,
    330             };
    331             if (@intFromEnum(r.opcode) != g.opcode or
    332                 (!skip_data and !skip_small and r.small != g.small) or
    333                 (!skip_data and r.operand != g.operand))
    334             {
    335                 std.debug.print(
    336                     "inst_datas[{d}] (extended) mismatch:\n" ++
    337                         "  ref: opcode={d} small=0x{x:0>4} operand={d}\n" ++
    338                         "  got: opcode={d} small=0x{x:0>4} operand={d}\n",
    339                     .{
    340                         idx,
    341                         @intFromEnum(r.opcode),
    342                         r.small,
    343                         r.operand,
    344                         g.opcode,
    345                         g.small,
    346                         g.operand,
    347                     },
    348                 );
    349                 return error.TestExpectedEqual;
    350             }
    351         },
    352         .declaration => {
    353             const r = ref.declaration;
    354             const g = got.declaration;
    355             if (@intFromEnum(r.src_node) != g.src_node or
    356                 r.payload_index != g.payload_index)
    357             {
    358                 std.debug.print(
    359                     "inst_datas[{d}] (declaration) mismatch:\n" ++
    360                         "  ref: src_node={d} payload_index={d}\n" ++
    361                         "  got: src_node={d} payload_index={d}\n",
    362                     .{
    363                         idx,
    364                         @intFromEnum(r.src_node),
    365                         r.payload_index,
    366                         g.src_node,
    367                         g.payload_index,
    368                     },
    369                 );
    370                 return error.TestExpectedEqual;
    371             }
    372         },
    373         .break_inline => {
    374             const r = ref.@"break";
    375             const g = got.break_data;
    376             if (@intFromEnum(r.operand) != g.operand or
    377                 r.payload_index != g.payload_index)
    378             {
    379                 std.debug.print(
    380                     "inst_datas[{d}] (break_inline) mismatch:\n" ++
    381                         "  ref: operand={d} payload_index={d}\n" ++
    382                         "  got: operand={d} payload_index={d}\n",
    383                     .{
    384                         idx,
    385                         @intFromEnum(r.operand),
    386                         r.payload_index,
    387                         g.operand,
    388                         g.payload_index,
    389                     },
    390                 );
    391                 return error.TestExpectedEqual;
    392             }
    393         },
    394         .import => {
    395             const r = ref.pl_tok;
    396             const g = got.pl_tok;
    397             if (@intFromEnum(r.src_tok) != g.src_tok or
    398                 r.payload_index != g.payload_index)
    399             {
    400                 std.debug.print(
    401                     "inst_datas[{d}] (import) mismatch:\n" ++
    402                         "  ref: src_tok={d} payload_index={d}\n" ++
    403                         "  got: src_tok={d} payload_index={d}\n",
    404                     .{
    405                         idx,
    406                         @intFromEnum(r.src_tok),
    407                         r.payload_index,
    408                         g.src_tok,
    409                         g.payload_index,
    410                     },
    411                 );
    412                 return error.TestExpectedEqual;
    413             }
    414         },
    415         .dbg_stmt => {
    416             const r = ref.dbg_stmt;
    417             const g = got.dbg_stmt;
    418             if (r.line != g.line or r.column != g.column) {
    419                 std.debug.print(
    420                     "inst_datas[{d}] (dbg_stmt) mismatch:\n" ++
    421                         "  ref: line={d} column={d}\n" ++
    422                         "  got: line={d} column={d}\n",
    423                     .{ idx, r.line, r.column, g.line, g.column },
    424                 );
    425                 return error.TestExpectedEqual;
    426             }
    427         },
    428         .ensure_result_non_error,
    429         .restore_err_ret_index_unconditional,
    430         .validate_struct_init_ty,
    431         .validate_struct_init_result_ty,
    432         .struct_init_empty_result,
    433         .struct_init_empty,
    434         .struct_init_empty_ref_result,
    435         => {
    436             const r = ref.un_node;
    437             const g = got.un_node;
    438             if (@intFromEnum(r.src_node) != g.src_node or
    439                 @intFromEnum(r.operand) != g.operand)
    440             {
    441                 std.debug.print(
    442                     "inst_datas[{d}] ({s}) mismatch:\n" ++
    443                         "  ref: src_node={d} operand={d}\n" ++
    444                         "  got: src_node={d} operand={d}\n",
    445                     .{
    446                         idx,
    447                         @tagName(tag),
    448                         @intFromEnum(r.src_node),
    449                         @intFromEnum(r.operand),
    450                         g.src_node,
    451                         g.operand,
    452                     },
    453                 );
    454                 return error.TestExpectedEqual;
    455             }
    456         },
    457         .ret_implicit => {
    458             const r = ref.un_tok;
    459             const g = got.un_tok;
    460             if (@intFromEnum(r.src_tok) != g.src_tok or
    461                 @intFromEnum(r.operand) != g.operand)
    462             {
    463                 std.debug.print(
    464                     "inst_datas[{d}] (ret_implicit) mismatch:\n" ++
    465                         "  ref: src_tok={d} operand={d}\n" ++
    466                         "  got: src_tok={d} operand={d}\n",
    467                     .{
    468                         idx,
    469                         @intFromEnum(r.src_tok),
    470                         @intFromEnum(r.operand),
    471                         g.src_tok,
    472                         g.operand,
    473                     },
    474                 );
    475                 return error.TestExpectedEqual;
    476             }
    477         },
    478         .func,
    479         .func_inferred,
    480         .array_type,
    481         .array_type_sentinel,
    482         .array_cat,
    483         .array_init,
    484         .array_init_ref,
    485         .error_set_decl,
    486         .struct_init_field_type,
    487         .struct_init,
    488         .struct_init_ref,
    489         .validate_array_init_ref_ty,
    490         .validate_array_init_ty,
    491         => {
    492             const r = ref.pl_node;
    493             const g = got.pl_node;
    494             if (@intFromEnum(r.src_node) != g.src_node or
    495                 r.payload_index != g.payload_index)
    496             {
    497                 std.debug.print(
    498                     "inst_datas[{d}] ({s}) mismatch:\n" ++
    499                         "  ref: src_node={d} payload_index={d}\n" ++
    500                         "  got: src_node={d} payload_index={d}\n",
    501                     .{
    502                         idx,
    503                         @tagName(tag),
    504                         @intFromEnum(r.src_node),
    505                         r.payload_index,
    506                         g.src_node,
    507                         g.payload_index,
    508                     },
    509                 );
    510                 return error.TestExpectedEqual;
    511             }
    512         },
    513         .decl_val, .decl_ref => {
    514             const r = ref.str_tok;
    515             const g = got.str_tok;
    516             if (@intFromEnum(r.start) != g.start or @intFromEnum(r.src_tok) != g.src_tok) {
    517                 std.debug.print(
    518                     "inst_datas[{d}] ({s}) mismatch:\n" ++
    519                         "  ref: start={d} src_tok={d}\n" ++
    520                         "  got: start={d} src_tok={d}\n",
    521                     .{
    522                         idx,
    523                         @tagName(tag),
    524                         @intFromEnum(r.start),
    525                         @intFromEnum(r.src_tok),
    526                         g.start,
    527                         g.src_tok,
    528                     },
    529                 );
    530                 return error.TestExpectedEqual;
    531             }
    532         },
    533         .field_val, .field_ptr, .field_val_named, .field_ptr_named => {
    534             const r = ref.pl_node;
    535             const g = got.pl_node;
    536             if (@intFromEnum(r.src_node) != g.src_node or
    537                 r.payload_index != g.payload_index)
    538             {
    539                 std.debug.print(
    540                     "inst_datas[{d}] ({s}) mismatch:\n" ++
    541                         "  ref: src_node={d} payload_index={d}\n" ++
    542                         "  got: src_node={d} payload_index={d}\n",
    543                     .{
    544                         idx,
    545                         @tagName(tag),
    546                         @intFromEnum(r.src_node),
    547                         r.payload_index,
    548                         g.src_node,
    549                         g.payload_index,
    550                     },
    551                 );
    552                 return error.TestExpectedEqual;
    553             }
    554         },
    555         .int => {
    556             if (ref.int != got.int_val) {
    557                 std.debug.print(
    558                     "inst_datas[{d}] (int) mismatch: ref={d} got={d}\n",
    559                     .{ idx, ref.int, got.int_val },
    560                 );
    561                 return error.TestExpectedEqual;
    562             }
    563         },
    564         .ptr_type => {
    565             // Compare ptr_type data: flags, size, payload_index.
    566             if (@as(u8, @bitCast(ref.ptr_type.flags)) != got.ptr_type.flags or
    567                 @intFromEnum(ref.ptr_type.size) != got.ptr_type.size or
    568                 ref.ptr_type.payload_index != got.ptr_type.payload_index)
    569             {
    570                 std.debug.print(
    571                     "inst_datas[{d}] (ptr_type) mismatch:\n" ++
    572                         "  ref: flags=0x{x} size={d} pi={d}\n" ++
    573                         "  got: flags=0x{x} size={d} pi={d}\n",
    574                     .{
    575                         idx,
    576                         @as(u8, @bitCast(ref.ptr_type.flags)),
    577                         @intFromEnum(ref.ptr_type.size),
    578                         ref.ptr_type.payload_index,
    579                         got.ptr_type.flags,
    580                         got.ptr_type.size,
    581                         got.ptr_type.payload_index,
    582                     },
    583                 );
    584                 return error.TestExpectedEqual;
    585             }
    586         },
    587         .int_type => {
    588             const r = ref.int_type;
    589             const g = got.int_type;
    590             if (@intFromEnum(r.src_node) != g.src_node or
    591                 @intFromEnum(r.signedness) != g.signedness or
    592                 r.bit_count != g.bit_count)
    593             {
    594                 std.debug.print(
    595                     "inst_datas[{d}] (int_type) mismatch\n",
    596                     .{idx},
    597                 );
    598                 return error.TestExpectedEqual;
    599             }
    600         },
    601         .str => {
    602             const r = ref.str;
    603             const g = got.str;
    604             if (@intFromEnum(r.start) != g.start or r.len != g.len) {
    605                 std.debug.print(
    606                     "inst_datas[{d}] (str) mismatch:\n" ++
    607                         "  ref: start={d} len={d}\n" ++
    608                         "  got: start={d} len={d}\n",
    609                     .{ idx, @intFromEnum(r.start), r.len, g.start, g.len },
    610                 );
    611                 return error.TestExpectedEqual;
    612             }
    613         },
    614         else => {
    615             // Generic raw comparison: treat data as two u32 words.
    616             // Tags using .node data format have undefined second word.
    617             const ref_raw = @as([*]const u32, @ptrCast(&ref));
    618             const got_raw = @as([*]const u32, @ptrCast(&got));
    619             // Tags where only the first u32 word is meaningful
    620             // (second word is padding/undefined).
    621             const first_word_only = switch (tag) {
    622                 // .node data format (single i32):
    623                 .repeat,
    624                 .repeat_inline,
    625                 .ret_ptr,
    626                 .ret_type,
    627                 .trap,
    628                 .alloc_inferred,
    629                 .alloc_inferred_mut,
    630                 .alloc_inferred_comptime,
    631                 .alloc_inferred_comptime_mut,
    632                 // .@"unreachable" data format (src_node + padding):
    633                 .@"unreachable",
    634                 // .save_err_ret_index data format (operand only):
    635                 .save_err_ret_index,
    636                 => true,
    637                 else => false,
    638             };
    639             const w1_match = ref_raw[0] == got_raw[0];
    640             const w2_match = first_word_only or ref_raw[1] == got_raw[1];
    641             if (!w1_match or !w2_match) {
    642                 std.debug.print(
    643                     "inst_datas[{d}] ({s}) raw mismatch:\n" ++
    644                         "  ref: 0x{x:0>8} 0x{x:0>8}\n" ++
    645                         "  got: 0x{x:0>8} 0x{x:0>8}\n",
    646                     .{
    647                         idx,
    648                         @tagName(tag),
    649                         ref_raw[0],
    650                         ref_raw[1],
    651                         got_raw[0],
    652                         got_raw[1],
    653                     },
    654                 );
    655                 return error.TestExpectedEqual;
    656             }
    657         },
    658     }
    659 }
    660 
    661 const corpus_files = .{
    662     .{ "astgen_test.zig", @embedFile("astgen_test.zig") },
    663     .{ "build.zig", @embedFile("build.zig") },
    664     .{ "parser_test.zig", @embedFile("parser_test.zig") },
    665     .{ "test_all.zig", @embedFile("test_all.zig") },
    666     .{ "tokenizer_test.zig", @embedFile("tokenizer_test.zig") },
    667 };
    668 
    669 fn corpusCheck(gpa: Allocator, source: [:0]const u8) !void {
    670     var tree = try Ast.parse(gpa, source, .zig);
    671     defer tree.deinit(gpa);
    672 
    673     var ref_zir = try AstGen.generate(gpa, tree);
    674     defer ref_zir.deinit(gpa);
    675 
    676     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    677     defer c.astDeinit(&c_ast);
    678     var c_zir = c.astGen(&c_ast);
    679     defer c.zirDeinit(&c_zir);
    680 
    681     if (c_zir.has_compile_errors) {
    682         std.debug.print("C port returned compile errors (inst_len={d})\n", .{c_zir.inst_len});
    683         return error.TestUnexpectedResult;
    684     }
    685 
    686     try expectEqualZir(gpa, ref_zir, c_zir);
    687 }
    688 
    689 test "astgen: struct single field" {
    690     const gpa = std.testing.allocator;
    691     const source: [:0]const u8 = "const T = struct { x: u32 };";
    692     var ref_zir = try refZir(gpa, source);
    693     defer ref_zir.deinit(gpa);
    694     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    695     defer c.astDeinit(&c_ast);
    696     var c_zir = c.astGen(&c_ast);
    697     defer c.zirDeinit(&c_zir);
    698     try expectEqualZir(gpa, ref_zir, c_zir);
    699 }
    700 
    701 test "astgen: struct multiple fields" {
    702     const gpa = std.testing.allocator;
    703     const source: [:0]const u8 = "const T = struct { x: u32, y: bool };";
    704     var ref_zir = try refZir(gpa, source);
    705     defer ref_zir.deinit(gpa);
    706     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    707     defer c.astDeinit(&c_ast);
    708     var c_zir = c.astGen(&c_ast);
    709     defer c.zirDeinit(&c_zir);
    710     try expectEqualZir(gpa, ref_zir, c_zir);
    711 }
    712 
    713 test "astgen: struct field with default" {
    714     const gpa = std.testing.allocator;
    715     const source: [:0]const u8 = "const T = struct { x: u32 = 0 };";
    716     var ref_zir = try refZir(gpa, source);
    717     defer ref_zir.deinit(gpa);
    718     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    719     defer c.astDeinit(&c_ast);
    720     var c_zir = c.astGen(&c_ast);
    721     defer c.zirDeinit(&c_zir);
    722     try expectEqualZir(gpa, ref_zir, c_zir);
    723 }
    724 
    725 test "astgen: struct field with align" {
    726     const gpa = std.testing.allocator;
    727     const source: [:0]const u8 = "const T = struct { x: u32 align(4) };";
    728     var ref_zir = try refZir(gpa, source);
    729     defer ref_zir.deinit(gpa);
    730     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    731     defer c.astDeinit(&c_ast);
    732     var c_zir = c.astGen(&c_ast);
    733     defer c.zirDeinit(&c_zir);
    734     try expectEqualZir(gpa, ref_zir, c_zir);
    735 }
    736 
    737 test "astgen: struct comptime field" {
    738     const gpa = std.testing.allocator;
    739     const source: [:0]const u8 = "const T = struct { comptime x: u32 = 0 };";
    740     var ref_zir = try refZir(gpa, source);
    741     defer ref_zir.deinit(gpa);
    742     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    743     defer c.astDeinit(&c_ast);
    744     var c_zir = c.astGen(&c_ast);
    745     defer c.zirDeinit(&c_zir);
    746     try expectEqualZir(gpa, ref_zir, c_zir);
    747 }
    748 
    749 test "astgen: empty error set" {
    750     const gpa = std.testing.allocator;
    751     const source: [:0]const u8 = "const E = error{};";
    752     var ref_zir = try refZir(gpa, source);
    753     defer ref_zir.deinit(gpa);
    754     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    755     defer c.astDeinit(&c_ast);
    756     var c_zir = c.astGen(&c_ast);
    757     defer c.zirDeinit(&c_zir);
    758     try expectEqualZir(gpa, ref_zir, c_zir);
    759 }
    760 
    761 test "astgen: error set with members" {
    762     const gpa = std.testing.allocator;
    763     const source: [:0]const u8 = "const E = error{ OutOfMemory, OutOfTime };";
    764     var ref_zir = try refZir(gpa, source);
    765     defer ref_zir.deinit(gpa);
    766     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    767     defer c.astDeinit(&c_ast);
    768     var c_zir = c.astGen(&c_ast);
    769     defer c.zirDeinit(&c_zir);
    770     try expectEqualZir(gpa, ref_zir, c_zir);
    771 }
    772 
    773 test "astgen: extern var" {
    774     const gpa = std.testing.allocator;
    775     const source: [:0]const u8 = "extern var x: u32;";
    776     var ref_zir = try refZir(gpa, source);
    777     defer ref_zir.deinit(gpa);
    778     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    779     defer c.astDeinit(&c_ast);
    780     var c_zir = c.astGen(&c_ast);
    781     defer c.zirDeinit(&c_zir);
    782     try expectEqualZir(gpa, ref_zir, c_zir);
    783 }
    784 
    785 test "astgen: corpus test_all.zig" {
    786     const gpa = std.testing.allocator;
    787     try corpusCheck(gpa, @embedFile("test_all.zig"));
    788 }
    789 
    790 test "astgen: corpus build.zig" {
    791     const gpa = std.testing.allocator;
    792     try corpusCheck(gpa, @embedFile("build.zig"));
    793 }
    794 
    795 test "astgen: corpus tokenizer_test.zig" {
    796     const gpa = std.testing.allocator;
    797     try corpusCheck(gpa, @embedFile("tokenizer_test.zig"));
    798 }
    799 
    800 test "astgen: corpus parser_test.zig" {
    801     // TODO: 10+ extra data mismatches (ref=48 got=32, bit 4 = propagate_error_trace)
    802     // in call instruction flags — ctx propagation differs from upstream.
    803     if (true) return error.SkipZigTest;
    804     const gpa = std.testing.allocator;
    805     try corpusCheck(gpa, @embedFile("parser_test.zig"));
    806 }
    807 
    808 test "astgen: corpus astgen_test.zig" {
    809     const gpa = std.testing.allocator;
    810     try corpusCheck(gpa, @embedFile("astgen_test.zig"));
    811 }
    812 
    813 test "astgen: enum decl" {
    814     const gpa = std.testing.allocator;
    815     const source: [:0]const u8 = "const E = enum { a, b, c };";
    816     var ref_zir = try refZir(gpa, source);
    817     defer ref_zir.deinit(gpa);
    818     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    819     defer c.astDeinit(&c_ast);
    820     var c_zir = c.astGen(&c_ast);
    821     defer c.zirDeinit(&c_zir);
    822     try expectEqualZir(gpa, ref_zir, c_zir);
    823 }
    824 
    825 test "astgen: struct init typed" {
    826     const gpa = std.testing.allocator;
    827     const source: [:0]const u8 =
    828         \\const T = struct { x: u32 };
    829         \\const v = T{ .x = 1 };
    830     ;
    831     var ref_zir = try refZir(gpa, source);
    832     defer ref_zir.deinit(gpa);
    833     var c_ast = c.astParse(source.ptr, @intCast(source.len));
    834     defer c.astDeinit(&c_ast);
    835     var c_zir = c.astGen(&c_ast);
    836     defer c.zirDeinit(&c_zir);
    837     try expectEqualZir(gpa, ref_zir, c_zir);
    838 }
    839 
    840 test "astgen: corpus" {
    841     if (true) return error.SkipZigTest; // TODO: parser_test.zig fails
    842     const gpa = std.testing.allocator;
    843 
    844     var any_fail = false;
    845     inline for (corpus_files) |entry| {
    846         corpusCheck(gpa, entry[1]) catch {
    847             any_fail = true;
    848         };
    849     }
    850     if (any_fail) return error.ZirMismatch;
    851 }