From 615453a687527577fe1209d53ca2752100384cf7 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 18 Apr 2025 09:56:08 +0800 Subject: [PATCH] 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. --- src/browser/html/document.zig | 2 +- src/runtime/js.zig | 64 ++++++++++++++++++++++++----------- src/runtime/subtype.zig | 20 +++++++++++ 3 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 src/runtime/subtype.zig diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig index d434ee21..e4f39067 100644 --- a/src/browser/html/document.zig +++ b/src/browser/html/document.zig @@ -36,7 +36,7 @@ pub const HTMLDocument = struct { pub const Self = parser.DocumentHTML; pub const prototype = *Document; - pub const sub_type = "node"; + pub const subtype = "node"; // JS funcs // -------- diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 329a8728..e7233966 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -20,6 +20,8 @@ const std = @import("std"); const builtin = @import("builtin"); const v8 = @import("v8"); +const SubType = @import("subtype.zig").SubType; + const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; @@ -72,14 +74,14 @@ pub fn Env(comptime S: type, comptime types: anytype) type { // that looks like: // // const TypeLookup = struct { - // comptime cat: usize = 0, - // comptime owner: usize = 1, + // comptime cat: usize = TypeMeta{.index = 0, ...}, + // comptime owner: usize = TypeMeta{.index = 1, ...}, // ... // } // // 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: { 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) })); } + const subtype: ?SubType = + if (@hasDecl(Struct, "subtype")) std.meta.stringToEnum(SubType, Struct.subtype) else null; + const R = Receiver(@field(types, s.name)); fields[i] = .{ .name = @typeName(R), - .type = usize, + .type = TypeMeta, .is_comptime = true, .alignment = @alignOf(usize), - .default_value_ptr = @ptrCast(&i), + .default_value_ptr = &TypeMeta{ .index = i, .subtype = subtype }, }; } break :blk @Type(.{ .@"struct" = .{ @@ -135,7 +140,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type { if (@hasDecl(Struct, "prototype")) { const TI = @typeInfo(Struct.prototype); 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; } @@ -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 // of it) // I.e.: - // const index = @field(TYPE_LOOKUP, @typeName(type_name)) + // const index = @field(TYPE_LOOKUP, @typeName(type_name)).index // const template = templates[index]; 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 // v8.FunctionTemplate, which we store in our env.templates. // 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; inline for (Types, 0..) |s, i| { 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 // 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]); } } @@ -288,7 +293,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type { if (@hasDecl(Global, "prototype")) { const proto_type = Receiver(@typeInfo(Global.prototype).pointer.child); 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]); } @@ -309,7 +314,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type { @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 self_obj = templates[i].getFunction(context).toObject(); @@ -640,7 +645,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type { .one => { const type_name = @typeName(ptr.child); 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); return js_obj.toValue(); } @@ -676,7 +681,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type { .@"struct" => |s| { const type_name = @typeName(T); 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); 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. // See the TaggedAnyOpaque struct for more details. const tao = try scope_arena.create(TaggedAnyOpaque); + const meta = @field(TYPE_LOOKUP, @typeName(ptr.child)); tao.* = .{ .ptr = value, - .index = @field(TYPE_LOOKUP, @typeName(ptr.child)), - .sub_type = if (@hasDecl(ptr.child, "sub_type")) ptr.child.sub_type else null, + .index = meta.index, + .subtype = meta.subtype, .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 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; 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 { 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 // can get a v8.Object, and from the v8.Object, we can get out TaggedAnyOpaque // 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 { @@ -2263,7 +2289,7 @@ pub export fn v8_inspector__Client__IMPL__valueSubtype( c_value: *const v8.C_Value, ) callconv(.C) [*c]const u8 { 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. @@ -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 // 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; - return if (external_entry.sub_type == null) null else ""; + return if (external_entry.subtype == null) null else ""; } fn getTaggedAnyOpaque(value: v8.Value) ?*TaggedAnyOpaque { diff --git a/src/runtime/subtype.zig b/src/runtime/subtype.zig new file mode 100644 index 00000000..ce7be1ed --- /dev/null +++ b/src/runtime/subtype.zig @@ -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, +};