Remove all @import("zig_internals") from stage0/ so that test_obj
compilation is independent of the Zig compiler (~6min). The sema
comparison now uses text-based dumpers:
- Zig side (src/verbose_air.zig): compiles source through the full Zig
pipeline, captures verbose_air output, exports zig_dump_air() as a C
function. Compiled as a separate dumper_obj that is cached
independently.
- C side (stage0/verbose_air.c): formats C Air structs to text in the
same format as Zig's Air/print.zig.
Changing stage0 code no longer triggers Zig compiler recompilation:
C compile + cached test_obj + cached dumper + link = seconds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
273 lines
10 KiB
Zig
273 lines
10 KiB
Zig
const std = @import("std");
|
|
|
|
// Import C types including sema.h (which transitively includes air.h, intern_pool.h, etc.)
|
|
// Also include astgen.h and verbose_air.h so we have the full pipeline in one namespace.
|
|
pub const c = @cImport({
|
|
@cInclude("astgen.h");
|
|
@cInclude("sema.h");
|
|
@cInclude("verbose_air.h");
|
|
@cInclude("dump.h");
|
|
});
|
|
|
|
// Zig-side dumper functions (linked from dumper_obj, not imported).
|
|
const DumpResult = extern struct {
|
|
text: ?[*:0]u8,
|
|
error_msg: ?[*:0]u8,
|
|
};
|
|
extern fn zig_dump_air([*:0]const u8, ?[*:0]const u8) DumpResult;
|
|
|
|
// Helper to convert C #define integer constants (c_int) to u32 for comparison
|
|
// with uint32_t fields (InternPoolIndex, etc.).
|
|
fn idx(val: c_int) u32 {
|
|
return @bitCast(val);
|
|
}
|
|
|
|
// Helper to convert C enum values (c_uint) to the expected tag type for comparison.
|
|
fn tag(val: c_uint) c_uint {
|
|
return val;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// InternPool unit tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
test "intern_pool: init and pre-interned types" {
|
|
var ip = c.ipInit();
|
|
defer c.ipDeinit(&ip);
|
|
|
|
// Verify pre-interned count
|
|
try std.testing.expectEqual(@as(u32, 124), ip.items_len);
|
|
|
|
// Verify some key type indices
|
|
const void_key = c.ipIndexToKey(&ip, idx(c.IP_INDEX_VOID_TYPE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_SIMPLE_TYPE), void_key.tag);
|
|
try std.testing.expectEqual(tag(c.SIMPLE_TYPE_VOID), void_key.data.simple_type);
|
|
|
|
const u32_key = c.ipIndexToKey(&ip, idx(c.IP_INDEX_U32_TYPE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_INT_TYPE), u32_key.tag);
|
|
try std.testing.expectEqual(@as(u16, 32), u32_key.data.int_type.bits);
|
|
try std.testing.expectEqual(@as(u8, 0), u32_key.data.int_type.signedness); // unsigned
|
|
|
|
const i32_key = c.ipIndexToKey(&ip, idx(c.IP_INDEX_I32_TYPE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_INT_TYPE), i32_key.tag);
|
|
try std.testing.expectEqual(@as(u16, 32), i32_key.data.int_type.bits);
|
|
try std.testing.expectEqual(@as(u8, 1), i32_key.data.int_type.signedness); // signed
|
|
|
|
const bool_key = c.ipIndexToKey(&ip, idx(c.IP_INDEX_BOOL_TYPE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_SIMPLE_TYPE), bool_key.tag);
|
|
try std.testing.expectEqual(tag(c.SIMPLE_TYPE_BOOL), bool_key.data.simple_type);
|
|
}
|
|
|
|
test "intern_pool: pre-interned values" {
|
|
var ip = c.ipInit();
|
|
defer c.ipDeinit(&ip);
|
|
|
|
// Check void value
|
|
const void_val = c.ipIndexToKey(&ip, idx(c.IP_INDEX_VOID_VALUE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_SIMPLE_VALUE), void_val.tag);
|
|
try std.testing.expectEqual(tag(c.SIMPLE_VALUE_VOID), void_val.data.simple_value);
|
|
|
|
// Check bool true/false
|
|
const true_val = c.ipIndexToKey(&ip, idx(c.IP_INDEX_BOOL_TRUE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_SIMPLE_VALUE), true_val.tag);
|
|
try std.testing.expectEqual(tag(c.SIMPLE_VALUE_TRUE), true_val.data.simple_value);
|
|
|
|
const false_val = c.ipIndexToKey(&ip, idx(c.IP_INDEX_BOOL_FALSE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_SIMPLE_VALUE), false_val.tag);
|
|
try std.testing.expectEqual(tag(c.SIMPLE_VALUE_FALSE), false_val.data.simple_value);
|
|
|
|
// Check zero
|
|
const zero_key = c.ipIndexToKey(&ip, idx(c.IP_INDEX_ZERO));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_INT), zero_key.tag);
|
|
}
|
|
|
|
test "intern_pool: ipTypeOf" {
|
|
var ip = c.ipInit();
|
|
defer c.ipDeinit(&ip);
|
|
|
|
// Types have type 'type'
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_TYPE_TYPE), c.ipTypeOf(&ip, idx(c.IP_INDEX_VOID_TYPE)));
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_TYPE_TYPE), c.ipTypeOf(&ip, idx(c.IP_INDEX_U32_TYPE)));
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_TYPE_TYPE), c.ipTypeOf(&ip, idx(c.IP_INDEX_BOOL_TYPE)));
|
|
|
|
// Values have their respective types
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_VOID_TYPE), c.ipTypeOf(&ip, idx(c.IP_INDEX_VOID_VALUE)));
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_BOOL_TYPE), c.ipTypeOf(&ip, idx(c.IP_INDEX_BOOL_TRUE)));
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_BOOL_TYPE), c.ipTypeOf(&ip, idx(c.IP_INDEX_BOOL_FALSE)));
|
|
}
|
|
|
|
test "intern_pool: ipIntern deduplication" {
|
|
var ip = c.ipInit();
|
|
defer c.ipDeinit(&ip);
|
|
|
|
// Interning an existing key should return the same index
|
|
var void_key: c.InternPoolKey = undefined;
|
|
@memset(std.mem.asBytes(&void_key), 0);
|
|
void_key.tag = c.IP_KEY_SIMPLE_TYPE;
|
|
void_key.data.simple_type = c.SIMPLE_TYPE_VOID;
|
|
|
|
const result = c.ipIntern(&ip, void_key);
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_VOID_TYPE), result);
|
|
|
|
// Items count shouldn't increase for duplicate
|
|
try std.testing.expectEqual(@as(u32, 124), ip.items_len);
|
|
}
|
|
|
|
test "intern_pool: ipIntern new key" {
|
|
var ip = c.ipInit();
|
|
defer c.ipDeinit(&ip);
|
|
|
|
// Intern a new array type
|
|
var arr_key: c.InternPoolKey = undefined;
|
|
@memset(std.mem.asBytes(&arr_key), 0);
|
|
arr_key.tag = c.IP_KEY_ARRAY_TYPE;
|
|
arr_key.data.array_type = .{
|
|
.len = 10,
|
|
.child = idx(c.IP_INDEX_U8_TYPE),
|
|
.sentinel = c.IP_INDEX_NONE,
|
|
};
|
|
|
|
const idx1 = c.ipIntern(&ip, arr_key);
|
|
try std.testing.expect(idx1 >= idx(c.IP_INDEX_PREINTERN_COUNT));
|
|
try std.testing.expectEqual(@as(u32, 125), ip.items_len);
|
|
|
|
// Re-interning should return same index
|
|
const idx2 = c.ipIntern(&ip, arr_key);
|
|
try std.testing.expectEqual(idx1, idx2);
|
|
try std.testing.expectEqual(@as(u32, 125), ip.items_len);
|
|
}
|
|
|
|
test "intern_pool: vector types" {
|
|
var ip = c.ipInit();
|
|
defer c.ipDeinit(&ip);
|
|
|
|
// Verify vector_8_i8 at index 52
|
|
const v8i8 = c.ipIndexToKey(&ip, idx(c.IP_INDEX_VECTOR_8_I8_TYPE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_VECTOR_TYPE), v8i8.tag);
|
|
try std.testing.expectEqual(@as(u32, 8), v8i8.data.vector_type.len);
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_I8_TYPE), v8i8.data.vector_type.child);
|
|
|
|
// Verify vector_4_f32 at index 93
|
|
const v4f32 = c.ipIndexToKey(&ip, idx(c.IP_INDEX_VECTOR_4_F32_TYPE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_VECTOR_TYPE), v4f32.tag);
|
|
try std.testing.expectEqual(@as(u32, 4), v4f32.data.vector_type.len);
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_F32_TYPE), v4f32.data.vector_type.child);
|
|
}
|
|
|
|
test "intern_pool: pointer types" {
|
|
var ip = c.ipInit();
|
|
defer c.ipDeinit(&ip);
|
|
|
|
// ptr_usize (index 45): *usize
|
|
const ptr_usize = c.ipIndexToKey(&ip, idx(c.IP_INDEX_PTR_USIZE_TYPE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_PTR_TYPE), ptr_usize.tag);
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_USIZE_TYPE), ptr_usize.data.ptr_type.child);
|
|
|
|
// manyptr_const_u8 (index 48): [*]const u8
|
|
const manyptr = c.ipIndexToKey(&ip, idx(c.IP_INDEX_MANYPTR_CONST_U8_TYPE));
|
|
try std.testing.expectEqual(tag(c.IP_KEY_PTR_TYPE), manyptr.tag);
|
|
try std.testing.expectEqual(idx(c.IP_INDEX_U8_TYPE), manyptr.data.ptr_type.child);
|
|
try std.testing.expect((manyptr.data.ptr_type.flags & idx(c.PTR_FLAGS_SIZE_MASK)) == idx(c.PTR_FLAGS_SIZE_MANY));
|
|
try std.testing.expect((manyptr.data.ptr_type.flags & idx(c.PTR_FLAGS_IS_CONST)) != 0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sema smoke tests (using C sema pipeline directly)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const SemaCheckResult = struct {
|
|
c_ip: c.InternPool,
|
|
c_sema: c.Sema,
|
|
c_func_air_list: c.SemaFuncAirList,
|
|
|
|
fn deinit(self: *SemaCheckResult) void {
|
|
c.semaFuncAirListDeinit(&self.c_func_air_list);
|
|
c.semaDeinit(&self.c_sema);
|
|
c.ipDeinit(&self.c_ip);
|
|
}
|
|
};
|
|
|
|
fn semaCheck(source: [:0]const u8) !SemaCheckResult {
|
|
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);
|
|
var result: SemaCheckResult = undefined;
|
|
result.c_ip = c.ipInit();
|
|
result.c_sema = c.semaInit(&result.c_ip, c_zir);
|
|
result.c_func_air_list = c.semaAnalyze(&result.c_sema);
|
|
return result;
|
|
}
|
|
|
|
test "sema: empty source smoke test" {
|
|
var result = try semaCheck("");
|
|
defer result.deinit();
|
|
|
|
// semaAnalyze frees AIR arrays and nulls out sema's pointers.
|
|
try std.testing.expect(result.c_sema.air_inst_tags == null);
|
|
try std.testing.expect(result.c_sema.air_inst_datas == null);
|
|
try std.testing.expect(result.c_sema.air_extra == null);
|
|
|
|
// No functions analyzed yet, so func_airs should be empty.
|
|
try std.testing.expectEqual(@as(u32, 0), result.c_func_air_list.len);
|
|
}
|
|
|
|
test "sema: const x = 0 smoke test" {
|
|
var result = try semaCheck("const x = 0;");
|
|
defer result.deinit();
|
|
|
|
// No functions, so func_airs should be empty.
|
|
try std.testing.expectEqual(@as(u32, 0), result.c_func_air_list.len);
|
|
}
|
|
|
|
test "sema: function decl smoke test" {
|
|
var result = try semaCheck("fn foo() void {}");
|
|
defer result.deinit();
|
|
|
|
// zirFunc not yet ported, so func_airs should be empty.
|
|
try std.testing.expectEqual(@as(u32, 0), result.c_func_air_list.len);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Air dump: C vs Zig text comparison
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn semaAirDumpCheck(source: [:0]const u8, func_filter: ?[*:0]const u8) !void {
|
|
// C pipeline: parse → astgen → sema → c_dump_air
|
|
var result = try semaCheck(source);
|
|
defer result.deinit();
|
|
const c_text_ptr = c.c_dump_air(&result.c_func_air_list, &result.c_ip, func_filter);
|
|
defer std.c.free(c_text_ptr);
|
|
const c_text: []const u8 = if (c_text_ptr) |p| std.mem.span(p) else "";
|
|
|
|
// Zig pipeline: write source to temp file, compile, dump Air text
|
|
const tmp_path = "/tmp/zig0_sema_test_tmp.zig";
|
|
{
|
|
const f = std.fs.cwd().createFile(tmp_path, .{}) catch return error.TmpFileCreate;
|
|
defer f.close();
|
|
f.writeAll(source) catch return error.TmpFileWrite;
|
|
}
|
|
defer std.fs.cwd().deleteFile(tmp_path) catch {};
|
|
|
|
const zig_result = zig_dump_air(tmp_path, func_filter);
|
|
defer {
|
|
if (zig_result.text) |t| std.c.free(t);
|
|
if (zig_result.error_msg) |e| std.c.free(e);
|
|
}
|
|
if (zig_result.error_msg) |e| {
|
|
std.debug.print("zig_dump_air error: {s}\n", .{std.mem.span(e)});
|
|
return error.ZigDumpFailed;
|
|
}
|
|
const zig_text: []const u8 = if (zig_result.text) |t| std.mem.span(t) else "";
|
|
|
|
try std.testing.expectEqualStrings(zig_text, c_text);
|
|
}
|
|
|
|
test "sema: Air dump C vs Zig comparison (empty)" {
|
|
try semaAirDumpCheck("", null);
|
|
}
|
|
|
|
test "sema: Air dump C vs Zig comparison (const)" {
|
|
try semaAirDumpCheck("const x = 0;", null);
|
|
}
|