Files
sx/issues/0080-global-array-struct-literal-initializer-zero.md
agra d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00

4.5 KiB

0080 - global array of struct literals silently zero-initializes

RESOLVED. Root cause: Lowering.constExprValue (src/ir/lower.zig) — the constant- aggregate serializer for global initializers — handled primitive and nested- array leaves but had no .struct_literal arm. A module-global [N]Struct initialized with struct literals reached constArrayLiteralconstExprValue per element; each struct-literal element returned null, collapsing the whole array initializer to null. globalInitValue then emitted no payload, so the LLVM backend zero-initialized the global (@pairs = ... zeroinitializer), silently dropping every declared field — the same silent-zero class as 0071/0072, one level inside an array literal. (A global struct literal and a struct-with-array already worked, because constStructLiteral existed and was reached directly; the gap was specifically struct literals as array elements.) Fix: make constExprValue type-aware — thread the destination element/field TypeId so a .struct_literal leaf routes through constStructLiteral and a nested .array_literal through constArrayLiteral with the correct element type. constArrayLiteral derives its element type from the array TypeId; constStructLiteral passes each field's type. A global aggregate initializer that still does not fully reduce to a compile-time constant is now rejected loudly (diagnoseNonConstGlobal) instead of falling through to a zeroed global. The downstream emitConstAggregate already recurses over nested aggregates, so const/AOT (sx build) and JIT (sx run) both materialize the declared values. Regression: examples/0137-types-global-aggregate-literal-init.sx (global [N]Struct literal, global struct literal, struct-with-array, nested array-of- struct-with-array; values read back with no prior store, plus a store on top). FAILS on the pre-fix compiler (array-of-struct fields read 0), PASSES after.

Symptom

A module-global fixed array whose elements are struct literals is emitted as zero-initialized storage instead of preserving the literal fields.

Observed: reading pairs[0].b and pairs[1].a prints 0. Expected: the global should contain the declared struct literal values (2 and 3), or the compiler should reject the initializer loudly if this constant shape is unsupported.

Reproduction

#import "modules/std.sx";

Pair :: struct {
    a: i64;
    b: i64;
}

pairs : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];

main :: () -> i32 {
    print("pairs[0]={},{}\n", pairs[0].a, pairs[0].b);
    print("pairs[1]={},{}\n", pairs[1].a, pairs[1].b);
    if pairs[0].a == 1 and pairs[0].b == 2 and pairs[1].a == 3 and pairs[1].b == 4 {
        print("PASS\n");
        return 0;
    }
    print("FAIL: global array struct literal initializer zeroed\n");
    return 1;
}

On the current compiler this prints:

pairs[0]=0,0
pairs[1]=0,0
FAIL: global array struct literal initializer zeroed

sx ir <file> shows the global as:

@pairs = internal global [2 x { i64, i64 }] zeroinitializer

Investigation prompt

Fix issue 0080: a module-global array initialized with struct literal elements silently becomes zeroinitializer.

Suspected area:

  • src/ir/lower.zig, Lowering.globalInitValue.
  • src/ir/lower.zig, Lowering.constArrayLiteral.
  • src/ir/lower.zig, Lowering.constExprValue.
  • src/ir/lower.zig, Lowering.constStructLiteral.

Likely root cause: globalInitValue handles a top-level .array_literal by calling constArrayLiteral, and constArrayLiteral serializes each element via constExprValue. constExprValue handles primitive literals and nested arrays, but not .struct_literal, so an array whose element is a struct literal returns null. That null initializer payload is later emitted as zero-initialized storage, recreating the silent zero pattern from issues 0071/0072 one level inside an otherwise-supported array literal.

Likely fix:

  • Thread the expected element TypeId into constArrayLiteral, or otherwise make constExprValue type-aware for struct literals.
  • Serialize each struct element through constStructLiteral with the array's element type.
  • If any element shape is still unsupported, emit a diagnostic naming the global instead of returning null and allowing zero-initialization.

Verification:

  • Run the repro above and expect:
pairs[0]=1,2
pairs[1]=3,4
PASS
  • Add a pinned regression in the 01xx types block.
  • Run:
zig build
zig build test
bash tests/run_examples.sh