diff --git a/examples/0525-packs-pack-as-type-slice-arg.sx b/examples/0525-packs-pack-as-type-slice-arg.sx new file mode 100644 index 00000000..07ad6f33 --- /dev/null +++ b/examples/0525-packs-pack-as-type-slice-arg.sx @@ -0,0 +1,26 @@ +// Regression (issue 0143): a variadic `..$args` pack forwarded as a `[]Type` +// ARGUMENT across a call must read the right element types. The pack-slice +// materialization (`buildPackSliceValue`) built a `[]Any` (16-byte) array while +// `Type` is now `.type_value` (8 bytes) — so a `[]Type` reader (8-byte stride) +// read `[t0, pad, t1, …]` instead of `[t0, t1, …]`. The legacy interp's +// tagged-Value model hid it; the byte-accurate comptime VM exposed it. +#import "modules/std.sx"; + +inner :: (args: []Type) -> string { + s := ""; + i : i64 = 0; + while i < args.len { + s = concat(s, type_name(args[i])); + s = concat(s, " "); + i = i + 1; + } + return s; +} + +outer :: (..$args) -> string { return inner($args); } + +R :: #run outer(42, "hi", true); + +main :: () { + print("[{}]\n", R); +} diff --git a/examples/expected/0525-packs-pack-as-type-slice-arg.exit b/examples/expected/0525-packs-pack-as-type-slice-arg.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/expected/0525-packs-pack-as-type-slice-arg.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0525-packs-pack-as-type-slice-arg.stderr b/examples/expected/0525-packs-pack-as-type-slice-arg.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/expected/0525-packs-pack-as-type-slice-arg.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0525-packs-pack-as-type-slice-arg.stdout b/examples/expected/0525-packs-pack-as-type-slice-arg.stdout new file mode 100644 index 00000000..2f1c96ea --- /dev/null +++ b/examples/expected/0525-packs-pack-as-type-slice-arg.stdout @@ -0,0 +1 @@ +[i64 string bool ] diff --git a/issues/0143-pack-as-type-slice-stride-mismatch.md b/issues/0143-pack-as-type-slice-stride-mismatch.md index bfe4f09e..dcf680ad 100644 --- a/issues/0143-pack-as-type-slice-stride-mismatch.md +++ b/issues/0143-pack-as-type-slice-stride-mismatch.md @@ -1,3 +1,12 @@ +> **RESOLVED (2026-06-18).** Root cause: `buildPackSliceValue` (`src/ir/lower/pack.zig`) +> built the `$` `[]Type` slice as `[]Any` (16-byte elements) — a stale mapping +> from before the dedicated `Type` builtin (`.type_value`, 8 bytes) replaced `Type → .any`. +> It stored 8-byte `const_type` words into 16-byte slots, so a `[]Type` reader (8-byte +> stride) read `[t0, pad, t1, …]`. Fix: build the slice/array as `.type_value` (8 bytes). +> Regression test: `examples/0525-packs-pack-as-type-slice-arg.sx`. The stopgap +> `type_name` `.unresolved` guard added in 379ed05 was removed (root cause fixed). +> 700/0 both gates. + # 0143 — A variadic pack passed as `[]Type` across a call is mis-strided (Any-sized backing, Type-sized view) **Symptom** — When a variadic `..$args` pack is forwarded as a `[]Type` *argument* diff --git a/src/ir/comptime_vm.zig b/src/ir/comptime_vm.zig index ac873062..e27b3492 100644 --- a/src/ir/comptime_vm.zig +++ b/src/ir/comptime_vm.zig @@ -1500,12 +1500,6 @@ pub const Vm = struct { } return self.failMsg("comptime type_name: arg is not a Type value or an Any box"); }; - // An `.unresolved` TypeId means the read produced a bad type (e.g. a - // mis-strided `[]Type` slice over an Any-boxed pack — see issue 0143): - // the VM can't faithfully name it, so BAIL rather than emit - // "". (The legacy reads Values, not bytes, so it gets the - // real type; the fallback then handles this correctly.) - if (tid == .unresolved) return self.failMsg("comptime type_name: unresolved type (bad slice/pack read — see issue 0143)"); return try self.makeStringValue(table, table.typeName(tid)); }, // type_info($T) → reflect a type INTO a TypeInfo VALUE (the inverse of diff --git a/src/ir/lower/pack.zig b/src/ir/lower/pack.zig index 5660a633..2ea8732c 100644 --- a/src/ir/lower/pack.zig +++ b/src/ir/lower/pack.zig @@ -475,42 +475,44 @@ pub fn packVariadicCallArgs(self: *Lowering, fd: *const ast.FnDecl, c: *const as /// Slice IR type is `[]Any` (since `Type → .any`); the interp /// stores whichever Value the elements actually carry. pub fn buildPackSliceValue(self: *Lowering, arg_types: []const TypeId) Ref { - const any_slice_ty = self.module.types.sliceOf(.any); - const any_ptr_ty = self.module.types.ptrTo(.any); + // A bare `$` is a `[]Type` value. Since the dedicated `Type` builtin + // (`.type_value`, 8 bytes) replaced the old `Type → .any` (16-byte) mapping, + // the slice element is `type_value` — building it as `[]Any` here stored 8-byte + // `const_type` words into 16-byte slots, so a `[]Type` reader (8-byte stride) + // read `[t0, pad, t1, …]` instead of `[t0, t1, …]` (issue 0143). + const ty_slice_ty = self.module.types.sliceOf(.type_value); + const ty_ptr_ty = self.module.types.ptrTo(.type_value); if (arg_types.len == 0) { - const null_ptr = self.builder.constNull(any_ptr_ty); + const null_ptr = self.builder.constNull(ty_ptr_ty); const zero_len = self.builder.constInt(0, .i64); - const slice_slot = self.builder.alloca(any_slice_ty); - const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty); + const slice_slot = self.builder.alloca(ty_slice_ty); + const ptr_gep = self.builder.structGepTyped(slice_slot, 0, ty_ptr_ty, ty_slice_ty); self.builder.store(ptr_gep, null_ptr); - const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, any_slice_ty); + const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, ty_slice_ty); self.builder.store(len_gep, zero_len); - return self.builder.load(slice_slot, any_slice_ty); + return self.builder.load(slice_slot, ty_slice_ty); } - const array_ty = self.module.types.arrayOf(.any, @intCast(arg_types.len)); + const array_ty = self.module.types.arrayOf(.type_value, @intCast(arg_types.len)); const array_slot = self.builder.alloca(array_ty); for (arg_types, 0..) |ty, i| { - // `const_type` produces an `.any`-typed Type value - // (`{tag=.any, value=tid}`) — already the canonical Any - // shape, so no re-box needed. - const type_val = self.builder.constType(ty); + const type_val = self.builder.constType(ty); // an 8-byte `.type_value` word const idx_ref = self.builder.constInt(@intCast(i), .i64); - const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, any_ptr_ty); + const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, ty_ptr_ty); self.builder.store(elem_ptr, type_val); } - const slice_slot = self.builder.alloca(any_slice_ty); + const slice_slot = self.builder.alloca(ty_slice_ty); const zero = self.builder.constInt(0, .i64); - const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, any_ptr_ty); + const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, ty_ptr_ty); const len_ref = self.builder.constInt(@intCast(arg_types.len), .i64); - const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty); + const ptr_gep = self.builder.structGepTyped(slice_slot, 0, ty_ptr_ty, ty_slice_ty); self.builder.store(ptr_gep, data_ptr); - const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, any_slice_ty); + const len_gep = self.builder.structGepTyped(slice_slot, 1, .i64, ty_slice_ty); self.builder.store(len_gep, len_ref); - return self.builder.load(slice_slot, any_slice_ty); + return self.builder.load(slice_slot, ty_slice_ty); } pub fn materialisePackSlice(