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:
11
build.zig
11
build.zig
@@ -244,8 +244,19 @@ pub fn build(b: *std.Build) void {
|
||||
"comptime-flat",
|
||||
"Default comptime evaluation to the flat-memory VM (legacy interp as fallback)",
|
||||
) orelse false;
|
||||
// `-Dcomptime-flat-strict` (or env `SX_COMPTIME_FLAT_STRICT`): run EVERY comptime
|
||||
// eval on the VM with NO legacy fallback — a VM bail becomes a build-gating error
|
||||
// naming the reason. The enumeration gate for retiring `interp.zig`: when the
|
||||
// corpus is green under strict mode, the VM handles everything and legacy can be
|
||||
// deleted. Implies `comptime_flat`.
|
||||
const comptime_flat_strict = b.option(
|
||||
bool,
|
||||
"comptime-flat-strict",
|
||||
"Run all comptime eval on the VM with NO fallback; a bail is a hard error (interp-retirement gate)",
|
||||
) orelse false;
|
||||
const build_opts = b.addOptions();
|
||||
build_opts.addOption(bool, "comptime_flat", comptime_flat);
|
||||
build_opts.addOption(bool, "comptime_flat_strict", comptime_flat_strict);
|
||||
mod.addOptions("build_opts", build_opts);
|
||||
|
||||
const mod_tests = b.addTest(.{
|
||||
|
||||
@@ -373,6 +373,35 @@ when reached (sentinels or accessor fns; see the design doc Risks).
|
||||
`List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.)
|
||||
|
||||
## Log
|
||||
- **Phase 4 — STRICT no-fallback mode (the interp-retirement enumeration gate) + full gap list (2026-06-18).**
|
||||
Added `-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. This 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, the VM handles everything and
|
||||
`interp.zig` can be deleted (4F). 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, so
|
||||
every type-fn falsely reported `<unknown>`; now strict implies flat there too.)
|
||||
**THE DELETION CHECKLIST (19 strict bails, swept via `SX_COMPTIME_FLAT_STRICT=1` over examples+issues;
|
||||
0103/0800 "WRONG" were false positives — raw heap-pointer addresses the corpus normalizes):**
|
||||
- `switch_br` (5): 0114, 0521, 0522, 0524, 1035 — port the type-category multi-way branch (trivial
|
||||
jump). **CAUTION:** porting it (+`type_name`) UNMASKS a silent-wrong in 0114 — a `[]Type` slice
|
||||
materialized when a pack (`$args`) is passed ACROSS A CALL reads its `string` element as
|
||||
`<unresolved>`. Must fix that VM pack-Type-materialization bug, not just add the op.
|
||||
- `compiler_call` (6): 0602, 0603, 1604, 1609, 1611, 1615 — the **BuildOptions → `abi(.zig) extern
|
||||
compiler`** migration (delete `#compiler`/`compiler_call`; thread `BuildConfig` into the VM). Big.
|
||||
- `out` (2): 0613, 1038 — comptime print. Direct write to fd 1, BUT only safe when the WHOLE eval is
|
||||
VM-handled (a print-then-bail double-prints under the legacy re-run — 0613). Flip atomically.
|
||||
- `type_name` (1): 0520 — reflection reader (`.type_value` word / Any-box tag → `table.typeName`).
|
||||
- `global_addr` (1): 0600 — only `&__sx_default_context` is materialised (mirror legacy).
|
||||
- `interp_print_frames` (1): 1034 — return-trace frame printing.
|
||||
- VM-native diagnostics (4B) (2): 1179, 1180 — NEGATIVE tests; the VM bail (`define: enum has no
|
||||
variants` / `duplicate variant name`) IS the expected outcome → must surface as the proper
|
||||
build-gating diagnostic, not the generic strict error.
|
||||
- dlsym not found (1): 1654 — a target-specific `extern` (asm global) called at comptime; likely a
|
||||
legitimately-unresolvable case → confirm it stays a clean diagnostic.
|
||||
**Sweep command:** `SX_COMPTIME_FLAT_STRICT=1 ./zig-out/bin/sx run <ex>` per example, diff vs legacy;
|
||||
a strict bail prints `... bailed on the VM (strict, no fallback): <reason>`.
|
||||
- **Phase 4D.2 (VM plan) — extern SLICE/string args (→ NUL-terminated `char*`) + float guards (2026-06-18).**
|
||||
Extracted `marshalExternArg`: a scalar/pointer WORD passes verbatim (a `cstring` arg already works
|
||||
as a pointer word via 4D.1); a `string`/slice `{ptr,len}` fat pointer is copied into a
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user