comptime VM: strict no-fallback mode — the interp-retirement enumeration gate (Phase 4)

Add -Dcomptime-flat-strict / env SX_COMPTIME_FLAT_STRICT (implies comptime_flat):
at all three comptime sites (type-fn in lower/comptime.zig, const-init + #run in
emit_llvm.zig) a VM bail becomes a build-gating error naming the reason INSTEAD of
falling back to legacy. Forces every comptime eval onto the VM so the complete gap
set is enumerable in one sweep; when the corpus is green under strict mode AND every
example matches legacy, interp.zig can be deleted.

Default behaviour unchanged (699/0 both default gates). Fixed a wiring bug: the
type-fn site's local comptime_flat didn't include the strict flag (every type-fn
falsely reported <unknown>); strict now implies flat there too.

Swept the gap list (19 strict bails): switch_br (5, + unmasks a []Type-across-call
silent-wrong in 0114), compiler_call (6, = the BuildOptions->abi(.zig) extern
compiler migration), out (2), type_name (1), global_addr (1), interp_print_frames
(1), 2 negative-test diagnostics (1179/1180), 1 dlsym (1654). Recorded as the
deletion checklist in CHECKPOINT-COMPILER-API.md.
This commit is contained in:
agra
2026-06-18 19:06:51 +03:00
parent da6a8423c7
commit dcb1392255
4 changed files with 74 additions and 2 deletions

View File

@@ -127,6 +127,11 @@ pub const LLVMEmitter = struct {
// for porting the next ops. Default OFF.
comptime_flat_trace: bool = false,
// When set (`-Dcomptime-flat-strict` / env `SX_COMPTIME_FLAT_STRICT`), a VM bail
// does NOT fall back to the legacy interpreter — it becomes a build-gating error.
// The enumeration gate for retiring `interp.zig`. Implies `comptime_flat`.
comptime_flat_strict: bool = false,
// Allocator for temporary bookkeeping
alloc: Allocator,
@@ -337,8 +342,10 @@ pub const LLVMEmitter = struct {
.frame_str_cache = std.StringHashMap(c.LLVMValueRef).init(alloc),
// Enabled by the `-Dcomptime-flat` build flag OR the `SX_COMPTIME_FLAT`
// env var (either turns it on); default OFF (legacy interpreter).
.comptime_flat = build_opts.comptime_flat or std.c.getenv("SX_COMPTIME_FLAT") != null,
.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,
.comptime_flat_trace = std.c.getenv("SX_COMPTIME_FLAT_TRACE") != null,
.comptime_flat_strict = build_opts.comptime_flat_strict or std.c.getenv("SX_COMPTIME_FLAT_STRICT") != null,
};
}
@@ -878,6 +885,13 @@ pub const LLVMEmitter = struct {
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: NO fallback — a VM bail is a build-gating error naming
// the reason (the interp-retirement enumeration gate).
if (self.comptime_flat_strict) {
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).
@@ -982,6 +996,14 @@ pub const LLVMEmitter = struct {
}
}
const result = vm_result orelse fallback: {
// Strict mode: NO fallback — a VM bail is a build-gating error
// (the interp-retirement enumeration gate).
if (self.comptime_flat_strict) {
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();

View File

@@ -525,7 +525,9 @@ pub fn runComptimeTypeFunc(self: *Lowering, func_id: FuncId, span: ast.Span) ?Ty
// 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.
const comptime_flat = build_opts.comptime_flat or std.c.getenv("SX_COMPTIME_FLAT") != null;
// 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)
else
@@ -541,6 +543,14 @@ pub fn runComptimeTypeFunc(self: *Lowering, func_id: FuncId, span: ast.Span) ?Ty
return checkComptimeTypeResult(self, tid_vm, span);
}
// Strict mode: NO fallback — a VM bail is a build-gating failure naming the
// reason (the interp-retirement enumeration gate). Returning null leaves the
// type unresolved → a downstream diagnostic fails the build.
if (build_opts.comptime_flat_strict or std.c.getenv("SX_COMPTIME_FLAT_STRICT") != null) {
std.debug.print("error: comptime type-fn bailed on the VM (strict, no fallback): {s}\n", .{comptime_vm.last_bail_reason orelse "<unknown>"});
return null;
}
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