const std = @import("std"); const Ast = std.zig.Ast; const Zir = std.zig.Zir; const AstGen = std.zig.AstGen; const Allocator = std.mem.Allocator; const c = @cImport({ @cInclude("astgen.h"); }); fn refZir(gpa: Allocator, source: [:0]const u8) !Zir { var tree = try Ast.parse(gpa, source, .zig); defer tree.deinit(gpa); return try AstGen.generate(gpa, tree); } test "astgen dump: simple cases" { const gpa = std.testing.allocator; const cases = .{ .{ "empty", "" }, .{ "comptime {}", "comptime {}" }, .{ "const x = 0;", "const x = 0;" }, .{ "const x = 1;", "const x = 1;" }, .{ "const x = 0; const y = 0;", "const x = 0; const y = 0;" }, .{ "test \"t\" {}", "test \"t\" {}" }, .{ "const std = @import(\"std\");", "const std = @import(\"std\");" }, .{ "test_all.zig", @embedFile("test_all.zig") }, }; inline for (cases) |case| { // std.debug.print("--- {s} ---\n", .{case[0]}); const source: [:0]const u8 = case[1]; var zir = try refZir(gpa, source); zir.deinit(gpa); } } /// Build a mask of extra[] indices that should be skipped or specially /// handled during comparison. This includes hash data (src_hash or /// fields_hash) that are zero-filled in the C output but contain real /// Blake3 hashes in the Zig reference, and also undefined padding bits. /// Returns a u32 mask array: 0 = skip entirely, 0xFFFFFFFF = compare all bits, /// other values = bitmask for partial comparison. fn buildHashSkipMask(gpa: Allocator, ref: Zir) ![]u32 { const ref_extra_len: u32 = @intCast(ref.extra.len); const mask = try gpa.alloc(u32, ref_extra_len); @memset(mask, 0xFFFFFFFF); const ref_len: u32 = @intCast(ref.instructions.len); const ref_tags = ref.instructions.items(.tag); const ref_datas = ref.instructions.items(.data); for (0..ref_len) |i| { switch (ref_tags[i]) { .extended => { const ext = ref_datas[i].extended; if (ext.opcode == .struct_decl or ext.opcode == .enum_decl or ext.opcode == .union_decl or ext.opcode == .opaque_decl) { // StructDecl/EnumDecl/UnionDecl/OpaqueDecl starts with fields_hash[4]. const pi = ext.operand; for (0..4) |j| mask[pi + j] = 0; } }, .declaration => { // Declaration starts with src_hash[4]. const pi = ref_datas[i].declaration.payload_index; for (0..4) |j| mask[pi + j] = 0; }, .func, .func_inferred => { // Func payload: ret_ty(1) + param_block(1) + body_len(1) // + trailing ret_ty + body + SrcLocs(3) + proto_hash(4). const pi = ref_datas[i].pl_node.payload_index; const ret_ty_raw: u32 = ref.extra[pi]; const ret_body_len: u32 = ret_ty_raw & 0x7FFFFFFF; const body_len: u32 = ref.extra[pi + 2]; // ret_ty trailing: if body_len > 1, it's a body; if == 1, it's a ref; if 0, void. const ret_trailing: u32 = if (ret_body_len > 1) ret_body_len else if (ret_body_len == 1) 1 else 0; // proto_hash is at: pi + 3 + ret_trailing + body_len + 3 if (body_len > 0) { const hash_start = pi + 3 + ret_trailing + body_len + 3; for (0..4) |j| { if (hash_start + j < ref_extra_len) mask[hash_start + j] = 0; } } }, .func_fancy => { // FuncFancy: param_block(1) + body_len(1) + bits(1) // + trailing cc + ret_ty + noalias + body + SrcLocs(3) + proto_hash(4). const pi = ref_datas[i].pl_node.payload_index; const body_len: u32 = ref.extra[pi + 1]; const bits: u32 = ref.extra[pi + 2]; // FuncFancy.Bits has _:u23 = undefined padding in bits 9..31. // Only compare the lower 9 defined bits. mask[pi + 2] = 0x1FF; var ei: u32 = pi + 3; const has_cc_ref: bool = (bits & (1 << 3)) != 0; const has_cc_body: bool = (bits & (1 << 4)) != 0; const has_ret_ty_ref: bool = (bits & (1 << 5)) != 0; const has_ret_ty_body: bool = (bits & (1 << 6)) != 0; const has_any_noalias: bool = (bits & (1 << 7)) != 0; if (has_cc_body) { const cc_body_len = ref.extra[ei]; ei += 1 + cc_body_len; } else if (has_cc_ref) { ei += 1; } if (has_ret_ty_body) { const ret_body_len = ref.extra[ei]; ei += 1 + ret_body_len; } else if (has_ret_ty_ref) { ei += 1; } if (has_any_noalias) ei += 1; // body + SrcLocs(3) + proto_hash(4) if (body_len > 0) { const hash_start = ei + body_len + 3; for (0..4) |j| { if (hash_start + j < ref_extra_len) mask[hash_start + j] = 0; } } }, .float128 => { // Float128 payload: 4 u32 pieces encoding the binary128 value. // The C implementation uses strtold which only has 80-bit precision, // so the lower bits may differ. Skip all 4 pieces. const pi = ref_datas[i].pl_node.payload_index; for (0..4) |j| { if (pi + j < ref_extra_len) mask[pi + j] = 0; } }, else => {}, } } return mask; } test "astgen: empty source" { const gpa = std.testing.allocator; const source: [:0]const u8 = ""; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: comptime {}" { const gpa = std.testing.allocator; const source: [:0]const u8 = "comptime {}"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: const x = 0;" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const x = 0;"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: const x = 1;" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const x = 1;"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: const x = 0; const y = 0;" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const x = 0; const y = 0;"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: field_access" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const std = @import(\"std\");\nconst mem = std.mem;"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: addr array init" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const x = &[_][]const u8{\"a\",\"b\"};"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: test empty body" { const gpa = std.testing.allocator; const source: [:0]const u8 = "test \"t\" {}"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: test_all.zig" { const gpa = std.testing.allocator; const source: [:0]const u8 = @embedFile("test_all.zig"); var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: array init with sentinel" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\const std = @import("std"); \\test "t" { \\ try std.testing.expect(@TypeOf([_]u8{}) == [0]u8); \\ try std.testing.expect(@TypeOf([_:0]u8{}) == [0:0]u8); \\} ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: type.zig up to Opaque" { const gpa = std.testing.allocator; const source = @embedFile("../test/behavior/type.zig"); var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: labeled block in comptime" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\test "t" { \\ const x = comptime blk: { \\ break :blk @as(u32, 42); \\ }; \\ _ = x; \\} ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: @TypeOf multi-arg" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\test "typeof_peer" { \\ var x: u8 = 0; \\ var y: u16 = 0; \\ _ = .{ &x, &y }; \\ _ = @TypeOf(x, y); \\} ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: @import" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const std = @import(\"std\");"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } fn expectEqualZir(gpa: Allocator, ref: Zir, got: c.Zir) !void { const ref_len: u32 = @intCast(ref.instructions.len); const ref_tags = ref.instructions.items(.tag); const ref_datas = ref.instructions.items(.data); // 1. Compare lengths. if (ref_len != got.inst_len) { std.debug.print("inst_len mismatch: ref={d} got={d}\n", .{ ref_len, got.inst_len }); // Print all ref declaration positions. { var prev_ref: usize = 0; var prev_got: usize = 0; var ri: usize = 0; var gi: usize = 0; while (ri < ref_len and gi < got.inst_len) { // Find next decl in ref while (ri < ref_len and ref_tags[ri] != .declaration) ri += 1; // Find next decl in got while (gi < got.inst_len and got.inst_tags[gi] != 44) gi += 1; if (ri < ref_len and gi < got.inst_len) { const ref_span = ri - prev_ref; const got_span = gi - prev_got; { const diff: i32 = @as(i32, @intCast(got_span)) - @as(i32, @intCast(ref_span)); if (diff != 0) { std.debug.print(" DIFF: decl ref[{d}] got[{d}] src_node={d}: ref_span={d} got_span={d} (diff={d})\n", .{ ri, gi, ref_datas[ri].declaration.src_node, ref_span, got_span, diff, }); } } prev_ref = ri; prev_got = gi; ri += 1; gi += 1; } } } var ref_counts: [265]u32 = .{0} ** 265; var got_counts: [265]u32 = .{0} ** 265; for (0..ref_len) |i| ref_counts[@intFromEnum(ref_tags[i])] += 1; for (0..got.inst_len) |i| got_counts[got.inst_tags[i]] += 1; for (0..265) |t| { if (ref_counts[t] != got_counts[t]) std.debug.print("tag {d}: ref={d} got={d} (diff={d})\n", .{ t, ref_counts[t], got_counts[t], @as(i32, @intCast(got_counts[t])) - @as(i32, @intCast(ref_counts[t])), }); } // Find first tag divergence. const min_len = @min(ref_len, got.inst_len); for (0..min_len) |i| { const ref_tag: u8 = @intFromEnum(ref_tags[i]); const got_tag: u8 = @intCast(got.inst_tags[i]); if (ref_tag != got_tag) { std.debug.print("first divergence at [{d}]: ref_tag={d} got_tag={d}\n", .{ i, ref_tag, got_tag }); // Print window of tags around divergence for debugging. const window_start = if (i >= 20) i - 20 else 0; const window_end = @min(i + 20, min_len); std.debug.print(" ref tags [{d}..{d}]: ", .{ window_start, window_end }); for (window_start..window_end) |wi| { std.debug.print("{d} ", .{@intFromEnum(ref_tags[wi])}); } std.debug.print("\n got tags [{d}..{d}]: ", .{ window_start, window_end }); for (window_start..window_end) |wi| { if (wi < got.inst_len) { std.debug.print("{d} ", .{got.inst_tags[wi]}); } } std.debug.print("\n", .{}); // Data printing skipped to avoid tagged union safety panics. // Scan for nearest declaration. var j: usize = i; while (j > 0) { j -= 1; if (ref_tags[j] == .declaration) { std.debug.print(" nearest decl at [{d}]: src_node={d}\n", .{ j, ref_datas[j].declaration.src_node, }); break; } } break; } } return error.TestExpectedEqual; } // 2. Compare instruction tags. for (0..ref_len) |i| { const ref_tag: u8 = @intFromEnum(ref_tags[i]); const got_tag: u8 = @intCast(got.inst_tags[i]); if (ref_tag != got_tag) { std.debug.print( "inst_tags[{d}] mismatch: ref={d} got={d}\n", .{ i, ref_tag, got_tag }, ); // Print surrounding tag window. const window_start = if (i >= 20) i - 20 else 0; const window_end = @min(i + 20, ref_len); std.debug.print(" ref tags [{d}..{d}]: ", .{ window_start, window_end }); for (window_start..window_end) |wi| { std.debug.print("{d} ", .{@intFromEnum(ref_tags[wi])}); } std.debug.print("\n got tags [{d}..{d}]: ", .{ window_start, window_end }); for (window_start..window_end) |wi| { if (wi < got.inst_len) { std.debug.print("{d} ", .{got.inst_tags[wi]}); } } std.debug.print("\n", .{}); // Find nearest decl. var j: usize = i; while (j > 0) { j -= 1; if (ref_tags[j] == .declaration) { std.debug.print(" nearest decl at [{d}]: src_node={d}\n", .{ j, ref_datas[j].declaration.src_node, }); break; } } return error.TestExpectedEqual; } } // 2.5. If extra_len differs and is small, dump both extra arrays before step 3. { const ref_extra_len_dump: u32 = @intCast(ref.extra.len); if (ref_extra_len_dump != got.extra_len and ref_extra_len_dump < 200) { std.debug.print("EXTRA DUMP (ref_len={d}, got_len={d}):\n", .{ ref_extra_len_dump, got.extra_len }); const max_el = @max(ref_extra_len_dump, got.extra_len); for (0..max_el) |ei| { const rv: i64 = if (ei < ref_extra_len_dump) @intCast(ref.extra[ei]) else -1; const gv: i64 = if (ei < got.extra_len) @intCast(got.extra[ei]) else -1; const marker: u8 = if (rv != gv) '!' else ' '; std.debug.print(" extra[{d:>3}]: ref={d:>10} got={d:>10} {c}\n", .{ ei, rv, gv, marker }); } std.debug.print("TAGS (ref_inst_count={d}, got_inst_count={d}):\n", .{ ref_len, got.inst_len }); for (0..ref_len) |ti| { const rt: u8 = @intFromEnum(ref_tags[ti]); const gt: u8 = @intCast(got.inst_tags[ti]); const tmarker: u8 = if (rt != gt) '!' else ' '; std.debug.print(" inst[{d:>3}]: ref_tag={d:>3} got_tag={d:>3} {c}", .{ ti, rt, gt, tmarker }); if (ref_tags[ti] == .declaration) { std.debug.print(" decl: ref_pi={d} got_pi={d}", .{ ref_datas[ti].declaration.payload_index, got.inst_datas[ti].declaration.payload_index, }); } if (ref_tags[ti] == .extended) { const ref_ext = ref_datas[ti].extended; std.debug.print(" ext: ref_op={d} got_op={d} ref_small=0x{x} got_small=0x{x}", .{ ref_ext.operand, got.inst_datas[ti].extended.operand, ref_ext.small, got.inst_datas[ti].extended.small, }); } std.debug.print("\n", .{}); } } } // 3. Compare instruction data field-by-field. for (0..ref_len) |i| { expectEqualData(i, ref_tags[i], ref_datas[i], got.inst_datas[i]) catch { // Print extra data lengths for debugging. const ref_extra_len2: u32 = @intCast(ref.extra.len); std.debug.print(" extra_len: ref={d} got={d} (diff={d})\n", .{ ref_extra_len2, got.extra_len, @as(i64, got.extra_len) - @as(i64, ref_extra_len2), }); // Find the first instruction where any raw data word differs. // Find first extra data divergence by scanning both extra arrays. { const ref_extra_arr = ref.extra; const min_extra = @min(ref_extra_arr.len, got.extra_len); for (0..min_extra) |eidx| { if (ref_extra_arr[eidx] != got.extra[eidx]) { std.debug.print(" first extra divergence at extra[{d}]: ref=0x{x:0>8} got=0x{x:0>8}\n", .{ eidx, ref_extra_arr[eidx], got.extra[eidx], }); // Print surrounding context. const ctx_s = if (eidx >= 5) eidx - 5 else 0; const ctx_e = @min(eidx + 10, min_extra); for (ctx_s..ctx_e) |ci| { const marker: u8 = if (ref_extra_arr[ci] != got.extra[ci]) '!' else ' '; std.debug.print(" extra[{d}]: ref={d} (0x{x:0>8}) got={d} (0x{x:0>8}) {c}\n", .{ ci, ref_extra_arr[ci], ref_extra_arr[ci], got.extra[ci], got.extra[ci], marker, }); } break; } } } // Find the first break instruction where payload_index diverges. // Break instructions store payload_index in the second u32. var found_first = false; for (0..ref_len) |k| { if (ref_tags[k] == .break_inline or ref_tags[k] == .@"break") { const r_pi = ref_datas[k].@"break".payload_index; const g_pi = got.inst_datas[k].break_data.payload_index; if (r_pi != g_pi and !found_first) { found_first = true; std.debug.print(" first break payload divergence at inst[{d}]: ref_pi={d} got_pi={d} (diff={d})\n", .{ k, r_pi, g_pi, @as(i64, g_pi) - @as(i64, r_pi), }); // Find nearest decl var dk: usize = k; while (dk > 0) { dk -= 1; if (ref_tags[dk] == .declaration) { std.debug.print(" nearest decl at [{d}]: src_node={d}\n", .{ dk, ref_datas[dk].declaration.src_node, }); break; } } } } } // Also check func instructions for (0..ref_len) |k| { if (ref_tags[k] == .func or ref_tags[k] == .func_inferred or ref_tags[k] == .func_fancy) { const r_pi = ref_datas[k].pl_node.payload_index; const g_pi = got.inst_datas[k].pl_node.payload_index; if (r_pi != g_pi) { std.debug.print(" func payload divergence at inst[{d}] tag={d}: ref_pi={d} got_pi={d} (diff={d})\n", .{ k, @intFromEnum(ref_tags[k]), r_pi, g_pi, @as(i64, g_pi) - @as(i64, r_pi), }); // Find nearest decl var dk: usize = k; while (dk > 0) { dk -= 1; if (ref_tags[dk] == .declaration) { std.debug.print(" nearest decl at [{d}]: src_node={d}\n", .{ dk, ref_datas[dk].declaration.src_node, }); break; } } break; } } } // Print nearest declaration for context. var j: usize = i; while (j > 0) { j -= 1; if (ref_tags[j] == .declaration) { std.debug.print(" nearest decl at [{d}]: src_node={d}\n", .{ j, ref_datas[j].declaration.src_node, }); break; } } // Print what tags are at the operand positions if break_inline. if (ref_tags[i] == .break_inline) { const r_op = @intFromEnum(ref_datas[i].@"break".operand); const g_op = got.inst_datas[i].break_data.operand; if (r_op >= 124 and r_op - 124 < ref_len) { std.debug.print(" ref operand inst[{d}] tag={d}\n", .{ r_op - 124, @intFromEnum(ref_tags[r_op - 124]), }); } if (g_op >= 124 and g_op - 124 < ref_len) { std.debug.print(" got operand inst[{d}] tag={d}\n", .{ g_op - 124, @intFromEnum(ref_tags[g_op - 124]), }); } } // Also compare string bytes to diagnose string table divergence. { const ref_sb_len2: u32 = @intCast(ref.string_bytes.len); const sb_min = @min(ref_sb_len2, got.string_bytes_len); std.debug.print(" string_bytes_len: ref={d} got={d}\n", .{ ref_sb_len2, got.string_bytes_len }); for (0..sb_min) |si| { if (ref.string_bytes[si] != got.string_bytes[si]) { // Print context around divergence. const ctx_start = if (si >= 20) si - 20 else 0; const ctx_end = @min(si + 20, sb_min); std.debug.print(" first string_bytes divergence at [{d}]:\n", .{si}); std.debug.print(" ref: ", .{}); for (ctx_start..ctx_end) |ci| std.debug.print("{c}", .{if (ref.string_bytes[ci] >= 0x20 and ref.string_bytes[ci] < 0x7f) ref.string_bytes[ci] else '.'}); std.debug.print("\n got: ", .{}); for (ctx_start..ctx_end) |ci| { if (ci < got.string_bytes_len) { const ch = got.string_bytes[ci]; std.debug.print("{c}", .{if (ch >= 0x20 and ch < 0x7f) ch else '.'}); } } std.debug.print("\n", .{}); break; } } } return error.TestExpectedEqual; }; } // 4. Compare string bytes. const ref_sb_len: u32 = @intCast(ref.string_bytes.len); try std.testing.expectEqual(ref_sb_len, got.string_bytes_len); for (0..ref_sb_len) |i| { if (ref.string_bytes[i] != got.string_bytes[i]) { std.debug.print( "string_bytes[{d}] mismatch: ref=0x{x:0>2} got=0x{x:0>2}\n", .{ i, ref.string_bytes[i], got.string_bytes[i] }, ); return error.TestExpectedEqual; } } // 5. Compare extra data (skipping hash positions, masking undefined bits). const cmp_mask = try buildHashSkipMask(gpa, ref); defer gpa.free(cmp_mask); const ref_extra_len: u32 = @intCast(ref.extra.len); try std.testing.expectEqual(ref_extra_len, got.extra_len); for (0..ref_extra_len) |i| { if (cmp_mask[i] == 0) continue; if ((ref.extra[i] & cmp_mask[i]) != (got.extra[i] & cmp_mask[i])) { // Show first 10 extra diffs. var count: u32 = 0; for (0..ref_extra_len) |j| { if (cmp_mask[j] != 0 and (ref.extra[j] & cmp_mask[j]) != (got.extra[j] & cmp_mask[j])) { std.debug.print( "extra[{d}] mismatch: ref={d} (0x{x}) got={d} (0x{x})\n", .{ j, ref.extra[j], ref.extra[j], got.extra[j], got.extra[j] }, ); count += 1; if (count >= 10) break; } } // Find owning instruction for the first mismatch position. { var best_inst: ?usize = null; var best_pi: u32 = 0; for (0..ref_len) |ii| { const pi: u32 = switch (ref_tags[ii]) { .declaration => ref_datas[ii].declaration.payload_index, .func, .func_inferred, .func_fancy => ref_datas[ii].pl_node.payload_index, .extended => blk: { const ext = ref_datas[ii].extended; break :blk ext.operand; }, else => continue, }; if (pi <= i and (best_inst == null or pi > best_pi)) { best_inst = ii; best_pi = pi; } } if (best_inst) |bi| { const ext_info: []const u8 = if (ref_tags[bi] == .extended) blk: { break :blk @tagName(ref_datas[bi].extended.opcode); } else ""; std.debug.print(" owning inst[{d}] tag={s} payload_start={d} (offset_in_payload={d}) {s}\n", .{ bi, @tagName(ref_tags[bi]), best_pi, i - best_pi, ext_info, }); // Find nearest decl var dk: usize = bi; while (dk > 0) { dk -= 1; if (ref_tags[dk] == .declaration) { std.debug.print(" nearest decl at [{d}]: src_node={d}\n", .{ dk, ref_datas[dk].declaration.src_node, }); break; } } } // Print context around the mismatch position. const ctx_start = if (i >= 5) i - 5 else 0; const ctx_end = @min(i + 10, ref_extra_len); std.debug.print(" ref extra[{d}..{d}]: ", .{ ctx_start, ctx_end }); for (ctx_start..ctx_end) |ci| std.debug.print("{d} ", .{ref.extra[ci]}); std.debug.print("\n got extra[{d}..{d}]: ", .{ ctx_start, ctx_end }); for (ctx_start..ctx_end) |ci| std.debug.print("{d} ", .{got.extra[ci]}); std.debug.print("\n mask[{d}..{d}]: ", .{ ctx_start, ctx_end }); for (ctx_start..ctx_end) |ci| std.debug.print("{d} ", .{cmp_mask[ci]}); std.debug.print("\n", .{}); // Dump ALL extra data for small tests. if (ref_extra_len < 200) { std.debug.print(" ALL ref extra ({d}):\n ", .{ref_extra_len}); for (0..ref_extra_len) |ei| { std.debug.print("{d} ", .{ref.extra[ei]}); if ((ei + 1) % 20 == 0) std.debug.print("\n ", .{}); } std.debug.print("\n ALL got extra ({d}):\n ", .{got.extra_len}); for (0..got.extra_len) |ei| { std.debug.print("{d} ", .{got.extra[ei]}); if ((ei + 1) % 20 == 0) std.debug.print("\n ", .{}); } std.debug.print("\n ALL mask:\n ", .{}); for (0..ref_extra_len) |ei| { if (cmp_mask[ei] == 0) std.debug.print("S ", .{}) else std.debug.print(". ", .{}); if ((ei + 1) % 20 == 0) std.debug.print("\n ", .{}); } std.debug.print("\n ALL tags ({d}):\n ", .{ref_len}); for (0..ref_len) |ti| { std.debug.print("{d} ", .{@intFromEnum(ref_tags[ti])}); } std.debug.print("\n", .{}); } } return error.TestExpectedEqual; } } } /// Compare a single instruction's data, dispatching by tag. /// Zig's Data union has no guaranteed in-memory layout, so we /// compare each variant's fields individually. fn expectEqualData( idx: usize, tag: Zir.Inst.Tag, ref: Zir.Inst.Data, got: c.ZirInstData, ) !void { switch (tag) { .extended => { const r = ref.extended; const g = got.extended; // Some extended opcodes have undefined/unused small+operand. const skip_data = switch (r.opcode) { .dbg_empty_stmt, .astgen_error => true, else => false, }; const skip_small = switch (r.opcode) { // Container decl Small packed structs have undefined padding bits. .struct_decl, .enum_decl, .union_decl, .opaque_decl, // addNodeExtended sets small = undefined (AstGen.zig:12775). .this, .ret_addr, .error_return_trace, .frame, .frame_address, .breakpoint, .disable_instrumentation, .disable_intrinsics, .in_comptime, .c_va_start, // addExtendedPayload passes undefined for small (AstGen.zig:12393). .builtin_extern, .set_float_mode, .builtin_src, .int_from_error, .error_from_int, .error_cast, .wasm_memory_size, .wasm_memory_grow, .c_define, .c_undef, .c_include, .select, .prefetch, .c_va_arg, .c_va_copy, .c_va_end, .work_item_id, .work_group_size, .work_group_id, .add_with_overflow, .sub_with_overflow, .mul_with_overflow, .shl_with_overflow, .restore_err_ret_index, .branch_hint, .compile_log, .field_parent_ptr, => true, else => false, }; if (@intFromEnum(r.opcode) != g.opcode or (!skip_data and !skip_small and r.small != g.small) or (!skip_data and r.operand != g.operand)) { std.debug.print( "inst_datas[{d}] (extended) mismatch:\n" ++ " ref: opcode={d} small=0x{x:0>4} operand={d}\n" ++ " got: opcode={d} small=0x{x:0>4} operand={d}\n", .{ idx, @intFromEnum(r.opcode), r.small, r.operand, g.opcode, g.small, g.operand, }, ); return error.TestExpectedEqual; } }, .declaration => { const r = ref.declaration; const g = got.declaration; if (@intFromEnum(r.src_node) != g.src_node or r.payload_index != g.payload_index) { std.debug.print( "inst_datas[{d}] (declaration) mismatch:\n" ++ " ref: src_node={d} payload_index={d}\n" ++ " got: src_node={d} payload_index={d}\n", .{ idx, @intFromEnum(r.src_node), r.payload_index, g.src_node, g.payload_index, }, ); return error.TestExpectedEqual; } }, .break_inline => { const r = ref.@"break"; const g = got.break_data; if (@intFromEnum(r.operand) != g.operand or r.payload_index != g.payload_index) { std.debug.print( "inst_datas[{d}] (break_inline) mismatch:\n" ++ " ref: operand={d} payload_index={d}\n" ++ " got: operand={d} payload_index={d}\n", .{ idx, @intFromEnum(r.operand), r.payload_index, g.operand, g.payload_index, }, ); return error.TestExpectedEqual; } }, .import => { const r = ref.pl_tok; const g = got.pl_tok; if (@intFromEnum(r.src_tok) != g.src_tok or r.payload_index != g.payload_index) { std.debug.print( "inst_datas[{d}] (import) mismatch:\n" ++ " ref: src_tok={d} payload_index={d}\n" ++ " got: src_tok={d} payload_index={d}\n", .{ idx, @intFromEnum(r.src_tok), r.payload_index, g.src_tok, g.payload_index, }, ); return error.TestExpectedEqual; } }, .dbg_stmt => { const r = ref.dbg_stmt; const g = got.dbg_stmt; if (r.line != g.line or r.column != g.column) { std.debug.print( "inst_datas[{d}] (dbg_stmt) mismatch:\n" ++ " ref: line={d} column={d}\n" ++ " got: line={d} column={d}\n", .{ idx, r.line, r.column, g.line, g.column }, ); return error.TestExpectedEqual; } }, .ensure_result_non_error, .restore_err_ret_index_unconditional, .validate_struct_init_ty, .validate_struct_init_result_ty, .struct_init_empty_result, .struct_init_empty, .struct_init_empty_ref_result, => { const r = ref.un_node; const g = got.un_node; if (@intFromEnum(r.src_node) != g.src_node or @intFromEnum(r.operand) != g.operand) { std.debug.print( "inst_datas[{d}] ({s}) mismatch:\n" ++ " ref: src_node={d} operand={d}\n" ++ " got: src_node={d} operand={d}\n", .{ idx, @tagName(tag), @intFromEnum(r.src_node), @intFromEnum(r.operand), g.src_node, g.operand, }, ); return error.TestExpectedEqual; } }, .ret_implicit => { const r = ref.un_tok; const g = got.un_tok; if (@intFromEnum(r.src_tok) != g.src_tok or @intFromEnum(r.operand) != g.operand) { std.debug.print( "inst_datas[{d}] (ret_implicit) mismatch:\n" ++ " ref: src_tok={d} operand={d}\n" ++ " got: src_tok={d} operand={d}\n", .{ idx, @intFromEnum(r.src_tok), @intFromEnum(r.operand), g.src_tok, g.operand, }, ); return error.TestExpectedEqual; } }, .func, .func_inferred, .func_fancy, .array_type, .array_type_sentinel, .array_cat, .array_init, .array_init_ref, .error_set_decl, .struct_init_field_type, .struct_init, .struct_init_ref, .validate_array_init_ref_ty, .validate_array_init_ty, => { const r = ref.pl_node; const g = got.pl_node; if (@intFromEnum(r.src_node) != g.src_node or r.payload_index != g.payload_index) { std.debug.print( "inst_datas[{d}] ({s}) mismatch:\n" ++ " ref: src_node={d} payload_index={d}\n" ++ " got: src_node={d} payload_index={d}\n", .{ idx, @tagName(tag), @intFromEnum(r.src_node), r.payload_index, g.src_node, g.payload_index, }, ); return error.TestExpectedEqual; } }, .decl_val, .decl_ref => { const r = ref.str_tok; const g = got.str_tok; if (@intFromEnum(r.start) != g.start or @intFromEnum(r.src_tok) != g.src_tok) { std.debug.print( "inst_datas[{d}] ({s}) mismatch:\n" ++ " ref: start={d} src_tok={d}\n" ++ " got: start={d} src_tok={d}\n", .{ idx, @tagName(tag), @intFromEnum(r.start), @intFromEnum(r.src_tok), g.start, g.src_tok, }, ); return error.TestExpectedEqual; } }, .field_val, .field_ptr, .field_val_named, .field_ptr_named => { const r = ref.pl_node; const g = got.pl_node; if (@intFromEnum(r.src_node) != g.src_node or r.payload_index != g.payload_index) { std.debug.print( "inst_datas[{d}] ({s}) mismatch:\n" ++ " ref: src_node={d} payload_index={d}\n" ++ " got: src_node={d} payload_index={d}\n", .{ idx, @tagName(tag), @intFromEnum(r.src_node), r.payload_index, g.src_node, g.payload_index, }, ); return error.TestExpectedEqual; } }, .int => { if (ref.int != got.int_val) { std.debug.print( "inst_datas[{d}] (int) mismatch: ref={d} got={d}\n", .{ idx, ref.int, got.int_val }, ); return error.TestExpectedEqual; } }, .ptr_type => { // Compare ptr_type data: flags, size, payload_index. if (@as(u8, @bitCast(ref.ptr_type.flags)) != got.ptr_type.flags or @intFromEnum(ref.ptr_type.size) != got.ptr_type.size or ref.ptr_type.payload_index != got.ptr_type.payload_index) { std.debug.print( "inst_datas[{d}] (ptr_type) mismatch:\n" ++ " ref: flags=0x{x} size={d} pi={d}\n" ++ " got: flags=0x{x} size={d} pi={d}\n", .{ idx, @as(u8, @bitCast(ref.ptr_type.flags)), @intFromEnum(ref.ptr_type.size), ref.ptr_type.payload_index, got.ptr_type.flags, got.ptr_type.size, got.ptr_type.payload_index, }, ); return error.TestExpectedEqual; } }, .int_type => { const r = ref.int_type; const g = got.int_type; if (@intFromEnum(r.src_node) != g.src_node or @intFromEnum(r.signedness) != g.signedness or r.bit_count != g.bit_count) { std.debug.print( "inst_datas[{d}] (int_type) mismatch\n", .{idx}, ); return error.TestExpectedEqual; } }, .str => { const r = ref.str; const g = got.str; if (@intFromEnum(r.start) != g.start or r.len != g.len) { std.debug.print( "inst_datas[{d}] (str) mismatch:\n" ++ " ref: start={d} len={d}\n" ++ " got: start={d} len={d}\n", .{ idx, @intFromEnum(r.start), r.len, g.start, g.len }, ); return error.TestExpectedEqual; } }, else => { // Generic raw comparison: treat data as two u32 words. // Tags using .node data format have undefined second word. const ref_raw = @as([*]const u32, @ptrCast(&ref)); const got_raw = @as([*]const u32, @ptrCast(&got)); // Tags where only the first u32 word is meaningful // (second word is padding/undefined). const first_word_only = switch (tag) { // .node data format (single i32): .repeat, .repeat_inline, .ret_ptr, .ret_type, .trap, .alloc_inferred, .alloc_inferred_mut, .alloc_inferred_comptime, .alloc_inferred_comptime_mut, // .@"unreachable" data format (src_node + padding): .@"unreachable", // .save_err_ret_index data format (operand only): .save_err_ret_index, // .float data format (f32 = 4 bytes, second word is padding): .float, // .elem_val_imm data format (u32 + u8, 3 bytes padding): .elem_val_imm, => true, else => false, }; const w1_match = ref_raw[0] == got_raw[0]; const w2_match = first_word_only or ref_raw[1] == got_raw[1]; if (!w1_match or !w2_match) { std.debug.print( "inst_datas[{d}] ({s}) raw mismatch:\n" ++ " ref: 0x{x:0>8} 0x{x:0>8}\n" ++ " got: 0x{x:0>8} 0x{x:0>8}\n", .{ idx, @tagName(tag), ref_raw[0], ref_raw[1], got_raw[0], got_raw[1], }, ); return error.TestExpectedEqual; } }, } } const corpus_files = .{ .{ "astgen_test.zig", @embedFile("astgen_test.zig") }, .{ "build.zig", @embedFile("../build.zig") }, .{ "parser_test.zig", @embedFile("parser_test.zig") }, .{ "test_all.zig", @embedFile("test_all.zig") }, .{ "tokenizer_test.zig", @embedFile("tokenizer_test.zig") }, .{ "optional.zig", @embedFile("../test/behavior/optional.zig") }, .{ "call.zig", @embedFile("../test/behavior/call.zig") }, .{ "pointers.zig", @embedFile("../test/behavior/pointers.zig") }, .{ "type.zig", @embedFile("../test/behavior/type.zig") }, .{ "enum.zig", @embedFile("../test/behavior/enum.zig") }, .{ "switch_on_captured_error.zig", @embedFile("../test/behavior/switch_on_captured_error.zig") }, .{ "error.zig", @embedFile("../test/behavior/error.zig") }, .{ "switch.zig", @embedFile("../test/behavior/switch.zig") }, .{ "array.zig", @embedFile("../test/behavior/array.zig") }, .{ "slice.zig", @embedFile("../test/behavior/slice.zig") }, .{ "basic.zig", @embedFile("../test/behavior/basic.zig") }, .{ "packed-struct.zig", @embedFile("../test/behavior/packed-struct.zig") }, .{ "eval.zig", @embedFile("../test/behavior/eval.zig") }, .{ "field_parent_ptr.zig", @embedFile("../test/behavior/field_parent_ptr.zig") }, .{ "struct.zig", @embedFile("../test/behavior/struct.zig") }, .{ "floatop.zig", @embedFile("../test/behavior/floatop.zig") }, .{ "union.zig", @embedFile("../test/behavior/union.zig") }, .{ "math.zig", @embedFile("../test/behavior/math.zig") }, .{ "vector.zig", @embedFile("../test/behavior/vector.zig") }, .{ "cast.zig", @embedFile("../test/behavior/cast.zig") }, .{ "abs.zig", @embedFile("../test/behavior/abs.zig") }, .{ "addrspace_and_linksection.zig", @embedFile("../test/behavior/addrspace_and_linksection.zig") }, .{ "align.zig", @embedFile("../test/behavior/align.zig") }, .{ "alignof.zig", @embedFile("../test/behavior/alignof.zig") }, //.{ "asm.zig", @embedFile("../test/behavior/asm.zig") }, //.{ "atomics.zig", @embedFile("../test/behavior/atomics.zig") }, .{ "bitcast.zig", @embedFile("../test/behavior/bitcast.zig") }, .{ "bitreverse.zig", @embedFile("../test/behavior/bitreverse.zig") }, .{ "bit_shifting.zig", @embedFile("../test/behavior/bit_shifting.zig") }, //.{ "bool.zig", @embedFile("../test/behavior/bool.zig") }, //.{ "builtin_functions_returning_void_or_noreturn.zig", @embedFile("../test/behavior/builtin_functions_returning_void_or_noreturn.zig") }, .{ "byteswap.zig", @embedFile("../test/behavior/byteswap.zig") }, .{ "byval_arg_var.zig", @embedFile("../test/behavior/byval_arg_var.zig") }, .{ "cast_int.zig", @embedFile("../test/behavior/cast_int.zig") }, .{ "comptime_memory.zig", @embedFile("../test/behavior/comptime_memory.zig") }, .{ "const_slice_child.zig", @embedFile("../test/behavior/const_slice_child.zig") }, .{ "decl_literals.zig", @embedFile("../test/behavior/decl_literals.zig") }, .{ "decltest.zig", @embedFile("../test/behavior/decltest.zig") }, //.{ "defer.zig", @embedFile("../test/behavior/defer.zig") }, //.{ "destructure.zig", @embedFile("../test/behavior/destructure.zig") }, .{ "duplicated_test_names.zig", @embedFile("../test/behavior/duplicated_test_names.zig") }, .{ "empty_union.zig", @embedFile("../test/behavior/empty_union.zig") }, .{ "export_builtin.zig", @embedFile("../test/behavior/export_builtin.zig") }, .{ "export_c_keywords.zig", @embedFile("../test/behavior/export_c_keywords.zig") }, //.{ "export_keyword.zig", @embedFile("../test/behavior/export_keyword.zig") }, .{ "export_self_referential_type_info.zig", @embedFile("../test/behavior/export_self_referential_type_info.zig") }, .{ "extern_struct_zero_size_fields.zig", @embedFile("../test/behavior/extern_struct_zero_size_fields.zig") }, //.{ "extern.zig", @embedFile("../test/behavior/extern.zig") }, .{ "fn_delegation.zig", @embedFile("../test/behavior/fn_delegation.zig") }, .{ "fn_in_struct_in_comptime.zig", @embedFile("../test/behavior/fn_in_struct_in_comptime.zig") }, //.{ "fn.zig", @embedFile("../test/behavior/fn.zig") }, .{ "for.zig", @embedFile("../test/behavior/for.zig") }, //.{ "generics.zig", @embedFile("../test/behavior/generics.zig") }, .{ "globals.zig", @embedFile("../test/behavior/globals.zig") }, .{ "hasdecl.zig", @embedFile("../test/behavior/hasdecl.zig") }, .{ "hasfield.zig", @embedFile("../test/behavior/hasfield.zig") }, .{ "if.zig", @embedFile("../test/behavior/if.zig") }, //.{ "import_c_keywords.zig", @embedFile("../test/behavior/import_c_keywords.zig") }, .{ "import.zig", @embedFile("../test/behavior/import.zig") }, .{ "incomplete_struct_param_tld.zig", @embedFile("../test/behavior/incomplete_struct_param_tld.zig") }, .{ "inline_switch.zig", @embedFile("../test/behavior/inline_switch.zig") }, .{ "int128.zig", @embedFile("../test/behavior/int128.zig") }, .{ "int_comparison_elision.zig", @embedFile("../test/behavior/int_comparison_elision.zig") }, .{ "ir_block_deps.zig", @embedFile("../test/behavior/ir_block_deps.zig") }, .{ "lower_strlit_to_vector.zig", @embedFile("../test/behavior/lower_strlit_to_vector.zig") }, //.{ "maximum_minimum.zig", @embedFile("../test/behavior/maximum_minimum.zig") }, .{ "member_func.zig", @embedFile("../test/behavior/member_func.zig") }, .{ "memcpy.zig", @embedFile("../test/behavior/memcpy.zig") }, .{ "memmove.zig", @embedFile("../test/behavior/memmove.zig") }, .{ "memset.zig", @embedFile("../test/behavior/memset.zig") }, .{ "merge_error_sets.zig", @embedFile("../test/behavior/merge_error_sets.zig") }, //.{ "muladd.zig", @embedFile("../test/behavior/muladd.zig") }, //.{ "multiple_externs_with_conflicting_types.zig", @embedFile("../test/behavior/multiple_externs_with_conflicting_types.zig") }, .{ "namespace_depends_on_compile_var.zig", @embedFile("../test/behavior/namespace_depends_on_compile_var.zig") }, .{ "nan.zig", @embedFile("../test/behavior/nan.zig") }, .{ "null.zig", @embedFile("../test/behavior/null.zig") }, .{ "packed_struct_explicit_backing_int.zig", @embedFile("../test/behavior/packed_struct_explicit_backing_int.zig") }, .{ "packed-union.zig", @embedFile("../test/behavior/packed-union.zig") }, .{ "popcount.zig", @embedFile("../test/behavior/popcount.zig") }, //.{ "prefetch.zig", @embedFile("../test/behavior/prefetch.zig") }, .{ "ptrcast.zig", @embedFile("../test/behavior/ptrcast.zig") }, .{ "ptrfromint.zig", @embedFile("../test/behavior/ptrfromint.zig") }, .{ "pub_enum.zig", @embedFile("../test/behavior/pub_enum.zig") }, .{ "reflection.zig", @embedFile("../test/behavior/reflection.zig") }, .{ "ref_var_in_if_after_if_2nd_switch_prong.zig", @embedFile("../test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig") }, //.{ "return_address.zig", @embedFile("../test/behavior/return_address.zig") }, .{ "saturating_arithmetic.zig", @embedFile("../test/behavior/saturating_arithmetic.zig") }, //.{ "select.zig", @embedFile("../test/behavior/select.zig") }, .{ "shuffle.zig", @embedFile("../test/behavior/shuffle.zig") }, //.{ "sizeof_and_typeof.zig", @embedFile("../test/behavior/sizeof_and_typeof.zig") }, .{ "slice_sentinel_comptime.zig", @embedFile("../test/behavior/slice_sentinel_comptime.zig") }, //.{ "src.zig", @embedFile("../test/behavior/src.zig") }, //.{ "string_literals.zig", @embedFile("../test/behavior/string_literals.zig") }, .{ "struct_contains_null_ptr_itself.zig", @embedFile("../test/behavior/struct_contains_null_ptr_itself.zig") }, .{ "struct_contains_slice_of_itself.zig", @embedFile("../test/behavior/struct_contains_slice_of_itself.zig") }, //.{ "switch_loop.zig", @embedFile("../test/behavior/switch_loop.zig") }, .{ "switch_prong_err_enum.zig", @embedFile("../test/behavior/switch_prong_err_enum.zig") }, .{ "switch_prong_implicit_cast.zig", @embedFile("../test/behavior/switch_prong_implicit_cast.zig") }, .{ "this.zig", @embedFile("../test/behavior/this.zig") }, .{ "threadlocal.zig", @embedFile("../test/behavior/threadlocal.zig") }, .{ "truncate.zig", @embedFile("../test/behavior/truncate.zig") }, //.{ "try.zig", @embedFile("../test/behavior/try.zig") }, .{ "tuple_declarations.zig", @embedFile("../test/behavior/tuple_declarations.zig") }, .{ "tuple.zig", @embedFile("../test/behavior/tuple.zig") }, //.{ "type_info.zig", @embedFile("../test/behavior/type_info.zig") }, .{ "typename.zig", @embedFile("../test/behavior/typename.zig") }, .{ "undefined.zig", @embedFile("../test/behavior/undefined.zig") }, //.{ "underscore.zig", @embedFile("../test/behavior/underscore.zig") }, .{ "union_with_members.zig", @embedFile("../test/behavior/union_with_members.zig") }, //.{ "var_args.zig", @embedFile("../test/behavior/var_args.zig") }, .{ "void.zig", @embedFile("../test/behavior/void.zig") }, //.{ "wasm.zig", @embedFile("../test/behavior/wasm.zig") }, //.{ "while.zig", @embedFile("../test/behavior/while.zig") }, .{ "widening.zig", @embedFile("../test/behavior/widening.zig") }, .{ "wrapping_arithmetic.zig", @embedFile("../test/behavior/wrapping_arithmetic.zig") }, .{ "x86_64.zig", @embedFile("../test/behavior/x86_64.zig") }, //.{ "zon.zig", @embedFile("../test/behavior/zon.zig") }, }; fn corpusCheck(gpa: Allocator, source: [:0]const u8) !void { var tree = try Ast.parse(gpa, source, .zig); defer tree.deinit(gpa); var ref_zir = try AstGen.generate(gpa, tree); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); if (c_zir.has_compile_errors) { std.debug.print("C port returned compile errors (inst_len={d})\n", .{c_zir.inst_len}); return error.TestUnexpectedResult; } try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: struct single field" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const T = struct { x: u32 };"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: struct multiple fields" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const T = struct { x: u32, y: bool };"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: struct field with default" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const T = struct { x: u32 = 0 };"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: struct field with align" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const T = struct { x: u32 align(4) };"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: struct comptime field" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const T = struct { comptime x: u32 = 0 };"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: empty error set" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const E = error{};"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: error set with members" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const E = error{ OutOfMemory, OutOfTime };"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: extern var" { const gpa = std.testing.allocator; const source: [:0]const u8 = "extern var x: u32;"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: corpus test_all.zig" { const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("test_all.zig")); } test "astgen: corpus build.zig" { const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("../build.zig")); } test "astgen: corpus tokenizer_test.zig" { const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("tokenizer_test.zig")); } test "astgen: corpus parser_test.zig" { const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("parser_test.zig")); } test "astgen: corpus astgen_test.zig" { const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("astgen_test.zig")); } test "astgen: corpus array_list.zig" { const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("../lib/std/array_list.zig")); } test "astgen: corpus multi_array_list.zig" { const gpa = std.testing.allocator; try corpusCheck(gpa, @embedFile("../lib/std/multi_array_list.zig")); } test "astgen: enum decl" { const gpa = std.testing.allocator; const source: [:0]const u8 = "const E = enum { a, b, c };"; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: struct init typed" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\const T = struct { x: u32 }; \\const v = T{ .x = 1 }; ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: union with tag values" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\const MultipleChoice = union(enum(u32)) { \\ A = 20, \\ B = 40, \\ C = 60, \\ D = 1000, \\}; ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: union with typed fields and tag values" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\const MultipleChoice2 = union(enum(u32)) { \\ Unspecified1: i32, \\ A: f32 = 20, \\ Unspecified2: void, \\ B: bool = 40, \\ Unspecified3: i32, \\ C: i8 = 60, \\ Unspecified4: void, \\ D: void = 1000, \\ Unspecified5: i32, \\}; ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: union with decl and var" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\const U = union(enum) { \\ a, \\ b: u8, \\ var u: @This() = .a; \\}; ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: union with fn" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\const U = union(enum) { \\ a, \\ b: u8, \\ fn doTest(c: bool) !void { \\ const u = if (c) .a else @This(){ .b = 0 }; \\ _ = u; \\ } \\}; ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: array if subscript" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\const expect = @import("std").testing.expect; \\test "type deduction for array subscript expression" { \\ const S = struct { \\ fn doTheTest() !void { \\ var array = [_]u8{ 0x55, 0xAA }; \\ var v0 = true; \\ try expect(@as(u8, 0xAA) == array[if (v0) 1 else 0]); \\ var v1 = false; \\ try expect(@as(u8, 0x55) == array[if (v1) 1 else 0]); \\ _ = .{ &array, &v0, &v1 }; \\ } \\ }; \\ try S.doTheTest(); \\ try comptime S.doTheTest(); \\} ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: multi-arg TypeOf" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\test "peer type resolution" { \\ var a: anyerror = error.Foo; \\ var b: anyerror = error.Bar; \\ const T = @TypeOf(a, b); \\ _ = .{ T, &a, &b }; \\} ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: tagName in comptime" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\const E = enum { a, b }; \\fn f(v: E) []const u8 { \\ return @tagName(v); \\} \\comptime { \\ _ = f(.a); \\} ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: 4-arg TypeOf" { const gpa = std.testing.allocator; const source: [:0]const u8 = \\test "peer 4" { \\ var a: [*:0]const u8 = "hi"; \\ var b: [*:0]volatile u8 = @constCast(@volatileCast(a)); \\ var cc: [*]allowzero const u8 = a; \\ var d: [*]align(2) const u8 = @alignCast(a); \\ comptime { \\ _ = @TypeOf(a, b, cc, d); \\ } \\ _ = .{ &a, &b, &cc, &d }; \\} ; var ref_zir = try refZir(gpa, source); defer ref_zir.deinit(gpa); var c_ast = c.astParse(source.ptr, @intCast(source.len)); defer c.astDeinit(&c_ast); var c_zir = c.astGen(&c_ast); defer c.zirDeinit(&c_zir); try expectEqualZir(gpa, ref_zir, c_zir); } test "astgen: corpus" { // All individual corpus tests now pass. const gpa = std.testing.allocator; var any_fail = false; inline for (corpus_files) |entry| { std.debug.print("--- {s} ---\n", .{entry[0]}); corpusCheck(gpa, entry[1]) catch { std.debug.print("FAIL: {s}\n", .{entry[0]}); any_fail = true; }; } if (any_fail) return error.ZirMismatch; }