fix(ir): store to module-global array element targets live storage (issue 0079)

A store to a module-global array element (`g[i] = v`) was silently dropped:
a subsequent `g[i]` read the array's initializer, not `v`. Constant index,
variable index, and cross-function stores were all affected, in both `sx run`
and `sx build`. Global scalars and local arrays were fine.

Root cause: `Lowering.lowerExprAsPtr` (the lvalue/address path) handled only
local identifiers. A module-global identifier fell through to the value
fallback `lowerExpr`, which emits `global_get` — loading the whole array by
value. The LLVM backend's `emitIndexGep` then allocas a throwaway temp, copies
the value in, and GEPs into the temp, so the store wrote a discarded copy.

Fix: teach `lowerExprAsPtr`'s identifier arm about globals — emit `global_addr`
(a pointer into the global's live storage), or `global_get` for a pointer-typed
global (mirroring the local pointer case). Route the `address_of(index_expr)`
array base through `lowerExprAsPtr` too so `&g[i]` is likewise an lvalue into
the global. `index_gep` now GEPs directly into the global for const and variable
index, across functions. This also fixes global struct field stores, which
shared the same root cause.

Regression: examples/0136-types-global-array-element-store.sx (const-index,
var-index, cross-function store on a scalar global array; struct-element array
for stride; nested-array global for the recursive lvalue). Fails on the pre-fix
compiler, passes after.
This commit is contained in:
agra
2026-06-04 03:44:19 +03:00
parent 483b14015f
commit 7306d37748
6 changed files with 193 additions and 13 deletions

View File

@@ -2324,20 +2324,29 @@ pub const Lowering = struct {
fn lowerExprAsPtr(self: *Lowering, node: *const Node) Ref {
switch (node.data) {
.identifier => |id| {
if (self.scope) |scope| {
if (scope.lookup(id.name)) |binding| {
if (binding.is_alloca) {
// If the variable IS a pointer (e.g., p: *Vec2), load it
// to get the actual pointer value for GEP/store operations
if (!binding.ty.isBuiltin()) {
const info = self.module.types.get(binding.ty);
if (info == .pointer) {
return self.builder.load(binding.ref, binding.ty);
}
const local = if (self.scope) |scope| scope.lookup(id.name) else null;
if (local) |binding| {
if (binding.is_alloca) {
// If the variable IS a pointer (e.g., p: *Vec2), load it
// to get the actual pointer value for GEP/store operations
if (!binding.ty.isBuiltin()) {
const info = self.module.types.get(binding.ty);
if (info == .pointer) {
return self.builder.load(binding.ref, binding.ty);
}
return binding.ref;
}
return binding.ref;
}
} else if (self.program_index.global_names.get(id.name)) |gi| {
// Module-global lvalue: address into the global's live storage
// so a downstream GEP/store targets the global itself, not a
// loaded copy. A pointer-typed global is loaded first to get
// the pointer value to GEP through (mirrors the local pointer
// case above); any other global yields its storage address.
if (!gi.ty.isBuiltin() and self.module.types.get(gi.ty) == .pointer) {
return self.builder.emit(.{ .global_get = gi.id }, gi.ty);
}
return self.builder.emit(.{ .global_addr = gi.id }, self.module.types.ptrTo(gi.ty));
}
},
.field_access => |fa| {
@@ -2697,9 +2706,11 @@ pub const Lowering = struct {
const obj_ty = self.inferExprType(ie.object);
const elem_ty = self.getElementType(obj_ty);
const ptr_ty = self.module.types.ptrTo(elem_ty);
// For array targets, use the alloca directly so the pointer is persistent
// For array targets, use the storage pointer (alloca for a
// local, global_addr for a module global) so the resulting
// pointer is into live storage, not a loaded copy.
const is_array = !obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .array;
const base = if (is_array) (self.getExprAlloca(ie.object) orelse self.lowerExpr(ie.object)) else self.lowerExpr(ie.object);
const base = if (is_array) (self.getExprAlloca(ie.object) orelse self.lowerExprAsPtr(ie.object)) else self.lowerExpr(ie.object);
break :blk self.builder.emit(.{ .index_gep = .{ .lhs = base, .rhs = idx } }, ptr_ty);
}
// address_of(field_access) → use lowerExprAsPtr for GEP chain