Add corpus tests for tokenizer_test.zig and astgen_test.zig, skipped pending fixes: - tokenizer_test.zig: needs ref_coerced_ty result location (428 inst diff) - astgen_test.zig: 1 missing dbg_stmt, extra_len mismatch (375 extra diff) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1047 lines
37 KiB
Zig
1047 lines
37 KiB
Zig
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 dumpZir(ref_zir: Zir) void {
|
|
const tags = ref_zir.instructions.items(.tag);
|
|
const datas = ref_zir.instructions.items(.data);
|
|
std.debug.print(" instructions: {d}\n", .{ref_zir.instructions.len});
|
|
for (0..ref_zir.instructions.len) |i| {
|
|
const tag = tags[i];
|
|
std.debug.print(" [{d}] tag={d} ({s})", .{ i, @intFromEnum(tag), @tagName(tag) });
|
|
switch (tag) {
|
|
.extended => {
|
|
const ext = datas[i].extended;
|
|
std.debug.print(" opcode={d} small=0x{x:0>4} operand={d}", .{ @intFromEnum(ext.opcode), ext.small, ext.operand });
|
|
},
|
|
.declaration => {
|
|
const decl = datas[i].declaration;
|
|
std.debug.print(" src_node={d} payload_index={d}", .{ @intFromEnum(decl.src_node), decl.payload_index });
|
|
},
|
|
.break_inline => {
|
|
const brk = datas[i].@"break";
|
|
std.debug.print(" operand={d} payload_index={d}", .{ @intFromEnum(brk.operand), brk.payload_index });
|
|
},
|
|
else => {},
|
|
}
|
|
std.debug.print("\n", .{});
|
|
}
|
|
std.debug.print(" extra ({d}):\n", .{ref_zir.extra.len});
|
|
for (0..ref_zir.extra.len) |i| {
|
|
std.debug.print(" [{d}] = 0x{x:0>8} ({d})\n", .{ i, ref_zir.extra[i], ref_zir.extra[i] });
|
|
}
|
|
std.debug.print(" string_bytes ({d}):", .{ref_zir.string_bytes.len});
|
|
for (0..ref_zir.string_bytes.len) |i| {
|
|
std.debug.print(" {x:0>2}", .{ref_zir.string_bytes[i]});
|
|
}
|
|
std.debug.print("\n", .{});
|
|
}
|
|
|
|
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 contain hash data (src_hash or
|
|
/// fields_hash). These are zero-filled in the C output but contain real
|
|
/// Blake3 hashes in the Zig reference. We skip these positions during
|
|
/// comparison.
|
|
fn buildHashSkipMask(gpa: Allocator, ref: Zir) ![]bool {
|
|
const ref_extra_len: u32 = @intCast(ref.extra.len);
|
|
const skip = try gpa.alloc(bool, ref_extra_len);
|
|
@memset(skip, false);
|
|
|
|
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) {
|
|
// StructDecl/EnumDecl starts with fields_hash[4].
|
|
const pi = ext.operand;
|
|
for (0..4) |j| skip[pi + j] = true;
|
|
}
|
|
},
|
|
.declaration => {
|
|
// Declaration starts with src_hash[4].
|
|
const pi = ref_datas[i].declaration.payload_index;
|
|
for (0..4) |j| skip[pi + j] = true;
|
|
},
|
|
.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)
|
|
skip[hash_start + j] = true;
|
|
}
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
return skip;
|
|
}
|
|
|
|
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: @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 {
|
|
// Compare instruction count.
|
|
const ref_len: u32 = @intCast(ref.instructions.len);
|
|
if (ref_len != got.inst_len) {
|
|
std.debug.print("inst_len mismatch: ref={d} got={d}\n", .{ ref_len, got.inst_len });
|
|
return error.TestExpectedEqual;
|
|
}
|
|
|
|
// Compare instructions (tag + data) field-by-field.
|
|
const ref_tags = ref.instructions.items(.tag);
|
|
const ref_datas = ref.instructions.items(.data);
|
|
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 },
|
|
);
|
|
return error.TestExpectedEqual;
|
|
}
|
|
try expectEqualData(i, ref_tags[i], ref_datas[i], got.inst_datas[i]);
|
|
}
|
|
|
|
// Build hash skip mask for extra comparison.
|
|
const skip = try buildHashSkipMask(gpa, ref);
|
|
defer gpa.free(skip);
|
|
|
|
// Compare extra data, skipping hash positions.
|
|
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 (skip[i]) continue;
|
|
if (ref.extra[i] != got.extra[i]) {
|
|
std.debug.print(
|
|
"extra[{d}] mismatch: ref=0x{x:0>8} got=0x{x:0>8}\n",
|
|
.{ i, ref.extra[i], got.extra[i] },
|
|
);
|
|
return error.TestExpectedEqual;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
if (@intFromEnum(r.opcode) != g.opcode or
|
|
r.small != g.small or
|
|
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,
|
|
.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,
|
|
.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 => {
|
|
std.debug.print(
|
|
"inst_datas[{d}]: unhandled tag {d} ({s}) in comparison\n",
|
|
.{ idx, @intFromEnum(tag), @tagName(tag) },
|
|
);
|
|
return error.TestUnexpectedResult;
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Silent ZIR comparison: returns true if ZIR matches, false otherwise.
|
|
/// Unlike expectEqualZir, does not print diagnostics or return errors.
|
|
fn zirMatches(_: Allocator, ref: Zir, got: c.Zir) bool {
|
|
const ref_len: u32 = @intCast(ref.instructions.len);
|
|
if (ref_len != got.inst_len) {
|
|
std.debug.print(" inst_len: ref={d} got={d}\n", .{ ref_len, got.inst_len });
|
|
}
|
|
|
|
{
|
|
const elen: u32 = @intCast(ref.extra.len);
|
|
const slen: u32 = @intCast(ref.string_bytes.len);
|
|
std.debug.print(" inst_len: ref={d} got={d}\n", .{ ref_len, got.inst_len });
|
|
std.debug.print(" extra_len: ref={d} got={d} diff={d}\n", .{ elen, got.extra_len, @as(i64, elen) - @as(i64, got.extra_len) });
|
|
std.debug.print(" string_bytes_len: ref={d} got={d} diff={d}\n", .{ slen, got.string_bytes_len, @as(i64, slen) - @as(i64, got.string_bytes_len) });
|
|
}
|
|
|
|
const ref_tags = ref.instructions.items(.tag);
|
|
const ref_datas = ref.instructions.items(.data);
|
|
const min_len = @min(ref_len, got.inst_len);
|
|
var first_tag_mismatch: ?u32 = null;
|
|
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) {
|
|
first_tag_mismatch = @intCast(i);
|
|
break;
|
|
}
|
|
}
|
|
if (first_tag_mismatch) |ftm| {
|
|
const start = if (ftm > 15) ftm - 15 else 0;
|
|
const end = @min(ftm + 30, min_len);
|
|
std.debug.print(" first tag mismatch at inst[{d}]:\n", .{ftm});
|
|
for (start..end) |i| {
|
|
const ref_tag: u8 = @intFromEnum(ref_tags[i]);
|
|
const got_tag: u8 = @intCast(got.inst_tags[i]);
|
|
const marker: u8 = if (i == ftm) '>' else ' ';
|
|
if (ref_tag == 251) {
|
|
const ext_op: u16 = @intFromEnum(ref_datas[i].extended.opcode);
|
|
std.debug.print(" {c} [{d}] ref_tag=251(EXT:{d}) got_tag={d}\n", .{ marker, i, ext_op, got_tag });
|
|
} else {
|
|
std.debug.print(" {c} [{d}] ref_tag={d} got_tag={d}\n", .{ marker, i, ref_tag, got_tag });
|
|
}
|
|
}
|
|
// Tag histogram: count each tag in ref vs got and show diffs.
|
|
var ref_hist: [256]i32 = undefined;
|
|
var got_hist: [256]i32 = undefined;
|
|
for (&ref_hist) |*h| h.* = 0;
|
|
for (&got_hist) |*h| h.* = 0;
|
|
for (0..ref_len) |j| {
|
|
ref_hist[@intFromEnum(ref_tags[j])] += 1;
|
|
}
|
|
for (0..got.inst_len) |j| {
|
|
got_hist[@as(u8, @intCast(got.inst_tags[j]))] += 1;
|
|
}
|
|
std.debug.print(" tag histogram diff (ref-got):\n", .{});
|
|
for (0..256) |t| {
|
|
const diff = ref_hist[t] - got_hist[t];
|
|
if (diff != 0) {
|
|
std.debug.print(" tag {d}: ref={d} got={d} diff={d}\n", .{ t, ref_hist[t], got_hist[t], diff });
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// Skip inst_datas comparison for now (extra indices shift).
|
|
// Go straight to extra/string_bytes.
|
|
if (ref_len != got.inst_len) return false;
|
|
|
|
// Compare string_bytes first (smaller diff).
|
|
const ref_sb_len2: u32 = @intCast(ref.string_bytes.len);
|
|
const sb_min = @min(ref_sb_len2, got.string_bytes_len);
|
|
for (0..sb_min) |i| {
|
|
if (ref.string_bytes[i] != got.string_bytes[i]) {
|
|
// Print surrounding context.
|
|
const ctx_start = if (i > 30) i - 30 else 0;
|
|
std.debug.print(" string_bytes[{d}] first diff (ref=0x{x:0>2} got=0x{x:0>2})\n", .{ i, ref.string_bytes[i], got.string_bytes[i] });
|
|
std.debug.print(" ref context: \"", .{});
|
|
for (ctx_start..@min(i + 30, sb_min)) |j| {
|
|
const ch = ref.string_bytes[j];
|
|
if (ch >= 0x20 and ch < 0x7f) {
|
|
std.debug.print("{c}", .{ch});
|
|
} else {
|
|
std.debug.print("\\x{x:0>2}", .{ch});
|
|
}
|
|
}
|
|
std.debug.print("\"\n", .{});
|
|
std.debug.print(" got context: \"", .{});
|
|
for (ctx_start..@min(i + 30, sb_min)) |j| {
|
|
const ch = got.string_bytes[j];
|
|
if (ch >= 0x20 and ch < 0x7f) {
|
|
std.debug.print("{c}", .{ch});
|
|
} else {
|
|
std.debug.print("\\x{x:0>2}", .{ch});
|
|
}
|
|
}
|
|
std.debug.print("\"\n", .{});
|
|
return false;
|
|
}
|
|
}
|
|
if (ref_sb_len2 != got.string_bytes_len) {
|
|
std.debug.print(" string_bytes_len mismatch: ref={d} got={d} (content matched up to {d})\n", .{ ref_sb_len2, got.string_bytes_len, sb_min });
|
|
// Print what ref has at the end.
|
|
if (ref_sb_len2 > got.string_bytes_len) {
|
|
const extra_start = got.string_bytes_len;
|
|
std.debug.print(" ref extra at [{d}]: \"", .{extra_start});
|
|
for (extra_start..@min(extra_start + 60, ref_sb_len2)) |j| {
|
|
const ch = ref.string_bytes[j];
|
|
if (ch >= 0x20 and ch < 0x7f) {
|
|
std.debug.print("{c}", .{ch});
|
|
} else {
|
|
std.debug.print("\\x{x:0>2}", .{ch});
|
|
}
|
|
}
|
|
std.debug.print("\"\n", .{});
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const ref_extra_len2: u32 = @intCast(ref.extra.len);
|
|
if (ref_extra_len2 != got.extra_len) {
|
|
std.debug.print(" extra_len mismatch: ref={d} got={d}\n", .{ ref_extra_len2, got.extra_len });
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Silent data comparison: returns true if fields match, false otherwise.
|
|
fn dataMatches(tag: Zir.Inst.Tag, ref: Zir.Inst.Data, got: c.ZirInstData) bool {
|
|
switch (tag) {
|
|
.extended => {
|
|
const r = ref.extended;
|
|
const g = got.extended;
|
|
return @intFromEnum(r.opcode) == g.opcode and
|
|
r.small == g.small and
|
|
r.operand == g.operand;
|
|
},
|
|
.declaration => {
|
|
const r = ref.declaration;
|
|
const g = got.declaration;
|
|
return @intFromEnum(r.src_node) == g.src_node and
|
|
r.payload_index == g.payload_index;
|
|
},
|
|
.break_inline => {
|
|
const r = ref.@"break";
|
|
const g = got.break_data;
|
|
return @intFromEnum(r.operand) == g.operand and
|
|
r.payload_index == g.payload_index;
|
|
},
|
|
.import => {
|
|
const r = ref.pl_tok;
|
|
const g = got.pl_tok;
|
|
return @intFromEnum(r.src_tok) == g.src_tok and
|
|
r.payload_index == g.payload_index;
|
|
},
|
|
.dbg_stmt => {
|
|
return ref.dbg_stmt.line == got.dbg_stmt.line and
|
|
ref.dbg_stmt.column == got.dbg_stmt.column;
|
|
},
|
|
.ensure_result_non_error,
|
|
.restore_err_ret_index_unconditional,
|
|
.validate_struct_init_ty,
|
|
.struct_init_empty_result,
|
|
.struct_init_empty,
|
|
.struct_init_empty_ref_result,
|
|
=> {
|
|
return @intFromEnum(ref.un_node.src_node) == got.un_node.src_node and
|
|
@intFromEnum(ref.un_node.operand) == got.un_node.operand;
|
|
},
|
|
.ret_implicit => {
|
|
return @intFromEnum(ref.un_tok.src_tok) == got.un_tok.src_tok and
|
|
@intFromEnum(ref.un_tok.operand) == got.un_tok.operand;
|
|
},
|
|
.func,
|
|
.func_inferred,
|
|
.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,
|
|
=> {
|
|
return @intFromEnum(ref.pl_node.src_node) == got.pl_node.src_node and
|
|
ref.pl_node.payload_index == got.pl_node.payload_index;
|
|
},
|
|
.ptr_type => {
|
|
return @as(u8, @bitCast(ref.ptr_type.flags)) == got.ptr_type.flags and
|
|
@intFromEnum(ref.ptr_type.size) == got.ptr_type.size and
|
|
ref.ptr_type.payload_index == got.ptr_type.payload_index;
|
|
},
|
|
.int_type => {
|
|
return @intFromEnum(ref.int_type.src_node) == got.int_type.src_node and
|
|
@intFromEnum(ref.int_type.signedness) == got.int_type.signedness and
|
|
ref.int_type.bit_count == got.int_type.bit_count;
|
|
},
|
|
.decl_val, .decl_ref => {
|
|
return @intFromEnum(ref.str_tok.start) == got.str_tok.start and
|
|
@intFromEnum(ref.str_tok.src_tok) == got.str_tok.src_tok;
|
|
},
|
|
.field_val, .field_ptr, .field_val_named, .field_ptr_named => {
|
|
return @intFromEnum(ref.pl_node.src_node) == got.pl_node.src_node and
|
|
ref.pl_node.payload_index == got.pl_node.payload_index;
|
|
},
|
|
.int => return ref.int == got.int_val,
|
|
.str => {
|
|
return @intFromEnum(ref.str.start) == got.str.start and
|
|
ref.str.len == got.str.len;
|
|
},
|
|
.@"defer" => {
|
|
return ref.@"defer".index == got.defer_data.index and
|
|
ref.@"defer".len == got.defer_data.len;
|
|
},
|
|
else => return false,
|
|
}
|
|
}
|
|
|
|
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") },
|
|
};
|
|
|
|
fn corpusCheck(gpa: Allocator, name: []const u8, 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(" {s} -> has_compile_errors\n", .{name});
|
|
return error.ZirCompileErrors;
|
|
}
|
|
|
|
if (zirMatches(gpa, ref_zir, c_zir)) {
|
|
return;
|
|
} else {
|
|
std.debug.print(" {s} -> zir mismatch\n", .{name});
|
|
return error.ZirMismatch;
|
|
}
|
|
}
|
|
|
|
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, "test_all.zig", @embedFile("test_all.zig"));
|
|
}
|
|
|
|
// TODO: build.zig needs ref_coerced_ty result location and fn body ordering fixes.
|
|
// test "astgen: corpus build.zig" {
|
|
// const gpa = std.testing.allocator;
|
|
// try corpusCheck(gpa, "build.zig", @embedFile("build.zig"));
|
|
// }
|
|
|
|
test "astgen: corpus tokenizer_test.zig" {
|
|
if (true) return error.SkipZigTest; // TODO: needs ref_coerced_ty
|
|
const gpa = std.testing.allocator;
|
|
try corpusCheck(gpa, "tokenizer_test.zig", @embedFile("tokenizer_test.zig"));
|
|
}
|
|
|
|
test "astgen: corpus astgen_test.zig" {
|
|
if (true) return error.SkipZigTest; // TODO: 1 missing dbg_stmt, extra_len mismatch
|
|
const gpa = std.testing.allocator;
|
|
try corpusCheck(gpa, "astgen_test.zig", @embedFile("astgen_test.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: corpus" {
|
|
if (true) return error.SkipZigTest;
|
|
const gpa = std.testing.allocator;
|
|
|
|
var any_fail = false;
|
|
inline for (corpus_files) |entry| {
|
|
corpusCheck(gpa, entry[0], entry[1]) catch {
|
|
any_fail = true;
|
|
};
|
|
}
|
|
if (any_fail) return error.ZirMismatch;
|
|
}
|