diff --git a/current/CHECKPOINT-COMPILER-API.md b/current/CHECKPOINT-COMPILER-API.md index 203f13bc..f0044b3c 100644 --- a/current/CHECKPOINT-COMPILER-API.md +++ b/current/CHECKPOINT-COMPILER-API.md @@ -373,6 +373,25 @@ when reached (sentinels or accessor fns; see the design doc Risks). `List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.) ## Log +- **Phase 4 burndown — switch_br + type_name ported; issue 0143 filed; KEY sequencing insight: `out` is end-state-only (2026-06-18).** + Ported two PURE comptime ops (`379ed05`): `switch_br` (i64-discriminant multi-way branch — enum/error + tag or `.type_value` index) and `type_name` (Type value / Any box → `table.typeName`, with an + `.unresolved`-bail guard). Correct in isolation; 0520–0524 run GREEN under strict. **Two blockers found:** + 1. **issue 0143 (FILED, OPEN) — pack-as-`[]Type` stride mismatch.** A `..$args` pack forwarded as a + `[]Type` ARGUMENT across a call is backed by a `[N x Any]` (16B) array but viewed as `[]type_value` + (8B) → half-stride reads (`[i64 string]` vs legacy `[i64 string bool]`). A LOWERING bug + the legacy's Value model masks; the byte-accurate VM exposes it. Blocks `examples/0114` from running + HANDLED. **Per CLAUDE.md: filed, NOT worked around** (the `type_name` `.unresolved` guard just makes + the VM decline rather than emit garbage). Repro + fix-prompt in `issues/0143-…md`. + 2. **`out` (comptime print) is an END-STATE op — it cannot land while the fallback exists.** Under the + legacy fallback, an eval that prints via `out` then BAILS double-prints (the VM wrote to fd 1, then + legacy re-runs the whole eval — no rewind). 0114 demonstrated it. So a direct-write `out` is only + safe once the fallback is GONE (strict-by-default). **Revised ordering:** land the PURE ops + (switch_br/type_name/type_is_unsigned/error_tag_name_get/global_addr/interp_print_frames) + the + BuildOptions migration + #insert + bundler FIRST; then in the FINAL step flip strict-to-default + (removing the fallback) AND add `out` together — at which point every `out`-using example flips + atomically with deletion. (Most of the gap-list examples print, so they stay on fallback until that + final flip — that's expected, not a regression.) 699/0 both default gates. - **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 diff --git a/issues/0143-pack-as-type-slice-stride-mismatch.md b/issues/0143-pack-as-type-slice-stride-mismatch.md new file mode 100644 index 00000000..bfe4f09e --- /dev/null +++ b/issues/0143-pack-as-type-slice-stride-mismatch.md @@ -0,0 +1,86 @@ +# 0143 — A variadic pack passed as `[]Type` across a call is mis-strided (Any-sized backing, Type-sized view) + +**Symptom** — When a variadic `..$args` pack is forwarded as a `[]Type` *argument* +to another function, reading `args[i]` yields the **wrong** element: the backing +array is laid out as `[N x Any]` (16-byte `{i64,i64}` slots) but the slice's +element type is `Type`/`type_value` (8 bytes), so an 8-byte-strided index read +lands on `elem0`, then the *padding* of `elem0` (reads as `.unresolved`), then +`elem1`, … — i.e. half-stride reads. + +Observed (on the flat-memory comptime VM, which is byte-accurate) vs expected +(legacy interp, whose tagged-`Value` model is stride-agnostic and reads the right +logical element): + +``` +VM: [i64 string ] +legacy: [i64 string bool ] +``` + +The legacy interpreter masks the bug (it indexes a logical sequence of `Value`s, +not bytes). The VM exposes it. This is what makes `examples/0114-types-build-block-convert` +read `arg1: ` instead of `arg1: string` once the VM handles `type_name` +(it currently bails on the `.unresolved` read — see the guard added in `comptime_vm.zig` +`type_name`, commit 379ed05 — so under the fallback it falls back; under +`SX_COMPTIME_FLAT_STRICT` it is a hard error). + +## Reproduction + +```sx +#import "modules/std.sx"; + +inner :: (args: []Type) -> string { + s := ""; i : i64 = 0; + while i < args.len { s = concat(s, type_name(args[i])); s = concat(s, " "); i = i + 1; } + return s; +} +outer :: (..$args) -> string { return inner($args); } // <-- pack passed as []Type across a call + +R :: #run outer(42, "hi", true); +main :: () { print("[{}]\n", R); } +``` + +Expected: `[i64 string bool ]`. Actual (VM): `[i64 string ]`. + +Note the *direct* use (no cross-call) is fine — `walk :: (..$args) { list := $args; … type_name(list[i]) … }` +reads correctly. Only forwarding the pack as a `[]Type` **argument** mis-strides. + +## Root cause (hypothesis) + +The pack `$args` is materialised as a `[N x Any]` array (each arg boxed as a +16-byte `Any` — confirmed in the LLVM IR: `array_to_string__AR_3_Any` / +`[3 x { i64, i64 }]`), but when forwarded to a `[]Type` parameter the slice is +typed with element `type_value` (8 bytes). The slice `{ptr,len}` points at the +`[N x Any]` data, so `index_get` with an 8-byte element stride reads garbage. + +The fix belongs in **lowering**, not the VM: a pack consumed as `[]Type` must +either (a) materialise a real `[N x Type]` (8-byte) array by extracting each +arg's type tag from its Any box, or (b) keep the slice element type as `Any` and +have `type_name`/reflection read the Any's tag (the VM's `type_name` already +handles an `.any` arg). Option (b) is likely smaller. Whichever: the slice's +element type and its backing array's element size MUST agree. + +Suspected area: the pack-expansion / pack-as-slice lowering (search `src/ir/lower/` +for the `..$args` → slice materialisation and the `[]Type` coercion of a value +pack; `pack.zig`). + +## Investigation prompt (paste into a fresh session) + +> A variadic `..$args` pack forwarded as a `[]Type` argument across a call is +> mis-strided: the backing array is `[N x Any]` (16B slots) but the slice element +> type is `type_value` (8B), so `args[i]` reads at the wrong offset. The legacy +> interp masks it (Value-indexed); the byte-accurate comptime VM exposes it +> (`examples/0114`, and the minimal repro in `issues/0143-…md`). Fix it in +> lowering: make the pack→`[]Type` materialisation agree on element size — either +> build a real `[N x Type]` array (extract each Any's type tag) or type the slice +> `[]Any` and let reflection read the Any tag (`type_name` already handles `.any`). +> Verify: run the repro on the VM (`SX_COMPTIME_FLAT=1 sx run`) and expect +> `[i64 string bool ]`; then `examples/0114` must produce `arg1: string` (not +> ``) and run HANDLED under `SX_COMPTIME_FLAT_STRICT=1` matching legacy. +> Drop the `tid == .unresolved` guard in `comptime_vm.zig` `type_name` once the +> root cause is fixed (it was a stopgap so the VM declines rather than emits +> garbage). + +## Status + +OPEN. Blocks `examples/0114` from running HANDLED on the comptime VM (Phase 4 +legacy-interp retirement). Not a blocker for the other comptime-op ports.