comptime VM: Phase 3 — find_type + type_field_count reflection readers

First read-only compiler-API reflection readers, bound the same way as the
intern/text_of seed (compiler_lib.bound_fns + Vm.callCompilerFn, native on flat
memory, no marshaling). A type handle is a plain u32 TypeId (like StringId), so
both stay clean scalar host-calls:

  find_type(name: StringId) -> TypeId          (TypeTable.findByName; unresolved/0 if absent)
  type_field_count(t: TypeId) -> i64           (new TypeTable.memberCount; loud-bail, no silent 0)

memberCount is the single source both the legacy handler and the VM read, so the
two paths can't drift. find_type returns a non-optional TypeId using the
unresolved(0) sentinel for not-found rather than ?Type — a Type value is
.any-typed (which the flat-memory VM does not represent) and an optional can't
cross the legacy<->VM eval boundary; unresolved is the project-blessed "no type"
marker.

Example 0628 chains intern -> find_type -> type_field_count (+ a not-found
lookup), folded at #run, VM-HANDLED natively. VM unit test added.

Parity 689/689 (gate OFF and -Dcomptime-flat).
This commit is contained in:
agra
2026-06-18 09:25:26 +03:00
parent 0367d96d9b
commit a9302a8b50
10 changed files with 247 additions and 15 deletions

View File

@@ -1011,6 +1011,30 @@ pub const Vm = struct {
if (text.len > 0) @memcpy(try self.machine.bytes(data, text.len), text);
return try self.makeSlice(table, data, text.len);
}
// ── read-only reflection readers (Phase 3) ──────────────────────────
// Type handle = a u32 `TypeId` (a word), exactly like `StringId` — so
// these mirror intern/text_of's shape: word in, word out, no marshaling.
if (std.mem.eql(u8, name, "find_type")) {
if (args.len != 1) return self.failMsg("comptime find_type: expected one StringId arg");
const raw = frame.get(args[0].index());
if (raw > std.math.maxInt(u32)) return self.failMsg("comptime find_type: StringId out of range");
const sid: types.StringId = @enumFromInt(@as(u32, @intCast(raw)));
// Not found → the dedicated `unresolved` (0) sentinel, never a real
// type id (mirrors `compiler_lib.handleFindType`).
const tid = table.findByName(sid) orelse TypeId.unresolved;
return @as(Reg, tid.index());
}
if (std.mem.eql(u8, name, "type_field_count")) {
if (args.len != 1) return self.failMsg("comptime type_field_count: expected one TypeId arg");
const raw = frame.get(args[0].index());
if (raw > std.math.maxInt(u32)) return self.failMsg("comptime type_field_count: TypeId out of range");
const tid: TypeId = @enumFromInt(@as(u32, @intCast(raw)));
// Same `TypeTable.memberCount` the legacy handler reads → no drift; a
// type with no member count bails loudly (no silent 0).
const count = table.memberCount(tid) orelse
return self.failMsg("comptime type_field_count: type has no field/variant count");
return @as(Reg, @bitCast(count));
}
return null; // not a known compiler function → caller bails to legacy
}