From 1aca22f21934cfaae7be5dde895da45227afb092 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 20 Nov 2025 13:49:36 +0300 Subject: [PATCH 1/2] JS API table changes for zigdom Applies the `rework-types` changes to zigdom branch. --- src/browser/js/Context.zig | 15 ++-- src/browser/js/Env.zig | 4 +- src/browser/js/ExecutionWorld.zig | 2 +- src/browser/js/bridge.zig | 120 +++++++++++++++++------------- src/browser/js/js.zig | 2 +- 5 files changed, 77 insertions(+), 66 deletions(-) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 1eee85c85..865122044 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -457,8 +457,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp .pointer => |ptr| switch (ptr.size) { .one => { if (@typeInfo(ptr.child) == .@"struct" and @hasDecl(ptr.child, "JsApi")) { - const type_name = @typeName(ptr.child.JsApi); - if (@hasField(bridge.JsApiLookup, type_name)) { + if (bridge.JsApiLookup.has(ptr.child.JsApi)) { const js_obj = try self.mapZigInstanceToJs(null, value); return js_obj.toValue(); } @@ -499,8 +498,7 @@ pub fn zigValueToJs(self: *Context, value: anytype, comptime opts: Caller.CallOp }, .@"struct" => |s| { if (@hasDecl(T, "JsApi")) { - const type_name = @typeName(T.JsApi); - if (@hasField(bridge.JsApiLookup, type_name)) { + if (bridge.JsApiLookup.has(T.JsApi)) { const js_obj = try self.mapZigInstanceToJs(null, value); return js_obj.toValue(); } @@ -707,7 +705,7 @@ pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T { return error.InvalidArgument; } if (@hasDecl(ptr.child, "JsApi")) { - std.debug.assert(@hasField(bridge.JsApiLookup, @typeName(ptr.child.JsApi))); + //std.debug.assert(bridge.JsApiLookup.has(ptr.child.JsApi)); const js_obj = js_value.castTo(v8.Object); return typeTaggedAnyOpaque(*ptr.child, js_obj); } @@ -1537,14 +1535,13 @@ pub fn typeTaggedAnyOpaque(comptime R: type, js_obj: v8.Object) !R { return error.InvalidArgument; } - const type_name = @typeName(JsApi); - if (@hasField(bridge.JsApiLookup, type_name) == false) { + if (!bridge.JsApiLookup.has(JsApi)) { @compileError("unknown Zig type: " ++ @typeName(R)); } const op = js_obj.getInternalField(0).castTo(v8.External).get(); const tao: *TaggedAnyOpaque = @ptrCast(@alignCast(op)); - const expected_type_index = @field(bridge.JS_API_LOOKUP, type_name); + const expected_type_index = bridge.JsApiLookup.getId(JsApi); const prototype_chain = tao.prototype_chain[0..tao.prototype_len]; if (prototype_chain[0].index == expected_type_index) { @@ -1643,7 +1640,7 @@ fn probeJsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !Prob if (!js_value.isObject()) { return .{ .invalid = {} }; } - if (@hasField(bridge.JsApiLookup, @typeName(ptr.child.JsApi))) { + if (bridge.JsApiLookup.has(ptr.child.JsApi)) { const js_obj = js_value.castTo(v8.Object); // There's a bit of overhead in doing this, so instead // of having a version of typeTaggedAnyOpaque which diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index d75afd4bd..973ef43eb 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -335,7 +335,7 @@ fn generateConstructor(comptime JsApi: type, isolate: v8.Isolate) v8.FunctionTem // } // } -pub fn protoIndexLookup(comptime JsApi: type) ?u16 { +pub fn protoIndexLookup(comptime JsApi: type) ?bridge.JsApiLookup.BackingInt { @setEvalBranchQuota(2000); comptime { const T = JsApi.bridge.type; @@ -344,6 +344,6 @@ pub fn protoIndexLookup(comptime JsApi: type) ?u16 { } const Ptr = std.meta.fieldInfo(T, ._proto).type; const F = @typeInfo(Ptr).pointer.child; - return @field(bridge.JS_API_LOOKUP, @typeName(F.JsApi)); + return bridge.JsApiLookup.getId(F.JsApi); } } diff --git a/src/browser/js/ExecutionWorld.zig b/src/browser/js/ExecutionWorld.zig index 106d02e52..723833436 100644 --- a/src/browser/js/ExecutionWorld.zig +++ b/src/browser/js/ExecutionWorld.zig @@ -128,7 +128,7 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal // specific instance of the the Window. { const proto_type = @typeInfo(@TypeOf(page.window._proto)).pointer.child; - const proto_index = @field(bridge.JS_API_LOOKUP, @typeName(proto_type.JsApi)); + const proto_index = bridge.JsApiLookup.getId(proto_type.JsApi); js_global.inherit(templates[proto_index]); } diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index bd75bee86..5a95257f0 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -64,20 +64,17 @@ pub fn Builder(comptime T: type) type { pub fn prototypeChain() [prototypeChainLength(T)]js.PrototypeChainEntry { var entries: [prototypeChainLength(T)]js.PrototypeChainEntry = undefined; - entries[0] = .{ - .offset = 0, - .index = @field(JS_API_LOOKUP, @typeName(T.JsApi)), - }; + entries[0] = .{ .offset = 0, .index = JsApiLookup.getId(T.JsApi) }; if (entries.len == 1) { return entries; } var Prototype = T; - for (entries[1..]) |*entry| { + inline for (entries[1..]) |*entry| { const Next = PrototypeType(Prototype).?; entry.* = .{ - .index = @field(JS_API_LOOKUP, @typeName(Next.JsApi)), + .index = JsApiLookup.getId(Next.JsApi), .offset = @offsetOf(Prototype, "_proto"), }; Prototype = Next; @@ -394,55 +391,72 @@ pub fn Struct(comptime T: type) type { }; } -// Imagine we have a type Cat which has a getter: -// -// fn getOwner(self: *Cat) *Owner { -// return self.owner; -// } -// -// When we execute caller.getter, we'll end up doing something like: -// const res = @call(.auto, Cat.getOwner, .{cat_instance}); -// -// How do we turn `res`, which is an *Owner, into something we can return -// to v8? We need the ObjectTemplate associated with Owner. How do we -// get that? Well, we store all the ObjectTemplates in an array that's -// tied to env. So we do something like: -// -// env.templates[index_of_owner].initInstance(...); -// -// But how do we get that `index_of_owner`? `Lookup` is a struct -// that looks like: -// -// const Lookup = struct { -// comptime cat: usize = 0, -// comptime owner: usize = 1, -// ... -// } -// -// So to get the template index of `owner`, we can do: -// -// const index_id = @field(type_lookup, @typeName(@TypeOf(res)); -// -pub const JsApiLookup = blk: { - var fields: [JsApis.len]std.builtin.Type.StructField = undefined; - for (JsApis, 0..) |JsApi, i| { - fields[i] = .{ - .name = @typeName(JsApi), - .type = u16, - .is_comptime = true, - .alignment = @alignOf(u16), - .default_value_ptr = @ptrCast(&i), - }; +pub const JsApiLookup = struct { + /// Integer type we use for `JsApiLookup` enum. Can be u8 at min. + pub const BackingInt = std.math.IntFittingRange(0, @max(std.math.maxInt(u8), JsApis.len)); + + /// Imagine we have a type `Cat` which has a getter: + /// + /// fn get_owner(self: *Cat) *Owner { + /// return self.owner; + /// } + /// + /// When we execute `caller.getter`, we'll end up doing something like: + /// + /// const res = @call(.auto, Cat.get_owner, .{cat_instance}); + /// + /// How do we turn `res`, which is an *Owner, into something we can return + /// to v8? We need the ObjectTemplate associated with Owner. How do we + /// get that? Well, we store all the ObjectTemplates in an array that's + /// tied to env. So we do something like: + /// + /// env.templates[index_of_owner].initInstance(...); + /// + /// But how do we get that `index_of_owner`? `Index` is an enum + /// that looks like: + /// + /// pub const Enum = enum(BackingInt) { + /// cat = 0, + /// owner = 1, + /// ... + /// } + /// + /// (`BackingInt` is calculated at comptime regarding to interfaces we have) + /// So to get the template index of `owner`, simply do: + /// + /// const index_id = types.getId(@TypeOf(res)); + /// + pub const Enum = blk: { + var fields: [JsApis.len]std.builtin.Type.EnumField = undefined; + for (JsApis, 0..) |JsApi, i| { + fields[i] = .{ .name = @typeName(JsApi), .value = i }; + } + + break :blk @Type(.{ + .@"enum" = .{ + .fields = &fields, + .tag_type = BackingInt, + .is_exhaustive = true, + .decls = &.{}, + }, + }); + }; + + /// Returns a boolean indicating if a type exist in the lookup. + pub inline fn has(t: type) bool { + return @hasField(Enum, @typeName(t)); } - break :blk @Type(.{ .@"struct" = .{ - .layout = .auto, - .decls = &.{}, - .is_tuple = false, - .fields = &fields, - } }); -}; -pub const JS_API_LOOKUP = JsApiLookup{}; + /// Returns the `Enum` for the given type. + pub inline fn getIndex(t: type) Enum { + return @field(Enum, @typeName(t)); + } + + /// Returns the ID for the given type. + pub inline fn getId(t: type) BackingInt { + return @intFromEnum(getIndex(t)); + } +}; pub const SubType = enum { @"error", diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 2ddc84b82..6a50576c5 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -478,7 +478,7 @@ pub const TaggedAnyOpaque = struct { }; pub const PrototypeChainEntry = struct { - index: u16, + index: bridge.JsApiLookup.BackingInt, offset: u16, // offset to the _proto field }; From 629297e0c2daba8236e5c66824876eca1cd45a0c Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Thu, 20 Nov 2025 14:22:32 +0300 Subject: [PATCH 2/2] uncomment the assertion Forgot to revert this... --- src/browser/js/Context.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 865122044..8c37b47eb 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -705,7 +705,7 @@ pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T { return error.InvalidArgument; } if (@hasDecl(ptr.child, "JsApi")) { - //std.debug.assert(bridge.JsApiLookup.has(ptr.child.JsApi)); + std.debug.assert(bridge.JsApiLookup.has(ptr.child.JsApi)); const js_obj = js_value.castTo(v8.Object); return typeTaggedAnyOpaque(*ptr.child, js_obj); }