fix issue 0143: pack-as-[]Type built as []Any — build it as []type_value
buildPackSliceValue (lower/pack.zig) materialized a bare `$<pack>` []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, ...]
instead of [t0, t1, ...]. The legacy interp's tagged-Value model hid it; the
byte-accurate comptime VM exposed it (a `..$args` pack forwarded as a []Type
argument across a call read its elements shifted/garbled).
Fix: build the pack-slice array + slice as .type_value (8 bytes). Removed the
stopgap type_name .unresolved guard (379ed05) now that the root cause is fixed.
Regression test examples/0525-packs-pack-as-type-slice-arg.sx (outer(42,"hi",true)
-> inner($args: []Type) -> "i64 string bool"). 700/0 both gates; issue 0143 RESOLVED.
This commit is contained in:
26
examples/0525-packs-pack-as-type-slice-arg.sx
Normal file
26
examples/0525-packs-pack-as-type-slice-arg.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
1
examples/expected/0525-packs-pack-as-type-slice-arg.exit
Normal file
1
examples/expected/0525-packs-pack-as-type-slice-arg.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[i64 string bool ]
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
|
> **RESOLVED (2026-06-18).** Root cause: `buildPackSliceValue` (`src/ir/lower/pack.zig`)
|
||||||
|
> built the `$<pack>` `[]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)
|
# 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*
|
**Symptom** — When a variadic `..$args` pack is forwarded as a `[]Type` *argument*
|
||||||
|
|||||||
@@ -1500,12 +1500,6 @@ pub const Vm = struct {
|
|||||||
}
|
}
|
||||||
return self.failMsg("comptime type_name: arg is not a Type value or an Any box");
|
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
|
|
||||||
// "<unresolved>". (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));
|
return try self.makeStringValue(table, table.typeName(tid));
|
||||||
},
|
},
|
||||||
// type_info($T) → reflect a type INTO a TypeInfo VALUE (the inverse of
|
// type_info($T) → reflect a type INTO a TypeInfo VALUE (the inverse of
|
||||||
|
|||||||
@@ -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
|
/// Slice IR type is `[]Any` (since `Type → .any`); the interp
|
||||||
/// stores whichever Value the elements actually carry.
|
/// stores whichever Value the elements actually carry.
|
||||||
pub fn buildPackSliceValue(self: *Lowering, arg_types: []const TypeId) Ref {
|
pub fn buildPackSliceValue(self: *Lowering, arg_types: []const TypeId) Ref {
|
||||||
const any_slice_ty = self.module.types.sliceOf(.any);
|
// A bare `$<pack>` is a `[]Type` value. Since the dedicated `Type` builtin
|
||||||
const any_ptr_ty = self.module.types.ptrTo(.any);
|
// (`.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) {
|
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 zero_len = self.builder.constInt(0, .i64);
|
||||||
const slice_slot = self.builder.alloca(any_slice_ty);
|
const slice_slot = self.builder.alloca(ty_slice_ty);
|
||||||
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, null_ptr);
|
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);
|
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);
|
const array_slot = self.builder.alloca(array_ty);
|
||||||
|
|
||||||
for (arg_types, 0..) |ty, i| {
|
for (arg_types, 0..) |ty, i| {
|
||||||
// `const_type` produces an `.any`-typed Type value
|
const type_val = self.builder.constType(ty); // an 8-byte `.type_value` word
|
||||||
// (`{tag=.any, value=tid}`) — already the canonical Any
|
|
||||||
// shape, so no re-box needed.
|
|
||||||
const type_val = self.builder.constType(ty);
|
|
||||||
const idx_ref = self.builder.constInt(@intCast(i), .i64);
|
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);
|
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 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 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);
|
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);
|
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(
|
pub fn materialisePackSlice(
|
||||||
|
|||||||
Reference in New Issue
Block a user