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>
This commit is contained in:
174
src/verbose_air.zig
Normal file
174
src/verbose_air.zig
Normal file
@@ -0,0 +1,174 @@
|
||||
// verbose_air.zig — Zig-side dumper for Air text output.
|
||||
// Compiles source via the Zig compiler pipeline and captures verbose_air output.
|
||||
// Exports C-compatible functions for use by stage0 tests.
|
||||
|
||||
const std = @import("std");
|
||||
const zig_internals = @import("zig_internals");
|
||||
const Compilation = zig_internals.Compilation;
|
||||
const Package = zig_internals.Package;
|
||||
|
||||
comptime {
|
||||
_ = @import("verbose_intern_pool.zig");
|
||||
}
|
||||
|
||||
const DumpResult = extern struct {
|
||||
text: ?[*:0]u8,
|
||||
error_msg: ?[*:0]u8,
|
||||
};
|
||||
|
||||
/// Compile the source file at `src_path` (relative to cwd) through the Zig
|
||||
/// pipeline and return the verbose_air text output.
|
||||
/// func_filter: NULL=all functions, "foo"=only functions containing 'foo'.
|
||||
export fn zig_dump_air(
|
||||
src_path_ptr: [*:0]const u8,
|
||||
func_filter: ?[*:0]const u8,
|
||||
) DumpResult {
|
||||
return zigDumpAirImpl(std.mem.span(src_path_ptr), func_filter) catch |err| {
|
||||
return errResult(@errorName(err));
|
||||
};
|
||||
}
|
||||
|
||||
fn errResult(msg: []const u8) DumpResult {
|
||||
const duped = std.heap.c_allocator.dupeZ(u8, msg) catch
|
||||
return .{ .text = null, .error_msg = null };
|
||||
return .{ .text = null, .error_msg = duped.ptr };
|
||||
}
|
||||
|
||||
fn zigDumpAirImpl(src_path: []const u8, func_filter: ?[*:0]const u8) !DumpResult {
|
||||
const gpa = std.heap.c_allocator;
|
||||
|
||||
var arena_state = std.heap.ArenaAllocator.init(gpa);
|
||||
defer arena_state.deinit();
|
||||
const arena = arena_state.allocator();
|
||||
|
||||
var dirs: Compilation.Directories = .init(
|
||||
arena,
|
||||
"lib/",
|
||||
null,
|
||||
.search,
|
||||
{},
|
||||
"",
|
||||
);
|
||||
defer dirs.deinit();
|
||||
|
||||
// Hardcode x86_64-linux-musl target.
|
||||
const resolved_target: Package.Module.ResolvedTarget = .{
|
||||
.result = try std.zig.system.resolveTargetQuery(.{
|
||||
.cpu_arch = .x86_64,
|
||||
.os_tag = .linux,
|
||||
.abi = .musl,
|
||||
}),
|
||||
.is_native_os = false,
|
||||
.is_native_abi = false,
|
||||
.is_explicit_dynamic_linker = false,
|
||||
};
|
||||
|
||||
const config = try Compilation.Config.resolve(.{
|
||||
.output_mode = .Obj,
|
||||
.resolved_target = resolved_target,
|
||||
.have_zcu = true,
|
||||
.emit_bin = false,
|
||||
.is_test = false,
|
||||
});
|
||||
|
||||
// Split src_path into directory and filename for the Module.
|
||||
// Use .root = .none with absolute path to avoid the source being
|
||||
// associated with zig_lib (which would conflict with the std module
|
||||
// when compiling files under lib/).
|
||||
const src_dir = std.fs.path.dirname(src_path) orelse ".";
|
||||
const src_basename = std.fs.path.basename(src_path);
|
||||
const abs_src_dir = try std.fs.cwd().realpathAlloc(arena, src_dir);
|
||||
const root_path: Compilation.Path = .{ .root = .none, .sub_path = abs_src_dir };
|
||||
|
||||
const root_mod = try Package.Module.create(arena, .{
|
||||
.paths = .{
|
||||
.root = root_path,
|
||||
.root_src_path = src_basename,
|
||||
},
|
||||
.fully_qualified_name = "root",
|
||||
.cc_argv = &.{},
|
||||
.inherited = .{
|
||||
.resolved_target = resolved_target,
|
||||
},
|
||||
.global = config,
|
||||
.parent = null,
|
||||
});
|
||||
|
||||
// Heap-allocate the thread pool so its address stays stable.
|
||||
const thread_pool = try gpa.create(std.Thread.Pool);
|
||||
thread_pool.* = undefined;
|
||||
try thread_pool.init(.{
|
||||
.allocator = gpa,
|
||||
.n_jobs = 1,
|
||||
.track_ids = true,
|
||||
.stack_size = 60 << 20,
|
||||
});
|
||||
defer {
|
||||
thread_pool.deinit();
|
||||
gpa.destroy(thread_pool);
|
||||
}
|
||||
|
||||
var create_diag: Compilation.CreateDiagnostic = undefined;
|
||||
const comp = Compilation.create(gpa, arena, &create_diag, .{
|
||||
.dirs = dirs,
|
||||
.root_name = "test",
|
||||
.config = config,
|
||||
.root_mod = root_mod,
|
||||
.emit_bin = .no,
|
||||
.thread_pool = thread_pool,
|
||||
.cache_mode = .whole,
|
||||
.verbose_air = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.CreateFail => {
|
||||
return errResult("Compilation.create failed");
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
defer comp.destroy();
|
||||
|
||||
// Capture per-function Air text via the verbose_air_output mechanism.
|
||||
var air_output: std.io.Writer.Allocating = .init(gpa);
|
||||
defer air_output.deinit();
|
||||
comp.verbose_air_output = &air_output.writer;
|
||||
|
||||
try comp.update(std.Progress.Node.none);
|
||||
|
||||
var error_bundle = try comp.getAllErrorsAlloc();
|
||||
defer error_bundle.deinit(gpa);
|
||||
if (error_bundle.errorMessageCount() > 0) {
|
||||
return errResult("compilation produced errors");
|
||||
}
|
||||
|
||||
const full_text = air_output.written();
|
||||
|
||||
// Filter by function name if specified.
|
||||
if (func_filter) |filter_ptr| {
|
||||
const filter_str = std.mem.span(filter_ptr);
|
||||
var filtered: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer filtered.deinit(gpa);
|
||||
|
||||
const begin_marker = "# Begin Function AIR: ";
|
||||
const end_marker = "# End Function AIR: ";
|
||||
var pos: usize = 0;
|
||||
while (std.mem.indexOfPos(u8, full_text, pos, begin_marker)) |begin| {
|
||||
const fqn_start = begin + begin_marker.len;
|
||||
const fqn_end = std.mem.indexOfPos(u8, full_text, fqn_start, ":\n") orelse break;
|
||||
const fqn = full_text[fqn_start..fqn_end];
|
||||
|
||||
const end_search = std.mem.indexOfPos(u8, full_text, fqn_end, end_marker) orelse break;
|
||||
const line_end = std.mem.indexOfPos(u8, full_text, end_search, "\n\n") orelse full_text.len;
|
||||
const section_end = @min(line_end + 2, full_text.len);
|
||||
|
||||
if (std.mem.indexOf(u8, fqn, filter_str) != null) {
|
||||
try filtered.appendSlice(gpa, full_text[begin..section_end]);
|
||||
}
|
||||
pos = section_end;
|
||||
}
|
||||
|
||||
const result = try gpa.dupeZ(u8, filtered.items);
|
||||
return .{ .text = result.ptr, .error_msg = null };
|
||||
} else {
|
||||
const result = try gpa.dupeZ(u8, full_text);
|
||||
return .{ .text = result.ptr, .error_msg = null };
|
||||
}
|
||||
}
|
||||
23
src/verbose_intern_pool.zig
Normal file
23
src/verbose_intern_pool.zig
Normal file
@@ -0,0 +1,23 @@
|
||||
// verbose_intern_pool.zig — Zig-side dumper for InternPool text output.
|
||||
// Compiles source via the Zig compiler pipeline and dumps the InternPool.
|
||||
// Exports a C-compatible function for use by stage0 tests.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const DumpResult = extern struct {
|
||||
text: ?[*:0]u8,
|
||||
error_msg: ?[*:0]u8,
|
||||
};
|
||||
|
||||
export fn zig_dump_intern_pool(
|
||||
source_ptr: [*]const u8,
|
||||
source_len: usize,
|
||||
) DumpResult {
|
||||
// Stub: not yet implemented.
|
||||
_ = source_ptr;
|
||||
_ = source_len;
|
||||
const gpa = std.heap.c_allocator;
|
||||
const result = gpa.dupeZ(u8, "") catch
|
||||
return .{ .text = null, .error_msg = null };
|
||||
return .{ .text = result.ptr, .error_msg = null };
|
||||
}
|
||||
Reference in New Issue
Block a user