Files
sx/issues/0143-pack-as-type-slice-stride-mismatch.md
agra f807436f04 fix issue 0143: pack-as-[]Type built as []Any — build it as []type_value
buildPackSliceValue (lower/pack.zig) materialized a bare `$<pack>` []Type slice
as []Any (16-byte elements) — a stale mapping from before the dedicated Type
builtin (.type_value, 8 bytes) replaced Type -> .any. It stored 8-byte const_type
words into 16-byte slots, so a []Type reader (8-byte stride) read [t0, pad, t1, ...]
instead of [t0, t1, ...]. The legacy interp's tagged-Value model hid it; the
byte-accurate comptime VM exposed it (a `..$args` pack forwarded as a []Type
argument across a call read its elements shifted/garbled).

Fix: build the pack-slice array + slice as .type_value (8 bytes). Removed the
stopgap type_name .unresolved guard (379ed05) now that the root cause is fixed.

Regression test examples/0525-packs-pack-as-type-slice-arg.sx (outer(42,"hi",true)
-> inner($args: []Type) -> "i64 string bool"). 700/0 both gates; issue 0143 RESOLVED.
2026-06-18 19:42:42 +03:00

4.8 KiB

RESOLVED (2026-06-18). Root cause: buildPackSliceValue (src/ir/lower/pack.zig) built the $<pack> []Type slice as []Any (16-byte elements) — a stale mapping from before the dedicated Type builtin (.type_value, 8 bytes) replaced Type → .any. It stored 8-byte const_type words into 16-byte slots, so a []Type reader (8-byte stride) read [t0, pad, t1, …]. Fix: build the slice/array as .type_value (8 bytes). Regression test: examples/0525-packs-pack-as-type-slice-arg.sx. The stopgap type_name .unresolved guard added in 379ed05 was removed (root cause fixed). 700/0 both gates.

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 <unresolved> string ]
legacy: [i64 string bool ]

The legacy interpreter masks the bug (it indexes a logical sequence of Values, not bytes). The VM exposes it. This is what makes examples/0114-types-build-block-convert read arg1: <unresolved> 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

#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 <unresolved> 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 <unresolved>) 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.