Files
zig/stage0/sema_test.zig
Motiejus Jakštys 00a6cb4fd4 Replace structural Air/IP comparison with text-based dumpers
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>
2026-02-19 14:48:40 +00:00

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);
}