Files
sx/issues/0083-named-const-array-dimension-miscompiled.md
agra d2bf8f3f2d fix(ir): unify named-const array-dim resolution + kill length-0 fabrication (0083)
A type alias whose dimension is a named const (`Arr :: [N]T`) resolves its
dimension eagerly during scanDecls pass 1, on the stateless registration path,
which can only read `module_const_map`. Typed consts (`N : s64 : 16`) register
only in pass 2 and a forward-declared untyped const had not registered yet, so
the stateless resolver saw an empty table, printed a non-fatal warning,
fabricated length 0, and continued — yielding a 0-byte alloca, garbage reads,
and a segfault for slice/struct elements.

- scanDecls pass 0 pre-registers every integer-valued module const before any
  type alias resolves, so typed, untyped, and forward-referenced consts all
  resolve identically.
- Both dim resolvers now share `program_index.moduleConstInt`, so the stateful
  body-lowering path and the stateless registration path cannot diverge.
- `resolveArrayLen` returns `?u32`; `resolveCompound` yields `.unresolved` on
  null instead of a 0-length array. The stateful path emits a diagnostic; the
  alias-registration path surfaces an unresolved alias as a clean compile error
  that aborts the build. The Vector lane-count `else => 0` is fixed the same way.

Regressions: examples/0143 (typed-const dim direct + via alias for s64/string/
struct, forward-ref alias, nested) and examples/1129 (an unresolvable computed
dim halts with a clean diagnostic + non-zero exit). Both fail on the pre-fix
compiler (garbage/segfault; warning+exit0) and pass after.
2026-06-04 09:39:18 +03:00

80 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0083 — fixed array with a named-constant dimension is miscompiled
> **RESOLVED.** Root cause: `TypeResolver.resolveCompound`'s array arm resolved
> the dimension with `if (length.data == .int_literal) ... else 0` — a named
> const (`N :: 16`) hit the silent `else 0`, so `[N]T` became a 0-length / 0-byte
> array and element access ran out of bounds (garbage for scalars, bus error for
> slice/pointer/struct elements). Fix: the array arm now delegates the dimension
> to `inner.resolveArrayLen` (symmetric with `inner.resolveInner` for the element
> type). The stateful `Lowering.resolveArrayLen` evaluates the dimension as a
> compile-time integer across the comptime-constant, generic-value, and
> module-global const tables, and emits a diagnostic (no fabricated length) when
> it isn't one.
>
> **Exhaustive follow-up (attempt 2).** The first fix covered every *stateful*
> resolution path (direct local decls, struct fields, function params/returns),
> but the *stateless* registration-time resolver (`type_bridge`, used for type
> aliases `Arr :: [N]T` and inline union/enum field types) still resolved the
> named dim with a silent `else 0` — so `Arr :: [N]s64; a : Arr` and
> `union { a: [N]s64 }` were still miscompiled. Fix: the module-global const
> table (`ProgramIndex.module_const_map`) is now threaded into `type_bridge`
> alongside the alias map, so `StatelessInner.resolveArrayLen` resolves a named
> module-const dim to the same length everywhere. The remaining unresolvable case
> (a computed/comptime dimension on the binding-free path) bails LOUDLY instead of
> fabricating a 0 length. Files: `src/ir/type_resolver.zig`, `src/ir/lower.zig`,
> `src/ir/type_bridge.zig`. Regression: `examples/0140-types-named-const-array-dim.sx`
> (direct + type-alias + nested `[N][M]T` + union-field dims, s64 / string /
> struct element types).
>
> **Root-cause close-out (attempt 3).** Attempt 2 threaded the const map into
> `type_bridge` but the map wasn't fully populated when an alias resolved its
> dimension: type aliases (`Arr :: [N]T`) resolve EAGERLY in scanDecls pass 1,
> while TYPED consts (`N : s64 : 16`) register only in pass 2 and a
> forward-declared untyped const (`Arr :: [N]T; N :: 16`) hadn't registered yet
> either — so the stateless resolver saw an empty table, printed a non-fatal
> warning, fabricated length 0, and CONTINUED to garbage / a segfault. Three
> coordinated fixes: (1) a scanDecls **pass 0** pre-registers every integer-valued
> module const into `module_const_map` BEFORE any alias resolves, so typed,
> untyped, and forward-referenced consts all resolve identically; (2) both the
> stateful and stateless dim resolvers now share one routine
> (`program_index.moduleConstInt`) so they cannot disagree again; (3) the length-0
> fabrications are GONE — `resolveArrayLen` returns `?u32`, `resolveCompound`
> yields the `.unresolved` sentinel on null (never a 0-byte array), the stateful
> path emits a diagnostic, and the registration path surfaces an unresolved alias
> as a clean compile error that aborts the build (the `type_bridge.zig:270`
> Vector-lane `else => 0` is fixed the same way). Files:
> `src/ir/program_index.zig`, `src/ir/lower.zig`, `src/ir/type_bridge.zig`,
> `src/ir/type_resolver.zig`. Regressions:
> `examples/0143-types-typed-const-array-dim.sx` (typed-const dim direct + via
> alias for s64/string/struct, forward-ref alias, nested) and
> `examples/1129-diagnostics-array-dim-not-const.sx` (an unresolvable computed dim
> halts with a clean diagnostic + non-zero exit, not a fabricated 0-length array).
## Symptom
A fixed array whose dimension is a module-global integer constant (`N :: 16;
a : [N]T`) miscompiles element access: reads/writes compute a wrong address.
With `s64` elements `a[0]` returns GARBAGE (silent); with slice/pointer element
types (`[N]string`) it Bus-errors. The identical program with a LITERAL dimension
(`a : [16]T`) is correct. Silent-miscompile class (cf. 00790082).
## Reproduction
```sx
#import "modules/std.sx";
N :: 16;
main :: () { a : [N]s64 = ---; a[0] = 7; print("a0={}\n", a[0]); }
```
`./zig-out/bin/sx run` prints `a0=8472789232` (garbage); want `a0=7`. Replacing
`[N]` with `[16]` prints `7`.
## Investigation prompt
A fixed-array TYPE whose dimension is a named const (`N :: 16; [N]T`) resolves to
a wrong element stride / array length in codegen — element address computation is
wrong (garbage for scalars, bad pointer for slice/pointer elements). Literal
dimensions are correct, so the defect is in resolving the array-type DIMENSION
from a constant expression (vs a literal) — the dim likely resolves to 0/unknown
or the element size is wrong. Look at array-type resolution where the length is a
const-expr (type lowering / sizeof / element-stride computation). Fix so a
named-const dimension yields the same layout as the literal. Verify with the
repro (expect 7) + a `[N]string`/`[N]struct` case (no bus error, correct reads),
and `zig build && zig build test && bash tests/run_examples.sh` green.