Change TypeLookup values from simple index (usize) to a TypeMeta

TypeMeta constains the index and the subtype. This allows retrieving the subtype
based on the actual value being bound, as opposed to the struct type. (I.e. it
returns the correct subtype when a Zig class is a proxy for another using the
Self declaration).

Internally store the subtype as an enum. Reduces @sizeOf(TaggedAnyOpaque) from
32 to 16.

Finally, sub_type renamed to subtype for consistency with v8.
This commit is contained in:
Karl Seguin
2025-04-18 09:56:08 +08:00
parent 1c08b3e5e4
commit 615453a687
3 changed files with 66 additions and 20 deletions

View File

@@ -36,7 +36,7 @@ pub const HTMLDocument = struct {
pub const Self = parser.DocumentHTML; pub const Self = parser.DocumentHTML;
pub const prototype = *Document; pub const prototype = *Document;
pub const sub_type = "node"; pub const subtype = "node";
// JS funcs // JS funcs
// -------- // --------

View File

@@ -20,6 +20,8 @@ const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const v8 = @import("v8"); const v8 = @import("v8");
const SubType = @import("subtype.zig").SubType;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
@@ -72,14 +74,14 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
// that looks like: // that looks like:
// //
// const TypeLookup = struct { // const TypeLookup = struct {
// comptime cat: usize = 0, // comptime cat: usize = TypeMeta{.index = 0, ...},
// comptime owner: usize = 1, // comptime owner: usize = TypeMeta{.index = 1, ...},
// ... // ...
// } // }
// //
// So to get the template index of `owner`, we can do: // So to get the template index of `owner`, we can do:
// //
// const index_id = @field(type_lookup, @typeName(@TypeOf(res)); // const index_id = @field(type_lookup, @typeName(@TypeOf(res)).index;
// //
const TypeLookup = comptime blk: { const TypeLookup = comptime blk: {
var fields: [Types.len]std.builtin.Type.StructField = undefined; var fields: [Types.len]std.builtin.Type.StructField = undefined;
@@ -94,13 +96,16 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
@compileError(std.fmt.comptimePrint("Prototype '{s}' for type '{s} must be a pointer", .{ @typeName(Struct.prototype), @typeName(Struct) })); @compileError(std.fmt.comptimePrint("Prototype '{s}' for type '{s} must be a pointer", .{ @typeName(Struct.prototype), @typeName(Struct) }));
} }
const subtype: ?SubType =
if (@hasDecl(Struct, "subtype")) std.meta.stringToEnum(SubType, Struct.subtype) else null;
const R = Receiver(@field(types, s.name)); const R = Receiver(@field(types, s.name));
fields[i] = .{ fields[i] = .{
.name = @typeName(R), .name = @typeName(R),
.type = usize, .type = TypeMeta,
.is_comptime = true, .is_comptime = true,
.alignment = @alignOf(usize), .alignment = @alignOf(usize),
.default_value_ptr = @ptrCast(&i), .default_value_ptr = &TypeMeta{ .index = i, .subtype = subtype },
}; };
} }
break :blk @Type(.{ .@"struct" = .{ break :blk @Type(.{ .@"struct" = .{
@@ -135,7 +140,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
if (@hasDecl(Struct, "prototype")) { if (@hasDecl(Struct, "prototype")) {
const TI = @typeInfo(Struct.prototype); const TI = @typeInfo(Struct.prototype);
const proto_name = @typeName(Receiver(TI.pointer.child)); const proto_name = @typeName(Receiver(TI.pointer.child));
prototype_index = @field(TYPE_LOOKUP, proto_name); prototype_index = @field(TYPE_LOOKUP, proto_name).index;
} }
table[i] = prototype_index; table[i] = prototype_index;
} }
@@ -158,7 +163,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
// access to its TunctionTemplate (the thing we need to create an instance // access to its TunctionTemplate (the thing we need to create an instance
// of it) // of it)
// I.e.: // I.e.:
// const index = @field(TYPE_LOOKUP, @typeName(type_name)) // const index = @field(TYPE_LOOKUP, @typeName(type_name)).index
// const template = templates[index]; // const template = templates[index];
templates: [Types.len]v8.FunctionTemplate, templates: [Types.len]v8.FunctionTemplate,
@@ -214,7 +219,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
// Populate our templates lookup. generateClass creates the // Populate our templates lookup. generateClass creates the
// v8.FunctionTemplate, which we store in our env.templates. // v8.FunctionTemplate, which we store in our env.templates.
// The ordering doesn't matter. What matters is that, given a type // The ordering doesn't matter. What matters is that, given a type
// we can get its index via: @field(TYPE_LOOKUP, type_name) // we can get its index via: @field(TYPE_LOOKUP, type_name).index
const templates = &env.templates; const templates = &env.templates;
inline for (Types, 0..) |s, i| { inline for (Types, 0..) |s, i| {
templates[i] = env.generateClass(@field(types, s.name)); templates[i] = env.generateClass(@field(types, s.name));
@@ -234,7 +239,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
// Just like we said above, given a type, we can get its // Just like we said above, given a type, we can get its
// template index. // template index.
const proto_index = @field(TYPE_LOOKUP, proto_name); const proto_index = @field(TYPE_LOOKUP, proto_name).index;
templates[i].inherit(templates[proto_index]); templates[i].inherit(templates[proto_index]);
} }
} }
@@ -288,7 +293,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
if (@hasDecl(Global, "prototype")) { if (@hasDecl(Global, "prototype")) {
const proto_type = Receiver(@typeInfo(Global.prototype).pointer.child); const proto_type = Receiver(@typeInfo(Global.prototype).pointer.child);
const proto_name = @typeName(proto_type); const proto_name = @typeName(proto_type);
const proto_index = @field(TYPE_LOOKUP, proto_name); const proto_index = @field(TYPE_LOOKUP, proto_name).index;
globals.inherit(templates[proto_index]); globals.inherit(templates[proto_index]);
} }
@@ -309,7 +314,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
@compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name); @compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name);
} }
const proto_index = @field(TYPE_LOOKUP, proto_name); const proto_index = @field(TYPE_LOOKUP, proto_name).index;
const proto_obj = templates[proto_index].getFunction(context).toObject(); const proto_obj = templates[proto_index].getFunction(context).toObject();
const self_obj = templates[i].getFunction(context).toObject(); const self_obj = templates[i].getFunction(context).toObject();
@@ -640,7 +645,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
.one => { .one => {
const type_name = @typeName(ptr.child); const type_name = @typeName(ptr.child);
if (@hasField(TypeLookup, type_name)) { if (@hasField(TypeLookup, type_name)) {
const template = templates[@field(TYPE_LOOKUP, type_name)]; const template = templates[@field(TYPE_LOOKUP, type_name).index];
const js_obj = try Executor.mapZigInstanceToJs(context, template, value); const js_obj = try Executor.mapZigInstanceToJs(context, template, value);
return js_obj.toValue(); return js_obj.toValue();
} }
@@ -676,7 +681,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
.@"struct" => |s| { .@"struct" => |s| {
const type_name = @typeName(T); const type_name = @typeName(T);
if (@hasField(TypeLookup, type_name)) { if (@hasField(TypeLookup, type_name)) {
const template = templates[@field(TYPE_LOOKUP, type_name)]; const template = templates[@field(TYPE_LOOKUP, type_name).index];
const js_obj = try Executor.mapZigInstanceToJs(context, template, value); const js_obj = try Executor.mapZigInstanceToJs(context, template, value);
return js_obj.toValue(); return js_obj.toValue();
} }
@@ -960,10 +965,11 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
// well as any meta data we'll need to use it later. // well as any meta data we'll need to use it later.
// See the TaggedAnyOpaque struct for more details. // See the TaggedAnyOpaque struct for more details.
const tao = try scope_arena.create(TaggedAnyOpaque); const tao = try scope_arena.create(TaggedAnyOpaque);
const meta = @field(TYPE_LOOKUP, @typeName(ptr.child));
tao.* = .{ tao.* = .{
.ptr = value, .ptr = value,
.index = @field(TYPE_LOOKUP, @typeName(ptr.child)), .index = meta.index,
.sub_type = if (@hasDecl(ptr.child, "sub_type")) ptr.child.sub_type else null, .subtype = meta.subtype,
.offset = if (@typeInfo(ptr.child) != .@"opaque" and @hasField(ptr.child, "proto")) @offsetOf(ptr.child, "proto") else -1, .offset = if (@typeInfo(ptr.child) != .@"opaque" and @hasField(ptr.child, "proto")) @offsetOf(ptr.child, "proto") else -1,
}; };
@@ -1345,7 +1351,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
const op = js_obj.getInternalField(0).castTo(v8.External).get(); const op = js_obj.getInternalField(0).castTo(v8.External).get();
const toa: *TaggedAnyOpaque = @alignCast(@ptrCast(op)); const toa: *TaggedAnyOpaque = @alignCast(@ptrCast(op));
const expected_type_index = @field(TYPE_LOOKUP, type_name); const expected_type_index = @field(TYPE_LOOKUP, type_name).index;
var type_index = toa.index; var type_index = toa.index;
if (type_index == expected_type_index) { if (type_index == expected_type_index) {
@@ -1379,6 +1385,26 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
}; };
} }
// We'll create a struct with all the types we want to bind to JavaScript. The
// fields for this struct will be the type names. The values, will be an
// instance of this struct.
// const TypeLookup = struct {
// comptime cat: usize = TypeMeta{.index = 0, subtype = null},
// comptime owner: usize = TypeMeta{.index = 1, subtype = .array}.
// ...
// }
// This is essentially meta data for each type.
const TypeMeta = struct {
// Every type is given a unique index. That index is used to lookup various
// things, i.e. the prototype chain.
index: usize,
// We store the type's subtype here, so that when we create an instance of
// the type, and bind it to JavaScript, we can store the subtype along with
// the created TaggedAnyOpaque.s
subtype: ?SubType,
};
fn isEmpty(comptime T: type) bool { fn isEmpty(comptime T: type) bool {
return @typeInfo(T) != .@"opaque" and @sizeOf(T) == 0; return @typeInfo(T) != .@"opaque" and @sizeOf(T) == 0;
} }
@@ -2204,7 +2230,7 @@ const TaggedAnyOpaque = struct {
// V8 will give us a Value and ask us for the subtype. From the v8.Value we // V8 will give us a Value and ask us for the subtype. From the v8.Value we
// can get a v8.Object, and from the v8.Object, we can get out TaggedAnyOpaque // can get a v8.Object, and from the v8.Object, we can get out TaggedAnyOpaque
// which is where we store the subtype. // which is where we store the subtype.
sub_type: ?[*c]const u8, subtype: ?SubType,
}; };
fn valueToString(allocator: Allocator, value: v8.Value, isolate: v8.Isolate, context: v8.Context) ![]u8 { fn valueToString(allocator: Allocator, value: v8.Value, isolate: v8.Isolate, context: v8.Context) ![]u8 {
@@ -2263,7 +2289,7 @@ pub export fn v8_inspector__Client__IMPL__valueSubtype(
c_value: *const v8.C_Value, c_value: *const v8.C_Value,
) callconv(.C) [*c]const u8 { ) callconv(.C) [*c]const u8 {
const external_entry = getTaggedAnyOpaque(.{ .handle = c_value }) orelse return null; const external_entry = getTaggedAnyOpaque(.{ .handle = c_value }) orelse return null;
return if (external_entry.sub_type) |st| st else null; return if (external_entry.subtype) |st| @tagName(st) else null;
} }
// Same as valueSubType above, but for the optional description field. // Same as valueSubType above, but for the optional description field.
@@ -2280,7 +2306,7 @@ pub export fn v8_inspector__Client__IMPL__descriptionForValueSubtype(
// We _must_ include a non-null description in order for the subtype value // We _must_ include a non-null description in order for the subtype value
// to be included. Besides that, I don't know if the value has any meaning // to be included. Besides that, I don't know if the value has any meaning
const external_entry = getTaggedAnyOpaque(.{ .handle = c_value }) orelse return null; const external_entry = getTaggedAnyOpaque(.{ .handle = c_value }) orelse return null;
return if (external_entry.sub_type == null) null else ""; return if (external_entry.subtype == null) null else "";
} }
fn getTaggedAnyOpaque(value: v8.Value) ?*TaggedAnyOpaque { fn getTaggedAnyOpaque(value: v8.Value) ?*TaggedAnyOpaque {

20
src/runtime/subtype.zig Normal file
View File

@@ -0,0 +1,20 @@
pub const SubType = enum {
@"error",
array,
arraybuffer,
dataview,
date,
generator,
iterator,
map,
node,
promise,
proxy,
regexp,
set,
typedarray,
wasmvalue,
weakmap,
weakset,
webassemblymemory,
};