P5.7 Step D: delete dead .define builtin arm, defineFromInfo, decodeTypeSlice

Now that define() is sx over register_type, remove the bespoke metatype define
surface from the comptime VM: the .define callBuiltinVm arm, the defineFromInfo
helper (kind-branching minting), and decodeTypeSlice (its only caller). Remove the
BuiltinId.define enum member. The .declare/.define interceptions in lowering and
their BuiltinIds are now gone; only type_info/field_type remain as metatype
builtins. register_type/decodeMemberSlice stay (shared by the sx define and the
compiler-API graph builder).
This commit is contained in:
agra
2026-06-19 21:14:05 +03:00
parent 7b1d8ceb83
commit ccba704378
3 changed files with 13 additions and 138 deletions

View File

@@ -1884,22 +1884,6 @@ pub const Vm = struct {
}
}
/// Decode a `[]Type` slice (a metatype `TupleInfo.elements` — POSITIONAL, bare
/// `Type` elements with no name) from comptime memory into `TypeId`s.
fn decodeTypeSlice(self: *Vm, table: *const types.TypeTable, slice_word: Reg, slice_ty: TypeId, out: *std.ArrayList(TypeId)) Error!void {
if (slice_ty.isBuiltin() or table.get(slice_ty) != .slice)
return self.failMsg("comptime define(): tuple elements arg is not a slice");
const elem_ty = table.get(slice_ty).slice.element; // Type (.type_value)
const len = try self.sliceLen(slice_word);
const base = try self.sliceData(table, slice_word);
const stride: Addr = @intCast(table.typeSizeBytes(elem_ty));
for (0..@intCast(len)) |i| {
const e = base + @as(Addr, @intCast(i)) * stride;
const t: TypeId = @enumFromInt(@as(u32, @intCast(try self.readField(table, e, .type_value))));
out.append(self.gpa, t) catch return self.failMsg("comptime define(): out of memory");
}
}
/// Resolve the `TypeId` a reflection builtin (`type_name` / `type_is_unsigned`)
/// queries, given the arg's IR type `aty` and its register word `w`. A
/// `.type_value` word IS a `TypeId`; an Any box `{ tag@0, value@8 }` yields its
@@ -1935,35 +1919,10 @@ pub const Vm = struct {
/// parity holds). Keeps BOTH paths alive during the VM-default transition.
fn callBuiltinVm(self: *Vm, bi: inst_mod.BuiltinCall, ins_ty: TypeId, frame: *Frame, ref_types: []const TypeId) Error!?Reg {
switch (bi.builtin) {
// `declare(name)` is no longer a builtin — it's plain sx in
// `modules/std/meta.sx` over the `declare_type` compiler-API primitive
// (`callCompilerFn`). The `.declare` BuiltinId was removed with it.
// define(handle, info) → complete the declared slot from a TypeInfo VALUE.
.define => {
const table = try self.requireTable();
if (bi.args.len != 2) return self.failMsg("comptime define(): expected (handle, info)");
const handle = try self.argTypeId(bi.args, frame, 0);
// `info`: a TypeInfo tagged-union value `{ tag@0, payload@tag_size }`.
const info_ty = try self.refTy(ref_types, bi.args[1]);
if (info_ty.isBuiltin() or table.get(info_ty) != .tagged_union)
return self.failMsg("comptime define(): info arg is not a TypeInfo tagged union");
const tu = table.get(info_ty).tagged_union;
// The `{ tag@0, payload@tag_size }` read below assumes a tag-headed
// layout (true for `TypeInfo`); a `backing_type` union is laid out
// differently, so bail rather than read the tag from the wrong bytes.
if (tu.backing_type != null)
return self.failMsg("comptime define(): info is a backing_type tagged union (unexpected layout)");
const info_addr = frame.get(bi.args[1].index());
const tag_size: Addr = @intCast(table.typeSizeBytes(tu.tag_type));
const tag = try self.machine.readWord(info_addr, tag_size);
if (tag >= tu.fields.len) return self.failMsg("comptime define(): TypeInfo tag out of range");
// The active payload (EnumInfo / StructInfo / TupleInfo) is a struct
// holding ONE slice field; its bytes live at `info_addr + tag_size`.
const payload_ty = tu.fields[@intCast(tag)].ty;
if (payload_ty.isBuiltin() or table.get(payload_ty) != .@"struct" or table.get(payload_ty).@"struct".fields.len != 1)
return self.failMsg("comptime define(): TypeInfo payload is not a single-slice info struct");
return try self.defineFromInfo(table, handle, @intCast(tag), payload_ty, info_addr + tag_size);
},
// `declare(name)` and `define(handle, info)` are no longer builtins
// they're plain sx in `modules/std/meta.sx` over the compiler-API
// primitives `declare_type` / `register_type` (`callCompilerFn`). The
// `.declare` / `.define` BuiltinIds and `defineFromInfo` were removed.
// type_name(x) → the type's name as a string. The arg is a Type value
// (`.type_value` word = a TypeId) or an Any box (`{tag@0, value@8}` whose
// tag IS the boxed value's type, unless tag == type_value: then the boxed
@@ -1997,87 +1956,8 @@ pub const Vm = struct {
}
}
/// Complete a `declare()`d slot from a decoded `TypeInfo` (the VM-native mirror
/// of `interp.defineType` → `defineEnum`/`defineStruct`/`defineTuple`). `tag` is
/// the TypeInfo variant index (meta.sx order: 0 `enum`, 1 `struct`, 2 `tuple`);
/// `payload_ty`/`payload_addr` locate the active info struct (one slice field).
/// Mirrors the legacy minting exactly (all-void enum → real `.@"enum"`, dup-name
/// rejection, `updatePreservingKey` vs `replaceKeyedInfo`) so the result is
/// byte-identical and the dual paths can't drift. Mutates the table LAST (after
/// decoding succeeds) so a mid-decode bail leaves the slot untouched — parity
/// with the legacy "no mutation before the bail".
fn defineFromInfo(self: *Vm, table: *const types.TypeTable, handle: TypeId, tag: u32, payload_ty: TypeId, payload_addr: Addr) Error!Reg {
const tbl = @constCast(table);
const cur = table.get(handle);
const ident = nominalIdentOf(cur) orelse
return self.failMsg("comptime define(): handle is not a declare()'d nominal slot");
if (cur != .tagged_union) return self.failMsg("comptime define(): handle is not a declare()'d slot");
// The info struct's single field is the member/element slice; read its
// fat-pointer (embedded at field-0 offset within the info struct).
const slice_field_ty = table.get(payload_ty).@"struct".fields[0].ty;
const slice_word = try self.readField(table, payload_addr + fieldOffset(table, payload_ty, 0), slice_field_ty);
switch (tag) {
0 => { // .enum(EnumInfo{ variants: []EnumVariant{name, payload} })
var members = std.ArrayList(NamedMember).empty;
defer members.deinit(self.gpa);
try self.decodeMemberSlice(table, slice_word, slice_field_ty, &members);
if (members.items.len == 0) return self.failMsg("comptime define(): enum has no variants");
// A FULLY payloadless variant set (every payload `void`) is an actual
// `.@"enum"` (a kind change → `replaceKeyedInfo`); minting it as an
// all-void tagged_union trips `verifySizes` at codegen (issue 0142).
var all_void = true;
for (members.items) |m| if (m.ty != .void) {
all_void = false;
break;
};
if (all_void) {
const names = self.gpa.alloc(types.StringId, members.items.len) catch return self.failMsg("comptime define(): out of memory");
for (members.items, 0..) |m, i| {
for (names[0..i]) |prev| if (prev == m.name) return self.failFmt("comptime define(): duplicate variant name '{s}'", .{tbl.getString(m.name)});
names[i] = m.name;
}
tbl.replaceKeyedInfo(handle, .{ .@"enum" = .{ .name = ident.name, .variants = names, .nominal_id = ident.nominal_id } });
} else {
const flds = self.gpa.alloc(types.TypeInfo.StructInfo.Field, members.items.len) catch return self.failMsg("comptime define(): out of memory");
for (members.items, 0..) |m, i| {
for (flds[0..i]) |prev| if (prev.name == m.name) return self.failFmt("comptime define(): duplicate variant name '{s}'", .{tbl.getString(m.name)});
flds[i] = .{ .name = m.name, .ty = m.ty };
}
// Name/id unchanged → still a tagged_union → stable key.
tbl.updatePreservingKey(handle, .{ .tagged_union = .{ .name = ident.name, .fields = flds, .tag_type = .i64, .nominal_id = ident.nominal_id } });
}
},
1 => { // .struct(StructInfo{ fields: []StructField{name, type} })
var members = std.ArrayList(NamedMember).empty;
defer members.deinit(self.gpa);
try self.decodeMemberSlice(table, slice_word, slice_field_ty, &members);
if (members.items.len == 0) return self.failMsg("comptime define(): struct has no fields");
const flds = self.gpa.alloc(types.TypeInfo.StructInfo.Field, members.items.len) catch return self.failMsg("comptime define(): out of memory");
for (members.items, 0..) |m, i| {
for (flds[0..i]) |prev| if (prev.name == m.name) return self.failFmt("comptime define(): duplicate field name '{s}'", .{tbl.getString(m.name)});
flds[i] = .{ .name = m.name, .ty = m.ty };
}
// tagged_union slot → struct is a kind change → `replaceKeyedInfo`.
tbl.replaceKeyedInfo(handle, .{ .@"struct" = .{ .name = ident.name, .fields = flds, .nominal_id = ident.nominal_id } });
},
2 => { // .tuple(TupleInfo{ elements: []Type }) — positional, no names
var elems = std.ArrayList(TypeId).empty;
defer elems.deinit(self.gpa);
try self.decodeTypeSlice(table, slice_word, slice_field_ty, &elems);
if (elems.items.len == 0) return self.failMsg("comptime define(): tuple has no elements");
const tys = self.gpa.alloc(TypeId, elems.items.len) catch return self.failMsg("comptime define(): out of memory");
@memcpy(tys, elems.items);
tbl.replaceKeyedInfo(handle, .{ .tuple = .{ .fields = tys, .names = null } });
},
else => return self.failMsg("comptime define(): unknown TypeInfo variant"),
}
return @as(Reg, handle.index());
}
/// Reflect type `tid` INTO a `TypeInfo` VALUE built in comptime memory — the inverse
/// of `defineFromInfo` and the VM-native mirror of legacy `reflectTypeInfo`. The
/// of the sx `define` (which calls `register_type`). The
/// element/struct layouts come from the `result_ty` (= the metatype `TypeInfo`
/// tagged union): variant tag `t` → payload struct `EnumInfo`/`StructInfo`/
/// `TupleInfo` (one slice field) → the slice element (`EnumVariant`/`StructField`/

View File

@@ -452,15 +452,9 @@ pub const BuiltinId = enum(u16) {
type_eq,
type_is_unsigned,
has_impl,
// The comptime type-CONSTRUCTION terminator builtin. Higher-level
// constructors (one-shot, channel-result, etc.) are ordinary sx built over
// it — the compiler knows none of them by name. Comptime-only (the comptime
// VM mutates the type table); reaching it at runtime / emit is a hard error.
// (`declare` is no longer a builtin — it's plain sx over the `declare_type`
// compiler-API primitive in `modules/std/meta.sx`.)
// define(handle, info) → decode the `TypeInfo` VALUE (the name travels in
// it) and complete the slot.
define,
// (`declare` and `define` are no longer builtins — they're plain sx over the
// `declare_type` / `register_type` compiler-API primitives in
// `modules/std/meta.sx`.)
// The comptime reflection INVERSE of `define`: read a type's variants
// (name + payload type) out of the type table and CONSTRUCT the same
// `.enum(EnumInfo{ variants })` value `define` decodes. Comptime-only

View File

@@ -1816,10 +1816,11 @@ pub fn findReturnTypeExpr(body: *const Node) ?*const Node {
/// True when a type-fn's return expression mints a type at comptime and must be
/// run through the interpreter rather than statically resolved. Two shapes:
/// - a call to the `define` construction primitive — `return define(declare(),
/// info)`, the one-shot constructor form; or
/// - a call to the metatype `define` constructor — `return define(declare(),
/// info)`, the one-shot constructor form (now an sx fn over `register_type`,
/// caught here as a fast-path before the `fn_ast_map` lookup below); or
/// - a call to a NON-generic, bodied, `Type`-returning sx fn (a constructor
/// helper that itself ends in `define`).
/// helper that itself ends in `define` / `register_type`).
/// Excludes generic / static type constructors (`Vector(N,T)`, `Make($T)`,
/// `return [K]T`, `return T`), which the static `resolveTypeWithBindings` path
/// handles.
@@ -1828,7 +1829,7 @@ pub fn returnExprMintsType(self: *Lowering, ret: *const Node) bool {
const callee = ret.data.call.callee;
if (callee.data != .identifier) return false;
const name = callee.data.identifier.name;
// The construction terminator builtin — a constructor's final act.
// The construction terminator — a constructor's final act.
if (std.mem.eql(u8, name, "define")) return true;
// A bodied, non-generic, Type-returning sx helper.
const fd = self.program_index.fn_ast_map.get(name) orelse return false;