diff --git a/src/test_exports.zig b/src/test_exports.zig index 880612ac90..637b84dd47 100644 --- a/src/test_exports.zig +++ b/src/test_exports.zig @@ -2,4 +2,5 @@ pub const InternPool = @import("InternPool.zig"); pub const Air = @import("Air.zig"); pub const Compilation = @import("Compilation.zig"); pub const Package = @import("Package.zig"); +pub const Zcu = @import("Zcu.zig"); // Later: pub const Sema = @import("Sema.zig"); diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig index aafa7c33f7..9dcaf8a582 100644 --- a/stage0/stages_test.zig +++ b/stage0/stages_test.zig @@ -74,24 +74,78 @@ fn stagesCheck(gpa: Allocator, source: [:0]const u8, src_path: []const u8, check var zig_result = try sema.zigSema(gpa, src_path); defer zig_result.deinit(); - // Compare per-function Air: C and Zig must produce the same count. + // Parse Zig per-function Air sections from captured verbose_air output. const zig_air_text = zig_result.air_output.written(); - const zig_func_count = countAirSections(zig_air_text); - try std.testing.expectEqual(zig_func_count, result.func_airs.len); + var zig_sections = try parseAirSections(gpa, zig_air_text); + defer deinitAirSections(gpa, &zig_sections); + + // C and Zig must produce the same number of per-function Airs. + try std.testing.expectEqual(zig_sections.count(), result.func_airs.len); + + // Compare per-function Air text. + const Zcu = zig_internals.Zcu; + const pt: Zcu.PerThread = .activate(zig_result.comp.zcu.?, .main); + defer pt.deactivate(); + + for (result.func_airs) |func_air| { + const zig_section = zig_sections.get(func_air.name) orelse { + std.debug.print("C sema produced function '{s}' not found in Zig sema\n", .{func_air.name}); + return error.TestUnexpectedResult; + }; + + // Render C Air as text using Zig's PerThread. InternPool indices + // must match between C and Zig for the same source — if they don't, + // the text will differ and the test catches the bug. + var c_air_buf: std.io.Writer.Allocating = .init(gpa); + defer c_air_buf.deinit(); + func_air.owned_air.air().write(&c_air_buf.writer, pt, null); + + try std.testing.expectEqualStrings(zig_section, c_air_buf.written()); + } } } -/// Count the number of per-function Air sections in verbose_air output. -/// Sections are delimited by "# Begin Function AIR: " markers. -fn countAirSections(text: []const u8) usize { - const marker = "# Begin Function AIR: "; - var count: usize = 0; +/// Parse verbose_air output into per-function sections keyed by FQN. +/// Sections are delimited by "# Begin Function AIR: {fqn}:\n" and +/// "# End Function AIR: {fqn}\n\n" markers. +/// Returns owned keys and values that the caller must free. +fn parseAirSections(gpa: Allocator, text: []const u8) !std.StringHashMapUnmanaged([]const u8) { + const begin_marker = "# Begin Function AIR: "; + const end_marker = "# End Function AIR: "; + var map: std.StringHashMapUnmanaged([]const u8) = .empty; + errdefer deinitAirSections(gpa, &map); + var pos: usize = 0; - while (std.mem.indexOfPos(u8, text, pos, marker)) |begin| { - count += 1; - pos = begin + marker.len; + while (std.mem.indexOfPos(u8, text, pos, begin_marker)) |begin| { + const fqn_start = begin + begin_marker.len; + // FQN ends at ":\n" + const fqn_end = std.mem.indexOfPos(u8, text, fqn_start, ":\n") orelse break; + const fqn = text[fqn_start..fqn_end]; + + // Body starts after ":\n" + const body_start = fqn_end + 2; + + // Find the matching end marker + const end_pos = std.mem.indexOfPos(u8, text, body_start, end_marker) orelse break; + + const body = text[body_start..end_pos]; + const key = try gpa.dupe(u8, fqn); + errdefer gpa.free(key); + const val = try gpa.dupe(u8, body); + try map.put(gpa, key, val); + + pos = end_pos + end_marker.len; } - return count; + return map; +} + +fn deinitAirSections(gpa: Allocator, map: *std.StringHashMapUnmanaged([]const u8)) void { + var it = map.iterator(); + while (it.next()) |entry| { + gpa.free(entry.value_ptr.*); + gpa.free(entry.key_ptr.*); + } + map.deinit(gpa); } const last_successful_corpus = "../lib/std/crypto/codecs.zig";