From ccba70437862c8f2c383180a529bf3c8e7615512 Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 19 Jun 2026 21:14:05 +0300 Subject: [PATCH] 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). --- src/ir/comptime_vm.zig | 130 ++------------------------------------- src/ir/inst.zig | 12 +--- src/ir/lower/generic.zig | 9 +-- 3 files changed, 13 insertions(+), 138 deletions(-) diff --git a/src/ir/comptime_vm.zig b/src/ir/comptime_vm.zig index b2325be0..fca1b4be 100644 --- a/src/ir/comptime_vm.zig +++ b/src/ir/comptime_vm.zig @@ -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`/ diff --git a/src/ir/inst.zig b/src/ir/inst.zig index 62890569..46e93e83 100644 --- a/src/ir/inst.zig +++ b/src/ir/inst.zig @@ -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 diff --git a/src/ir/lower/generic.zig b/src/ir/lower/generic.zig index c59832a1..6476f13a 100644 --- a/src/ir/lower/generic.zig +++ b/src/ir/lower/generic.zig @@ -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;