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.
4.9 KiB
0079 — stores to module-global array elements are silently dropped
RESOLVED. Root cause:
Lowering.lowerExprAsPtr(src/ir/lower.zig) — the lvalue/ address path — handled only local identifiers (alloca pointers). A module-global identifier fell through to the value fallbacklowerExpr, which emitsglobal_get(loads the whole array by value). The LLVM backend'semitIndexGepthen sees an array value, allocas a throwaway temp, copies the value in, and GEPs into the temp — sog[i] = vwrote a discarded copy and a laterg[i]read the global's untouched initializer. Local arrays worked because they hit the alloca-pointer path; global scalar stores worked viaglobal_set. Fix: teachlowerExprAsPtr's identifier arm about globals — emitglobal_addr(a pointer into the global's live storage) for a normal global, orglobal_getfor a pointer-typed global (mirroring the local pointer case). The same array-base resolution in theaddress_of(index_expr)path now routes throughlowerExprAsPtrso&g[i]is also an lvalue into the global.index_gepthen GEPs directly into@gfor const AND variable index, across functions, in bothsx runandsx build. Regression:examples/0136-types-global-array-element-store.sx(const-index, var-index, cross-function store on a scalar global array; a struct-element global array for element-stride; a nested-array global for the recursive indexed lvalue). FAILS on the pre-fix compiler (reads return the initializer / zero), PASSES after.
Symptom
A store to a module-global (file-scope) ARRAY element is silently lost: after
g[i] = v, reading g[i] returns the array's INITIALIZER value, not v. No
diagnostic. Reproduces with a constant index, a variable index, and a store from
another function, in BOTH sx run (JIT) and sx build (AOT). Local-array stores
and global-SCALAR stores work correctly — so the indexed load/store on a global
array appears to read/write the initializer constant rather than the global's
live storage. This is a SILENT data-corruption bug (the dangerous class per the
project rules), not a crash.
Manager-reproduced output (JIT):
global[1] const-idx=20 (want 222)
global[k] var-idx=30 (want 333)
global[0] via fn=10 (want 111)
Reproduction
#import "modules/std.sx";
g : [3]i64 = .[10, 20, 30];
write_global :: (i: i64, v: i64) { g[i] = v; }
main :: () {
loc : [3]i64 = .[10, 20, 30];
loc[1] = 222;
print("local[1]={}\n", loc[1]); // 222 (correct)
g[1] = 222;
print("global[1] const-idx={}\n", g[1]); // 20 (WRONG, want 222)
k := 2;
g[k] = 333;
print("global[k] var-idx={}\n", g[k]); // 30 (WRONG, want 333)
write_global(0, 111);
print("global[0] via fn={}\n", g[0]); // 10 (WRONG, want 111)
}
./zig-out/bin/sx run <file> → prints the WRONG values above. Expected:
local[1]=222 / global[1]=222 / global[k]=333 / global[0]=111.
Investigation prompt
A store to a module-global array element (g[i] = v) is silently lost: a
subsequent g[i] yields the initializer value, in BOTH the JIT (sx run) and
compiled (sx build) paths. Global SCALAR stores and LOCAL array stores both
work, so the defect is specific to INDEXED lvalue access on a GLOBAL array.
Suspect the IR-gen / codegen for an indexed expression on a global symbol:
the load of global_array[idx] is likely decaying / constant-folding to the
array's initializer constant (or the address computation for the store targets a
private copy of the initializer rather than the global's live storage). Look in
the index-expression lowering and global-symbol address resolution (and how
global array initializers are materialized) — src/ir/lower.zig /
src/ir/emit_llvm.zig and the global-symbol/constant-initializer paths.
The fix must make load(global_array[idx]) read the global's live storage and
store(global_array[idx], v) write it — for constant AND variable indices, and
when the store happens in a different function than the read. Do NOT special-case;
fix the address resolution so a global array element is an lvalue into the
global's storage like any other.
Verification (once fixed)
- The repro above prints
222 / 333 / 111for the global cases (exit 0). - Add a pinned regression
examples/NNNN-*.sxcovering const-index, var-index, and cross-function store to a global array (+ a global array of a struct/larger element if practical). zig build && zig build test && bash tests/run_examples.shall green.
Provenance
Discovered by the distribution flow (sx-foundation step F3.1, std.cli argv
accessor) while exploring argv backing strategies. The shipped F3.1 code does NOT
depend on this (it uses caller-provided buffers + zero-copy views over the C argv
block), so F3.1 is correct independently — this is a separate latent
silent-miscompile surfaced per the STOP-on-compiler-bug rule.