comptime VM: dedicated Type builtin TypeId (8B), distinct from .any — foundation (dead)

Add TypeId.type_value (slot 19) + matching TypeInfo.type_value variant: an
8-byte type handle, distinct from the 16-byte boxed .any. All types.zig layout
handlers wired (size/align 8, display "Type", hash/eql); toLLVMTypeInfo -> i64.

Reserve builtin headroom: first_user 19 -> 100 (slots 20-99 padded with the
unresolved tripwire) so future builtins don't renumber user TypeIds / churn
sx ir snapshots. 22 IR snapshots regenerated (pure renumber to 100-base).

type_resolver still returns .any for "Type" — nothing produces .type_value
yet, so no behavior change. 697/0 both gates.
This commit is contained in:
agra
2026-06-18 13:03:21 +03:00
parent 7d59b5eeb6
commit 6844fb90e7
25 changed files with 39319 additions and 37485 deletions

View File

@@ -97,6 +97,10 @@ pub const TypeLowering = struct {
return c.LLVMVectorType(elem, vec.length);
},
.any => self.e.getAnyStructType(),
// A comptime `Type` value is an 8-byte type handle (a `TypeId` in a
// word), distinct from the 16-byte boxed Any. It is comptime-only, but
// the type still lowers (dead comptime-body code / a global slot) as i64.
.type_value => self.e.cached_i64,
.noreturn => self.e.cached_void,
.@"struct" => |s| {
// Build LLVM struct type from fields

View File

@@ -34,9 +34,23 @@ pub const TypeId = enum(u32) {
usize = 16,
void = 17,
cstring = 18, // thin null-terminated char* (see TypeInfo.cstring)
_, // user-defined types start at 19
/// A comptime `Type` VALUE — an 8-byte handle (a `TypeId` stored in a word),
/// DISTINCT from `.any`. A `Type` value is the reified type itself
/// (`reflect`/`const_type`/the comptime compiler-API), not a boxed Any. It used
/// to share `.any`'s slot, but `.any` is a 16-byte `{tag,value}` box (variadic
/// any), so a `Type` stored in an aggregate was sized 16B while the value is 8B
/// — which blocked the flat-memory comptime VM. Its own slot fixes the size and
/// keeps every downstream `== .any`/`switch` check from conflating the two.
type_value = 19,
_, // user-defined types start at `first_user` (slots 2099 reserved for future builtins)
pub const first_user: u32 = 19;
/// User-defined types start here. Builtins occupy 018 (plus `type_value` at
/// 19); slots 2099 are RESERVED headroom so adding a new builtin doesn't
/// renumber every user type (which would churn every `sx ir` snapshot in the
/// corpus). The `TypeTable` pads its `infos` array out to this index with the
/// `unresolved` tripwire so an accidental reference to a reserved slot panics
/// rather than silently aliasing a real type.
pub const first_user: u32 = 100;
pub fn index(self: TypeId) u32 {
return @intFromEnum(self);
@@ -86,6 +100,9 @@ pub const TypeInfo = union(enum) {
noreturn,
usize,
isize,
/// A comptime `Type` VALUE (see `TypeId.type_value`): an 8-byte type handle,
/// distinct from the 16-byte boxed `any`.
type_value,
/// Resolution-failure sentinel (see `TypeId.unresolved`).
unresolved,
@@ -385,10 +402,20 @@ pub const TypeTable = struct {
.usize, // 16: usize (pointer-sized unsigned)
.void, // 17
.cstring, // 18: thin null-terminated char*
.type_value, // 19: comptime `Type` value (8-byte handle, distinct from any)
};
for (&builtins) |info| {
table.infos.append(alloc, info) catch unreachable;
}
// Pad the reserved builtin headroom (slots after the real builtins, up to
// `first_user`) with the `unresolved` tripwire: these slots are never a
// legitimate type, so any reference panics in `sizeOf`/`get` rather than
// silently aliasing a user type. Reserving the range keeps user TypeIds
// stable as new builtins are added (no snapshot churn).
std.debug.assert(table.infos.items.len <= TypeId.first_user);
while (table.infos.items.len < TypeId.first_user) {
table.infos.append(alloc, .unresolved) catch unreachable;
}
return table;
}
@@ -721,6 +748,7 @@ pub const TypeTable = struct {
.array => |arr| arr.length * self.sizeOf(arr.element),
.vector => |vec| vec.length * self.sizeOf(vec.element),
.any => 16, // {type_tag, data_ptr}
.type_value => 8, // an 8-byte type handle (a `TypeId` in a word), NOT the 16-byte any box
.@"struct" => |s| {
var total: u32 = 0;
for (s.fields) |f| total += @max(self.sizeOf(f.ty), 8);
@@ -803,6 +831,7 @@ pub const TypeTable = struct {
if (ty == .cstring) return ptr_size;
if (ty == .string) return 16; // {ptr, i64} — always 16 (i64 alignment pads on wasm32)
if (ty == .any) return 16; // {i64 tag, i64 value} — Any boxed layout
if (ty == .type_value) return 8; // 8-byte type handle (a `TypeId` in a word)
if (ty.isBuiltin()) return ptr_size; // default for unknown builtins
const info = self.get(ty);
return switch (info) {
@@ -904,6 +933,7 @@ pub const TypeTable = struct {
if (ty == .string) return 8; // i64 drives alignment
if (ty == .cstring) return ptr_align;
if (ty == .any) return 8; // {i64, i64} aligns to 8
if (ty == .type_value) return 8; // 8-byte type handle
if (ty.isBuiltin()) return ptr_align;
const info = self.get(ty);
return switch (info) {
@@ -975,6 +1005,7 @@ pub const TypeTable = struct {
.string => "string",
.cstring => "cstring",
.any => "Any",
.type_value => "Type",
.noreturn => "noreturn",
.isize => "isize",
.usize => "usize",
@@ -1120,7 +1151,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
switch (info) {
.signed => |w| h.update(&.{w}),
.unsigned => |w| h.update(&.{w}),
.f32, .f64, .void, .bool, .string, .cstring, .any, .noreturn, .usize, .isize, .unresolved => {},
.f32, .f64, .void, .bool, .string, .cstring, .any, .type_value, .noreturn, .usize, .isize, .unresolved => {},
.pointer => |p| h.update(std.mem.asBytes(&p.pointee)),
.many_pointer => |p| h.update(std.mem.asBytes(&p.element)),
.slice => |s| h.update(std.mem.asBytes(&s.element)),
@@ -1191,7 +1222,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
return switch (a) {
.signed => |w| w == b.signed,
.unsigned => |w| w == b.unsigned,
.f32, .f64, .void, .bool, .string, .cstring, .any, .noreturn, .usize, .isize, .unresolved => true,
.f32, .f64, .void, .bool, .string, .cstring, .any, .type_value, .noreturn, .usize, .isize, .unresolved => true,
.pointer => |p| p.pointee == b.pointer.pointee,
.many_pointer => |p| p.element == b.many_pointer.element,
.slice => |s| s.element == b.slice.element,