comptime VM arc: abi(.compiler) ABI, out as sx fn, VM-native diagnostics, BuildConfig threaded
Lands the full VM/compiler-API arc on branch reify (701/0 both gates): - abi(.compiler) ABI replaces abi(.zig) extern compiler + the fake #library "compiler"; bodiless decl = compiler-API surface, bodied = user compiler-domain fn (lowered for VM eval, emit-skipped). - out is a plain sx fn (libc write) — the out builtin deleted; the VM handles it via host-FFI. trace_resolve + interp_print_frames ported. - 4B VM-native diagnostics: 1179/1180 render proper comptime type construction failed: under strict. - S5a: build_options/set_post_link_callback on abi(.compiler) with BuildConfig threaded into the VM (green intermediate). - 0522 fixed (describe(args: []Type)); regression 0638. Strict deletion-gate down to 4 compiler_call bails (1609/1614/1615/1616) + 1654 (legitimate unresolvable-symbol diagnostic).
This commit is contained in:
@@ -57,14 +57,37 @@ with ONE welded mechanism. Branch: `reify` (off `master`). Update after every st
|
||||
> instead of `struct #compiler`; (3) delete the `compiler_call` op + `compiler_hooks.zig`
|
||||
> `HookFn`/`Registry` + the `#compiler` parse/lower path. See `PLAN-COMPILER-VM.md` Phase 4.
|
||||
>
|
||||
> **Corpus-driven remainder (independent of the BuildOptions migration):** **4A leftovers** —
|
||||
> out/print (double-output-on-fallback caveat: write directly only once the whole eval is VM-handled),
|
||||
> global_addr, trace, and `switch_br` for the Any-tag type-switch (0114/0520–0524/1035, the box_any
|
||||
> examples that now bail further at `switch_br`/`type_name`) · **4B** VM diagnostics · **4C** `#insert`.
|
||||
> Then the BuildOptions migration + **4E** bundler (+ dedicated bundle tests) + **4F** flip default +
|
||||
> delete `interp.zig`/`Value` + re-express `define`/`make_enum`.
|
||||
> Starting at **4A.1 (box_any/unbox_any)**. See `PLAN-COMPILER-VM.md` → Phase 4 for the full plan +
|
||||
> top risks (flat-pointer escape on buffer realloc; bundler test coverage).
|
||||
> **Corpus-driven remainder (independent of the BuildOptions migration):** ALL PURE ops are DONE:
|
||||
> `switch_br`, `type_name`, `error_tag_name_get`, `global_addr`, `type_is_unsigned`. **`out` DONE (2026-06-19,
|
||||
> newest Log entry):** removed the `out` builtin — it's a plain sx fn calling libc `write`, so the VM handles it
|
||||
> via host-FFI (no buffer, no special arm; no double-print because there's no `out` op to bail-then-fallback on).
|
||||
> `trace_resolve` PORTED (1035). 0613/1035/0522/1038 run VM-HANDLED. Remaining side-effect op: `interp_print_frames`
|
||||
> (1034 — writes the comptime frame chain; could likewise become a plain sx fn over the trace runtime).
|
||||
> · **4B VM diagnostics (1179/1180) — DONE** (strict renders the proper `comptime type construction failed:`
|
||||
> diagnostic; VM-gap strict bails are now ONLY the 4 `compiler_call`) · **4C** `#insert`. **BuildOptions migration — design settled +
|
||||
> foundation underway (2026-06-18, see the two newest Log entries):** `#compiler`/`compiler_call` is replaced
|
||||
> by `abi(.compiler)` (a compiler-domain ABI — runs in the comptime evaluator, never in the binary). **S1+S2
|
||||
> DONE:** `abi(.compiler)` introduced, the old `abi(.zig) extern compiler` + `#library "compiler"` fully removed,
|
||||
> all compiler-API examples migrated. **S3 DONE:** emit_llvm skips BODIED `abi(.compiler)` (compiler-domain)
|
||||
> functions; a comptime-only call from a dead body emits `undef` (regression `0638`; 701/0 both gates). The
|
||||
> earlier "runtime-reachability gating" blocker is MOOT — a compiler-domain callback isn't LLVM-emitted, so its
|
||||
> `build_options()` calls never reach the `emitCall` gate. **S4 SKIPPED (optional ergonomics):** an
|
||||
> `abi(.compiler)` function is type-compatible with a plain `() -> R` param (the ABI marks the *function*, not
|
||||
> its *type*), so callbacks/registrars just declare themselves `abi(.compiler)` (S3) — no param-propagation
|
||||
> needed. **S5a DONE:** `build_options` + `set_post_link_callback` → `abi(.compiler)`, `BuildConfig` threaded
|
||||
> into the VM; `bundle_main` + the platform registrars marked `abi(.compiler)`; strict `compiler_call` bails
|
||||
> 6→2 (0602/0603/1604/1611 HANDLED). **S5a is a GREEN INTERMEDIATE — do NOT extend it.** **DESIGN PIVOT
|
||||
> (2026-06-18, user): the 37-hook BuildOptions port is DEAD — DRIVE THE BUILD PIPELINE FROM SX** (newest Log
|
||||
> entry + `PLAN-COMPILER-VM.md` → Phase 5). `BuildConfig` becomes plain sx data; the compiler shrinks to a few
|
||||
> `abi(.compiler)` primitives (`emit_object`/`link`/queries, explicit args, `-> !` not bool) + an `on_build`
|
||||
> slot (stdlib `default_build`, user override `#run on_build = build;`). **NEXT — P5.1 (= 4E):** route the
|
||||
> post-codegen / `on_build` invocation through the VM (`core.invokeByFuncId` → VM) — REQUIRED (the sx driver
|
||||
> allocates `List`s; legacy interp can't — 0141, verified) — + dedicated bundle smoke tests. Then P5.2
|
||||
> (primitives) · P5.3 (`on_build` slot) · P5.4 (sx `default_build` + delete `#compiler`/`compiler_call`/
|
||||
> `compiler_hooks` + the S5a `build_options`/`set_post_link_callback`).
|
||||
> **FINAL atomic step (4F):** (`out` already done — VM-native via libc `write`) handle `interp_print_frames` +
|
||||
> flip strict-to-default (remove the fallback) + delete `interp.zig`/`Value` + re-express `define`/`make_enum`.
|
||||
> See `PLAN-COMPILER-VM.md` → Phase 4 for the full plan + top risks (bundler test coverage).
|
||||
> Earlier landed: dedicated `Type` builtin TypeId (`6844fb9`/`94f60c5`/`554871b`); WRITE side
|
||||
> declare_type/register_type/pointer_to VM-native (`66005af`); real lowering-time Context (`eb68d9e`);
|
||||
> metatype construction declare/define/enum_init (`d0ebc55`).
|
||||
@@ -373,6 +396,229 @@ when reached (sentinels or accessor fns; see the design doc Risks).
|
||||
`List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.)
|
||||
|
||||
## Log
|
||||
- **4B (VM-native diagnostics) — the metatype negative tests (1179/1180) render proper diagnostics under strict; strict gap-bails now ONLY `compiler_call` (2026-06-19).**
|
||||
The legacy and VM both BAIL on a `define()` validation failure with an identical detail string; only the
|
||||
host's STRICT rendering differed (generic "bailed on the VM (strict)" vs the proper "comptime type
|
||||
construction failed: <detail>" + span the non-strict legacy path emits). Fixed: (1) aligned the VM's `define`
|
||||
messages with the legacy's exact text — `comptime define():` (was `comptime define:`), and the duplicate
|
||||
variant/field cases now NAME the offender via a new `failFmt` helper (`'...' duplicate variant name 'value'`).
|
||||
(2) The strict type-fn path (`lower/comptime.zig`) now emits `d.addFmt(.err, span, "comptime type construction
|
||||
failed: {s}", .{vm_reason})` — the SAME diagnostic as the legacy fallback, so **1179/1180 produce their exact
|
||||
expected `.stderr` under strict with NO legacy interp involved**. Left the const-init/`#run` strict paths on
|
||||
the "bailed on the VM" wrapper ON PURPOSE — they still carry genuine VM-gap bails (`compiler_call`), so the
|
||||
burndown sweep must keep distinguishing those. **701/0 both gates.** **STRICT GAP-BAILS NOW: only the 4
|
||||
`compiler_call` (1609/1614/1615/1616 → Phase 5 sx-build-pipeline)** + 1654 (a legitimate unresolvable-symbol
|
||||
diagnostic — an asm global called at comptime; the legacy can't resolve it either; reconciles to VM wording
|
||||
at the 4F flip). So: BuildOptions/Phase 5 is the ONLY thing between the VM and a green strict sweep.
|
||||
- **`out` is now a PLAIN SX FUNCTION (libc `write`), NOT a builtin — VM handles it via host-FFI; `trace_resolve` ported; 0522 fixed (2026-06-19).**
|
||||
Per user: removed the `out` `#builtin` entirely. `library/modules/std/core.sx` now defines
|
||||
`libc_write :: (fd, [*]u8, usize) -> isize extern libc "write"` + `out :: (str: string) { libc_write(1,
|
||||
str.ptr, xx str.len); }`. Deleted `BuiltinId.out` (`inst.zig`), the `resolveBuiltin` "out" mapping
|
||||
(`call.zig`), the sema builtins-list entry (`sema.zig`), and BOTH `.out` arms (`interp.zig` buffered-append,
|
||||
`ops.zig` LLVM `write` lowering). **At comptime `out` runs through the evaluator's host-FFI** (the VM's
|
||||
dlsym `write` path / the interp's extern call) — so the VM HANDLES `out` with NO special arm. Benign prelude
|
||||
`.ir` churn (`[*]u8` interned earlier + `@out`→`@write` + the `out` fn body) → regen'd 54 `.ir` snapshots
|
||||
(verified: only string-table renumber + the intended decl/fn-body change; zero stdout/exit changes).
|
||||
**This UNMASKED two latent VM gaps the `out`-bail was hiding** (the VM now runs past `out`):
|
||||
(1) **`trace_resolve`** (1035) — PORTED to the VM (`comptime_vm.zig`): unpack the `(func_id<<32|offset)`
|
||||
comptime frame, resolve func name + `file:line:col` + source line via a **`source_map` now threaded into the
|
||||
VM** (new `tryEval` param, `&import_sources` from emit_llvm), build the `{file,line,col,func,line_text}`
|
||||
`Frame` struct in flat memory (`makeStringValue`/`writeField`/`fieldOffset`). (2) **0522** (bare-pack
|
||||
`[]Any`) — was a CRASH (`reflectArgTypeId` `@intCast` of a garbage word) → hardened to a loud bail
|
||||
(`typeIdxOf` checked cast; the VM must never panic). ROOT CAUSE: after the 0143 fix `$args` materializes as
|
||||
`[]type_value` (8-byte), but the example declared `describe(args: []Any)` (16-byte) → every element past the
|
||||
first read at the wrong stride; the legacy's loose Value model tolerated it, the byte-accurate VM didn't. The
|
||||
bare-pack elements ARE `Type`s, so the fix is the honest type — `describe(args: []Type)` (output identical).
|
||||
**Result: `out`/`trace_resolve`/the 0522 pack-reflection all run VM-HANDLED under strict** (0613/1035/0522/1038
|
||||
no longer bail). **701/0 BOTH gates + full suite.** (Build-pipeline relevance: the sx `default_build` driver
|
||||
uses `out` for diagnostics — now VM-native; no compiler `out` builtin to special-case.)
|
||||
**THEN `interp_print_frames` ported to the VM too** (1034): unlike `out` it needs the live evaluator
|
||||
call-chain, so it's a VM arm (mirrors legacy `printInterpFrames`) — walks `call_stack` (skips the last frame),
|
||||
writes ` at <name>` lines straight to fd 1 (consistent with `out`'s direct `write`). 1034 matches; 701/0.
|
||||
**STRICT DELETION-GATE NOW DOWN TO 7 (all known categories):** `compiler_call` (4 — 1609/1614/1615/1616, the
|
||||
still-`#compiler` BuildOptions accessors → Phase 5 sx-build-pipeline) · VM-diagnostic negatives (2 —
|
||||
1179/1180, the `define` bail IS the expected outcome → **4B**: surface as a proper build diagnostic) ·
|
||||
target-specific dlsym (1 — 1654, an asm global called at comptime; legacy can't resolve it either → a clean
|
||||
diagnostic, not a bug). EVERY pure + side-effect op bail is cleared.
|
||||
- **DESIGN PIVOT (2026-06-18, user) — DRIVE THE BUILD PIPELINE FROM SX; the 37-hook BuildOptions port is dead.**
|
||||
Trigger: porting each `BuildOptions` accessor to an `abi(.compiler)` fn that delegates to a `compiler_hooks`
|
||||
hook just re-encodes sx-level logic (setters/getters, `is_macos` triple-matching, list appends) as compiler
|
||||
hooks — they need NOTHING from the compiler but the `BuildConfig` state. So instead: **`BuildConfig` becomes
|
||||
plain sx data** (ordinary struct, sx-owned, no `#compiler`/hooks/shared-state/weld), and the **build pipeline
|
||||
is an sx program** — the logical end of "bundling lives in sx". The compiler shrinks to a few `abi(.compiler)`
|
||||
PRIMITIVES taking EXPLICIT args (`emit_object() -> !string`, `link(objects, output, libs, fws, flags, target)
|
||||
-> !`, metadata queries) + an `on_build : (BuildConfig) -> ! abi(.compiler)` slot (stdlib default
|
||||
`default_build`; user overrides via `#run on_build = build;`). **Chosen boundary: Option B** (compiler keeps
|
||||
the Zig linker; sx owns config+orchestration+bundle); Option A (sx shells `cc`/`ld`) is a later refinement.
|
||||
**NO bool** — failures are the error channel (`-> !`); VERIFIED on the current build: void `#run`, `-> !`/`-> !E`
|
||||
failable `#run`, and a `raise` at `#run` fails the build with a return trace (+ suggests `#run … catch (e){…}`).
|
||||
`on_build` GENERALIZES today's `post_link_callback_fn` (assignable typed global w/ default, vs a setter).
|
||||
**Full design + step plan in `PLAN-COMPILER-VM.md` → Phase 5.** **S5a (below) is a green intermediate that the
|
||||
sx-pipeline replaces wholesale** (don't extend it; P5.4 deletes `build_options`/`set_post_link_callback` +
|
||||
all `#compiler`). **NEXT — P5.1 (= 4E):** route the post-codegen / `on_build` invocation through the VM
|
||||
(`core.invokeByFuncId` → VM), REQUIRED because the sx driver allocates `List`s and the legacy interp can't
|
||||
(0141, VERIFIED: comptime `List` growth works on the VM, fails on legacy with `struct_get: base has no
|
||||
fields`). Add dedicated bundle smoke tests (no corpus coverage today). Both gates **701/0**.
|
||||
- **S5a DONE — `build_options` + `set_post_link_callback` migrated off `#compiler` onto `abi(.compiler)`; `BuildConfig` threaded into the VM (2026-06-18).**
|
||||
The corpus-covered slice of the BuildOptions migration. (1) `comptime_vm.zig` — `Vm.build_config: ?*BuildConfig`,
|
||||
threaded via a new `tryEval` param (`&self.build_config` from emit_llvm's `#run`/const-init sites; `null` at
|
||||
lowering-time type-fn). (2) Two `callCompilerFn` arms: `build_options` (returns the null-sentinel handle) +
|
||||
`set_post_link_callback` (reads the cb `func_ref`, stores `post_link_callback_fn` on the threaded `BuildConfig`).
|
||||
(3) `compiler_lib.zig` — matching legacy `handleBuildOptions`/`handleSetPostLinkCallback` (gate-OFF dual path).
|
||||
(4) `build.sx` — `build_options :: () -> BuildOptions abi(.compiler);` and `set_post_link_callback` EXTRACTED
|
||||
from the `struct #compiler` as a free `ufcs (…) abi(.compiler)` (so `opts.set_post_link_callback(cb)` still
|
||||
resolves via UFCS); the other ~38 BuildOptions methods stay `#compiler` for now. (5) Registrars/callbacks that
|
||||
call these are now compiler-domain: `platform/bundle.sx` `bundle_main :: () -> bool abi(.compiler)`, and the
|
||||
six platform examples' `configure`/`configure_build` registrars marked `abi(.compiler)`; 0602/0603 reworked
|
||||
the same way. **KEY learning:** every example transitively imports `build.sx` via the prelude, so the
|
||||
`set_post_link_callback` method→free-function change is BENIGN `.ir` churn (declaration renumber + global
|
||||
`@str`/`@tag.str` suffix shift) in all 37 examples that have `.ir` snapshots — verified line-by-line that NO
|
||||
instruction/control-flow/payload changed (only auto-numbered global-name suffixes), then regen'd those 37
|
||||
snapshots scoped with `-Dname`. **Strict-VM `compiler_call` bail set dropped 6→2:** 0602/0603/1604/1611 now
|
||||
fully VM-HANDLED; 1609/1615 still bail on the *other* (still-`#compiler`) BuildOptions methods they use →
|
||||
**S5b** (migrate the remaining ~38 setters/getters). **701/0 BOTH gates + all unit tests.**
|
||||
- **S3 DONE — emit_llvm skips BODIED `abi(.compiler)` (compiler-domain) functions; comptime-only calls emit `undef` (2026-06-18).**
|
||||
A BODIED `abi(.compiler)` function is a user compiler-domain function (post-link callback / compiler helper):
|
||||
the comptime evaluator runs its sx body, but it NEVER runs in the binary, so the backend skips it. Changes:
|
||||
(1) IR `Function` gained `is_compiler_domain: bool` (`inst.zig`). (2) `decl.zig` — new `fnIsBodilessCompiler`
|
||||
splits the API surface (bodiless → declare-only, `compiler_welded`, no implicit ctx — the S1 behavior) from a
|
||||
bodied `abi(.compiler)` function (lowers its body for VM eval; flagged `is_compiler_domain` + `is_comptime`;
|
||||
gets normal implicit-ctx). The four S1 guards now gate on `fnIsBodilessCompiler` not `fd.abi == .compiler`.
|
||||
(3) `emit_llvm.zig` — Pass 2 skips `is_compiler_domain` bodies; Pass 1 declares them EXTERNAL-linkage (an
|
||||
internal empty decl fails LLVM verification). (4) **KEY** `ops.zig` `emitCall` — a call to a comptime-only
|
||||
callee (`compiler_welded` OR `is_compiler_domain`) from a dead comptime body now emits `undef` instead of a
|
||||
real `call`; the runtime-call gate covers both. Without the undef, an AOT `sx build` left an undefined
|
||||
`_double`/`_intern` symbol — this ALSO fixed a pre-existing, untested AOT breakage of the bodiless
|
||||
compiler-API examples (the corpus runs them JIT). Diagnostic reworded "compiler-library" → "compiler-domain"
|
||||
(1185 snapshot regen'd). Regression: `examples/0638-comptime-domain-fn-not-emitted` (`double` folds a `#run`
|
||||
const → 84, absent from the binary via `nm`, JIT + AOT both run). **701/0 both gates + all unit tests.**
|
||||
**NEXT: S4** — an `abi(.compiler)` function-TYPE param (`cb: () -> bool abi(.compiler)`) flags the bound
|
||||
function compiler-domain (so a plain `bundle_main :: () -> bool { … }` becomes compiler-domain when passed to
|
||||
`set_post_link_callback`). Then S5 (BuildOptions migration + delete `#compiler`/`compiler_call`/`compiler_hooks`).
|
||||
- **S1+S2 DONE — `abi(.compiler)` replaces `abi(.zig) extern compiler` + `#library "compiler"` (clean cutover, no legacy path) (2026-06-18).**
|
||||
Per the design pivot below, and the user's "no legacy paths": REMOVED the `.zig` ABI variant entirely (`ast.ABI`
|
||||
is now `{ default, c, compiler, pure }`) and made `abi(.compiler)` the sole spelling for a compiler-domain /
|
||||
compiler-API function — the ABI alone marks it, no `extern <lib>`, no fake `#library "compiler"`. Changes:
|
||||
(1) `ast.zig` — `.zig` → `.compiler` (doc rewritten). (2) `parser.zig` — `parseOptionalAbi` accepts `.compiler`
|
||||
(drops `.zig`); a **bodiless `abi(.compiler)` decl** (ends in `;`, no `extern`) is now accepted — synthesizes
|
||||
the empty-block placeholder like an `extern` import (the Zig/VM handler is the impl). (3) `decl.zig` —
|
||||
`weldedCompilerFn` keys off `fd.abi == .compiler` + export-list membership (no `extern_lib == "compiler"`
|
||||
check); a bodiless `abi(.compiler)` decl lowers extern-like (`is_extern_decl`, and the two body-lowering paths
|
||||
`lowerFunction`/`lazyLowerFunction` skip it) so it is declared-not-defined; `funcWantsImplicitCtx` returns
|
||||
false for `abi == .compiler` (an implicit `__sx_ctx` prepend would shift args and break the handler arity —
|
||||
this was the live bug surfaced + fixed). (4) `type_resolver.zig` — the function-TYPE CC switch handles
|
||||
`.compiler` (sx-default CC). (5) Migrated ALL 8 compiler-API examples (0626/0628/0629/0630/0631/0633 + the
|
||||
1184/1185 negatives) `… abi(.zig) extern compiler;` → `… abi(.compiler);` and deleted every `compiler ::
|
||||
#library "compiler";` line; regen'd the 1184 stderr snapshot (new "not a function exported by the compiler"
|
||||
wording + shifted line). (6) Updated the two parser unit tests. **All 8 examples run HANDLED on the strict VM
|
||||
with byte-correct output; 1184 (unexported name) + 1185 (runtime call) still error cleanly; gate-OFF legacy
|
||||
still works.** **700/0 BOTH gates + all unit tests.** NOTE: the general `#library`/`extern <lib>` PARSE paths
|
||||
stay (used by `libc :: #library "c"` etc.) — only the compiler-API's USE of them is gone. `compiler_lib.lib_name`
|
||||
+ the `main.zig` dlopen-skip for a "compiler" lib are now dead defensive code (harmless; a `#library "compiler"`
|
||||
is just meaningless now). The struct-`abi(...)` parse slot is vestigial (weld stripped) — parse-only test kept.
|
||||
**NEXT: S3** — emit_llvm skips BODIED `abi(.compiler)` functions (Pass 2, like `is_extern`); thread an
|
||||
`abi(.compiler)` flag onto the IR `Function` and refine the three "today every `abi(.compiler)` fn is bodiless"
|
||||
guards in `decl.zig` (marked with `S3 NOTE`) to allow a bodied callback's body to lower for VM eval while NOT
|
||||
emitting it. Then S4 (callback-param propagation) + S5 (BuildOptions migration).
|
||||
- **DESIGN PIVOT (2026-06-18, user) — `abi(.compiler)` is the compiler-domain ABI; DROP the fake `#library "compiler"`.**
|
||||
Supersedes both the `abi(.zig) extern compiler` + `#library "compiler"` binding mechanism AND the previous
|
||||
"runtime-reachability gating" idea for the BuildOptions blocker (entry below). **The unifying concept:** a
|
||||
function is *compiler-domain* (runs in the comptime evaluator, NEVER in the shipped binary) because its **ABI
|
||||
says so** — `abi(.compiler)` — not because it's "extern" to an imaginary library. One annotation covers
|
||||
BOTH roles:
|
||||
1. **Compiler-API surface** (`intern`, `text_of`, `find_type`, `declare_type`, `register_type`,
|
||||
`build_options`, `set_post_link_callback`, …): bodiless `abi(.compiler)` decls (the Zig/VM handler IS the
|
||||
impl). Replaces `… abi(.zig) extern compiler;` + the `compiler :: #library "compiler";` line — both GO AWAY.
|
||||
2. **User compiler-domain functions** (post-link callbacks like `platform.bundle.bundle_main`): BODIED
|
||||
`abi(.compiler)` functions. emit_llvm does NOT lower them (skip in Pass 2, like `is_extern`); the comptime
|
||||
VM/interp evaluates them. A callback PARAM type carries it too — `set_post_link_callback(self, cb: () -> bool
|
||||
abi(.compiler))` — so the bound function is flagged compiler-domain.
|
||||
**Why this dissolves the BuildOptions blocker:** the welded-call enforcement (`ops.zig` `emitCall`) only fired
|
||||
because comptime-only callback bodies (`bundle_main`, 0602's `configure`) were being LLVM-emitted. A bodied
|
||||
`abi(.compiler)` function is never emitted → its `build_options()`/`binary_path()` calls never reach `emitCall`
|
||||
as runtime code → no enforcement, no undefined-symbol risk. **1185 stays correct**: `main` is an ordinary
|
||||
runtime fn (not `abi(.compiler)`) calling a compiler-domain fn → still a clean build-gating error. (The
|
||||
registrar half is independently fine via the idiomatic `#run { … }` block — the welded calls sit in the
|
||||
`is_comptime` `__run` wrapper; 0602/0603 only tripped via an intermediate `configure()`, a test-shape artifact.)
|
||||
**Staged plan (each its own step, both gates green):**
|
||||
- **S1 — introduce `abi(.compiler)`** as a new `ABI` variant that marks a function `compiler_welded` (export-list
|
||||
checked) WITHOUT requiring `extern compiler`/`#library`. Add it ALONGSIDE the existing `.zig extern compiler`
|
||||
path so migration is incremental; prove with one example (0626 → `abi(.compiler)`). (`.zig` is a misnomer —
|
||||
"we don't really have a zig abi"; it becomes `.compiler`, ultimately replacing `.zig` once all callers move.)
|
||||
- **S2 — migrate the rest of the compiler-API decls** (0628–0633, 1184/1185) to `abi(.compiler)`; drop the
|
||||
`#library "compiler"` lines; regen snapshots (the 1184 unexported-name + 1185 runtime-call diagnostics must
|
||||
stay red with refreshed wording). Then retire the `.zig extern compiler` parse path + `#library "compiler"`.
|
||||
- **S3 — emit_llvm skips bodied `abi(.compiler)` functions** (Pass 2 `continue`, like `is_extern`); thread the
|
||||
`abi(.compiler)` flag onto the IR `Function`. Prove a bodied compiler-domain function isn't emitted.
|
||||
- **S4 — callback-param propagation**: an `abi(.compiler)` function-type PARAM flags the bound function
|
||||
compiler-domain.
|
||||
- **S5 — BuildOptions migration** (now unblocked): `build_options`/`set_post_link_callback`/… become
|
||||
`abi(.compiler)` (+ VM `callCompilerFn` arms / legacy `compiler_lib` handlers; `BuildConfig` threaded into the
|
||||
VM — the bundler 4E shares this); callbacks declared/typed `abi(.compiler)`; delete `#compiler`/`compiler_call`/
|
||||
`compiler_hooks` Registry. Then **4E** bundler on the VM.
|
||||
**Reusable facts from the reverted attempt:** only `build.sx` uses `#compiler`; VM dual-path bail-to-fallback
|
||||
means the VM needs only corpus-covered fns; UFCS on a free fn needs the `ufcs` marker (composes with the ABI
|
||||
annotation); the binding mechanism currently lives in `decl.zig` `weldedCompilerFn` (keys off `extern_lib ==
|
||||
"compiler"` — S1 makes it key off `abi == .compiler`). Mechanism files: `ast.zig` (`ABI` enum), `parser.zig`
|
||||
(`parseOptionalAbi` + the extern-compiler postfix), `decl.zig` (`weldedCompilerFn`), `compiler_lib.zig`
|
||||
(export list), `comptime_vm.zig` (`callCompilerFn`), `emit_llvm.zig` (Pass-2 skip), `ops.zig` (`emitCall` gate).
|
||||
- **Phase 4 — BuildOptions→`abi(.zig) extern compiler` migration ATTEMPTED, then REVERTED; BLOCKER found: the comptime-only welded-call enforcement (2026-06-18).**
|
||||
Scoped an incremental slice (migrate only the corpus-covered `build_options()` + `set_post_link_callback`,
|
||||
leaving the 38 bundler accessors on `#compiler` → VM bails → legacy fallback). Built it end-to-end:
|
||||
threaded `BuildConfig` into the `Vm` (`tryEval` gained a `?*BuildConfig` param, passed `&self.build_config`
|
||||
from emit_llvm's `#run`/const-init sites); added `callCompilerFn` arms + legacy `compiler_lib` bound-handlers
|
||||
for both; rewrote `build.sx` (`build_options` → `abi(.zig) extern compiler`; extracted `set_post_link_callback`
|
||||
out of the `struct #compiler` as a free `ufcs (...) abi(.zig) extern compiler` fn so `opts.set_post_link_callback(cb)`
|
||||
still resolves via UFCS; added `compiler :: #library "compiler";`). All COMPILED and the welded dispatch
|
||||
fired. **BLOCKED at LLVM emission, NOT a bug — a design limitation the migration surfaces:** a
|
||||
`compiler_welded` call inside a NON-`is_comptime` function is a hard build-gating error (`ops.zig`
|
||||
`emitCall`, the Phase-1 enforcement guarding genuine runtime misuse — example 1185). But the post-link
|
||||
callback idiom calls comptime-only-API functions (`build_options()`, `binary_path()`, `bundle_path()`, …)
|
||||
**inside callback bodies** (`platform/bundle.sx`'s `bundle_main :: () -> bool`, and 0602's `configure`) that
|
||||
run ONLY at comptime (post-link interp/VM) yet are still LLVM-emitted as real `() -> bool` bodies. The OLD
|
||||
`#compiler`/`compiler_call` path emitted those as dead `undef` (`emitCompilerCall`), so no error; the welded
|
||||
enforcement instead halts the build, and it CANNOT distinguish a dead comptime-reachable body from genuine
|
||||
runtime use (1185, reachable from `main`) without runtime-reachability analysis. **Reverted the whole attempt**
|
||||
(kept only the green pure-ops work); both gates back to **700/0**. **THE DECISION the next session must make
|
||||
FIRST (before any BuildOptions migration):** how to emit a welded call in a comptime-only-but-LLVM-emitted
|
||||
function. Recommended path **A — runtime-reachability gating:** in `emit_llvm`, mark functions reachable from
|
||||
runtime roots (`main` / exported runtime fns); a welded call in an UNREACHABLE function emits `undef` (dead,
|
||||
like `compiler_call` did) instead of erroring, while a reachable one still errors (1185 stays red). This is
|
||||
also the right foundation for eventually NOT emitting comptime-only bodies at all. Rejected: (B) marking
|
||||
callbacks `is_comptime` — can't statically identify which `func_ref`s become post-link callbacks; (C) blanket
|
||||
softening to `undef` — would silently swallow genuine runtime misuse (1185). **Other migration facts confirmed
|
||||
this attempt (reuse next session):** only `build.sx` uses `#compiler` (the `issues/*.md` hits are doc text);
|
||||
the VM dual-path bail-to-fallback means the VM needs only the corpus-covered fns, the 38 bundler accessors can
|
||||
ride legacy; UFCS on a free fn requires the `ufcs` marker, which composes with `abi(.zig) extern compiler`;
|
||||
`build.sx` must declare `compiler :: #library "compiler";`. Do the reachability fix as its OWN step (verify
|
||||
1185 still errors + a comptime-only-body welded call now emits clean), THEN redo the BuildOptions slice on top.
|
||||
- **Phase 4 burndown — three PURE comptime ops ported (`error_tag_name_get` + `global_addr` + `type_is_unsigned`); `interp_print_frames` correctly DEFERRED (2026-06-18).**
|
||||
Also ported `type_is_unsigned` (a `BuiltinId` via `callBuiltinVm`): resolves the queried `TypeId` the
|
||||
same way as `type_name` (a `.type_value` word, or an Any box `{tag@0,value@8}` whose tag IS the boxed
|
||||
value's type) then returns `table.isUnsignedInt(tid)`. Extracted the shared resolution into a
|
||||
`reflectArgTypeId` helper (VM-native `Value.reflectTypeId` mirror) so `type_name` + `type_is_unsigned`
|
||||
can't drift. MATCH-verified by a new VM unit test (`type_is_unsigned(u32) - type_is_unsigned(i64) == 1`).
|
||||
Strict sweep: 0600 `type_is_unsigned`→`out` (now its only remaining bail); no `type_is_unsigned` bails
|
||||
remain in the corpus. **With this, all PURE comptime ops are ported** — the remaining strict bails are
|
||||
side-effect (`out`/`interp_print_frames`), `compiler_call` (the BuildOptions migration), VM diagnostics
|
||||
(1179/1180), and `#insert`/bundler.
|
||||
Ported two side-effect-free ops onto the VM (`comptime_vm.zig` exec switch): (1) `error_tag_name_get`
|
||||
— a runtime tag-id word → its name string via `table.getTagName` + `makeStringValue` (uses the table,
|
||||
not the module, so it's unit-testable; `self.table == &module.types`); (2) `global_addr` — name-matches
|
||||
`__sx_default_context` and returns the already-tested `materializeDefaultContext` Addr (an aggregate
|
||||
value IS its address, so a downstream `load` sees the materialised Context), bailing for any other
|
||||
global exactly like legacy. **MATCH verification:** `error_tag_name_get` locked in by a new VM unit
|
||||
test (tag id → `"Bad"`, via `regToValue`); `global_addr` proven by the strict sweep (0600's first bail
|
||||
moved past it) and reuses `materializeDefaultContext`, already exercised by every implicit-ctx comptime
|
||||
call on the VM. **KEY CORRECTION to the handover's "three PURE ops" plan:** `interp_print_frames`
|
||||
(1034) is NOT pure — it WRITES the comptime call-frame chain to the build output, a side effect in the
|
||||
SAME bucket as `out` (the VM has no output buffer; output is direct-write, so a print-then-bail
|
||||
double-prints under the legacy fallback). It must land atomically in the FINAL `out`/strict-default
|
||||
step, NOT now. **Strict-sweep burndown:** 1035 `error_tag_name_get`→`out`; 0600 `global_addr`→
|
||||
`type_is_unsigned` (a NEW pure-op bail surfaced — still a known pure op, next to port); 1034 stays at
|
||||
`interp_print_frames` (deferred, as it should). Also fixed the stale `comptime_vm.zig` header comment
|
||||
(it still said "bump/stack allocator"; the memory model is an ARENA of stable host allocations since
|
||||
4D.0). **700/0 BOTH gates + all unit tests.** On `reify`.
|
||||
- **Phase 4 burndown — issue 0143 FIXED (pack-as-`[]Type` stride) + regression test (2026-06-18).**
|
||||
Root cause was a stale consequence of the `.type_value` migration: `buildPackSliceValue`
|
||||
(`lower/pack.zig`) materialized a bare `$<pack>` `[]Type` slice as `[]Any` (16-byte elements) while
|
||||
|
||||
Reference in New Issue
Block a user