Files
sx/issues/0071-global-initializer-module-const-silent-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

3.9 KiB

0071 — global initialized from module const silently zero-initializes

RESOLVED. Root cause: Lowering.registerTopLevelGlobal's init_val switch serialized only literal / array-literal / struct-literal initializers; an identifier initializer (g : A = K;) fell through to else => null, so the global was emitted with no payload and silently zero-initialized. Fix: extracted the initializer serialization into Lowering.globalInitValue and added an .identifier arm that materializes the global's static value from ProgramIndex.module_const_map (typed module consts are registered in the same pass-2 just before, via registerTypedModuleConst). An identifier that names no usable constant now emits a diagnostic instead of silently zeroing. Other initializer shapes (enum-literal shorthand, etc.) keep their established static-lowering behavior — this pass only closes the identifier/module-const hole. Regression: examples/0134-types-global-init-from-module-const.sx (g=42 / exit 42).

Symptom

A top-level global initialized from a module constant compiles but is zero-initialized instead of receiving the constant's value.

Observed:

g=0

Expected: g should be initialized to 42, or the compiler should reject the initializer loudly if identifier/module-const global initializers are not supported.

Reproduction

#import "modules/std.sx";

A :: B;
B :: i32;

K : A : 42;
g : A = K;

main :: () -> i32 {
    print("g={}\n", g);
    return g;
}

Run:

./zig-out/bin/sx run .sx-tmp/probe-0070-global-init-from-const.sx

The repro is standalone; the inline source above is sufficient to recreate the scratch file under .sx-tmp/.

Investigation prompt

Fix issue 0071: a top-level global initialized from a module constant must not silently become zero.

Context:

  • This surfaced during Codex re-review of 932cdfa, the issue-0070 fix.
  • 932cdfa correctly defers top-level global and typed-module-const annotation resolution until after the forward-alias fixpoint.
  • The remaining bug is in the global initializer path, not the annotation path: K : A : 42; g : A = K; resolves A correctly, registers K in ProgramIndex.module_const_map, but g is emitted as zero.

Suspected area:

  • src/ir/lower.zig, Lowering.registerTopLevelGlobal.
  • Its init_val switch serializes literal / array / struct-literal initializers, but an identifier initializer falls through to else => null, and the global is emitted with no initializer payload. That silently becomes zero-initialized.
  • Related facts: ProgramIndex.module_const_map already records typed module constants via registerTypedModuleConst, now in pass 2 after alias convergence.

Likely fix:

  • Add explicit handling for identifier initializers that name a module constant, converting the recorded constant value into the global's ConstantValue with the global's declared type.
  • If some initializer shape cannot be represented as a global constant yet, emit a diagnostic instead of returning null / zero-initializing.
  • Do not regress issue 0070: A :: B; B :: i32; g : A = 7; and K : A : 35; must still resolve through the converged alias map.
  • Preserve literal, array literal, struct literal, and foreign-global behavior.

Verification:

  • Add a focused regression, likely in the 01xx types block:
#import "modules/std.sx";
A :: B;
B :: i32;
K : A : 42;
g : A = K;
main :: () -> i32 { print("g={}\n", g); return g; }
  • Keep these green:
    • examples/0133-types-forward-alias-global.sx
    • examples/0132-types-forward-type-alias.sx
    • examples/0116-types-type-alias-size-align.sx
    • examples/0201-generics-generic-struct.sx
    • examples/1117-diagnostics-value-const-as-type-rejected.sx
  • Run:
zig build
zig build test
bash tests/run_examples.sh

Expected result: the new regression prints g=42 and exits 42; unsupported global initializer shapes no longer silently zero-initialize; the full suite passes.