comptime VM arc: abi(.compiler) ABI, out as sx fn, VM-native diagnostics, BuildConfig threaded
Lands the full VM/compiler-API arc on branch reify (701/0 both gates): - abi(.compiler) ABI replaces abi(.zig) extern compiler + the fake #library "compiler"; bodiless decl = compiler-API surface, bodied = user compiler-domain fn (lowered for VM eval, emit-skipped). - out is a plain sx fn (libc write) — the out builtin deleted; the VM handles it via host-FFI. trace_resolve + interp_print_frames ported. - 4B VM-native diagnostics: 1179/1180 render proper comptime type construction failed: under strict. - S5a: build_options/set_post_link_callback on abi(.compiler) with BuildConfig threaded into the VM (green intermediate). - 0522 fixed (describe(args: []Type)); regression 0638. Strict deletion-gate down to 4 compiler_call bails (1609/1614/1615/1616) + 1654 (legitimate unresolvable-symbol diagnostic).
This commit is contained in:
@@ -3,8 +3,11 @@
|
||||
//! The comptime evaluator is being rebuilt around a flat, byte-addressable memory
|
||||
//! so comptime values are NATIVE BYTES (like runtime), instead of the tagged
|
||||
//! `Value` union the legacy interpreter (`interp.zig`) uses. This module is the
|
||||
//! machine substrate: a linear byte memory with a bump/stack allocator, plus a
|
||||
//! per-call `Frame` holding a register file.
|
||||
//! machine substrate: byte-addressable memory backed by an ARENA of stable host
|
||||
//! allocations (each `allocBytes` never moves; freed wholesale on `deinit`), plus
|
||||
//! a per-call `Frame` holding a register file. `Addr` is the allocation's real
|
||||
//! host pointer, so a flat-memory pointer and an FFI-returned host pointer are the
|
||||
//! same kind of value.
|
||||
//!
|
||||
//! Value model (grows over later sub-steps): a register (`Reg`) is a raw 64-bit
|
||||
//! word that is EITHER an immediate scalar (its bits) OR an `Addr` into flat
|
||||
@@ -17,9 +20,10 @@
|
||||
//! bytes. Layout (sizes/offsets/pointer width) is supplied by the type table when
|
||||
//! the executor lays a value out, so cross-compilation stays correct.
|
||||
//!
|
||||
//! Sub-step 1 (this file): `Machine` (memory + bump/stack alloc + scalar word
|
||||
//! read/write + byte views) and `Frame` (register file + stack reclamation). No
|
||||
//! op execution yet — the executor + op handlers arrive in the next sub-step. The
|
||||
//! `Machine` (arena-backed memory + scalar word read/write + byte views) holds the
|
||||
//! comptime stack + heap; `Frame` is the per-call register file. A `Frame` does NOT
|
||||
//! reclaim the machine's memory on exit — a callee can return an aggregate whose
|
||||
//! register holds an `Addr` into flat memory, and reclaiming would dangle it. The
|
||||
//! legacy interpreter remains the live evaluator until the VM reaches parity.
|
||||
|
||||
const std = @import("std");
|
||||
@@ -28,6 +32,7 @@ const types = @import("types.zig");
|
||||
const mod_mod = @import("module.zig");
|
||||
const interp_mod = @import("interp.zig");
|
||||
const host_ffi = @import("host_ffi.zig");
|
||||
const errors_mod = @import("../errors.zig");
|
||||
const Value = interp_mod.Value;
|
||||
const Inst = inst_mod.Inst;
|
||||
const Ref = inst_mod.Ref;
|
||||
@@ -186,7 +191,7 @@ pub var last_bail_reason: ?[]const u8 = null;
|
||||
/// hardened to return `error.OutOfBounds` (not a debug panic) on a null/out-of-
|
||||
/// range/oversized access, so a malformed run bails to `null` (→ legacy fallback)
|
||||
/// rather than crashing the compiler. On a bail, `last_bail_reason` names the cause.
|
||||
pub fn tryEval(gpa: std.mem.Allocator, module: *const Module, func_id: inst_mod.FuncId) ?Value {
|
||||
pub fn tryEval(gpa: std.mem.Allocator, module: *const Module, func_id: inst_mod.FuncId, build_config: ?*interp_mod.BuildConfig, source_map: ?*const std.StringHashMap([:0]const u8)) ?Value {
|
||||
last_bail_reason = null;
|
||||
const func = module.getFunction(func_id);
|
||||
if (func.is_extern or func.blocks.items.len == 0) {
|
||||
@@ -197,6 +202,8 @@ pub fn tryEval(gpa: std.mem.Allocator, module: *const Module, func_id: inst_mod.
|
||||
defer vm.deinit();
|
||||
vm.table = &module.types;
|
||||
vm.module = module;
|
||||
vm.build_config = build_config;
|
||||
vm.source_map = source_map;
|
||||
|
||||
// `runEntry` materializes the implicit `*Context` (a comptime const-init /
|
||||
// `#run` wrapper is nullary in user args, so the implicit ctx is its sole
|
||||
@@ -276,6 +283,16 @@ pub const Vm = struct {
|
||||
/// The module — resolves a `call`'s callee `FuncId` to its `Function`. Optional
|
||||
/// so leaf functions (no calls) need none; a `call` bails loudly if it is absent.
|
||||
module: ?*const Module = null,
|
||||
/// The mutable build configuration (`BuildOptions` accumulator) — the SAME
|
||||
/// `BuildConfig` `EmitLLVM` owns and `main.zig` reads post-link. Threaded in at
|
||||
/// the `#run`/const-init eval sites so an `abi(.compiler)` `BuildOptions` function
|
||||
/// (e.g. `set_post_link_callback`) records into it directly. Null at lowering-time
|
||||
/// type-fn evals (no build config exists yet); such a function bails loudly.
|
||||
build_config: ?*interp_mod.BuildConfig = null,
|
||||
/// File → source text (the diagnostics' `import_sources`), threaded from the host
|
||||
/// so `trace_resolve` can turn a packed `(func_id, span.start)` comptime frame into
|
||||
/// `file:line:col` + the source line. Null → line/col degrade to 1 / "".
|
||||
source_map: ?*const std.StringHashMap([:0]const u8) = null,
|
||||
/// Current call-recursion depth, guarded against host stack overflow on deep /
|
||||
/// infinite comptime recursion (mirrors the legacy interp's `call_depth`).
|
||||
depth: u32 = 0,
|
||||
@@ -819,6 +836,76 @@ pub const Vm = struct {
|
||||
const fid: u64 = if (self.call_stack.items.len > 0) self.call_stack.items[self.call_stack.items.len - 1].index() else 0;
|
||||
return .{ .value = (fid << 32) | @as(u64, ins.span.start) };
|
||||
},
|
||||
// Dump the comptime call-frame chain (`trace.print_interpreter_frames`) —
|
||||
// the VM-native mirror of the legacy `printInterpFrames`. Walks the active
|
||||
// `call_stack` (skipping the last frame, the `print_interpreter_frames`
|
||||
// fn itself, like the legacy) and writes ` at <name>` lines straight to
|
||||
// fd 1 (consistent with `out`'s now-direct libc `write`).
|
||||
.interp_print_frames => {
|
||||
const module = self.module orelse return self.failMsg("comptime interp_print_frames: no module");
|
||||
const n = self.call_stack.items.len;
|
||||
if (n <= 1) return .{ .value = null_addr };
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
defer buf.deinit(self.gpa);
|
||||
buf.appendSlice(self.gpa, "comptime call frames (most recent call last):\n") catch return self.failMsg("comptime interp_print_frames: out of memory");
|
||||
var i: usize = 0;
|
||||
while (i < n - 1) : (i += 1) {
|
||||
const fname = module.types.getString(module.getFunction(self.call_stack.items[i]).name);
|
||||
buf.appendSlice(self.gpa, " at ") catch return self.failMsg("comptime interp_print_frames: out of memory");
|
||||
buf.appendSlice(self.gpa, fname) catch return self.failMsg("comptime interp_print_frames: out of memory");
|
||||
buf.append(self.gpa, '\n') catch return self.failMsg("comptime interp_print_frames: out of memory");
|
||||
}
|
||||
_ = std.c.write(1, buf.items.ptr, buf.items.len);
|
||||
return .{ .value = null_addr };
|
||||
},
|
||||
// Unpack a comptime frame `(func_id << 32 | span.start)` and build a
|
||||
// `Frame { file, line, col, func, line_text }` aggregate in flat memory —
|
||||
// the VM-native mirror of the legacy interp's `.trace_resolve`. `ins.ty`
|
||||
// is the `Frame` struct, so each field's type/offset comes from the table.
|
||||
.trace_resolve => |u| {
|
||||
const table = try self.requireTable();
|
||||
const module = self.module orelse return self.failMsg("comptime trace_resolve: no module");
|
||||
const raw = frame.get(u.operand.index());
|
||||
const fid: u32 = @intCast(raw >> 32);
|
||||
const offset: u32 = @truncate(raw);
|
||||
if (fid >= module.functions.items.len) return self.failMsg("comptime trace_resolve: func id out of range");
|
||||
const func = module.getFunction(inst_mod.FuncId.fromIndex(fid));
|
||||
const func_name = module.types.getString(func.name);
|
||||
const file_full = func.source_file orelse "";
|
||||
const file = std.fs.path.basename(file_full);
|
||||
var line: i64 = 1;
|
||||
var col: i64 = 1;
|
||||
var line_text: []const u8 = "";
|
||||
if (self.source_map) |sm| {
|
||||
if (sm.get(file_full)) |src| {
|
||||
const loc = errors_mod.SourceLoc.compute(src, offset);
|
||||
line = @intCast(loc.line);
|
||||
col = @intCast(loc.col);
|
||||
line_text = errors_mod.lineAt(src, offset);
|
||||
}
|
||||
}
|
||||
const fty = ins.ty;
|
||||
if (fty.isBuiltin() or table.get(fty) != .@"struct")
|
||||
return self.failMsg("comptime trace_resolve: result type is not a Frame struct");
|
||||
const sfields = table.get(fty).@"struct".fields;
|
||||
if (sfields.len != 5) return self.failMsg("comptime trace_resolve: Frame struct is not 5 fields");
|
||||
const addr = self.machine.allocBytes(table.typeSizeBytes(fty), table.typeAlignBytes(fty));
|
||||
// { file, line, col, func, line_text } — positional, matching the legacy build.
|
||||
try self.writeField(table, addr + fieldOffset(table, fty, 0), sfields[0].ty, try self.makeStringValue(table, file));
|
||||
try self.writeField(table, addr + fieldOffset(table, fty, 1), sfields[1].ty, @bitCast(line));
|
||||
try self.writeField(table, addr + fieldOffset(table, fty, 2), sfields[2].ty, @bitCast(col));
|
||||
try self.writeField(table, addr + fieldOffset(table, fty, 3), sfields[3].ty, try self.makeStringValue(table, func_name));
|
||||
try self.writeField(table, addr + fieldOffset(table, fty, 4), sfields[4].ty, try self.makeStringValue(table, line_text));
|
||||
return .{ .value = addr };
|
||||
},
|
||||
// `error_tag_name(e)` — the runtime tag id (a word) → its name string via
|
||||
// the always-linked tag-name table. Pure: builds a `{ptr,len}` string in
|
||||
// flat memory. Mirrors the legacy interp's `error_tag_name_get`.
|
||||
.error_tag_name_get => |u| {
|
||||
const table = try self.requireTable();
|
||||
const id: u32 = @intCast(frame.get(u.operand.index()));
|
||||
return .{ .value = try self.makeStringValue(table, table.getTagName(id)) };
|
||||
},
|
||||
|
||||
// ── Calls ───────────────────────────────────────────
|
||||
// Direct call: resolve the static callee `FuncId` and dispatch.
|
||||
@@ -840,6 +927,21 @@ pub const Vm = struct {
|
||||
// `comptime_func` run on this same VM, or a scalar static value),
|
||||
// memoized. Mirrors the legacy interp's `getGlobal`.
|
||||
.global_get => |gid| return .{ .value = try self.evalGlobal(gid) },
|
||||
// `&global` — only `&__sx_default_context` is materialised at comptime
|
||||
// (its address sees runtime use via the implicit-ctx plumbing). Return
|
||||
// the context's flat-memory address — an aggregate value IS its address,
|
||||
// so a later `load`/field read sees the materialised Context. Mirrors the
|
||||
// legacy interp's `global_addr` (the sole supported global); any other
|
||||
// global bails to legacy fallback.
|
||||
.global_addr => |gid| {
|
||||
const module = self.module orelse return self.failMsg("comptime VM: global_addr needs a module");
|
||||
if (gid.index() < module.globals.items.len and
|
||||
std.mem.eql(u8, module.types.getString(module.globals.items[gid.index()].name), "__sx_default_context"))
|
||||
{
|
||||
return .{ .value = try self.materializeDefaultContext(module) };
|
||||
}
|
||||
return self.failMsg("comptime global_addr: only `&__sx_default_context` is materialised at comptime");
|
||||
},
|
||||
// A function value is its encoded func-ref word (see `funcRefWord`).
|
||||
.func_ref => |fid| return .{ .value = funcRefWord(fid) },
|
||||
|
||||
@@ -1005,6 +1107,14 @@ pub const Vm = struct {
|
||||
return error.Unsupported;
|
||||
}
|
||||
|
||||
/// Like `failMsg` but for a runtime-formatted reason (e.g. naming the offending
|
||||
/// variant). Allocated in `gpa` so it survives to the host's diagnostic render;
|
||||
/// the build fails on this path, so the small leak is moot.
|
||||
fn failFmt(self: *Vm, comptime fmt: []const u8, args: anytype) error{Unsupported} {
|
||||
self.detail = std.fmt.allocPrint(self.gpa, fmt, args) catch "comptime VM: out of memory formatting diagnostic";
|
||||
return error.Unsupported;
|
||||
}
|
||||
|
||||
fn badRef(self: *Vm) error{Unsupported} {
|
||||
self.detail = "comptime VM: malformed IR — operand ref out of range (unresolved name?)";
|
||||
return error.Unsupported;
|
||||
@@ -1322,6 +1432,26 @@ pub const Vm = struct {
|
||||
if (std.mem.eql(u8, name, "register_type")) {
|
||||
return self.registerTypeVm(args, frame, ref_types);
|
||||
}
|
||||
// ── BuildOptions (migrated off `#compiler` onto `abi(.compiler)`) ───────
|
||||
// `build_options()` hands back an opaque, zero-field `BuildOptions` handle;
|
||||
// the real state lives on the threaded `BuildConfig`. Return the null
|
||||
// sentinel word (the handle is never dereferenced — every operation takes it
|
||||
// as an ignored `self`). Mirrors the legacy `hookBuildOptions` (`.void_val`).
|
||||
if (std.mem.eql(u8, name, "build_options")) {
|
||||
return @as(Reg, null_addr);
|
||||
}
|
||||
// `set_post_link_callback(self, cb)` — record the callback `FuncId` on the
|
||||
// build config so `main.zig` re-enters the evaluator post-link. The cb arg is
|
||||
// a `func_ref` word. Mirrors the legacy `hookSetPostLinkCallback`.
|
||||
if (std.mem.eql(u8, name, "set_post_link_callback")) {
|
||||
if (args.len != 2) return self.failMsg("comptime set_post_link_callback: expected (self, cb)");
|
||||
const bc = self.build_config orelse
|
||||
return self.failMsg("comptime set_post_link_callback: no build config threaded into the VM");
|
||||
const fid = funcRefToId(frame.get(args[1].index())) orelse
|
||||
return self.failMsg("comptime set_post_link_callback: cb arg is not a function value");
|
||||
bc.post_link_callback_fn = fid;
|
||||
return @as(Reg, null_addr);
|
||||
}
|
||||
return null; // not a known compiler function → caller bails to legacy
|
||||
}
|
||||
|
||||
@@ -1428,7 +1558,7 @@ pub const Vm = struct {
|
||||
/// `Type` elements with no name) from flat 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");
|
||||
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);
|
||||
@@ -1436,10 +1566,38 @@ pub const Vm = struct {
|
||||
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");
|
||||
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
|
||||
/// tag (the boxed value's runtime type), unless tag == `type_value` — a boxed
|
||||
/// Type (the `type_of(x)` shape) whose real id sits in the value slot. The
|
||||
/// VM-native mirror of the legacy `Value.reflectTypeId`.
|
||||
fn reflectArgTypeId(self: *Vm, aty: TypeId, w: Reg) Error!TypeId {
|
||||
// A `TypeId` index is a u32; a word that doesn't fit is a garbage/mis-read
|
||||
// value (e.g. a wrong slice stride yielding an `Any` element at the wrong
|
||||
// offset — see 0522). Bail loudly instead of letting `@intCast` abort: the
|
||||
// VM must never crash.
|
||||
if (aty == .type_value) return TypeId.fromIndex(try self.typeIdxOf(w));
|
||||
if (aty == .any) {
|
||||
const tag = try self.machine.readWord(w, 8);
|
||||
if (tag == @as(u64, TypeId.type_value.index()))
|
||||
return TypeId.fromIndex(try self.typeIdxOf(try self.machine.readWord(w + 8, 8)));
|
||||
return TypeId.fromIndex(try self.typeIdxOf(tag));
|
||||
}
|
||||
return self.failMsg("comptime reflection builtin: arg is not a Type value or an Any box");
|
||||
}
|
||||
|
||||
/// Narrow a 64-bit word to a `u32` `TypeId` index, bailing (never crashing) when
|
||||
/// it doesn't fit — the tripwire for a mis-read reflection arg.
|
||||
fn typeIdxOf(self: *Vm, w: u64) Error!u32 {
|
||||
return std.math.cast(u32, w) orelse
|
||||
self.failMsg("comptime reflection builtin: type word out of TypeId range (mis-read arg?)");
|
||||
}
|
||||
|
||||
/// Service a comptime metatype `#builtin` (`meta.sx`'s `declare`/`define`)
|
||||
/// natively on flat memory, the VM-native mirror of the legacy
|
||||
/// `interp.execBuiltinInner` arms. Returns the result word, or `null` for a
|
||||
@@ -1458,27 +1616,27 @@ pub const Vm = struct {
|
||||
// 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)");
|
||||
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");
|
||||
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)");
|
||||
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");
|
||||
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 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);
|
||||
},
|
||||
// type_name(x) → the type's name as a string. The arg is a Type value
|
||||
@@ -1488,20 +1646,19 @@ pub const Vm = struct {
|
||||
.type_name => {
|
||||
const table = try self.requireTable();
|
||||
if (bi.args.len < 1) return self.failMsg("comptime type_name: missing argument");
|
||||
const aty = try self.refTy(ref_types, bi.args[0]);
|
||||
const w = frame.get(bi.args[0].index());
|
||||
const tid: TypeId = blk: {
|
||||
if (aty == .type_value) break :blk TypeId.fromIndex(@intCast(w));
|
||||
if (aty == .any) {
|
||||
const tag = try self.machine.readWord(w, 8);
|
||||
if (tag == @as(u64, TypeId.type_value.index()))
|
||||
break :blk TypeId.fromIndex(@intCast(try self.machine.readWord(w + 8, 8)));
|
||||
break :blk TypeId.fromIndex(@intCast(tag));
|
||||
}
|
||||
return self.failMsg("comptime type_name: arg is not a Type value or an Any box");
|
||||
};
|
||||
const tid = try self.reflectArgTypeId(try self.refTy(ref_types, bi.args[0]), frame.get(bi.args[0].index()));
|
||||
return try self.makeStringValue(table, table.typeName(tid));
|
||||
},
|
||||
// type_is_unsigned(x) → is x's type an unsigned int? Resolves the TypeId
|
||||
// the same way as type_name (a `.type_value` word, or an Any box whose tag
|
||||
// IS the boxed value's type), then queries `isUnsignedInt`. Mirrors the
|
||||
// legacy `type_is_unsigned` builtin (`reflectTypeId` + `isUnsignedInt`).
|
||||
.type_is_unsigned => {
|
||||
const table = try self.requireTable();
|
||||
if (bi.args.len < 1) return self.failMsg("comptime type_is_unsigned: missing argument");
|
||||
const tid = try self.reflectArgTypeId(try self.refTy(ref_types, bi.args[0]), frame.get(bi.args[0].index()));
|
||||
return @as(Reg, @intFromBool(table.isUnsignedInt(tid)));
|
||||
},
|
||||
// type_info($T) → reflect a type INTO a TypeInfo VALUE (the inverse of
|
||||
// define's decode). The arg folded to a `const_type` (a `.type_value`
|
||||
// word = the source TypeId); build the value in flat memory.
|
||||
@@ -1528,8 +1685,8 @@ pub const Vm = struct {
|
||||
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");
|
||||
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).
|
||||
@@ -1541,7 +1698,7 @@ pub const Vm = struct {
|
||||
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");
|
||||
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).
|
||||
@@ -1551,16 +1708,16 @@ pub const Vm = struct {
|
||||
break;
|
||||
};
|
||||
if (all_void) {
|
||||
const names = self.gpa.alloc(types.StringId, members.items.len) catch return self.failMsg("comptime define: out of memory");
|
||||
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.failMsg("comptime define: duplicate variant name");
|
||||
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");
|
||||
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.failMsg("comptime define: duplicate variant name");
|
||||
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.
|
||||
@@ -1571,10 +1728,10 @@ pub const Vm = struct {
|
||||
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");
|
||||
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.failMsg("comptime define: duplicate field name");
|
||||
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`.
|
||||
@@ -1584,12 +1741,12 @@ pub const Vm = struct {
|
||||
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");
|
||||
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"),
|
||||
else => return self.failMsg("comptime define(): unknown TypeInfo variant"),
|
||||
}
|
||||
return @as(Reg, handle.index());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user