P5.7 Step A: VM is the sole comptime evaluator at emit-time + type-fn sites (no fallback)

Remove the comptime_flat/need_vm gate and the vm_result-orelse-legacy
fallback from emit_llvm.zig (runComptimeSideEffects + emitGlobals const-init)
and comptime.zig (runComptimeTypeFunc). The comptime VM now always runs;
a bail is always a build-gating diagnostic, never a fallback. Delete the
now-moot entryNeedsVm. runComptimeSideEffects drops the Interpreter entirely
(VM writes #run output direct to fd 1); emitGlobals keeps a fresh interp_inst
only as the valueToLLVMConst materialization context (the regToValue bridge,
removed with interp.zig in a later step).

#insert (evalComptimeString) still routes through the legacy interp — deferred
until interp.zig deletion.

Reconcile 1654: the comptime asm-global #run now reports the VM's clean dlsym
bail instead of the legacy CannotEvalComptime wrapper (exit still 1).

501/501 unit + 706/0 corpus.
This commit is contained in:
agra
2026-06-19 16:44:52 +03:00
parent ab8f0d41bb
commit 5d25e23143
5 changed files with 73 additions and 190 deletions

View File

@@ -506,74 +506,33 @@ pub fn runComptimeTypeFunc(self: *Lowering, func_id: FuncId, span: ast.Span) ?Ty
}
}
var interp = interp_mod.Interpreter.init(self.module, self.alloc);
defer interp.deinit();
if (self.diagnostics) |d| if (d.import_sources) |sm| interp.setSourceMap(sm);
interp.setMintTable(&self.module.types);
// Clear the interp's last-bail channel so a bail HERE is attributable to
// THIS construction (not a stale message from an earlier comptime eval).
interp_mod.Interpreter.last_bail_detail = null;
// Flat-memory VM fast path (gated by `-Dcomptime-flat` / `SX_COMPTIME_FLAT`),
// the THIRD comptime call site after the two emit-time folds. A type-fn runs
// on the VM; `null` (any bail) falls through to the legacy interpreter below,
// which mints identically. The VM bails BEFORE any table mutation — its
// compiler-WRITE fns (declare_type/register_type/pointer_to) aren't ported to
// `callCompilerFn`, and it can't yet model a `Type` result — so a minting
// type-fn bails at the first write call (no partial mint → no double-mint).
// The VM is hardened against malformed lowering-time IR (it BAILS, never
// panics; see `comptime_vm.refTy`/`badRef`). Today this is near-pure fallback;
// it lights up as `Type` modeling + the VM-native write side land.
// Strict mode implies flat (run the VM, then hard-error instead of falling back).
const comptime_flat = build_opts.comptime_flat or std.c.getenv("SX_COMPTIME_FLAT") != null or
build_opts.comptime_flat_strict or std.c.getenv("SX_COMPTIME_FLAT_STRICT") != null;
const vm_result: ?interp_mod.Value = if (comptime_flat)
comptime_vm.tryEval(self.alloc, self.module, func_id, null, null)
else
null;
if (comptime_flat and std.c.getenv("SX_COMPTIME_FLAT_TRACE") != null) {
// The comptime VM is the SOLE evaluator (P5.7) — no legacy fallback. A
// type-fn runs on the VM; a bail is ALWAYS a build-gating diagnostic, never a
// fallback. The VM is hardened against malformed lowering-time IR (it BAILS,
// never panics; see `comptime_vm.refTy`/`badRef`), and bails BEFORE any table
// mutation, so a failed mint never leaves a partial type.
const vm_result = comptime_vm.tryEval(self.alloc, self.module, func_id, null, null);
if (std.c.getenv("SX_COMPTIME_FLAT_TRACE") != null) {
if (vm_result != null)
std.debug.print("[comptime-vm] HANDLED type-fn\n", .{})
else
std.debug.print("[comptime-vm] fallback type-fn: {s}\n", .{comptime_vm.last_bail_reason orelse "<unknown>"});
std.debug.print("[comptime-vm] BAIL type-fn: {s}\n", .{comptime_vm.last_bail_reason orelse "<unknown>"});
}
if (vm_result) |v| {
const tid_vm = v.asTypeId() orelse return null;
return checkComptimeTypeResult(self, tid_vm, span);
}
// Strict mode: NO fallback — render the VM's bail reason as the SAME
// build-gating diagnostic the non-strict legacy path emits below (the VM and
// legacy set identical detail strings, e.g. "comptime define(): duplicate
// variant name 'x'"), so a comptime type-construction failure (1179/1180)
// produces its proper user diagnostic with no legacy interp in the loop — the
// 4B step toward deleting the fallback. (4B / VM-native diagnostics.)
if (build_opts.comptime_flat_strict or std.c.getenv("SX_COMPTIME_FLAT_STRICT") != null) {
if (self.diagnostics) |d| {
d.addFmt(.err, span, "comptime type construction failed: {s}", .{comptime_vm.last_bail_reason orelse "<unknown>"});
}
return null;
// VM bailed: render a build-gating diagnostic naming the reason — NOT poison
// to `.unresolved` silently and let that crash at LLVM emission ("unresolved
// type reached LLVM emission") or hide behind a downstream cascade (issue
// 0140). The VM's bail reason carries the precise cause (e.g. "comptime
// define(): duplicate variant name 'x'"), so a comptime type-construction
// failure (1179/1180) produces its proper user diagnostic.
if (self.diagnostics) |d| {
d.addFmt(.err, span, "comptime type construction failed: {s}", .{comptime_vm.last_bail_reason orelse "<unknown>"});
}
const result = interp.call(func_id, &.{}) catch |err| {
// A comptime type construction (declare/define, reflection) that bails
// must surface a build-gating diagnostic naming the reason — NOT poison
// to `.unresolved` silently and let that crash at LLVM emission
// ("unresolved type reached LLVM emission") or hide behind a downstream
// cascade (issue 0140). The interp's `bailDetail` already set the precise
// reason; mirror the `#run` path (emit_llvm.zig) and render it. Returning
// null keeps the `.unresolved` poison for the caller, but now the build
// is gated by a real message, so no unresolved type reaches emission
// unannounced.
if (self.diagnostics) |d| {
const detail = interp_mod.Interpreter.last_bail_detail orelse @errorName(err);
d.addFmt(.err, span, "comptime type construction failed: {s}", .{detail});
}
return null;
};
const tid = result.asTypeId() orelse return null;
return checkComptimeTypeResult(self, tid, span);
return null;
}
/// Post-check a comptime type-construction result (shared by the VM and legacy