mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 22:53:28 +00:00
Improve prototype resolution for native types
Prototype resolution of Zig types previously had 2 limitations (bug?). The first
was that the Zig prototype chain could only be 1 deep. You couldn't do A->B->C
where each of those was a Zig type (but you could do A->B->C->D->E ... so long
as every other type was a C opaque value).
The other limitation was that Zig prototypes only worked when the nested field
was directly embedded in the struct (i.e. not a pointer). So you could do:
```zig
const X = struct {
proto: XParent,
};
```
But not:
```zig
const X = struct {
proto: *XParent,
};
```
This addresses both limitations. The first issue is solved by keeping track
of the cumulative offset
This commit is contained in:
@@ -162,7 +162,7 @@ test "Performance: get_timeOrigin" {
|
||||
try testing.expect(time_origin >= 0);
|
||||
|
||||
// Check resolution
|
||||
try testing.expectDelta(@rem(time_origin * std.time.us_per_ms, 100.0), 0.0, 0.1);
|
||||
try testing.expectDelta(@rem(time_origin * std.time.us_per_ms, 100.0), 0.0, 0.2);
|
||||
}
|
||||
|
||||
test "Performance: now" {
|
||||
|
||||
@@ -81,14 +81,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
// that looks like:
|
||||
//
|
||||
// const TypeLookup = struct {
|
||||
// comptime cat: usize = TypeMeta{.index = 0, ...},
|
||||
// comptime owner: usize = TypeMeta{.index = 1, ...},
|
||||
// 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)).index;
|
||||
// const index_id = @field(type_lookup, @typeName(@TypeOf(res));
|
||||
//
|
||||
const TypeLookup = comptime blk: {
|
||||
var fields: [Types.len]std.builtin.Type.StructField = undefined;
|
||||
@@ -103,15 +103,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) 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")) Struct.subtype else null;
|
||||
|
||||
const R = Receiver(Struct);
|
||||
fields[i] = .{
|
||||
.name = @typeName(R),
|
||||
.type = TypeMeta,
|
||||
.name = @typeName(Receiver(Struct)),
|
||||
.type = usize,
|
||||
.is_comptime = true,
|
||||
.alignment = @alignOf(usize),
|
||||
.default_value_ptr = &TypeMeta{ .index = i, .subtype = subtype },
|
||||
.default_value_ptr = &i,
|
||||
};
|
||||
}
|
||||
break :blk @Type(.{ .@"struct" = .{
|
||||
@@ -146,7 +143,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) 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).index;
|
||||
prototype_index = @field(TYPE_LOOKUP, proto_name);
|
||||
}
|
||||
table[i] = prototype_index;
|
||||
}
|
||||
@@ -168,7 +165,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) 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)).index
|
||||
// const index = @field(TYPE_LOOKUP, @typeName(type_name))
|
||||
// const template = templates[index];
|
||||
templates: [Types.len]v8.FunctionTemplate,
|
||||
|
||||
@@ -177,6 +174,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
// index.
|
||||
prototype_lookup: [Types.len]u16,
|
||||
|
||||
meta_lookup: [Types.len]TypeMeta,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const TYPE_LOOKUP = TypeLookup{};
|
||||
@@ -222,13 +221,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
.templates = undefined,
|
||||
.allocator = allocator,
|
||||
.isolate_params = params,
|
||||
.meta_lookup = undefined,
|
||||
.prototype_lookup = undefined,
|
||||
};
|
||||
|
||||
// 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).index
|
||||
// we can get its index via: @field(TYPE_LOOKUP, type_name)
|
||||
const templates = &env.templates;
|
||||
inline for (Types, 0..) |s, i| {
|
||||
@setEvalBranchQuota(10_000);
|
||||
@@ -237,6 +237,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
|
||||
// Above, we've created all our our FunctionTemplates. Now that we
|
||||
// have them all, we can hook up the prototypes.
|
||||
const meta_lookup = &env.meta_lookup;
|
||||
inline for (Types, 0..) |s, i| {
|
||||
const Struct = s.defaultValue().?;
|
||||
if (@hasDecl(Struct, "prototype")) {
|
||||
@@ -249,9 +250,32 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
// Just like we said above, given a type, we can get its
|
||||
// template index.
|
||||
|
||||
const proto_index = @field(TYPE_LOOKUP, proto_name).index;
|
||||
const proto_index = @field(TYPE_LOOKUP, proto_name);
|
||||
templates[i].inherit(templates[proto_index]);
|
||||
}
|
||||
|
||||
// while we're here, let's populate our meta lookup
|
||||
const subtype: ?SubType = if (@hasDecl(Struct, "subtype")) Struct.subtype else null;
|
||||
|
||||
const proto_offset = comptime blk: {
|
||||
if (!@hasField(Struct, "proto")) {
|
||||
break :blk 0;
|
||||
}
|
||||
const proto_info = std.meta.fieldInfo(Struct, .proto);
|
||||
if (@typeInfo(proto_info.type) == .pointer) {
|
||||
// we store the offset as a negative, to so that,
|
||||
// when we reverse this, we know that it's
|
||||
// behind a pointer that we need to resolve.
|
||||
break :blk -@offsetOf(Struct, "proto");
|
||||
}
|
||||
break :blk @offsetOf(Struct, "proto");
|
||||
};
|
||||
|
||||
meta_lookup[i] = .{
|
||||
.index = i,
|
||||
.subtype = subtype,
|
||||
.proto_offset = proto_offset,
|
||||
};
|
||||
}
|
||||
|
||||
return env;
|
||||
@@ -391,7 +415,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) 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).index;
|
||||
const proto_index = @field(TYPE_LOOKUP, proto_name);
|
||||
js_global.inherit(templates[proto_index]);
|
||||
}
|
||||
|
||||
@@ -414,7 +438,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
@compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name);
|
||||
}
|
||||
|
||||
const proto_index = @field(TYPE_LOOKUP, proto_name).index;
|
||||
const proto_index = @field(TYPE_LOOKUP, proto_name);
|
||||
const proto_obj = templates[proto_index].getFunction(v8_context).toObject();
|
||||
|
||||
const self_obj = templates[i].getFunction(v8_context).toObject();
|
||||
@@ -449,6 +473,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
.isolate = isolate,
|
||||
.v8_context = v8_context,
|
||||
.templates = &env.templates,
|
||||
.meta_lookup = &env.meta_lookup,
|
||||
.handle_scope = handle_scope,
|
||||
.call_arena = self.call_arena.allocator(),
|
||||
.context_arena = self.context_arena.allocator(),
|
||||
@@ -551,9 +576,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
v8_context: v8.Context,
|
||||
handle_scope: ?v8.HandleScope,
|
||||
|
||||
// references the Env.template array
|
||||
// references Env.templates
|
||||
templates: []v8.FunctionTemplate,
|
||||
|
||||
// references the Env.meta_lookup
|
||||
meta_lookup: []TypeMeta,
|
||||
|
||||
// An arena for the lifetime of a call-group. Gets reset whenever
|
||||
// call_depth reaches 0.
|
||||
call_arena: Allocator,
|
||||
@@ -845,12 +873,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
// well as any meta data we'll need to use it later.
|
||||
// See the TaggedAnyOpaque struct for more details.
|
||||
const tao = try context_arena.create(TaggedAnyOpaque);
|
||||
const meta = @field(TYPE_LOOKUP, @typeName(ptr.child));
|
||||
const meta_index = @field(TYPE_LOOKUP, @typeName(ptr.child));
|
||||
const meta = self.meta_lookup[meta_index];
|
||||
|
||||
tao.* = .{
|
||||
.ptr = value,
|
||||
.index = meta.index,
|
||||
.subtype = meta.subtype,
|
||||
.offset = if (@typeInfo(ptr.child) != .@"opaque" and @hasField(ptr.child, "proto")) @offsetOf(ptr.child, "proto") else -1,
|
||||
};
|
||||
|
||||
js_obj.setInternalField(0, v8.External.init(isolate, tao));
|
||||
@@ -906,7 +935,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
}
|
||||
if (@hasField(TypeLookup, @typeName(ptr.child))) {
|
||||
const js_obj = js_value.castTo(v8.Object);
|
||||
return typeTaggedAnyOpaque(named_function, *Receiver(ptr.child), js_obj);
|
||||
return self.typeTaggedAnyOpaque(named_function, *Receiver(ptr.child), js_obj);
|
||||
}
|
||||
},
|
||||
.slice => {
|
||||
@@ -1201,7 +1230,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
// of having a version of typeTaggedAnyOpaque which
|
||||
// returns a boolean or an optional, we rely on the
|
||||
// main implementation and just handle the error.
|
||||
const attempt = typeTaggedAnyOpaque(named_function, *Receiver(ptr.child), js_obj);
|
||||
const attempt = self.typeTaggedAnyOpaque(named_function, *Receiver(ptr.child), js_obj);
|
||||
if (attempt) |value| {
|
||||
return .{ .value = value };
|
||||
} else |_| {
|
||||
@@ -1387,6 +1416,78 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_specifier);
|
||||
return m.handle;
|
||||
}
|
||||
|
||||
// Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque
|
||||
// contains a ptr to the correct type.
|
||||
fn typeTaggedAnyOpaque(self: *const JsContext, comptime named_function: NamedFunction, comptime R: type, js_obj: v8.Object) !R {
|
||||
const ti = @typeInfo(R);
|
||||
if (ti != .pointer) {
|
||||
@compileError(named_function.full_name ++ "has a non-pointer Zig parameter type: " ++ @typeName(R));
|
||||
}
|
||||
|
||||
const T = ti.pointer.child;
|
||||
if (comptime isEmpty(T)) {
|
||||
// Empty structs aren't stored as TOAs and there's no data
|
||||
// stored in the JSObject's IntenrnalField. Why bother when
|
||||
// we can just return an empty struct here?
|
||||
return @constCast(@as(*const T, &.{}));
|
||||
}
|
||||
|
||||
// if it isn't an empty struct, then the v8.Object should have an
|
||||
// InternalFieldCount > 0, since our toa pointer should be embedded
|
||||
// at index 0 of the internal field count.
|
||||
if (js_obj.internalFieldCount() == 0) {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
||||
const type_name = @typeName(T);
|
||||
if (@hasField(TypeLookup, type_name) == false) {
|
||||
@compileError(named_function.full_name ++ "has an unknown Zig type: " ++ @typeName(R));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var type_index = toa.index;
|
||||
if (type_index == expected_type_index) {
|
||||
return @alignCast(@ptrCast(toa.ptr));
|
||||
}
|
||||
|
||||
const meta_lookup = self.meta_lookup;
|
||||
|
||||
// If we have N levels deep of prototypes, then the offset is the
|
||||
// sum at each level...
|
||||
var total_offset: usize = 0;
|
||||
|
||||
// ...unless, the proto is behind a pointer, then total_offset will
|
||||
// get reset to 0, and our base_ptr will move to the address
|
||||
// referenced by the proto field.
|
||||
var base_ptr: usize = @intFromPtr(toa.ptr);
|
||||
|
||||
// search through the prototype tree
|
||||
while (true) {
|
||||
const proto_offset = meta_lookup[type_index].proto_offset;
|
||||
if (proto_offset < 0) {
|
||||
base_ptr = @as(*align(1) usize, @ptrFromInt(base_ptr + total_offset + @as(usize, @intCast(-proto_offset)))).*;
|
||||
total_offset = 0;
|
||||
} else {
|
||||
total_offset += @intCast(proto_offset);
|
||||
}
|
||||
|
||||
const prototype_index = PROTOTYPE_TABLE[type_index];
|
||||
if (prototype_index == expected_type_index) {
|
||||
return @ptrFromInt(base_ptr + total_offset);
|
||||
}
|
||||
|
||||
if (prototype_index == type_index) {
|
||||
// When a type has itself as the prototype, then we've
|
||||
// reached the end of the chain.
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
type_index = prototype_index;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Function = struct {
|
||||
@@ -2000,7 +2101,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
const template = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
// See comment above. We generateConstructor on all types
|
||||
@@ -2045,7 +2146,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
const function_template = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, name);
|
||||
@@ -2062,7 +2163,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
const function_template = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, "static_" ++ name);
|
||||
@@ -2098,7 +2199,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
const getter_callback = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, "get_" ++ name);
|
||||
@@ -2119,7 +2220,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||
std.debug.assert(info.length() == 1);
|
||||
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, "set_" ++ name);
|
||||
@@ -2140,7 +2241,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
.getter = struct {
|
||||
fn callback(idx: u32, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, "indexed_get");
|
||||
@@ -2176,7 +2277,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
.getter = struct {
|
||||
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, "named_get");
|
||||
@@ -2198,7 +2299,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
configuration.setter = struct {
|
||||
fn callback(c_name: ?*const v8.C_Name, c_value: ?*const v8.C_Value, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, "named_set");
|
||||
@@ -2214,7 +2315,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
configuration.deleter = struct {
|
||||
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, "named_delete");
|
||||
@@ -2260,7 +2361,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
template.setCallAsFunctionHandler(struct {
|
||||
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||
var caller = Caller(Self, State).init(info);
|
||||
var caller = Caller(JsContext, State).init(info);
|
||||
defer caller.deinit();
|
||||
|
||||
const named_function = comptime NamedFunction.init(Struct, "jsCallAsFunction");
|
||||
@@ -2315,7 +2416,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
.one => {
|
||||
const type_name = @typeName(ptr.child);
|
||||
if (@hasField(TypeLookup, type_name)) {
|
||||
const template = templates[@field(TYPE_LOOKUP, type_name).index];
|
||||
const template = templates[@field(TYPE_LOOKUP, type_name)];
|
||||
const js_obj = try JsContext.mapZigInstanceToJs(v8_context, template, value);
|
||||
return js_obj.toValue();
|
||||
}
|
||||
@@ -2351,7 +2452,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
.@"struct" => |s| {
|
||||
const type_name = @typeName(T);
|
||||
if (@hasField(TypeLookup, type_name)) {
|
||||
const template = templates[@field(TYPE_LOOKUP, type_name).index];
|
||||
const template = templates[@field(TYPE_LOOKUP, type_name)];
|
||||
const js_obj = try JsContext.mapZigInstanceToJs(v8_context, template, value);
|
||||
return js_obj.toValue();
|
||||
}
|
||||
@@ -2420,69 +2521,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
|
||||
|
||||
@compileError("A function returns an unsupported type: " ++ @typeName(T));
|
||||
}
|
||||
// Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque
|
||||
// contains a ptr to the correct type.
|
||||
fn typeTaggedAnyOpaque(comptime named_function: NamedFunction, comptime R: type, js_obj: v8.Object) !R {
|
||||
const ti = @typeInfo(R);
|
||||
if (ti != .pointer) {
|
||||
@compileError(named_function.full_name ++ "has a non-pointer Zig parameter type: " ++ @typeName(R));
|
||||
}
|
||||
|
||||
const T = ti.pointer.child;
|
||||
if (comptime isEmpty(T)) {
|
||||
// Empty structs aren't stored as TOAs and there's no data
|
||||
// stored in the JSObject's IntenrnalField. Why bother when
|
||||
// we can just return an empty struct here?
|
||||
return @constCast(@as(*const T, &.{}));
|
||||
}
|
||||
|
||||
// if it isn't an empty struct, then the v8.Object should have an
|
||||
// InternalFieldCount > 0, since our toa pointer should be embedded
|
||||
// at index 0 of the internal field count.
|
||||
if (js_obj.internalFieldCount() == 0) {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
||||
const type_name = @typeName(T);
|
||||
if (@hasField(TypeLookup, type_name) == false) {
|
||||
@compileError(named_function.full_name ++ "has an unknown Zig type: " ++ @typeName(R));
|
||||
}
|
||||
|
||||
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).index;
|
||||
|
||||
var type_index = toa.index;
|
||||
if (type_index == expected_type_index) {
|
||||
return @alignCast(@ptrCast(toa.ptr));
|
||||
}
|
||||
|
||||
// search through the prototype tree
|
||||
while (true) {
|
||||
const prototype_index = PROTOTYPE_TABLE[type_index];
|
||||
if (prototype_index == expected_type_index) {
|
||||
// -1 is a sentinel value used for non-composition prototype
|
||||
// This is used with netsurf and we just unsafely cast one
|
||||
// type to another
|
||||
const offset = toa.offset;
|
||||
if (offset == -1) {
|
||||
return @alignCast(@ptrCast(toa.ptr));
|
||||
}
|
||||
|
||||
// A non-negative offset means we're using composition prototype
|
||||
// (i.e. our struct has a "proto" field). the offset
|
||||
// reresents the byte offset of the field. We can use that
|
||||
// + the toa.ptr to get the field
|
||||
return @ptrFromInt(@intFromPtr(toa.ptr) + @as(usize, @intCast(offset)));
|
||||
}
|
||||
if (prototype_index == type_index) {
|
||||
// When a type has itself as the prototype, then we've
|
||||
// reached the end of the chain.
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
type_index = prototype_index;
|
||||
}
|
||||
}
|
||||
|
||||
// An interface for types that want to have their jsDeinit function to be
|
||||
// called when the call context ends
|
||||
@@ -2542,24 +2580,24 @@ pub fn Env(comptime State: type, comptime WebApis: type) 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.
|
||||
// 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];
|
||||
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,
|
||||
index: u16,
|
||||
|
||||
// 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,
|
||||
|
||||
// If this type has composition-based prototype, represents the byte-offset
|
||||
// from ptr where the `proto` field is located. A negative offsets is used
|
||||
// to indicate that the prototype field is behind a pointer.
|
||||
proto_offset: i32,
|
||||
};
|
||||
|
||||
// When we map a Zig instance into a JsObject, we'll normally store the a
|
||||
@@ -2590,9 +2628,9 @@ fn isComplexAttributeType(ti: std.builtin.Type) bool {
|
||||
// probably just contained in ExecutionWorld, but having this specific logic, which
|
||||
// is somewhat repetitive between constructors, functions, getters, etc contained
|
||||
// here does feel like it makes it clenaer.
|
||||
fn Caller(comptime E: type, comptime State: type) type {
|
||||
fn Caller(comptime JsContext: type, comptime State: type) type {
|
||||
return struct {
|
||||
js_context: *E.JsContext,
|
||||
js_context: *JsContext,
|
||||
v8_context: v8.Context,
|
||||
isolate: v8.Isolate,
|
||||
call_arena: Allocator,
|
||||
@@ -2605,7 +2643,7 @@ fn Caller(comptime E: type, comptime State: type) type {
|
||||
fn init(info: anytype) Self {
|
||||
const isolate = info.getIsolate();
|
||||
const v8_context = isolate.getCurrentContext();
|
||||
const js_context: *E.JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
||||
const js_context: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
||||
|
||||
js_context.call_depth += 1;
|
||||
return .{
|
||||
@@ -2653,9 +2691,9 @@ fn Caller(comptime E: type, comptime State: type) type {
|
||||
const this = info.getThis();
|
||||
if (@typeInfo(ReturnType) == .error_union) {
|
||||
const non_error_res = res catch |err| return err;
|
||||
_ = try E.JsContext.mapZigInstanceToJs(self.v8_context, this, non_error_res);
|
||||
_ = try JsContext.mapZigInstanceToJs(self.v8_context, this, non_error_res);
|
||||
} else {
|
||||
_ = try E.JsContext.mapZigInstanceToJs(self.v8_context, this, res);
|
||||
_ = try JsContext.mapZigInstanceToJs(self.v8_context, this, res);
|
||||
}
|
||||
info.getReturnValue().set(this);
|
||||
}
|
||||
@@ -2668,7 +2706,7 @@ fn Caller(comptime E: type, comptime State: type) type {
|
||||
const js_context = self.js_context;
|
||||
const func = @field(Struct, named_function.name);
|
||||
var args = try self.getArgs(Struct, named_function, 1, info);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
const zig_instance = try js_context.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
|
||||
// inject 'self' as the first parameter
|
||||
@field(args, "0") = zig_instance;
|
||||
@@ -2700,7 +2738,7 @@ fn Caller(comptime E: type, comptime State: type) type {
|
||||
switch (arg_fields.len) {
|
||||
0, 1, 2 => @compileError(named_function.full_name ++ " must take at least a u32 and *bool parameter"),
|
||||
3, 4 => {
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
const zig_instance = try js_context.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
comptime assertSelfReceiver(Struct, named_function);
|
||||
@field(args, "0") = zig_instance;
|
||||
@field(args, "1") = idx;
|
||||
@@ -2722,12 +2760,13 @@ fn Caller(comptime E: type, comptime State: type) type {
|
||||
}
|
||||
|
||||
fn getNamedIndex(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, name: v8.Name, info: v8.PropertyCallbackInfo) !u8 {
|
||||
const js_context = self.js_context;
|
||||
const func = @field(Struct, named_function.name);
|
||||
comptime assertSelfReceiver(Struct, named_function);
|
||||
|
||||
var has_value = true;
|
||||
var args = try self.getArgs(Struct, named_function, 3, info);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
const zig_instance = try js_context.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
@field(args, "0") = zig_instance;
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
@field(args, "2") = &has_value;
|
||||
@@ -2747,7 +2786,7 @@ fn Caller(comptime E: type, comptime State: type) type {
|
||||
|
||||
var has_value = true;
|
||||
var args = try self.getArgs(Struct, named_function, 4, info);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
const zig_instance = try js_context.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
@field(args, "0") = zig_instance;
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
@field(args, "2") = try js_context.jsValueToZig(named_function, @TypeOf(@field(args, "2")), js_value);
|
||||
@@ -2758,12 +2797,13 @@ fn Caller(comptime E: type, comptime State: type) type {
|
||||
}
|
||||
|
||||
fn deleteNamedIndex(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, name: v8.Name, info: v8.PropertyCallbackInfo) !u8 {
|
||||
const js_context = self.js_context;
|
||||
const func = @field(Struct, named_function.name);
|
||||
comptime assertSelfReceiver(Struct, named_function);
|
||||
|
||||
var has_value = true;
|
||||
var args = try self.getArgs(Struct, named_function, 3, info);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
const zig_instance = try js_context.typeTaggedAnyOpaque(named_function, *Receiver(Struct), info.getThis());
|
||||
@field(args, "0") = zig_instance;
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
@field(args, "2") = &has_value;
|
||||
@@ -3379,12 +3419,6 @@ const TaggedAnyOpaque = struct {
|
||||
// PROTOTYPE_TABLE
|
||||
index: u16,
|
||||
|
||||
// If this type has composition-based prototype, represents the byte-offset
|
||||
// from ptr where the `proto` field is located. The value -1 represents
|
||||
// unsafe prototype where we can just cast ptr to the destination type
|
||||
// (this is used extensively with netsurf)
|
||||
offset: i32,
|
||||
|
||||
// Ptr to the Zig instance. Between the context where it's called (i.e.
|
||||
// we have the comptime parameter info for all functions), and the index field
|
||||
// we can figure out what type this is.
|
||||
|
||||
@@ -77,6 +77,117 @@ pub const MyAPI = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Parent = packed struct {
|
||||
parent_id: i32 = 0,
|
||||
|
||||
pub fn get_parent(self: *const Parent) i32 {
|
||||
return self.parent_id;
|
||||
}
|
||||
pub fn set_parent(self: *Parent, id: i32) void {
|
||||
self.parent_id = id;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Middle = struct {
|
||||
pub const prototype = *Parent;
|
||||
|
||||
middle_id: i32 = 0,
|
||||
_padding_1: u8 = 0,
|
||||
_padding_2: u8 = 1,
|
||||
_padding_3: u8 = 2,
|
||||
proto: Parent,
|
||||
|
||||
pub fn constructor() Middle {
|
||||
return .{
|
||||
.middle_id = 0,
|
||||
.proto = .{ .parent_id = 0 },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_middle(self: *const Middle) i32 {
|
||||
return self.middle_id;
|
||||
}
|
||||
pub fn set_middle(self: *Middle, id: i32) void {
|
||||
self.middle_id = id;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Child = struct {
|
||||
pub const prototype = *Middle;
|
||||
|
||||
child_id: i32 = 0,
|
||||
_padding_1: u8 = 0,
|
||||
proto: Middle,
|
||||
|
||||
pub fn constructor() Child {
|
||||
return .{
|
||||
.child_id = 0,
|
||||
.proto = .{ .middle_id = 0, .proto = .{ .parent_id = 0 } },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_child(self: *const Child) i32 {
|
||||
return self.child_id;
|
||||
}
|
||||
pub fn set_child(self: *Child, id: i32) void {
|
||||
self.child_id = id;
|
||||
}
|
||||
};
|
||||
|
||||
pub const MiddlePtr = packed struct {
|
||||
pub const prototype = *Parent;
|
||||
|
||||
middle_id: i32 = 0,
|
||||
_padding_1: u8 = 0,
|
||||
_padding_2: u8 = 1,
|
||||
_padding_3: u8 = 2,
|
||||
proto: *Parent,
|
||||
|
||||
pub fn constructor(state: State) !MiddlePtr {
|
||||
const parent = try state.arena.create(Parent);
|
||||
parent.* = .{ .parent_id = 0 };
|
||||
return .{
|
||||
.middle_id = 0,
|
||||
.proto = parent,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_middle(self: *const MiddlePtr) i32 {
|
||||
return self.middle_id;
|
||||
}
|
||||
pub fn set_middle(self: *MiddlePtr, id: i32) void {
|
||||
self.middle_id = id;
|
||||
}
|
||||
};
|
||||
|
||||
pub const ChildPtr = packed struct {
|
||||
pub const prototype = *MiddlePtr;
|
||||
|
||||
child_id: i32 = 0,
|
||||
_padding_1: u8 = 0,
|
||||
_padding_2: u8 = 1,
|
||||
proto: *MiddlePtr,
|
||||
|
||||
pub fn constructor(state: State) !ChildPtr {
|
||||
const parent = try state.arena.create(Parent);
|
||||
const middle = try state.arena.create(MiddlePtr);
|
||||
|
||||
parent.* = .{ .parent_id = 0 };
|
||||
middle.* = .{ .middle_id = 0, .proto = parent };
|
||||
return .{
|
||||
.child_id = 0,
|
||||
.proto = middle,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_child(self: *const ChildPtr) i32 {
|
||||
return self.child_id;
|
||||
}
|
||||
pub fn set_child(self: *ChildPtr, id: i32) void {
|
||||
self.child_id = id;
|
||||
}
|
||||
};
|
||||
|
||||
const State = struct {
|
||||
arena: Allocator,
|
||||
};
|
||||
@@ -90,6 +201,11 @@ test "JS: object types" {
|
||||
Other,
|
||||
MyObject,
|
||||
MyAPI,
|
||||
Parent,
|
||||
Middle,
|
||||
Child,
|
||||
MiddlePtr,
|
||||
ChildPtr,
|
||||
}).init(.{ .arena = arena.allocator() }, {});
|
||||
|
||||
defer runner.deinit();
|
||||
@@ -120,4 +236,40 @@ test "JS: object types" {
|
||||
// check object property
|
||||
.{ "myObjIndirect.a.val()", "4" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "let m1 = new Middle();", null },
|
||||
.{ "m1.middle = 2", null },
|
||||
.{ "m1.parent = 3", null },
|
||||
.{ "m1.middle", "2" },
|
||||
.{ "m1.parent", "3" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "let c1 = new Child();", null },
|
||||
.{ "c1.child = 1", null },
|
||||
.{ "c1.middle = 2", null },
|
||||
.{ "c1.parent = 3", null },
|
||||
.{ "c1.child", "1" },
|
||||
.{ "c1.middle", "2" },
|
||||
.{ "c1.parent", "3" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "let m2 = new MiddlePtr();", null },
|
||||
.{ "m2.middle = 2", null },
|
||||
.{ "m2.parent = 3", null },
|
||||
.{ "m2.middle", "2" },
|
||||
.{ "m2.parent", "3" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "let c2 = new ChildPtr();", null },
|
||||
.{ "c2.child = 1", null },
|
||||
.{ "c2.middle = 2", null },
|
||||
.{ "c2.parent = 3", null },
|
||||
.{ "c2.child", "1" },
|
||||
.{ "c2.middle", "2" },
|
||||
.{ "c2.parent", "3" },
|
||||
}, .{});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user