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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user