comptime VM: Phase 3 — type_kind + type_field_value readers (read side complete)
The last two read-only readers the metatype's type_info(T) needs, each backed by
a TypeTable query both the legacy handler and the VM call (no drift):
type_kind(t: TypeId) -> i64 (kindCode; stable discriminant, total — never bails)
type_field_value(t: TypeId, idx) -> i64 (memberValue; enum explicit value or ordinal)
kindCode codes (compiler-owned, stable): 0 other / 1 struct / 2 enum /
3 tagged_union / 4 tuple / 5 union / 6 array / 7 vector / 8 error_set.
With these, the READ side is complete: find_type + type_kind + type_field_count +
type_field_{name,type} + type_nominal_name + type_field_value cover everything
reflectTypeInfo reads — a comptime sx fn can fully reflect a struct/enum/tuple
into data with no #builtin.
Example 0630 reflects Color / WindowFlags(flags) / Point. VM unit test added.
Revised forward direction: the write side will be ONE register_type(info) fn that
branches on the kind in the compiler (subsuming define's per-kind dispatch), not a
per-kind register_struct.
Parity 691/691 (gate OFF and -Dcomptime-flat).
This commit is contained in:
@@ -52,6 +52,8 @@ pub const bound_fns = [_]BoundFn{
|
||||
.{ .sx_name = "type_nominal_name", .handler = handleTypeNominalName },
|
||||
.{ .sx_name = "type_field_name", .handler = handleTypeFieldName },
|
||||
.{ .sx_name = "type_field_type", .handler = handleTypeFieldType },
|
||||
.{ .sx_name = "type_kind", .handler = handleTypeKind },
|
||||
.{ .sx_name = "type_field_value", .handler = handleTypeFieldValue },
|
||||
};
|
||||
|
||||
/// Look up a compiler function by its sx name. Returns null when the name is not
|
||||
@@ -151,3 +153,20 @@ fn handleTypeFieldType(interp: *Interpreter, args: []const Value) InterpError!Va
|
||||
const mty = interp.module.types.memberType(tid, args[1].int) orelse return error.TypeError;
|
||||
return Value{ .int = mty.index() };
|
||||
}
|
||||
|
||||
/// `type_kind(t: TypeId) -> i64` — the stable kind discriminant (see
|
||||
/// `TypeTable.kindCode`). Total: an unnamed/non-aggregate type reads `other` (0).
|
||||
fn handleTypeKind(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1) return error.TypeError;
|
||||
const tid: types.TypeId = @enumFromInt(try handleArg(args, 0));
|
||||
return Value{ .int = interp.module.types.kindCode(tid) };
|
||||
}
|
||||
|
||||
/// `type_field_value(t: TypeId, idx: i64) -> i64` — enum variant `idx`'s integer
|
||||
/// value (explicit or ordinal). Loud error for a non-enum or out-of-range idx.
|
||||
fn handleTypeFieldValue(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 2 or args[1] != .int) return error.TypeError;
|
||||
const tid: types.TypeId = @enumFromInt(try handleArg(args, 0));
|
||||
const v = interp.module.types.memberValue(tid, args[1].int) orelse return error.TypeError;
|
||||
return Value{ .int = v };
|
||||
}
|
||||
|
||||
@@ -907,6 +907,70 @@ test "comptime_vm exec: compiler-fn type_field_name/type/nominal_name (native re
|
||||
try std.testing.expectEqual(@as(i64, @intFromEnum(point_name)), toI64(try v.run(module.getFunction(main2), &.{})));
|
||||
}
|
||||
|
||||
test "comptime_vm exec: compiler-fn type_kind + type_field_value (native reflection)" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = Module.init(alloc);
|
||||
defer module.deinit();
|
||||
|
||||
// A struct and an enum with explicit values.
|
||||
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 = module.types.internString("Point"), .fields = &pfields } });
|
||||
const variants = [_]types.StringId{ module.types.internString("ok"), module.types.internString("missing") };
|
||||
const evals = [_]i64{ 200, 404 };
|
||||
const status = module.types.intern(.{ .@"enum" = .{
|
||||
.name = module.types.internString("Status"),
|
||||
.variants = &variants,
|
||||
.explicit_values = &evals,
|
||||
} });
|
||||
|
||||
// extern type_kind(t: u32) -> i64 [compiler] (FuncId 0)
|
||||
const kp = [_]Function.Param{param(.u32)};
|
||||
var kb = Fb.init(alloc, &kp, .i64);
|
||||
kb.func.is_extern = true;
|
||||
kb.func.compiler_welded = true;
|
||||
kb.func.name = module.types.internString("type_kind");
|
||||
const kind_id = module.addFunction(kb.func);
|
||||
|
||||
// extern type_field_value(t: u32, idx: i64) -> i64 [compiler] (FuncId 1)
|
||||
const vp = [_]Function.Param{ param(.u32), param(.i64) };
|
||||
var vb = Fb.init(alloc, &vp, .i64);
|
||||
vb.func.is_extern = true;
|
||||
vb.func.compiler_welded = true;
|
||||
vb.func.name = module.types.internString("type_field_value");
|
||||
const val_id = module.addFunction(vb.func);
|
||||
|
||||
var v = vm.Vm.init(alloc);
|
||||
v.table = &module.types;
|
||||
v.module = &module;
|
||||
defer v.deinit();
|
||||
|
||||
// type_kind(Point) → 1 (struct); type_kind(Status) → 2 (enum).
|
||||
inline for (.{ .{ point, 1 }, .{ status, 2 } }) |case| {
|
||||
var fb = Fb.init(alloc, &.{}, .i64);
|
||||
const b0 = fb.block(&.{});
|
||||
const t = fb.add(b0, inst(.{ .const_int = @intFromEnum(case[0]) }, .u32));
|
||||
const kargs = [_]Ref{ref(t)};
|
||||
const k = fb.add(b0, inst(.{ .call = .{ .callee = kind_id, .args = &kargs } }, .i64));
|
||||
_ = fb.add(b0, inst(.{ .ret = .{ .operand = ref(k) } }, .void));
|
||||
const mid = module.addFunction(fb.func);
|
||||
try std.testing.expectEqual(@as(i64, case[1]), toI64(try v.run(module.getFunction(mid), &.{})));
|
||||
}
|
||||
|
||||
// type_field_value(Status, 1) → 404 (explicit value).
|
||||
var fb = Fb.init(alloc, &.{}, .i64);
|
||||
const b0 = fb.block(&.{});
|
||||
const t = fb.add(b0, inst(.{ .const_int = @intFromEnum(status) }, .u32));
|
||||
const one = fb.add(b0, inst(.{ .const_int = 1 }, .i64));
|
||||
const vargs = [_]Ref{ ref(t), ref(one) };
|
||||
const val = fb.add(b0, inst(.{ .call = .{ .callee = val_id, .args = &vargs } }, .i64));
|
||||
_ = fb.add(b0, inst(.{ .ret = .{ .operand = ref(val) } }, .void));
|
||||
const mid = module.addFunction(fb.func);
|
||||
try std.testing.expectEqual(@as(i64, 404), toI64(try v.run(module.getFunction(mid), &.{})));
|
||||
}
|
||||
|
||||
test "comptime_vm exec: func_ref + call_indirect dispatch" {
|
||||
const alloc = std.testing.allocator;
|
||||
var module = Module.init(alloc);
|
||||
|
||||
@@ -1067,6 +1067,19 @@ pub const Vm = struct {
|
||||
return self.failMsg("comptime type_field_type: out-of-range idx or member has no type");
|
||||
return @as(Reg, mty.index());
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_kind")) {
|
||||
if (args.len != 1) return self.failMsg("comptime type_kind: expected one TypeId arg");
|
||||
const tid = try self.argTypeId(args, frame, 0);
|
||||
return @as(Reg, @bitCast(table.kindCode(tid))); // total — never bails
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_field_value")) {
|
||||
if (args.len != 2) return self.failMsg("comptime type_field_value: expected (TypeId, idx)");
|
||||
const tid = try self.argTypeId(args, frame, 0);
|
||||
const idx: i64 = @bitCast(frame.get(args[1].index()));
|
||||
const v = table.memberValue(tid, idx) orelse
|
||||
return self.failMsg("comptime type_field_value: non-enum or out-of-range idx");
|
||||
return @as(Reg, @bitCast(v));
|
||||
}
|
||||
return null; // not a known compiler function → caller bails to legacy
|
||||
}
|
||||
|
||||
|
||||
@@ -554,6 +554,46 @@ pub const TypeTable = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Stable kind discriminant of a type, for comptime reflection branching.
|
||||
/// TOTAL (never fails): an unnamed / non-aggregate type or an out-of-range id
|
||||
/// is `other` (0). Codes are compiler-owned and stable — NOT tied to any sx
|
||||
/// enum's declaration order; the sx side maps them. Backs the `type_kind`
|
||||
/// reader. (A `tagged_union` is a payload-carrying enum; the sx metatype folds
|
||||
/// codes 2 and 3 onto its single `.enum` TypeInfo variant.)
|
||||
/// 0 other · 1 struct · 2 enum · 3 tagged_union · 4 tuple
|
||||
/// 5 union · 6 array · 7 vector · 8 error_set
|
||||
pub fn kindCode(self: *const TypeTable, id: TypeId) i64 {
|
||||
if (id.index() >= self.infos.items.len) return 0;
|
||||
return switch (self.get(id)) {
|
||||
.@"struct" => 1,
|
||||
.@"enum" => 2,
|
||||
.tagged_union => 3,
|
||||
.tuple => 4,
|
||||
.@"union" => 5,
|
||||
.array => 6,
|
||||
.vector => 7,
|
||||
.error_set => 8,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Integer value of enum variant `idx`: its explicit value when the enum
|
||||
/// declares one (custom values or flags), else its ordinal. Null for a
|
||||
/// non-enum type, a negative / out-of-range `idx`, or an out-of-range id.
|
||||
/// Backs the `type_field_value` reader (mirrors the `field_value_int` builtin).
|
||||
pub fn memberValue(self: *const TypeTable, id: TypeId, idx: i64) ?i64 {
|
||||
if (idx < 0 or id.index() >= self.infos.items.len) return null;
|
||||
const i: usize = @intCast(idx);
|
||||
return switch (self.get(id)) {
|
||||
.@"enum" => |e| blk: {
|
||||
if (i >= e.variants.len) break :blk null;
|
||||
if (e.explicit_values) |vals| if (i < vals.len) break :blk vals[i];
|
||||
break :blk @intCast(i); // ordinal default
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Source-sensitive variant of `findByName`: asserts at most one named type
|
||||
/// matches, then returns it (or null). Quarantines the global first-match
|
||||
/// scan — new resolver code that must not silently pick a first-of-many
|
||||
|
||||
Reference in New Issue
Block a user