comptime VM: Phase 3 — field-level reflection readers

Three more read-only compiler-API readers on the TypeId-handle shape, each backed
by a new TypeTable query that both the legacy handler and the VM call (no drift):

  type_nominal_name(t: TypeId) -> StringId     (nominalName; loud-bail for unnamed types)
  type_field_name(t: TypeId, idx: i64) -> StringId   (memberName)
  type_field_type(t: TypeId, idx: i64) -> TypeId     (memberType)

All loud-bail on out-of-range idx / no-member — no silent default. First multi-arg
compiler fns (callCompilerFn now reads arg 1 = idx); added Vm.argHandle/argTypeId
range-checked arg readers and moved find_type/type_field_count onto them. Names use
the type_* family to avoid colliding with the std metatype builtins (field_name /
type_name in core.sx); the new TypeTable.nominalName is distinct from the existing
typeName(id) display-string renderer.

Example 0629 reflects Pair { lo: Point; hi: Point } — each field name + the nominal
name of a field's type, #run-folded, VM-HANDLED natively. VM unit test added.

Parity 690/690 (gate OFF and -Dcomptime-flat).
This commit is contained in:
agra
2026-06-18 09:34:36 +03:00
parent a9302a8b50
commit d23e208430
10 changed files with 316 additions and 33 deletions

View File

@@ -833,6 +833,80 @@ test "comptime_vm exec: compiler-fn find_type + type_field_count (native reflect
);
}
test "comptime_vm exec: compiler-fn type_field_name/type/nominal_name (native reflection)" {
const alloc = std.testing.allocator;
var module = Module.init(alloc);
defer module.deinit();
// Point { x, y } and Pair { lo: Point; hi: Point } in the type table.
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 },
};
const point = module.types.intern(.{ .@"struct" = .{ .name = point_name, .fields = &pfields } });
const lo_name = module.types.internString("lo");
const hi_name = module.types.internString("hi");
const rfields = [_]types.TypeInfo.StructInfo.Field{
.{ .name = lo_name, .ty = point },
.{ .name = hi_name, .ty = point },
};
const pair = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Pair"), .fields = &rfields } });
// extern type_field_name(t: u32, idx: i64) -> u32 [compiler] (FuncId 0)
const fnp = [_]Function.Param{ param(.u32), param(.i64) };
var fnb = Fb.init(alloc, &fnp, .u32);
fnb.func.is_extern = true;
fnb.func.compiler_welded = true;
fnb.func.name = module.types.internString("type_field_name");
const fname_id = module.addFunction(fnb.func);
// extern type_field_type(t: u32, idx: i64) -> u32 [compiler] (FuncId 1)
const ftp = [_]Function.Param{ param(.u32), param(.i64) };
var ftb = Fb.init(alloc, &ftp, .u32);
ftb.func.is_extern = true;
ftb.func.compiler_welded = true;
ftb.func.name = module.types.internString("type_field_type");
const ftype_id = module.addFunction(ftb.func);
// extern type_nominal_name(t: u32) -> u32 [compiler] (FuncId 2)
const nnp = [_]Function.Param{param(.u32)};
var nnb = Fb.init(alloc, &nnp, .u32);
nnb.func.is_extern = true;
nnb.func.compiler_welded = true;
nnb.func.name = module.types.internString("type_nominal_name");
const nname_id = module.addFunction(nnb.func);
// main(): return type_field_name(Pair, 1) → StringId("hi")
var fb = Fb.init(alloc, &.{}, .u32);
const b0 = fb.block(&.{});
const t = fb.add(b0, inst(.{ .const_int = @intFromEnum(pair) }, .u32));
const one = fb.add(b0, inst(.{ .const_int = 1 }, .i64));
const nargs = [_]Ref{ ref(t), ref(one) };
const fn1 = fb.add(b0, inst(.{ .call = .{ .callee = fname_id, .args = &nargs } }, .u32));
_ = fb.add(b0, inst(.{ .ret = .{ .operand = ref(fn1) } }, .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, @intFromEnum(hi_name)), toI64(try v.run(module.getFunction(main_id), &.{})));
// type_nominal_name(type_field_type(Pair, 0)) → StringId("Point")
var fb2 = Fb.init(alloc, &.{}, .u32);
const c0 = fb2.block(&.{});
const t2 = fb2.add(c0, inst(.{ .const_int = @intFromEnum(pair) }, .u32));
const zero = fb2.add(c0, inst(.{ .const_int = 0 }, .i64));
const targs = [_]Ref{ ref(t2), ref(zero) };
const fty = fb2.add(c0, inst(.{ .call = .{ .callee = ftype_id, .args = &targs } }, .u32));
const nnargs = [_]Ref{ref(fty)};
const nn = fb2.add(c0, inst(.{ .call = .{ .callee = nname_id, .args = &nnargs } }, .u32));
_ = fb2.add(c0, inst(.{ .ret = .{ .operand = ref(nn) } }, .void));
const main2 = module.addFunction(fb2.func);
try std.testing.expectEqual(@as(i64, @intFromEnum(point_name)), toI64(try v.run(module.getFunction(main2), &.{})));
}
test "comptime_vm exec: func_ref + call_indirect dispatch" {
const alloc = std.testing.allocator;
var module = Module.init(alloc);