A module-global array of struct literals (`pairs : [2]Pair = .[ .{...}, .{...} ]`)
was emitted as `zeroinitializer`, silently dropping every declared field — reads
returned 0 with no diagnostic. Global struct literals and struct-with-array
already worked; the gap was struct literals used as ARRAY elements.
Root cause: `Lowering.constExprValue` (the const-aggregate serializer for global
initializers) had no `.struct_literal` arm. `constArrayLiteral` serialized each
element through `constExprValue`, so a struct-literal element returned null,
collapsing the whole array initializer to null; `globalInitValue` then emitted no
payload and the LLVM backend zero-initialized the global — the same silent-zero
class as 0071/0072, one level inside an array literal.
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 silently zeroing. `emitConstAggregate`
already recurses over nested aggregates, so `sx run` (JIT) and `sx build` (AOT)
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.
Marks issues 0079 (already resolved) and 0080 RESOLVED.
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_literalarm. A module-global[N]Structinitialized with struct literals reachedconstArrayLiteral→constExprValueper element; each struct-literal element returnednull, collapsing the whole array initializer tonull.globalInitValuethen 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, becauseconstStructLiteralexisted and was reached directly; the gap was specifically struct literals as array elements.) Fix: makeconstExprValuetype-aware — thread the destination element/fieldTypeIdso a.struct_literalleaf routes throughconstStructLiteraland a nested.array_literalthroughconstArrayLiteralwith the correct element type.constArrayLiteralderives its element type from the arrayTypeId;constStructLiteralpasses 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 downstreamemitConstAggregatealready 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]Structliteral, 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: s64;
b: s64;
}
pairs : [2]Pair = .[ .{ a = 1, b = 2 }, .{ a = 3, b = 4 } ];
main :: () -> s32 {
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
TypeIdintoconstArrayLiteral, or otherwise makeconstExprValuetype-aware for struct literals. - Serialize each struct element through
constStructLiteralwith the array's element type. - If any element shape is still unsupported, emit a diagnostic naming the global
instead of returning
nulland 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
01xxtypes block. - Run:
zig build
zig build test
bash tests/run_examples.sh