P5.1: post-link build driver runs on the comptime VM (no fallback)

core.invokeByFuncId routes the post-link callback through comptime_vm.tryEval
instead of the legacy Interpreter. REQUIRED because the sx build driver
allocates/grows Lists, which the legacy interp can't do at comptime (issue
0141: struct_get: base has no fields); the VM can. No fallback (a
side-effecting post-link callback can't double-execute): a VM bail is a hard
build error (comptime_vm.last_bail_reason, surfaced by printInterpBailDiag).
BuildConfig + import_sources threaded in; non-empty args rejected loudly.
flushInterpOutput deleted (VM out writes direct via host-FFI).

Smoke test examples/1661-platform-post-link-vm-list (AOT): a post-link
callback grows a List to 3 + returns len==3, so the build succeeds (exit 0)
only via the VM. First corpus coverage of the post-link path.

702/0 both gates.
This commit is contained in:
agra
2026-06-19 07:20:42 +03:00
parent 2060373c16
commit 7cba33ea6d
9 changed files with 99 additions and 30 deletions

View File

@@ -208,28 +208,21 @@ pub const Compilation = struct {
/// invoke it later without name lookup.
pub fn invokeByFuncId(self: *Compilation, id: ir.FuncId, args: []const ir.Value) !ir.Value {
const mod = self.ir_module orelse return error.NoIRModule;
var interp = ir.Interpreter.init(mod, self.allocator);
defer interp.deinit();
interp.setSourceMap(&self.import_sources);
if (self.ir_emitter) |*e| interp.build_config = &e.build_config;
ir.Interpreter.last_bail_op = null;
ir.Interpreter.last_bail_builtin = null;
ir.Interpreter.last_bail_detail = null;
const result = interp.call(id, args) catch |err| {
flushInterpOutput(interp.output.items);
return err;
};
flushInterpOutput(interp.output.items);
return result;
}
/// #run / post-link callback `print` output lands here. Routes to
/// fd 1 (stdout) so it joins the JIT-executed runtime's output
/// stream — the user wrote `print(...)` in both call sites, so
/// the stream split is invisible to them. issue-0047.
fn flushInterpOutput(bytes: []const u8) void {
if (bytes.len == 0) return;
_ = std.c.write(1, bytes.ptr, bytes.len);
// The build driver (post-link callback) runs on the comptime VM — NOT
// the legacy interp. The driver allocates Lists, which the legacy interp
// cannot grow at comptime (issue 0141: `struct_get: base has no fields`);
// the VM can. There is **no fallback**: a side-effecting post-link
// callback can't safely re-run on a second evaluator (double execution),
// so a VM bail is a hard build error. The bail reason is in
// `comptime_vm.last_bail_reason` (surfaced by `main.printInterpBailDiag`).
// Post-link callbacks are nullary today (the implicit `*Context` is
// materialized by the VM's `runEntry`); a non-empty `args` would need a
// VM entry that marshals them, which arrives with the `on_build(config)`
// slot (Phase 5.3) — reject it loudly rather than silently drop.
if (args.len != 0) return error.ComptimeVmArgsUnsupported;
const build_config = if (self.ir_emitter) |*e| &e.build_config else null;
return ir.comptime_vm.tryEval(self.allocator, mod, id, build_config, &self.import_sources) orelse
error.ComptimeVmBail;
}
/// Get link flags accumulated from #run build blocks.