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:
@@ -855,30 +855,6 @@ pub const LLVMEmitter = struct {
|
||||
std.debug.print("help: handle it at the `#run` site — `#run <expr> catch (e) {{ ... }}` or `#run <expr> or <default>`\n", .{});
|
||||
}
|
||||
|
||||
/// True when comptime entry `func_id` directly calls a compiler-domain /
|
||||
/// compiler-welded function (or carries a `compiler_call` op). Such an entry
|
||||
/// MUST run on the comptime VM: the BuildOptions accessors (Phase 5.5) are
|
||||
/// VM-only (`comptime_vm.callBuildOptionFn`) with no legacy handler, so a
|
||||
/// legacy-interp run would bail. Routes the `#run` / const-init through the VM
|
||||
/// (no legacy fallback) regardless of the `-Dcomptime-flat` gate, keeping
|
||||
/// gate-OFF green until P5.7 retires the legacy interpreter entirely.
|
||||
fn entryNeedsVm(self: *const LLVMEmitter, func_id: ir_inst.FuncId) bool {
|
||||
const func = self.ir_mod.getFunction(func_id);
|
||||
for (func.blocks.items) |blk| {
|
||||
for (blk.insts.items) |inst| {
|
||||
switch (inst.op) {
|
||||
.call => |call_op| {
|
||||
const callee = self.ir_mod.getFunction(call_op.callee);
|
||||
if (callee.compiler_welded or callee.is_compiler_domain) return true;
|
||||
},
|
||||
.compiler_call => return true,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Run comptime side-effect functions (e.g., `#run main();` at top level).
|
||||
/// These are functions marked `is_comptime = true` with void return that
|
||||
/// aren't associated with any global. They produce compile-time output.
|
||||
@@ -893,70 +869,20 @@ pub const LLVMEmitter = struct {
|
||||
if (!std.mem.startsWith(u8, fname, "__run")) continue;
|
||||
|
||||
const func_id = ir_inst.FuncId.fromIndex(@intCast(i));
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
if (self.import_sources) |sm| interp_inst.setSourceMap(sm);
|
||||
sx_trace_clear();
|
||||
Interpreter.last_bail_op = null;
|
||||
Interpreter.last_bail_builtin = null;
|
||||
Interpreter.last_bail_detail = null;
|
||||
// Flat-memory VM fast path (gated by `SX_COMPTIME_FLAT`), same as the
|
||||
// const-init fold: a VM-handled side-effect that needs no `print`/extern
|
||||
// runs entirely on the VM (no buffered output); anything it can't handle
|
||||
// (`print`, an unported op) bails → `null` → the legacy interpreter below.
|
||||
// A compiler-domain entry (calls a BuildOptions accessor / other
|
||||
// compiler-welded fn) MUST run on the VM — its primitives have no legacy
|
||||
// handler (Phase 5.5). Force the VM attempt + no-fallback for it,
|
||||
// regardless of the `-Dcomptime-flat` gate.
|
||||
const need_vm = self.entryNeedsVm(func_id);
|
||||
const vm_result: ?Value = if (self.comptime_flat or need_vm)
|
||||
comptime_vm.tryEval(self.alloc, self.ir_mod, func_id, &self.build_config, self.import_sources)
|
||||
else
|
||||
null;
|
||||
if (self.comptime_flat and self.comptime_flat_trace) {
|
||||
if (vm_result != null)
|
||||
std.debug.print("[comptime-vm] HANDLED run '{s}'\n", .{fname})
|
||||
else
|
||||
std.debug.print("[comptime-vm] fallback run '{s}': {s}\n", .{ fname, comptime_vm.last_bail_reason orelse "<unknown>" });
|
||||
}
|
||||
const result = vm_result orelse fallback: {
|
||||
// Strict mode (or a compiler-domain entry): NO fallback — a VM bail
|
||||
// is a build-gating error naming the reason.
|
||||
if (self.comptime_flat_strict or need_vm) {
|
||||
std.debug.print("error: comptime `#run` ({s}) bailed on the VM (strict, no fallback): {s}\n", .{ fname, comptime_vm.last_bail_reason orelse "<unknown>" });
|
||||
self.comptime_failed = true;
|
||||
break :fallback Value.void_val;
|
||||
}
|
||||
// The VM bailed: discard any return-trace frames it pushed before
|
||||
// bailing (`sx_trace_push` is a side effect on the shared buffer),
|
||||
// else the legacy re-run double-pushes them (see 1035).
|
||||
if (self.comptime_flat) sx_trace_clear();
|
||||
break :fallback interp_inst.call(func_id, &.{}) catch |err| blk: {
|
||||
// A comptime `#run` side-effect that bails must NOT silently
|
||||
// truncate its output and still ship a successful build.
|
||||
// Surface the bail loudly and fail the build, mirroring the
|
||||
// const-init path in emitGlobals. Whatever output the run
|
||||
// produced before the bail is flushed below so the user sees
|
||||
// where execution stopped.
|
||||
const op = Interpreter.last_bail_op orelse "<unknown>";
|
||||
const detail = Interpreter.last_bail_detail orelse "";
|
||||
const sep: []const u8 = if (detail.len > 0) ": " else "";
|
||||
std.debug.print("error: comptime `#run` ({s}) failed: {s} (op={s}{s}{s})\n", .{ fname, @errorName(err), op, sep, detail });
|
||||
self.comptime_failed = true;
|
||||
break :blk Value.void_val;
|
||||
};
|
||||
// The comptime VM is the SOLE evaluator (P5.7) — no legacy fallback.
|
||||
// A VM-run `#run` side-effect writes its `print` output directly to
|
||||
// fd 1 via host-FFI (no buffered interp output to flush). A bail is a
|
||||
// build-gating error naming the reason.
|
||||
const result = comptime_vm.tryEval(self.alloc, self.ir_mod, func_id, &self.build_config, self.import_sources) orelse {
|
||||
std.debug.print("error: comptime `#run` ({s}) failed: {s}\n", .{ fname, comptime_vm.last_bail_reason orelse "<unknown>" });
|
||||
self.comptime_failed = true;
|
||||
continue;
|
||||
};
|
||||
// Route #run `print` output to fd 1 so it joins the
|
||||
// JIT-executed runtime's stream. Same call site shape as
|
||||
// `core.flushInterpOutput` — see issue-0047.
|
||||
if (interp_inst.output.items.len > 0) {
|
||||
_ = std.c.write(1, interp_inst.output.items.ptr, interp_inst.output.items.len);
|
||||
}
|
||||
// A bare failable `#run f();` whose error escapes → diagnostic + halt.
|
||||
if (self.comptimeErrChannel(func.ret) != null) {
|
||||
_ = self.checkComptimeFailable(result, func.ret, "top-level statement");
|
||||
}
|
||||
interp_inst.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,59 +931,25 @@ pub const LLVMEmitter = struct {
|
||||
|
||||
// Evaluate comptime initializer if present
|
||||
if (global.comptime_func) |func_id| {
|
||||
// The comptime VM is the SOLE evaluator (P5.7) — no legacy
|
||||
// fallback. A bail is ALWAYS a build-gating error naming the
|
||||
// reason. `interp_inst` is a fresh helper context that
|
||||
// `valueToLLVMConst` uses to materialize the VM's result Value
|
||||
// (strings / aggregates) — it does NOT evaluate anything.
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
if (self.import_sources) |sm| interp_inst.setSourceMap(sm);
|
||||
Interpreter.last_bail_op = null;
|
||||
Interpreter.last_bail_builtin = null;
|
||||
Interpreter.last_bail_detail = null;
|
||||
sx_trace_clear();
|
||||
// Flat-memory VM fast path (gated by `SX_COMPTIME_FLAT`): run the
|
||||
// comptime initializer on the VM; `null` (unsupported op / any
|
||||
// bail / implicit-ctx) falls through to the legacy interpreter
|
||||
// below, which produces the identical result. Default OFF.
|
||||
// A compiler-domain initializer (reaches a BuildOptions accessor /
|
||||
// other compiler-welded fn) MUST run on the VM — no legacy handler
|
||||
// exists (Phase 5.5). Force the VM + no-fallback for it.
|
||||
const need_vm = self.entryNeedsVm(func_id);
|
||||
const vm_result: ?Value = if (self.comptime_flat or need_vm)
|
||||
comptime_vm.tryEval(self.alloc, self.ir_mod, func_id, &self.build_config, self.import_sources)
|
||||
else
|
||||
null;
|
||||
// Coverage trace (gated): report whether the VM handled this
|
||||
// comptime init or fell back, and why — names what to port next.
|
||||
if (self.comptime_flat and self.comptime_flat_trace) {
|
||||
const result = comptime_vm.tryEval(self.alloc, self.ir_mod, func_id, &self.build_config, self.import_sources) orelse {
|
||||
// Surface the bail loudly instead of silently filling the
|
||||
// const with zero. Leave the global undef; comptime_failed
|
||||
// halts the build before it ships.
|
||||
const gname = self.ir_mod.types.getString(global.name);
|
||||
if (vm_result != null) {
|
||||
std.debug.print("[comptime-vm] HANDLED init '{s}'\n", .{gname});
|
||||
} else {
|
||||
std.debug.print("[comptime-vm] fallback init '{s}': {s}\n", .{ gname, comptime_vm.last_bail_reason orelse "<unknown>" });
|
||||
}
|
||||
}
|
||||
const result = vm_result orelse fallback: {
|
||||
// Strict mode (or a compiler-domain init): NO fallback — a VM bail
|
||||
// is a build-gating error.
|
||||
if (self.comptime_flat_strict or need_vm) {
|
||||
const gname = self.ir_mod.types.getString(global.name);
|
||||
std.debug.print("error: comptime init of '{s}' bailed on the VM (strict, no fallback): {s}\n", .{ gname, comptime_vm.last_bail_reason orelse "<unknown>" });
|
||||
self.comptime_failed = true;
|
||||
break :fallback Value.void_val;
|
||||
}
|
||||
// The VM bailed: discard any return-trace frames it pushed
|
||||
// before bailing, so the legacy re-run doesn't double-push.
|
||||
if (self.comptime_flat) sx_trace_clear();
|
||||
break :fallback interp_inst.call(func_id, &.{}) catch |err| blk: {
|
||||
// Surface the bail loudly instead of silently filling
|
||||
// the const with zero. Stale state from a previous
|
||||
// comptime function would otherwise hide the error.
|
||||
const op = Interpreter.last_bail_op orelse "<unknown>";
|
||||
const detail = Interpreter.last_bail_detail orelse "";
|
||||
const sep: []const u8 = if (detail.len > 0) ": " else "";
|
||||
const gname = self.ir_mod.types.getString(global.name);
|
||||
std.debug.print("error: comptime init of '{s}' failed: {s} (op={s}{s}{s})\n", .{ gname, @errorName(err), op, sep, detail });
|
||||
self.comptime_failed = true;
|
||||
break :blk .void_val;
|
||||
};
|
||||
std.debug.print("error: comptime init of '{s}' failed: {s}\n", .{ gname, comptime_vm.last_bail_reason orelse "<unknown>" });
|
||||
self.comptime_failed = true;
|
||||
c.LLVMSetInitializer(llvm_global, c.LLVMGetUndef(llvm_ty));
|
||||
self.global_map.put(@intCast(i), llvm_global) catch {};
|
||||
continue;
|
||||
};
|
||||
// A bare failable `NAME :: #run f();`: the comptime function
|
||||
// returns the failable tuple; split it. Escaping error →
|
||||
|
||||
Reference in New Issue
Block a user