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:
@@ -335,6 +335,23 @@ when reached (sentinels or accessor fns; see the design doc Risks).
|
||||
`List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.)
|
||||
|
||||
## Log
|
||||
- **Phase 3 P3.4 step 2 (VM plan) — dedicated `Type` builtin TypeId: FOUNDATION landed (dead/additive) (2026-06-18).**
|
||||
Added `TypeId.type_value` (slot 19) + a matching `TypeInfo.type_value` variant + the builtins
|
||||
init entry — an **8-byte type handle distinct from the 16-byte boxed `.any`** (THE WALL). All
|
||||
`types.zig` layout handlers wired: `sizeOf`/`typeSizeBytes` → 8, `typeAlignBytes` → 8,
|
||||
`typeName` → "Type", `hashTypeInfo`/`typeInfoEql` no-payload arms. Only ONE exhaustive switch
|
||||
needed a new arm (`backend/llvm/types.zig` `toLLVMTypeInfo` → `cached_i64`); every other
|
||||
`switch(TypeInfo)` site has an `else` (audited when the resolver flips). **`first_user` 19 → 100**
|
||||
(per the user): slots 20–99 are RESERVED builtin headroom (infos padded with the `unresolved`
|
||||
tripwire), so future builtins don't renumber user TypeIds / churn `sx ir` snapshots. Cost:
|
||||
~80 default entries in each binary's per-type reflection arrays (user opted in). **Still dead:**
|
||||
`type_resolver.zig:64` STILL returns `.any` for "Type" — nothing produces `.type_value` yet, so
|
||||
NO behavior change. Regenerated 22 IR snapshots (pure TypeId renumber to 100-base; `git diff
|
||||
--name-only` confirmed ONLY `.ir` files + the 2 source files changed — no stdout/stderr/exit).
|
||||
**697/0 both gates** (OFF and `-Dcomptime-flat`). **Next:** flip `type_resolver:64` →
|
||||
`.type_value`, then migrate the `.any` refs that mean "a Type value" (const_type result /
|
||||
reflection returns / metatype `Type` params / `.type_tag` checks) — leave the real boxed-Any
|
||||
refs — file-by-file with a build after each.
|
||||
- **Phase 3 P3.4 step 1 (VM plan) — lowering-time default context; first blocker cleared (2026-06-18).**
|
||||
`materializeDefaultContext` now falls back to a ZEROED `Context` (found by name) when the
|
||||
`__sx_default_context` global is absent — i.e. at LOWERING time, where the global isn't
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
@@ -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 20–99 reserved for future builtins)
|
||||
|
||||
pub const first_user: u32 = 19;
|
||||
/// User-defined types start here. Builtins occupy 0–18 (plus `type_value` at
|
||||
/// 19); slots 20–99 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,
|
||||
|
||||
Reference in New Issue
Block a user