ffi M5.A.next.2b.fu34.B: pack-mono materialises []Any slice for bare args

Fixes follow-ups #3 (bare `args` reference) and #4
(`args[<runtime_int>]`) 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[<runtime_int>]` 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.
This commit is contained in:
agra
2026-05-27 16:41:28 +03:00
parent dadf80b3f1
commit d30d566397
5 changed files with 68 additions and 8 deletions

View File

@@ -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[<runtime_int>]` (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()) {

View File

@@ -1 +1 @@
1
0

View File

@@ -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

View File

@@ -1 +1 @@
1
0

View File

@@ -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