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.
80 lines
4.9 KiB
Markdown
80 lines
4.9 KiB
Markdown
# 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. 0079–0082).
|
||
|
||
## 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.
|