diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index c1fc24be..a936f471 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -409,9 +409,8 @@ pub fn zigValueToJs(self: *Context, value: anytype) !v8.Value { }, .pointer => |ptr| switch (ptr.size) { .one => { - const type_name = @typeName(ptr.child); - if (@hasField(types.Lookup, type_name)) { - const template = self.templates[@field(types.LOOKUP, type_name)]; + if (types.has(ptr.child)) { + const template = self.templates[types.getId(ptr.child)]; const js_obj = try self.mapZigInstanceToJs(template, value); return js_obj.toValue(); } @@ -445,9 +444,8 @@ pub fn zigValueToJs(self: *Context, value: anytype) !v8.Value { else => {}, }, .@"struct" => |s| { - const type_name = @typeName(T); - if (@hasField(types.Lookup, type_name)) { - const template = self.templates[@field(types.LOOKUP, type_name)]; + if (types.has(T)) { + const template = self.templates[types.getId(T)]; const js_obj = try self.mapZigInstanceToJs(template, value); return js_obj.toValue(); } @@ -583,8 +581,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_or_template: anytype, value: an // well as any meta data we'll need to use it later. // See the TaggedAnyOpaque struct for more details. const tao = try arena.create(TaggedAnyOpaque); - const meta_index = @field(types.LOOKUP, @typeName(ptr.child)); - const meta = self.meta_lookup[meta_index]; + const meta = self.meta_lookup[types.getId(ptr.child)]; tao.* = .{ .ptr = value, @@ -664,7 +661,7 @@ pub fn jsValueToZig(self: *Context, comptime named_function: NamedFunction, comp if (!js_value.isObject()) { return error.InvalidArgument; } - if (@hasField(types.Lookup, @typeName(ptr.child))) { + if (types.has(ptr.child)) { const js_obj = js_value.castTo(v8.Object); return self.typeTaggedAnyOpaque(named_function, *types.Receiver(ptr.child), js_obj); } @@ -1454,14 +1451,13 @@ pub fn typeTaggedAnyOpaque(self: *const Context, comptime named_function: NamedF return error.InvalidArgument; } - const type_name = @typeName(T); - if (@hasField(types.Lookup, type_name) == false) { + if (!types.has(T)) { @compileError(named_function.full_name ++ "has an 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(types.LOOKUP, type_name); + const expected_type_index = types.getId(T); var type_index = tao.index; if (type_index == expected_type_index) { @@ -1489,7 +1485,7 @@ pub fn typeTaggedAnyOpaque(self: *const Context, comptime named_function: NamedF total_offset += @intCast(proto_offset); } - const prototype_index = types.PROTOTYPE_TABLE[type_index]; + const prototype_index = types.PrototypeTable[type_index]; if (prototype_index == expected_type_index) { return @ptrFromInt(base_ptr + total_offset); } @@ -1582,7 +1578,7 @@ fn probeJsValueToZig(self: *Context, comptime named_function: NamedFunction, com if (!js_value.isObject()) { return .{ .invalid = {} }; } - if (@hasField(types.Lookup, @typeName(ptr.child))) { + if (types.has(ptr.child)) { 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 370c6f7e..87da373c 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -111,16 +111,14 @@ pub fn init(allocator: Allocator, platform: *const Platform, _: Opts) !*Env { const Struct = s.defaultValue().?; if (@hasDecl(Struct, "prototype")) { const TI = @typeInfo(Struct.prototype); - const proto_name = @typeName(types.Receiver(TI.pointer.child)); - if (@hasField(types.Lookup, proto_name) == false) { - @compileError(std.fmt.comptimePrint("Prototype '{s}' for '{s}' is undefined", .{ proto_name, @typeName(Struct) })); + const ProtoType = types.Receiver(TI.pointer.child); + if (!types.has(ProtoType)) { + @compileError(std.fmt.comptimePrint("Prototype '{s}' for '{s}' is undefined", .{ @typeName(ProtoType), @typeName(Struct) })); } - // Hey, look! This is our first real usage of the types.LOOKUP. + // Hey, look! This is our first real usage of the `types.Index`. // Just like we said above, given a type, we can get its // template index. - - const proto_index = @field(types.LOOKUP, proto_name); - templates[i].inherit(templates[proto_index]); + templates[i].inherit(templates[types.getId(ProtoType)]); } // while we're here, let's populate our meta lookup diff --git a/src/browser/js/ExecutionWorld.zig b/src/browser/js/ExecutionWorld.zig index 73603227..d93e4385 100644 --- a/src/browser/js/ExecutionWorld.zig +++ b/src/browser/js/ExecutionWorld.zig @@ -104,10 +104,8 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal // though it's also a Window, we need to set the prototype for this // specific instance of the the Window. if (@hasDecl(Global, "prototype")) { - const proto_type = types.Receiver(@typeInfo(Global.prototype).pointer.child); - const proto_name = @typeName(proto_type); - const proto_index = @field(types.LOOKUP, proto_name); - js_global.inherit(templates[proto_index]); + const ProtoType = types.Receiver(@typeInfo(Global.prototype).pointer.child); + js_global.inherit(templates[types.getId(ProtoType)]); } const context_local = v8.Context.init(isolate, global_template, null); @@ -123,14 +121,12 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal const Struct = s.defaultValue().?; if (@hasDecl(Struct, "prototype")) { - const proto_type = types.Receiver(@typeInfo(Struct.prototype).pointer.child); - const proto_name = @typeName(proto_type); - if (@hasField(types.Lookup, proto_name) == false) { - @compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name); + const ProtoType = types.Receiver(@typeInfo(Struct.prototype).pointer.child); + if (!types.has(ProtoType)) { + @compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ @typeName(ProtoType)); } - const proto_index = @field(types.LOOKUP, proto_name); - const proto_obj = templates[proto_index].getFunction(v8_context).toObject(); + const proto_obj = templates[types.getId(ProtoType)].getFunction(v8_context).toObject(); const self_obj = templates[i].getFunction(v8_context).toObject(); _ = self_obj.setPrototype(v8_context, proto_obj); diff --git a/src/browser/js/types.zig b/src/browser/js/types.zig index bd4b595e..19073639 100644 --- a/src/browser/js/types.zig +++ b/src/browser/js/types.zig @@ -27,104 +27,127 @@ const Interfaces = generate.Tuple(.{ pub const Types = @typeInfo(Interfaces).@"struct".fields; -// 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`? `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 Lookup = blk: { - var fields: [Types.len]std.builtin.Type.StructField = undefined; +/// Integer type we use for `Index` enum. Can be u8 at min. +pub const BackingInt = std.math.IntFittingRange(0, @max(std.math.maxInt(u8), Types.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 Index = 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 Index = blk: { + var fields: [Types.len]std.builtin.Type.EnumField = undefined; for (Types, 0..) |s, i| { - - // This prototype type check has nothing to do with building our - // Lookup. But we put it here, early, so that the rest of the - // code doesn't have to worry about checking if Struct.prototype is - // a pointer. const Struct = s.defaultValue().?; - if (@hasDecl(Struct, "prototype") and @typeInfo(Struct.prototype) != .pointer) { - @compileError(std.fmt.comptimePrint("Prototype '{s}' for type '{s} must be a pointer", .{ @typeName(Struct.prototype), @typeName(Struct) })); - } - - fields[i] = .{ - .name = @typeName(Receiver(Struct)), - .type = usize, - .is_comptime = true, - .alignment = @alignOf(usize), - .default_value_ptr = &i, - }; + fields[i] = .{ .name = @typeName(Receiver(Struct)), .value = i }; } - break :blk @Type(.{ .@"struct" = .{ - .layout = .auto, - .decls = &.{}, - .is_tuple = false, - .fields = &fields, - } }); + + break :blk @Type(.{ + .@"enum" = .{ + .fields = &fields, + .tag_type = BackingInt, + .is_exhaustive = true, + .decls = &.{}, + }, + }); }; -pub const LOOKUP = Lookup{}; +/// Returns a boolean indicating if a type exist in the `Index`. +pub inline fn has(t: type) bool { + return @hasField(Index, @typeName(t)); +} -// Creates a list where the index of a type contains its prototype index -// const Animal = struct{}; -// const Cat = struct{ -// pub const prototype = *Animal; -// }; -// -// Would create an array: [0, 0] -// Animal, at index, 0, has no prototype, so we set it to itself -// Cat, at index 1, has an Animal prototype, so we set it to 0. -// -// When we're trying to pass an argument to a Zig function, we'll know the -// target type (the function parameter type), and we'll have a -// TaggedAnyOpaque which will have the index of the type of that parameter. -// We'll use the PROTOTYPE_TABLE to see if the TaggedAnyType should be -// cast to a prototype. -pub const PROTOTYPE_TABLE = blk: { - var table: [Types.len]u16 = undefined; +/// Returns the `Index` for the given type. +pub inline fn getIndex(t: type) Index { + return @field(Index, @typeName(t)); +} + +/// Returns the ID for the given type. +pub inline fn getId(t: type) BackingInt { + return @intFromEnum(getIndex(t)); +} + +/// Creates a list where the index of a type contains its prototype index. +/// const Animal = struct{}; +/// const Cat = struct{ +/// pub const prototype = *Animal; +/// }; +/// +/// Would create an array of indexes: +/// [Index.Animal, Index.Animal] +/// +/// `Animal`, at index, 0, has no prototype, so we set it to itself. +/// `Cat`, at index 1, has an `Animal` prototype, so we set it to `Animal`. +/// +/// When we're trying to pass an argument to a Zig function, we'll know the +/// target type (the function parameter type), and we'll have a +/// TaggedAnyOpaque which will have the index of the type of that parameter. +/// We'll use the `PrototypeTable` to see if the TaggedAnyType should be +/// cast to a prototype. +pub const PrototypeTable = blk: { + var table: [Types.len]BackingInt = undefined; for (Types, 0..) |s, i| { - var prototype_index = i; const Struct = s.defaultValue().?; - if (@hasDecl(Struct, "prototype")) { - const TI = @typeInfo(Struct.prototype); - const proto_name = @typeName(Receiver(TI.pointer.child)); - prototype_index = @field(LOOKUP, proto_name); - } - table[i] = prototype_index; + table[i] = proto_index: { + if (@hasDecl(Struct, "prototype")) { + const prototype_field = @field(Struct, "prototype"); + // This prototype type check has nothing to do with building our + // Lookup. But we put it here, early, so that the rest of the + // code doesn't have to worry about checking if Struct.prototype is + // a pointer. + break :proto_index switch (@typeInfo(prototype_field)) { + .pointer => |pointer| getId(Receiver(pointer.child)), + inline else => @compileError(std.fmt.comptimePrint("Prototype '{s}' for type '{s}' must be a pointer", .{ + prototype_field, + @typeName(Struct), + })), + }; + } + + break :proto_index i; + }; } + break :blk table; }; -// This is essentially meta data for each type. Each is stored in env.meta_lookup -// The index for a type can be retrieved via: -// const index = @field(TYPE_LOOKUP, @typeName(Receiver(Struct))); -// const meta = env.meta_lookup[index]; +/// This is essentially meta data for each type. Each is stored in `env.meta_lookup`. +/// The index for a type can be retrieved via: +/// const index = types.getIndex(Receiver(Struct)); +/// const meta = env.meta_lookup[@intFromEnum(index)]; +/// +/// Or: +/// const id = types.getId(Receiver(Struct)); +/// const meta = env.meta_lookup[id]; pub const Meta = struct { // Every type is given a unique index. That index is used to lookup various // things, i.e. the prototype chain. - index: u16, + index: BackingInt, // 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