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:
@@ -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 0–15 (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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user