Skip to content

Commit 707db81

Browse files
committed
prefer an enum instead of struct declarations for JS API table
Also adds utility functions (namely `has`, `getIndex` and `getId`) to work easily with types.
1 parent 1412c58 commit 707db81

File tree

4 files changed

+127
-114
lines changed

4 files changed

+127
-114
lines changed

src/browser/js/Context.zig

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -409,9 +409,8 @@ pub fn zigValueToJs(self: *Context, value: anytype) !v8.Value {
409409
},
410410
.pointer => |ptr| switch (ptr.size) {
411411
.one => {
412-
const type_name = @typeName(ptr.child);
413-
if (@hasField(types.Lookup, type_name)) {
414-
const template = self.templates[@field(types.LOOKUP, type_name)];
412+
if (types.has(ptr.child)) {
413+
const template = self.templates[types.getId(ptr.child)];
415414
const js_obj = try self.mapZigInstanceToJs(template, value);
416415
return js_obj.toValue();
417416
}
@@ -445,9 +444,8 @@ pub fn zigValueToJs(self: *Context, value: anytype) !v8.Value {
445444
else => {},
446445
},
447446
.@"struct" => |s| {
448-
const type_name = @typeName(T);
449-
if (@hasField(types.Lookup, type_name)) {
450-
const template = self.templates[@field(types.LOOKUP, type_name)];
447+
if (types.has(T)) {
448+
const template = self.templates[types.getId(T)];
451449
const js_obj = try self.mapZigInstanceToJs(template, value);
452450
return js_obj.toValue();
453451
}
@@ -583,8 +581,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_or_template: anytype, value: an
583581
// well as any meta data we'll need to use it later.
584582
// See the TaggedAnyOpaque struct for more details.
585583
const tao = try arena.create(TaggedAnyOpaque);
586-
const meta_index = @field(types.LOOKUP, @typeName(ptr.child));
587-
const meta = self.meta_lookup[meta_index];
584+
const meta = self.meta_lookup[types.getId(ptr.child)];
588585

589586
tao.* = .{
590587
.ptr = value,
@@ -664,7 +661,7 @@ pub fn jsValueToZig(self: *Context, comptime named_function: NamedFunction, comp
664661
if (!js_value.isObject()) {
665662
return error.InvalidArgument;
666663
}
667-
if (@hasField(types.Lookup, @typeName(ptr.child))) {
664+
if (types.has(ptr.child)) {
668665
const js_obj = js_value.castTo(v8.Object);
669666
return self.typeTaggedAnyOpaque(named_function, *types.Receiver(ptr.child), js_obj);
670667
}
@@ -1454,14 +1451,13 @@ pub fn typeTaggedAnyOpaque(self: *const Context, comptime named_function: NamedF
14541451
return error.InvalidArgument;
14551452
}
14561453

1457-
const type_name = @typeName(T);
1458-
if (@hasField(types.Lookup, type_name) == false) {
1454+
if (!types.has(T)) {
14591455
@compileError(named_function.full_name ++ "has an unknown Zig type: " ++ @typeName(R));
14601456
}
14611457

14621458
const op = js_obj.getInternalField(0).castTo(v8.External).get();
14631459
const tao: *TaggedAnyOpaque = @ptrCast(@alignCast(op));
1464-
const expected_type_index = @field(types.LOOKUP, type_name);
1460+
const expected_type_index = types.getId(T);
14651461

14661462
var type_index = tao.index;
14671463
if (type_index == expected_type_index) {
@@ -1489,7 +1485,7 @@ pub fn typeTaggedAnyOpaque(self: *const Context, comptime named_function: NamedF
14891485
total_offset += @intCast(proto_offset);
14901486
}
14911487

1492-
const prototype_index = types.PROTOTYPE_TABLE[type_index];
1488+
const prototype_index = types.PrototypeTable[type_index];
14931489
if (prototype_index == expected_type_index) {
14941490
return @ptrFromInt(base_ptr + total_offset);
14951491
}
@@ -1582,7 +1578,7 @@ fn probeJsValueToZig(self: *Context, comptime named_function: NamedFunction, com
15821578
if (!js_value.isObject()) {
15831579
return .{ .invalid = {} };
15841580
}
1585-
if (@hasField(types.Lookup, @typeName(ptr.child))) {
1581+
if (types.has(ptr.child)) {
15861582
const js_obj = js_value.castTo(v8.Object);
15871583
// There's a bit of overhead in doing this, so instead
15881584
// of having a version of typeTaggedAnyOpaque which

src/browser/js/Env.zig

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,14 @@ pub fn init(allocator: Allocator, platform: *const Platform, _: Opts) !*Env {
111111
const Struct = s.defaultValue().?;
112112
if (@hasDecl(Struct, "prototype")) {
113113
const TI = @typeInfo(Struct.prototype);
114-
const proto_name = @typeName(types.Receiver(TI.pointer.child));
115-
if (@hasField(types.Lookup, proto_name) == false) {
116-
@compileError(std.fmt.comptimePrint("Prototype '{s}' for '{s}' is undefined", .{ proto_name, @typeName(Struct) }));
114+
const ProtoType = types.Receiver(TI.pointer.child);
115+
if (!types.has(ProtoType)) {
116+
@compileError(std.fmt.comptimePrint("Prototype '{s}' for '{s}' is undefined", .{ @typeName(ProtoType), @typeName(Struct) }));
117117
}
118-
// Hey, look! This is our first real usage of the types.LOOKUP.
118+
// Hey, look! This is our first real usage of the `types.Index`.
119119
// Just like we said above, given a type, we can get its
120120
// template index.
121-
122-
const proto_index = @field(types.LOOKUP, proto_name);
123-
templates[i].inherit(templates[proto_index]);
121+
templates[i].inherit(templates[types.getId(ProtoType)]);
124122
}
125123

126124
// while we're here, let's populate our meta lookup

src/browser/js/ExecutionWorld.zig

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,8 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal
104104
// though it's also a Window, we need to set the prototype for this
105105
// specific instance of the the Window.
106106
if (@hasDecl(Global, "prototype")) {
107-
const proto_type = types.Receiver(@typeInfo(Global.prototype).pointer.child);
108-
const proto_name = @typeName(proto_type);
109-
const proto_index = @field(types.LOOKUP, proto_name);
110-
js_global.inherit(templates[proto_index]);
107+
const ProtoType = types.Receiver(@typeInfo(Global.prototype).pointer.child);
108+
js_global.inherit(templates[types.getId(ProtoType)]);
111109
}
112110

113111
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
123121
const Struct = s.defaultValue().?;
124122

125123
if (@hasDecl(Struct, "prototype")) {
126-
const proto_type = types.Receiver(@typeInfo(Struct.prototype).pointer.child);
127-
const proto_name = @typeName(proto_type);
128-
if (@hasField(types.Lookup, proto_name) == false) {
129-
@compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name);
124+
const ProtoType = types.Receiver(@typeInfo(Struct.prototype).pointer.child);
125+
if (!types.has(ProtoType)) {
126+
@compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ @typeName(ProtoType));
130127
}
131128

132-
const proto_index = @field(types.LOOKUP, proto_name);
133-
const proto_obj = templates[proto_index].getFunction(v8_context).toObject();
129+
const proto_obj = templates[types.getId(ProtoType)].getFunction(v8_context).toObject();
134130

135131
const self_obj = templates[i].getFunction(v8_context).toObject();
136132
_ = self_obj.setPrototype(v8_context, proto_obj);

src/browser/js/types.zig

Lines changed: 106 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -27,104 +27,127 @@ const Interfaces = generate.Tuple(.{
2727

2828
pub const Types = @typeInfo(Interfaces).@"struct".fields;
2929

30-
// Imagine we have a type Cat which has a getter:
31-
//
32-
// fn get_owner(self: *Cat) *Owner {
33-
// return self.owner;
34-
// }
35-
//
36-
// When we execute caller.getter, we'll end up doing something like:
37-
// const res = @call(.auto, Cat.get_owner, .{cat_instance});
38-
//
39-
// How do we turn `res`, which is an *Owner, into something we can return
40-
// to v8? We need the ObjectTemplate associated with Owner. How do we
41-
// get that? Well, we store all the ObjectTemplates in an array that's
42-
// tied to env. So we do something like:
43-
//
44-
// env.templates[index_of_owner].initInstance(...);
45-
//
46-
// But how do we get that `index_of_owner`? `Lookup` is a struct
47-
// that looks like:
48-
//
49-
// const Lookup = struct {
50-
// comptime cat: usize = 0,
51-
// comptime owner: usize = 1,
52-
// ...
53-
// }
54-
//
55-
// So to get the template index of `owner`, we can do:
56-
//
57-
// const index_id = @field(type_lookup, @typeName(@TypeOf(res));
58-
//
59-
pub const Lookup = blk: {
60-
var fields: [Types.len]std.builtin.Type.StructField = undefined;
61-
for (Types, 0..) |s, i| {
30+
/// Integer type we use for `Index` enum. Can be u8 at min.
31+
pub const BackingInt = std.math.IntFittingRange(0, @max(std.math.maxInt(u8), Types.len));
6232

63-
// This prototype type check has nothing to do with building our
64-
// Lookup. But we put it here, early, so that the rest of the
65-
// code doesn't have to worry about checking if Struct.prototype is
66-
// a pointer.
33+
/// Imagine we have a type `Cat` which has a getter:
34+
///
35+
/// fn get_owner(self: *Cat) *Owner {
36+
/// return self.owner;
37+
/// }
38+
///
39+
/// When we execute `caller.getter`, we'll end up doing something like:
40+
///
41+
/// const res = @call(.auto, Cat.get_owner, .{cat_instance});
42+
///
43+
/// How do we turn `res`, which is an *Owner, into something we can return
44+
/// to v8? We need the ObjectTemplate associated with Owner. How do we
45+
/// get that? Well, we store all the ObjectTemplates in an array that's
46+
/// tied to env. So we do something like:
47+
///
48+
/// env.templates[index_of_owner].initInstance(...);
49+
///
50+
/// But how do we get that `index_of_owner`? `Index` is an enum
51+
/// that looks like:
52+
///
53+
/// pub const Index = enum(BackingInt) {
54+
/// cat = 0,
55+
/// owner = 1,
56+
/// ...
57+
/// }
58+
///
59+
/// (`BackingInt` is calculated at comptime regarding to interfaces we have)
60+
/// So to get the template index of `owner`, simply do:
61+
///
62+
/// const index_id = types.getId(@TypeOf(res));
63+
pub const Index = blk: {
64+
var fields: [Types.len]std.builtin.Type.EnumField = undefined;
65+
for (Types, 0..) |s, i| {
6766
const Struct = s.defaultValue().?;
68-
if (@hasDecl(Struct, "prototype") and @typeInfo(Struct.prototype) != .pointer) {
69-
@compileError(std.fmt.comptimePrint("Prototype '{s}' for type '{s} must be a pointer", .{ @typeName(Struct.prototype), @typeName(Struct) }));
70-
}
71-
72-
fields[i] = .{
73-
.name = @typeName(Receiver(Struct)),
74-
.type = usize,
75-
.is_comptime = true,
76-
.alignment = @alignOf(usize),
77-
.default_value_ptr = &i,
78-
};
67+
fields[i] = .{ .name = @typeName(Receiver(Struct)), .value = i };
7968
}
80-
break :blk @Type(.{ .@"struct" = .{
81-
.layout = .auto,
82-
.decls = &.{},
83-
.is_tuple = false,
84-
.fields = &fields,
85-
} });
69+
70+
break :blk @Type(.{
71+
.@"enum" = .{
72+
.fields = &fields,
73+
.tag_type = BackingInt,
74+
.is_exhaustive = true,
75+
.decls = &.{},
76+
},
77+
});
8678
};
8779

88-
pub const LOOKUP = Lookup{};
80+
/// Returns a boolean indicating if a type exist in the `Index`.
81+
pub inline fn has(t: type) bool {
82+
return @hasField(Index, @typeName(t));
83+
}
8984

90-
// Creates a list where the index of a type contains its prototype index
91-
// const Animal = struct{};
92-
// const Cat = struct{
93-
// pub const prototype = *Animal;
94-
// };
95-
//
96-
// Would create an array: [0, 0]
97-
// Animal, at index, 0, has no prototype, so we set it to itself
98-
// Cat, at index 1, has an Animal prototype, so we set it to 0.
99-
//
100-
// When we're trying to pass an argument to a Zig function, we'll know the
101-
// target type (the function parameter type), and we'll have a
102-
// TaggedAnyOpaque which will have the index of the type of that parameter.
103-
// We'll use the PROTOTYPE_TABLE to see if the TaggedAnyType should be
104-
// cast to a prototype.
105-
pub const PROTOTYPE_TABLE = blk: {
106-
var table: [Types.len]u16 = undefined;
85+
/// Returns the `Index` for the given type.
86+
pub inline fn getIndex(t: type) Index {
87+
return @field(Index, @typeName(t));
88+
}
89+
90+
/// Returns the ID for the given type.
91+
pub inline fn getId(t: type) BackingInt {
92+
return @intFromEnum(getIndex(t));
93+
}
94+
95+
/// Creates a list where the index of a type contains its prototype index.
96+
/// const Animal = struct{};
97+
/// const Cat = struct{
98+
/// pub const prototype = *Animal;
99+
/// };
100+
///
101+
/// Would create an array of indexes:
102+
/// [Index.Animal, Index.Animal]
103+
///
104+
/// `Animal`, at index, 0, has no prototype, so we set it to itself.
105+
/// `Cat`, at index 1, has an `Animal` prototype, so we set it to `Animal`.
106+
///
107+
/// When we're trying to pass an argument to a Zig function, we'll know the
108+
/// target type (the function parameter type), and we'll have a
109+
/// TaggedAnyOpaque which will have the index of the type of that parameter.
110+
/// We'll use the `PrototypeTable` to see if the TaggedAnyType should be
111+
/// cast to a prototype.
112+
pub const PrototypeTable = blk: {
113+
var table: [Types.len]BackingInt = undefined;
107114
for (Types, 0..) |s, i| {
108-
var prototype_index = i;
109115
const Struct = s.defaultValue().?;
110-
if (@hasDecl(Struct, "prototype")) {
111-
const TI = @typeInfo(Struct.prototype);
112-
const proto_name = @typeName(Receiver(TI.pointer.child));
113-
prototype_index = @field(LOOKUP, proto_name);
114-
}
115-
table[i] = prototype_index;
116+
table[i] = proto_index: {
117+
if (@hasDecl(Struct, "prototype")) {
118+
const prototype_field = @field(Struct, "prototype");
119+
// This prototype type check has nothing to do with building our
120+
// Lookup. But we put it here, early, so that the rest of the
121+
// code doesn't have to worry about checking if Struct.prototype is
122+
// a pointer.
123+
break :proto_index switch (@typeInfo(prototype_field)) {
124+
.pointer => |pointer| getId(Receiver(pointer.child)),
125+
inline else => @compileError(std.fmt.comptimePrint("Prototype '{s}' for type '{s}' must be a pointer", .{
126+
prototype_field,
127+
@typeName(Struct),
128+
})),
129+
};
130+
}
131+
132+
break :proto_index i;
133+
};
116134
}
135+
117136
break :blk table;
118137
};
119138

120-
// This is essentially meta data for each type. Each is stored in env.meta_lookup
121-
// The index for a type can be retrieved via:
122-
// const index = @field(TYPE_LOOKUP, @typeName(Receiver(Struct)));
123-
// const meta = env.meta_lookup[index];
139+
/// This is essentially meta data for each type. Each is stored in `env.meta_lookup`.
140+
/// The index for a type can be retrieved via:
141+
/// const index = types.getIndex(Receiver(Struct));
142+
/// const meta = env.meta_lookup[@intFromEnum(index)];
143+
///
144+
/// Or:
145+
/// const id = types.getId(Receiver(Struct));
146+
/// const meta = env.meta_lookup[id];
124147
pub const Meta = struct {
125148
// Every type is given a unique index. That index is used to lookup various
126149
// things, i.e. the prototype chain.
127-
index: u16,
150+
index: BackingInt,
128151

129152
// We store the type's subtype here, so that when we create an instance of
130153
// the type, and bind it to JavaScript, we can store the subtype along with

0 commit comments

Comments
 (0)