fix(0110): for-over-array by-value fetch reads one element, not a full copy
lowerFor's by-value element fetch emitted index_get on the array VALUE; the emitter realizes that as a whole-array spill to a stack temp + GEP, per iteration — O(N^2) bytes copied per loop (and pre-0109 it also grew the stack per iteration, segfaulting a [4096]s64 loop). When the iterable is an array with addressable storage (and not deref'd from a pointer, whose identifier alloca holds the pointer rather than the array), the fetch is now index_gep on the storage + one element load. Storage-less arrays keep the index_get fallback. The loaded element remains a copy — mutating the capture does not write back. Regression: examples/0048-basic-for-array-large.sx (sum over 4096 elements + by-value copy-guard).
This commit is contained in:
@@ -289,11 +289,15 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
var iterable = self.lowerExpr(fe.iterable);
|
||||
var iterable_ty = self.inferExprType(fe.iterable);
|
||||
|
||||
// `*List` / `*[]T` etc. — deref to the collection value.
|
||||
// `*List` / `*[]T` etc. — deref to the collection value. Tracked because
|
||||
// a deref'd iterable's identifier binding holds the POINTER, so its
|
||||
// alloca is not the collection's storage.
|
||||
var was_deref = false;
|
||||
const ptr_info = if (iterable_ty.isBuiltin()) null else self.module.types.get(iterable_ty);
|
||||
if (ptr_info != null and ptr_info.? == .pointer) {
|
||||
iterable = self.builder.load(iterable, ptr_info.?.pointer.pointee);
|
||||
iterable_ty = ptr_info.?.pointer.pointee;
|
||||
was_deref = true;
|
||||
}
|
||||
|
||||
// A `List(T)`-like struct iterates its `items[0..len]`; arrays/slices
|
||||
@@ -333,14 +337,25 @@ pub fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
|
||||
// binds a value copy.
|
||||
const elem_ty = self.getElementType(iterable_ty);
|
||||
const bind_ty = if (fe.capture_by_ref) self.module.types.ptrTo(elem_ty) else elem_ty;
|
||||
const is_array = !iterable_ty.isBuiltin() and self.module.types.get(iterable_ty) == .array;
|
||||
const elem = if (fe.capture_by_ref) blk: {
|
||||
// A slice value carries its backing pointer, so GEP on it writes
|
||||
// through. An array is a value — GEP needs its storage (alloca) or
|
||||
// mutations would hit a copy.
|
||||
const is_array = !iterable_ty.isBuiltin() and self.module.types.get(iterable_ty) == .array;
|
||||
const base = if (is_array) (self.getExprAlloca(fe.iterable) orelse iterable) else iterable;
|
||||
break :blk self.builder.emit(.{ .index_gep = .{ .lhs = base, .rhs = idx_val } }, bind_ty);
|
||||
} else self.builder.emit(.{ .index_get = .{ .lhs = iterable, .rhs = idx_val } }, bind_ty);
|
||||
} else blk: {
|
||||
// By-value over an array with addressable storage: GEP + load ONE
|
||||
// element. `index_get` on the array VALUE spills the whole array to
|
||||
// a temp on every iteration — O(N²) bytes copied per loop.
|
||||
if (is_array and !was_deref) {
|
||||
if (self.getExprAlloca(fe.iterable)) |storage| {
|
||||
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = storage, .rhs = idx_val } }, self.module.types.ptrTo(elem_ty));
|
||||
break :blk self.builder.load(elem_ptr, elem_ty);
|
||||
}
|
||||
}
|
||||
break :blk self.builder.emit(.{ .index_get = .{ .lhs = iterable, .rhs = idx_val } }, bind_ty);
|
||||
};
|
||||
|
||||
var body_scope = Scope.init(self.alloc, self.scope);
|
||||
const old_scope = self.scope;
|
||||
|
||||
Reference in New Issue
Block a user