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

@@ -769,6 +769,70 @@ test "comptime_vm exec: compiler-fn intern/text_of round-trip (native, no legacy
try std.testing.expectEqual(@as(i64, 5), toI64(try v.run(module.getFunction(main_id), &.{})));
}
test "comptime_vm exec: compiler-fn find_type + type_field_count (native reflection)" {
const alloc = std.testing.allocator;
var module = Module.init(alloc);
defer module.deinit();
// A struct `Point { x, y, z }` registered in the type table (the thing the
// reflection readers look up by name and count the fields of).
const point_name = module.types.internString("Point");
const pfields = [_]types.TypeInfo.StructInfo.Field{
.{ .name = module.types.internString("x"), .ty = .i64 },
.{ .name = module.types.internString("y"), .ty = .i64 },
.{ .name = module.types.internString("z"), .ty = .i64 },
};
_ = module.types.intern(.{ .@"struct" = .{ .name = point_name, .fields = &pfields } });
// extern find_type(name: u32) -> u32 [compiler] (FuncId 0, no body)
const fp = [_]Function.Param{.{ .name = module.types.internString("name"), .ty = .u32 }};
var ffb = Fb.init(alloc, &fp, .u32);
ffb.func.is_extern = true;
ffb.func.compiler_welded = true;
ffb.func.name = module.types.internString("find_type");
const find_id = module.addFunction(ffb.func);
// extern type_field_count(t: u32) -> i64 [compiler] (FuncId 1, no body)
const cp = [_]Function.Param{.{ .name = module.types.internString("t"), .ty = .u32 }};
var cfb = Fb.init(alloc, &cp, .i64);
cfb.func.is_extern = true;
cfb.func.compiler_welded = true;
cfb.func.name = module.types.internString("type_field_count");
const count_id = module.addFunction(cfb.func);
// main(): return type_field_count(find_type(intern_id_of("Point"))) → 3
// ("Point" is already interned above; pass its StringId directly.)
var fb = Fb.init(alloc, &.{}, .i64);
const b0 = fb.block(&.{});
const nm = fb.add(b0, inst(.{ .const_int = @intFromEnum(point_name) }, .u32));
const nargs = [_]Ref{ref(nm)};
const tid = fb.add(b0, inst(.{ .call = .{ .callee = find_id, .args = &nargs } }, .u32));
const targs = [_]Ref{ref(tid)};
const cnt = fb.add(b0, inst(.{ .call = .{ .callee = count_id, .args = &targs } }, .i64));
_ = fb.add(b0, inst(.{ .ret = .{ .operand = ref(cnt) } }, .void));
const main_id = module.addFunction(fb.func);
var v = vm.Vm.init(alloc);
v.table = &module.types;
v.module = &module;
defer v.deinit();
try std.testing.expectEqual(@as(i64, 3), toI64(try v.run(module.getFunction(main_id), &.{})));
// A name with no matching type → the `unresolved` (0) sentinel.
const missing = module.types.internString("Nope");
var mfb = Fb.init(alloc, &.{}, .u32);
const mb = mfb.block(&.{});
const mnm = mfb.add(mb, inst(.{ .const_int = @intFromEnum(missing) }, .u32));
const margs = [_]Ref{ref(mnm)};
const mres = mfb.add(mb, inst(.{ .call = .{ .callee = find_id, .args = &margs } }, .u32));
_ = mfb.add(mb, inst(.{ .ret = .{ .operand = ref(mres) } }, .void));
const missing_main = module.addFunction(mfb.func);
try std.testing.expectEqual(
@as(i64, @intFromEnum(TypeId.unresolved)),
toI64(try v.run(module.getFunction(missing_main), &.{})),
);
}
test "comptime_vm exec: func_ref + call_indirect dispatch" {
const alloc = std.testing.allocator;
var module = Module.init(alloc);