Files
sx/issues/0071-global-initializer-module-const-silent-zero.md
agra b9cfe2554f refactor(ffi-linkage): Phase 9.3/9.4 — purge 'foreign' from issues/*.md; GATE PASS
Rewrote 20 issue writeups to the extern/runtime-class vocabulary (#foreign→extern,
foreign_class_map→runtime_class_map, parseForeignClassDecl→parseRuntimeClassDecl,
findForeignMethodInChain→findRuntimeMethodInChain, dedupeForeignSymbol→
dedupeExternSymbol, is_foreign_c_api→is_extern_c_api, stale filename refs to the
renamed examples, foreign-class→runtime-class, bare foreign→extern). Renamed
issues/0043-…-foreign-class-…→…-runtime-class-….

PHASE 9 COMPLETE — 9.4 GATE PASSES: zero 'foreign' across src/library/examples/
issues/docs/editors/specs/readme/CLAUDE, excluding only the SQLite API constant
SQLITE_CONSTRAINT_FOREIGNKEY + vendored sqlite3.c/.h (upstream third-party).
Suite green (644 corpus / 443 unit, 0 failed).
2026-06-15 11:18:35 +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 extern-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.