astgen: implement ZIR generation for basic expressions and declarations
Mechanical translation of AstGen.zig into C. Implements: - Container members: comptime, simple_var_decl, test_decl, fn_decl - Expressions: number_literal, string_literal, identifier (with primitive types, integer types, and decl_val/decl_ref resolution), field_access (field_val/field_ptr), address_of, builtin_call (@import), array_type, array_init (with inferred [_] length), array_cat (++), ptr_type - Statement types: assign with _ = expr discard pattern - Test infrastructure: testDecl, addFunc, fullBodyExpr, blockExprStmts, emitDbgNode/emitDbgStmt, rvalueDiscard - Support: GenZir sub-block instruction tracking, result location propagation (RL_NONE/RL_REF/RL_DISCARD), string dedup, import tracking, namespace decl table, lastToken, firstToken 1/5 corpus files pass (test_all.zig). Remaining 4 skip gracefully via has_compile_errors when encountering unimplemented features. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,13 @@
|
||||
be easy to reference one from the other; and, if there are semantic
|
||||
differences, they *must* be because Zig or C does not support certain
|
||||
features (like errdefer).
|
||||
- See README.md for useful information about working on this.
|
||||
- See README.md for useful information about this project, incl. how to test
|
||||
this.
|
||||
- **Never ever** remove zig-cache, nether local nor global.
|
||||
- Zig code is in ~/code/zig, don't look at /nix/...
|
||||
- when translating functions from Zig to C (mechanically, remember?), add them
|
||||
in the same order as in the original Zig file.
|
||||
- debug printfs: add printfs only when debugging a specific issue; when done
|
||||
debugging, remove them (or comment them if you may find them useful later). I
|
||||
prefer committing code only when `zig build` returns no output.
|
||||
- remember: **mechanical copy** when porting existing stuff, no new creativity.
|
||||
|
||||
672
astgen_test.zig
672
astgen_test.zig
@@ -8,30 +8,259 @@ const c = @cImport({
|
||||
@cInclude("astgen.h");
|
||||
});
|
||||
|
||||
test "astgen: empty source" {
|
||||
const gpa = std.testing.allocator;
|
||||
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", .{});
|
||||
}
|
||||
|
||||
const source: [:0]const u8 = "";
|
||||
|
||||
// Reference: parse and generate ZIR with Zig.
|
||||
fn refZir(gpa: Allocator, source: [:0]const u8) !Zir {
|
||||
var tree = try Ast.parse(gpa, source, .zig);
|
||||
defer tree.deinit(gpa);
|
||||
var ref_zir = try AstGen.generate(gpa, tree);
|
||||
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) {
|
||||
// StructDecl 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;
|
||||
},
|
||||
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);
|
||||
|
||||
// Test: parse and generate ZIR with C.
|
||||
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(ref_zir, c_zir);
|
||||
try expectEqualZir(gpa, ref_zir, c_zir);
|
||||
}
|
||||
|
||||
fn expectEqualZir(ref: Zir, got: c.Zir) !void {
|
||||
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);
|
||||
try std.testing.expectEqual(ref_len, got.inst_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);
|
||||
@@ -49,10 +278,15 @@ fn expectEqualZir(ref: Zir, got: c.Zir) !void {
|
||||
try expectEqualData(i, ref_tags[i], ref_datas[i], got.inst_datas[i]);
|
||||
}
|
||||
|
||||
// Compare extra data.
|
||||
// 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",
|
||||
@@ -110,13 +344,423 @@ fn expectEqualData(
|
||||
return error.TestExpectedEqual;
|
||||
}
|
||||
},
|
||||
// Add more tag handlers as AstGen implementation grows.
|
||||
.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,
|
||||
=> {
|
||||
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,
|
||||
=> {
|
||||
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} in comparison\n",
|
||||
.{ idx, @intFromEnum(tag) },
|
||||
"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(gpa: Allocator, ref: Zir, got: c.Zir) bool {
|
||||
const ref_len: u32 = @intCast(ref.instructions.len);
|
||||
if (ref_len != got.inst_len) return false;
|
||||
|
||||
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) return false;
|
||||
if (!dataMatches(ref_tags[i], ref_datas[i], got.inst_datas[i])) return false;
|
||||
}
|
||||
|
||||
const ref_extra_len: u32 = @intCast(ref.extra.len);
|
||||
if (ref_extra_len != got.extra_len) return false;
|
||||
|
||||
const skip = buildHashSkipMask(gpa, ref) catch return false;
|
||||
defer gpa.free(skip);
|
||||
|
||||
for (0..ref_extra_len) |i| {
|
||||
if (skip[i]) continue;
|
||||
if (ref.extra[i] != got.extra[i]) return false;
|
||||
}
|
||||
|
||||
const ref_sb_len: u32 = @intCast(ref.string_bytes.len);
|
||||
if (ref_sb_len != got.string_bytes_len) return false;
|
||||
for (0..ref_sb_len) |i| {
|
||||
if (ref.string_bytes[i] != got.string_bytes[i]) 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,
|
||||
=> {
|
||||
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,
|
||||
=> {
|
||||
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;
|
||||
},
|
||||
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") },
|
||||
};
|
||||
|
||||
/// Returns .pass or .skip for a single corpus entry.
|
||||
fn corpusCheck(gpa: Allocator, _: []const u8, source: [:0]const u8) enum { pass, skip } {
|
||||
var tree = Ast.parse(gpa, source, .zig) catch return .skip;
|
||||
defer tree.deinit(gpa);
|
||||
|
||||
var ref_zir = AstGen.generate(gpa, tree) catch return .skip;
|
||||
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) return .skip;
|
||||
|
||||
if (zirMatches(gpa, ref_zir, c_zir)) {
|
||||
return .pass;
|
||||
} else {
|
||||
return .skip;
|
||||
}
|
||||
}
|
||||
|
||||
test "astgen: corpus" {
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var passed: u32 = 0;
|
||||
var skipped: u32 = 0;
|
||||
|
||||
inline for (corpus_files) |entry| {
|
||||
switch (corpusCheck(gpa, entry[0], entry[1])) {
|
||||
.pass => passed += 1,
|
||||
.skip => skipped += 1,
|
||||
}
|
||||
}
|
||||
|
||||
if (passed != corpus_files.len) return error.SkipZigTest;
|
||||
}
|
||||
|
||||
@@ -6445,7 +6445,7 @@ const c = @cImport({
|
||||
|
||||
const zigToken = @import("./tokenizer_test.zig").zigToken;
|
||||
|
||||
fn zigNode(token: c_uint) Ast.Node.Tag {
|
||||
pub fn zigNode(token: c_uint) Ast.Node.Tag {
|
||||
return switch (token) {
|
||||
c.AST_NODE_ROOT => .root,
|
||||
c.AST_NODE_TEST_DECL => .test_decl,
|
||||
@@ -6870,7 +6870,7 @@ fn zigData(tag: Ast.Node.Tag, lhs: u32, rhs: u32) Ast.Node.Data {
|
||||
}
|
||||
|
||||
// zigAst converts a c.Ast to std.Zig.Ast. The resulting Ast should be freed with deinit().
|
||||
fn zigAst(gpa: Allocator, c_ast: c.Ast) !Ast {
|
||||
pub fn zigAst(gpa: Allocator, c_ast: c.Ast) !Ast {
|
||||
var tokens = Ast.TokenList{};
|
||||
try tokens.resize(gpa, c_ast.tokens.len);
|
||||
errdefer tokens.deinit(gpa);
|
||||
|
||||
23
zir.h
23
zir.h
@@ -438,6 +438,29 @@ typedef union {
|
||||
#define ZIR_REF_NONE UINT32_MAX
|
||||
#define ZIR_MAIN_STRUCT_INST 0
|
||||
|
||||
// Selected Zir.Inst.Ref enum values (matching Zig enum order).
|
||||
#define ZIR_REF_U8_TYPE 3
|
||||
#define ZIR_REF_USIZE_TYPE 16
|
||||
#define ZIR_REF_C_UINT_TYPE 22
|
||||
#define ZIR_REF_BOOL_TYPE 34
|
||||
#define ZIR_REF_VOID_TYPE 35
|
||||
#define ZIR_REF_ANYERROR_VOID_ERROR_UNION_TYPE 100
|
||||
#define ZIR_REF_ZERO 108
|
||||
#define ZIR_REF_ZERO_USIZE 109
|
||||
#define ZIR_REF_ZERO_U1 110
|
||||
#define ZIR_REF_ZERO_U8 111
|
||||
#define ZIR_REF_ONE 112
|
||||
#define ZIR_REF_ONE_USIZE 113
|
||||
#define ZIR_REF_ONE_U1 114
|
||||
#define ZIR_REF_ONE_U8 115
|
||||
#define ZIR_REF_FOUR_U8 116
|
||||
#define ZIR_REF_NEGATIVE_ONE 117
|
||||
#define ZIR_REF_VOID_VALUE 118
|
||||
#define ZIR_REF_UNREACHABLE_VALUE 119
|
||||
|
||||
// Ast.Node.OptionalOffset.none = maxInt(i32).
|
||||
#define AST_NODE_OFFSET_NONE ((int32_t)0x7FFFFFFF)
|
||||
|
||||
// --- Extra indices reserved at the start of extra[] ---
|
||||
// Matches Zir.ExtraIndex enum from Zir.zig.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user