From d30d5663971f0bcc4d426ec1edd0aa0b7e5c6e60 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 27 May 2026 16:41:28 +0300 Subject: [PATCH] ffi M5.A.next.2b.fu34.B: pack-mono materialises []Any slice for bare args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes follow-ups #3 (bare `args` reference) and #4 (`args[]`) from step 2b. The pack-mono now materialises an `[]Any` slice value for the pack name at body entry: each pack-param slot is loaded, boxed via `boxAny`, and stored into a stack [N x Any] array; the slice {data_ptr, len} binds to the pack name in scope. Plumbing in src/ir/lower.zig: - `materialisePackSlice(scope, pack_name, slot_refs, arg_types)` — new helper that emits the array alloca + box+store loop + slice alloca + bind. Empty-pack case (N == 0) emits {null, 0} directly. - `monomorphizePackFn` captures the pack-param slot Refs as they bind, then calls `materialisePackSlice` after binding so the slice load can pull each param value. After: `args` (bare) resolves as `[]Any` and forwards to slice-typed helpers; `args[]` lowers through the standard slice-indexing path, element type `Any`. Per-position type info is lost via Any boxing — that is the inherent cost of treating a heterogeneous pack as a uniform value. Literal- indexed access still routes through `packArgNodeAt` and keeps the concrete per-position types. `examples/162-pack-bare-args.sx` flips from "unresolved 'args'" to `3` (forwarded to `log_count(items: []Any)` which returns `items.len`). `examples/163-pack-runtime-index.sx` flips from the LLVM verifier crash to `4` (while-loop over `args.len`, indexing each `args[i]` runtime). 202/202 example tests + `zig build test` green. --- src/ir/lower.zig | 64 ++++++++++++++++++++++ tests/expected/162-pack-bare-args.exit | 2 +- tests/expected/162-pack-bare-args.txt | 2 +- tests/expected/163-pack-runtime-index.exit | 2 +- tests/expected/163-pack-runtime-index.txt | 6 +- 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 1764827..0bf9ed1 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -8103,6 +8103,58 @@ pub const Lowering = struct { return self.builder.constInt(0, .void); } + /// Build an `[]Any` slice value from the mono's pack params and + /// bind it to the pack name in scope. Each pack-param slot is + /// loaded, boxed via `boxAny`, and stored into a stack [N x Any] + /// array; the slice {data_ptr, len} is then bound. Used by + /// `monomorphizePackFn` so bodies that reference `args` bare or + /// index it with a runtime int resolve through the slice (with + /// element type `Any`). Literal-indexed accesses keep the + /// concrete per-position types via `packArgNodeAt`. + fn materialisePackSlice( + self: *Lowering, + scope: *Scope, + pack_name: []const u8, + slot_refs: []const Ref, + arg_types: []const TypeId, + ) void { + const any_slice_ty = self.module.types.sliceOf(.any); + const any_ptr_ty = self.module.types.ptrTo(.any); + + if (arg_types.len == 0) { + const null_ptr = self.builder.constNull(any_ptr_ty); + const zero_len = self.builder.constInt(0, .s64); + const slice_slot = self.builder.alloca(any_slice_ty); + const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty); + self.builder.store(ptr_gep, null_ptr); + const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty); + self.builder.store(len_gep, zero_len); + scope.put(pack_name, .{ .ref = slice_slot, .ty = any_slice_ty, .is_alloca = true }); + return; + } + + const array_ty = self.module.types.arrayOf(.any, @intCast(arg_types.len)); + const array_slot = self.builder.alloca(array_ty); + + for (slot_refs, arg_types, 0..) |slot, ty, i| { + const val = self.builder.load(slot, ty); + const boxed = if (ty == .any) val else self.builder.boxAny(val, ty); + const idx_ref = self.builder.constInt(@intCast(i), .s64); + const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, any_ptr_ty); + self.builder.store(elem_ptr, boxed); + } + + const slice_slot = self.builder.alloca(any_slice_ty); + const zero = self.builder.constInt(0, .s64); + const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, any_ptr_ty); + const len_ref = self.builder.constInt(@intCast(arg_types.len), .s64); + const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty); + self.builder.store(ptr_gep, data_ptr); + const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty); + self.builder.store(len_gep, len_ref); + scope.put(pack_name, .{ .ref = slice_slot, .ty = any_slice_ty, .is_alloca = true }); + } + /// Infer the return type of a pack-fn body for the generic-`$R` /// case. Walks the body looking for the first concrete return /// type: a `return X;` statement's value type, or — failing that — @@ -8329,17 +8381,29 @@ pub const Lowering = struct { scope.put(p.name, .{ .ref = slot, .ty = pty, .is_alloca = true }); param_idx += 1; } + var pack_param_slots = std.ArrayList(Ref).empty; + defer pack_param_slots.deinit(self.alloc); for (arg_types, 0..) |ty, i| { const synth_name = pack_synth_names.items[i]; const slot = self.builder.alloca(ty); self.builder.store(slot, Ref.fromIndex(param_idx)); scope.put(synth_name, .{ .ref = slot, .ty = ty, .is_alloca = true }); + pack_param_slots.append(self.alloc, slot) catch return; param_idx += 1; } // Pack bindings remain installed from the pre-resolution // (generic-`$R`) inference step above. No need to reinstall. + // Materialise an `[]Any` slice value for the pack name so + // bare `args` (forwarding) and `args[]` (loops) + // resolve at runtime. Per-position type info is lost via + // Any boxing — that's the inherent cost of treating a + // heterogeneous pack as a uniform value. Literal-indexed + // access still goes through `packArgNodeAt` and keeps the + // concrete per-position types. + self.materialisePackSlice(&scope, pack_name, pack_param_slots.items, arg_types); + if (ret_ty != .void) { const body_val = self.lowerBlockValue(fd.body); if (!self.currentBlockHasTerminator()) { diff --git a/tests/expected/162-pack-bare-args.exit b/tests/expected/162-pack-bare-args.exit index d00491f..573541a 100644 --- a/tests/expected/162-pack-bare-args.exit +++ b/tests/expected/162-pack-bare-args.exit @@ -1 +1 @@ -1 +0 diff --git a/tests/expected/162-pack-bare-args.txt b/tests/expected/162-pack-bare-args.txt index 7bea9a6..00750ed 100644 --- a/tests/expected/162-pack-bare-args.txt +++ b/tests/expected/162-pack-bare-args.txt @@ -1 +1 @@ -/Users/agra/projects/sx/examples/162-pack-bare-args.sx:23:22: error: unresolved 'args' (in /Users/agra/projects/sx/examples/162-pack-bare-args.sx fn forward__pack_s64_string_f64) +3 diff --git a/tests/expected/163-pack-runtime-index.exit b/tests/expected/163-pack-runtime-index.exit index d00491f..573541a 100644 --- a/tests/expected/163-pack-runtime-index.exit +++ b/tests/expected/163-pack-runtime-index.exit @@ -1 +1 @@ -1 +0 diff --git a/tests/expected/163-pack-runtime-index.txt b/tests/expected/163-pack-runtime-index.txt index 542253c..b8626c4 100644 --- a/tests/expected/163-pack-runtime-index.txt +++ b/tests/expected/163-pack-runtime-index.txt @@ -1,5 +1 @@ -LLVM verification failed: GEP base pointer is not a vector or a vector of pointers - %ig.ptr = getelementptr i64, i64 %ig.data, %load7 -Load operand must be a pointer. - %ig.val = load i64, i64 %ig.ptr, align 8 - +4