lang 2.1: Pack as a type-system value

Add a `pack` variant to IR `TypeInfo` — an ordered, interned sequence of
per-position element types (`PackInfo { elements: []const TypeId }`) — with
constructor (`packType`), structural equality + hashing, and a `pack(T0, …)`
printer. A pack is comptime-only: it lowers to flat positional args before
codegen and has no runtime layout, so `sizeOf` and `toLLVMType` bail loudly
rather than inventing a size. 5 unit tests (N=0/1/3, dedup, order/arity
distinctness, distinct-from-tuple, printer).

Also: give TypeTable an arena for the slices its constructors dupe (freed at
deinit), and add the missing `usize`/`isize` arms to `sizeOf` (a latent
non-exhaustive switch) so types.test.zig compiles and runs leak-free.
This commit is contained in:
agra
2026-05-29 15:24:46 +03:00
parent 98526ab9b4
commit 92638ae9b5
3 changed files with 132 additions and 4 deletions

View File

@@ -67,6 +67,7 @@ pub const TypeInfo = union(enum) {
closure: ClosureInfo,
optional: OptionalInfo,
tuple: TupleInfo,
pack: PackInfo,
any,
protocol: ProtocolInfo,
noreturn,
@@ -162,6 +163,14 @@ pub const TypeInfo = union(enum) {
names: ?[]const StringId,
};
/// A heterogeneous variadic pack as a first-class type-system value: an
/// ordered sequence of per-position element types. Comptime-only — a pack
/// lowers to flat positional args before codegen and has NO runtime layout
/// (sizeOf panics). `elements.len == 0` is a valid empty pack.
pub const PackInfo = struct {
elements: []const TypeId,
};
pub const ProtocolInfo = struct {
name: StringId,
methods: []const Method,
@@ -245,6 +254,12 @@ pub const TypeTable = struct {
/// Maps TypeInfo → TypeId for dedup of structural types
intern_map: std.HashMap(TypeKey, TypeId, TypeKeyContext, 80),
alloc: Allocator,
/// Owns the element/param slices duped by the type constructors
/// (`functionType*`, `closureType*`, `packType`). Freed wholesale in
/// `deinit` — these slices live as long as the table, so an arena avoids
/// per-slice bookkeeping and the owned-vs-borrowed ambiguity that blocks
/// freeing them individually.
slice_arena: std.heap.ArenaAllocator,
/// Target pointer size in bytes (4 for wasm32, 8 for 64-bit targets).
pointer_size: u8 = 8,
/// Borrowed pointer to Lowering's `type_alias_map`. When set,
@@ -262,6 +277,7 @@ pub const TypeTable = struct {
.strings = StringPool.init(alloc),
.intern_map = std.HashMap(TypeKey, TypeId, TypeKeyContext, 80).init(alloc),
.alloc = alloc,
.slice_arena = std.heap.ArenaAllocator.init(alloc),
};
// Pre-populate builtin slots 015 (must match TypeId enum order)
@@ -295,6 +311,7 @@ pub const TypeTable = struct {
self.infos.deinit(self.alloc);
self.strings.deinit(self.alloc);
self.intern_map.deinit();
self.slice_arena.deinit();
}
/// Look up the TypeInfo for a given TypeId.
@@ -366,22 +383,22 @@ pub const TypeTable = struct {
}
pub fn functionTypeCC(self: *TypeTable, params: []const TypeId, ret: TypeId, cc: TypeInfo.CallConv) TypeId {
const owned_params = self.alloc.dupe(TypeId, params) catch unreachable;
const owned_params = self.slice_arena.allocator().dupe(TypeId, params) catch unreachable;
return self.intern(.{ .function = .{ .params = owned_params, .ret = ret, .call_conv = cc } });
}
pub fn functionTypePack(self: *TypeTable, params: []const TypeId, ret: TypeId, cc: TypeInfo.CallConv, pack_start: u32) TypeId {
const owned_params = self.alloc.dupe(TypeId, params) catch unreachable;
const owned_params = self.slice_arena.allocator().dupe(TypeId, params) catch unreachable;
return self.intern(.{ .function = .{ .params = owned_params, .ret = ret, .call_conv = cc, .pack_start = pack_start } });
}
pub fn closureType(self: *TypeTable, params: []const TypeId, ret: TypeId) TypeId {
const owned_params = self.alloc.dupe(TypeId, params) catch unreachable;
const owned_params = self.slice_arena.allocator().dupe(TypeId, params) catch unreachable;
return self.intern(.{ .closure = .{ .params = owned_params, .ret = ret } });
}
pub fn closureTypePack(self: *TypeTable, params: []const TypeId, ret: TypeId, pack_start: u32) TypeId {
const owned_params = self.alloc.dupe(TypeId, params) catch unreachable;
const owned_params = self.slice_arena.allocator().dupe(TypeId, params) catch unreachable;
return self.intern(.{ .closure = .{ .params = owned_params, .ret = ret, .pack_start = pack_start } });
}
@@ -389,6 +406,13 @@ pub const TypeTable = struct {
return self.intern(.{ .vector = .{ .element = element, .length = length } });
}
/// Construct (and intern) a heterogeneous pack type from an ordered
/// element-type list. `elements.len == 0` yields the empty pack.
pub fn packType(self: *TypeTable, elements: []const TypeId) TypeId {
const owned = self.slice_arena.allocator().dupe(TypeId, elements) catch unreachable;
return self.intern(.{ .pack = .{ .elements = owned } });
}
/// Size in bytes for a type (pointer-sized = 8 on 64-bit).
pub fn sizeOf(self: *const TypeTable, id: TypeId) u32 {
const info = self.get(id);
@@ -449,6 +473,10 @@ pub const TypeTable = struct {
return if (total == 0) 8 else total;
},
.protocol => 16, // {ctx, vtable}
.usize, .isize => 8, // pointer-sized (this path is not target-aware; see typeSizeBytes)
// Comptime-only: a pack must be expanded to flat positional args
// before codegen. Reaching runtime layout means a pack leaked.
.pack => @panic("pack type has no runtime layout (comptime-only)"),
};
}
@@ -740,6 +768,17 @@ pub const TypeTable = struct {
buf.append(alloc, ')') catch break :blk "(?)";
break :blk buf.toOwnedSlice(alloc) catch "(?)";
},
.pack => |pk| blk: {
var buf = std.ArrayList(u8).empty;
defer buf.deinit(alloc);
buf.appendSlice(alloc, "pack(") catch break :blk "pack(?)";
for (pk.elements, 0..) |e, i| {
if (i > 0) buf.appendSlice(alloc, ", ") catch break :blk "pack(?)";
buf.appendSlice(alloc, self.formatTypeName(alloc, e)) catch break :blk "pack(?)";
}
buf.append(alloc, ')') catch break :blk "pack(?)";
break :blk buf.toOwnedSlice(alloc) catch "pack(?)";
},
.signed => |w| std.fmt.allocPrint(alloc, "s{d}", .{w}) catch "s?",
.unsigned => |w| std.fmt.allocPrint(alloc, "u{d}", .{w}) catch "u?",
else => self.typeName(id),
@@ -812,6 +851,9 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
for (t.fields) |f| h.update(std.mem.asBytes(&f));
if (t.names) |ns| for (ns) |n| h.update(std.mem.asBytes(&n));
},
.pack => |p| {
for (p.elements) |e| h.update(std.mem.asBytes(&e));
},
}
}
@@ -871,5 +913,13 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
}
return true;
},
.pack => |p| {
const q = b.pack;
if (p.elements.len != q.elements.len) return false;
for (p.elements, q.elements) |pe, qe| {
if (pe != qe) return false;
}
return true;
},
};
}