mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-23 05:04:42 +00:00
Merge pull request #1557 from lightpanda-io/internal_field_caching
Add internal field caching (for window.document and window.console)
This commit is contained in:
@@ -471,13 +471,14 @@ pub const Function = struct {
|
|||||||
// We support two ways to cache a value directly into a v8::Object. The
|
// We support two ways to cache a value directly into a v8::Object. The
|
||||||
// difference between the two is like the difference between a Map
|
// difference between the two is like the difference between a Map
|
||||||
// and a Struct.
|
// and a Struct.
|
||||||
// 1 - Using the object's private state with a v8::Private key. Think of
|
// 1 - Using the object's internal fields. Think of this as
|
||||||
// this as a HashMap. It takes no memory if the cache isn't used
|
|
||||||
// but has overhead when used.
|
|
||||||
// 2 - (TODO) Using the object's internal fields. Think of this as
|
|
||||||
// adding a field to the struct. It's fast, but the space is reserved
|
// adding a field to the struct. It's fast, but the space is reserved
|
||||||
// upfront for _every_ instance, whether we use it or not.
|
// upfront for _every_ instance, whether we use it or not.
|
||||||
//
|
//
|
||||||
|
// 2 - Using the object's private state with a v8::Private key. Think of
|
||||||
|
// this as a HashMap. It takes no memory if the cache isn't used
|
||||||
|
// but has overhead when used.
|
||||||
|
//
|
||||||
// Consider `window.document`, (1) we have relatively few Window objects,
|
// Consider `window.document`, (1) we have relatively few Window objects,
|
||||||
// (2) They all have a document and (3) The document is accessed _a lot_.
|
// (2) They all have a document and (3) The document is accessed _a lot_.
|
||||||
// An internal field makes sense.
|
// An internal field makes sense.
|
||||||
@@ -485,9 +486,9 @@ pub const Function = struct {
|
|||||||
// Consider `node.childNodes`, (1) we can have 20K+ node objects, (2)
|
// Consider `node.childNodes`, (1) we can have 20K+ node objects, (2)
|
||||||
// 95% of nodes will never have their .childNodes access by JavaScript.
|
// 95% of nodes will never have their .childNodes access by JavaScript.
|
||||||
// Private map lookup makes sense.
|
// Private map lookup makes sense.
|
||||||
const Caching = union(enum) {
|
pub const Caching = union(enum) {
|
||||||
|
internal: u8,
|
||||||
private: []const u8,
|
private: []const u8,
|
||||||
// TODO internal_field: u8,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -567,6 +568,24 @@ pub const Function = struct {
|
|||||||
const return_value = info.getReturnValue();
|
const return_value = info.getReturnValue();
|
||||||
|
|
||||||
switch (cache) {
|
switch (cache) {
|
||||||
|
.internal => |idx| {
|
||||||
|
if (v8.v8__Object__GetInternalField(js_this, idx)) |cached| {
|
||||||
|
// means we can't cache undefined, since we can't tell the
|
||||||
|
// difference between "it isn't in the cache" and "it's
|
||||||
|
// in the cache with a valud of undefined"
|
||||||
|
if (!v8.v8__Value__IsUndefined(cached)) {
|
||||||
|
return_value.set(cached);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store this so that we can quickly save the result into the cache
|
||||||
|
cache_state.* = .{
|
||||||
|
.js_this = js_this,
|
||||||
|
.v8_context = v8_context,
|
||||||
|
.mode = .{ .internal = idx },
|
||||||
|
};
|
||||||
|
},
|
||||||
.private => |private_symbol| {
|
.private => |private_symbol| {
|
||||||
const global_handle = &@field(ctx.env.private_symbols, private_symbol).handle;
|
const global_handle = &@field(ctx.env.private_symbols, private_symbol).handle;
|
||||||
const private_key: *const v8.Private = v8.v8__Global__Get(global_handle, ctx.isolate.handle).?;
|
const private_key: *const v8.Private = v8.v8__Global__Get(global_handle, ctx.isolate.handle).?;
|
||||||
@@ -599,11 +618,14 @@ pub const Function = struct {
|
|||||||
js_this: *const v8.Object,
|
js_this: *const v8.Object,
|
||||||
v8_context: *const v8.Context,
|
v8_context: *const v8.Context,
|
||||||
mode: union(enum) {
|
mode: union(enum) {
|
||||||
|
internal: u8,
|
||||||
private: *const v8.Private,
|
private: *const v8.Private,
|
||||||
},
|
},
|
||||||
|
|
||||||
pub fn save(self: *const CacheState, comptime cache: Opts.Caching, js_value: js.Value) void {
|
pub fn save(self: *const CacheState, comptime cache: Opts.Caching, js_value: js.Value) void {
|
||||||
if (comptime cache == .private) {
|
if (comptime cache == .internal) {
|
||||||
|
v8.v8__Object__SetInternalField(self.js_this, self.mode.internal, js_value.handle);
|
||||||
|
} else {
|
||||||
var out: v8.MaybeBool = undefined;
|
var out: v8.MaybeBool = undefined;
|
||||||
v8.v8__Object__SetPrivate(self.js_this, self.v8_context, self.mode.private, js_value.handle, &out);
|
v8.v8__Object__SetPrivate(self.js_this, self.v8_context, self.mode.private, js_value.handle, &out);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,14 +226,31 @@ pub fn createContext(self: *Env, page: *Page, enter: bool) !*Context {
|
|||||||
|
|
||||||
// Get the global template that was created once per isolate
|
// Get the global template that was created once per isolate
|
||||||
const global_template: *const v8.ObjectTemplate = @ptrCast(@alignCast(v8.v8__Eternal__Get(&self.global_template, isolate.handle).?));
|
const global_template: *const v8.ObjectTemplate = @ptrCast(@alignCast(v8.v8__Eternal__Get(&self.global_template, isolate.handle).?));
|
||||||
|
v8.v8__ObjectTemplate__SetInternalFieldCount(global_template, comptime Snapshot.countInternalFields(Window.JsApi));
|
||||||
const v8_context = v8.v8__Context__New(isolate.handle, global_template, null).?;
|
const v8_context = v8.v8__Context__New(isolate.handle, global_template, null).?;
|
||||||
|
|
||||||
// Create the v8::Context and wrap it in a v8::Global
|
// Create the v8::Context and wrap it in a v8::Global
|
||||||
var context_global: v8.Global = undefined;
|
var context_global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(isolate.handle, v8_context, &context_global);
|
v8.v8__Global__New(isolate.handle, v8_context, &context_global);
|
||||||
|
|
||||||
// our window wrapped in a v8::Global
|
// get the global object for the context, this maps to our Window
|
||||||
const global_obj = v8.v8__Context__Global(v8_context).?;
|
const global_obj = v8.v8__Context__Global(v8_context).?;
|
||||||
|
{
|
||||||
|
// Store our TAO inside the internal field of the global object. This
|
||||||
|
// maps the v8::Object -> Zig instance. Almost all objects have this, and
|
||||||
|
// it gets setup automatically as objects are created, but the Window
|
||||||
|
// object already exists in v8 (it's the global) so we manually create
|
||||||
|
// the mapping here.
|
||||||
|
const tao = try context_arena.create(@import("TaggedOpaque.zig"));
|
||||||
|
tao.* = .{
|
||||||
|
.value = @ptrCast(page.window),
|
||||||
|
.prototype_chain = (&Window.JsApi.Meta.prototype_chain).ptr,
|
||||||
|
.prototype_len = @intCast(Window.JsApi.Meta.prototype_chain.len),
|
||||||
|
.subtype = .node, // this probably isn't right, but it's what we've been doing all along
|
||||||
|
};
|
||||||
|
v8.v8__Object__SetAlignedPointerInInternalField(global_obj, 0, tao);
|
||||||
|
}
|
||||||
|
// our window wrapped in a v8::Global
|
||||||
var global_global: v8.Global = undefined;
|
var global_global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(isolate.handle, global_obj, &global_global);
|
v8.v8__Global__New(isolate.handle, global_obj, &global_global);
|
||||||
|
|
||||||
|
|||||||
@@ -433,9 +433,12 @@ fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *v8.FunctionT
|
|||||||
};
|
};
|
||||||
|
|
||||||
const template = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?);
|
const template = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?);
|
||||||
if (!@hasDecl(JsApi.Meta, "empty_with_no_proto")) {
|
{
|
||||||
|
const internal_field_count = comptime countInternalFields(JsApi);
|
||||||
|
if (internal_field_count > 0) {
|
||||||
const instance_template = v8.v8__FunctionTemplate__InstanceTemplate(template);
|
const instance_template = v8.v8__FunctionTemplate__InstanceTemplate(template);
|
||||||
v8.v8__ObjectTemplate__SetInternalFieldCount(instance_template, 1);
|
v8.v8__ObjectTemplate__SetInternalFieldCount(instance_template, internal_field_count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const name_str = if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi);
|
const name_str = if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi);
|
||||||
const class_name = v8.v8__String__NewFromUtf8(isolate, name_str.ptr, v8.kNormal, @intCast(name_str.len));
|
const class_name = v8.v8__String__NewFromUtf8(isolate, name_str.ptr, v8.kNormal, @intCast(name_str.len));
|
||||||
@@ -443,6 +446,44 @@ fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *v8.FunctionT
|
|||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn countInternalFields(comptime JsApi: type) u8 {
|
||||||
|
var last_used_id = 0;
|
||||||
|
var cache_count: u8 = 0;
|
||||||
|
|
||||||
|
inline for (@typeInfo(JsApi).@"struct".decls) |d| {
|
||||||
|
const name: [:0]const u8 = d.name;
|
||||||
|
const value = @field(JsApi, name);
|
||||||
|
const definition = @TypeOf(value);
|
||||||
|
|
||||||
|
switch (definition) {
|
||||||
|
inline bridge.Accessor, bridge.Function => {
|
||||||
|
const cache = value.cache orelse continue;
|
||||||
|
if (cache != .internal) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// We assert that they are declared in-order. This isn't necessary
|
||||||
|
// but I don't want to do anything fancy to look for gaps or
|
||||||
|
// duplicates.
|
||||||
|
const internal_id = cache.internal;
|
||||||
|
if (internal_id != last_used_id + 1) {
|
||||||
|
@compileError(@typeName(JsApi) ++ "." ++ name ++ " has a non-monotonic cache index");
|
||||||
|
}
|
||||||
|
last_used_id = internal_id;
|
||||||
|
cache_count += 1; // this is just last_used, but it's more explicit this way
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(JsApi.Meta, "empty_with_no_proto")) {
|
||||||
|
return cache_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need cache_count internal fields, + 1 for the TAO pointer (the v8 -> Zig)
|
||||||
|
// mapping) itself.
|
||||||
|
return cache_count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Attaches JsApi members to the prototype template (normal case)
|
// Attaches JsApi members to the prototype template (normal case)
|
||||||
fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.FunctionTemplate) void {
|
fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.FunctionTemplate) void {
|
||||||
const target = v8.v8__FunctionTemplate__PrototypeTemplate(template);
|
const target = v8.v8__FunctionTemplate__PrototypeTemplate(template);
|
||||||
|
|||||||
@@ -95,33 +95,6 @@ pub fn fromJS(comptime R: type, js_obj_handle: *const v8.Object) !R {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const internal_field_count = v8.v8__Object__InternalFieldCount(js_obj_handle);
|
const internal_field_count = v8.v8__Object__InternalFieldCount(js_obj_handle);
|
||||||
// Special case for Window: the global object doesn't have internal fields
|
|
||||||
// Window instance is stored in context.page.window instead
|
|
||||||
if (internal_field_count == 0) {
|
|
||||||
// Normally, this would be an error. All JsObject that map to a Zig type
|
|
||||||
// are either `empty_with_no_proto` (handled above) or have an
|
|
||||||
// interalFieldCount. The only exception to that is the Window...
|
|
||||||
const isolate = v8.v8__Object__GetIsolate(js_obj_handle).?;
|
|
||||||
const context = js.Context.fromIsolate(.{ .handle = isolate });
|
|
||||||
|
|
||||||
const Window = @import("../webapi/Window.zig");
|
|
||||||
if (T == Window) {
|
|
||||||
return context.page.window;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... Or the window's prototype.
|
|
||||||
// We could make this all comptime-fancy, but it's easier to hard-code
|
|
||||||
// the EventTarget
|
|
||||||
|
|
||||||
const EventTarget = @import("../webapi/EventTarget.zig");
|
|
||||||
if (T == EventTarget) {
|
|
||||||
return context.page.window._proto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type not found in Window's prototype chain
|
|
||||||
return error.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it isn't an empty struct, then the v8.Object should have an
|
// if it isn't an empty struct, then the v8.Object should have an
|
||||||
// InternalFieldCount > 0, since our toa pointer should be embedded
|
// InternalFieldCount > 0, since our toa pointer should be embedded
|
||||||
// at index 0 of the internal field count.
|
// at index 0 of the internal field count.
|
||||||
|
|||||||
@@ -160,10 +160,12 @@ pub const Constructor = struct {
|
|||||||
pub const Function = struct {
|
pub const Function = struct {
|
||||||
static: bool,
|
static: bool,
|
||||||
arity: usize,
|
arity: usize,
|
||||||
|
cache: ?Caller.Function.Opts.Caching = null,
|
||||||
func: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void,
|
func: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void,
|
||||||
|
|
||||||
fn init(comptime T: type, comptime func: anytype, comptime opts: Caller.Function.Opts) Function {
|
fn init(comptime T: type, comptime func: anytype, comptime opts: Caller.Function.Opts) Function {
|
||||||
return .{
|
return .{
|
||||||
|
.cache = opts.cache,
|
||||||
.static = opts.static,
|
.static = opts.static,
|
||||||
.arity = getArity(@TypeOf(func)),
|
.arity = getArity(@TypeOf(func)),
|
||||||
.func = struct {
|
.func = struct {
|
||||||
@@ -193,11 +195,13 @@ pub const Function = struct {
|
|||||||
|
|
||||||
pub const Accessor = struct {
|
pub const Accessor = struct {
|
||||||
static: bool = false,
|
static: bool = false,
|
||||||
|
cache: ?Caller.Function.Opts.Caching = null,
|
||||||
getter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
getter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
||||||
setter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
setter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
||||||
|
|
||||||
fn init(comptime T: type, comptime getter: anytype, comptime setter: anytype, comptime opts: Caller.Function.Opts) Accessor {
|
fn init(comptime T: type, comptime getter: anytype, comptime setter: anytype, comptime opts: Caller.Function.Opts) Accessor {
|
||||||
var accessor = Accessor{
|
var accessor = Accessor{
|
||||||
|
.cache = opts.cache,
|
||||||
.static = opts.static,
|
.static = opts.static,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -713,18 +713,19 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = .{ .internal = 1 } });
|
||||||
|
pub const console = bridge.accessor(Window.getConsole, null, .{ .cache = .{ .internal = 2 } });
|
||||||
|
|
||||||
pub const top = bridge.accessor(Window.getWindow, null, .{});
|
pub const top = bridge.accessor(Window.getWindow, null, .{});
|
||||||
pub const self = bridge.accessor(Window.getWindow, null, .{});
|
pub const self = bridge.accessor(Window.getWindow, null, .{});
|
||||||
pub const window = bridge.accessor(Window.getWindow, null, .{});
|
pub const window = bridge.accessor(Window.getWindow, null, .{});
|
||||||
pub const parent = bridge.accessor(Window.getWindow, null, .{});
|
pub const parent = bridge.accessor(Window.getWindow, null, .{});
|
||||||
pub const console = bridge.accessor(Window.getConsole, null, .{});
|
|
||||||
pub const navigator = bridge.accessor(Window.getNavigator, null, .{});
|
pub const navigator = bridge.accessor(Window.getNavigator, null, .{});
|
||||||
pub const screen = bridge.accessor(Window.getScreen, null, .{});
|
pub const screen = bridge.accessor(Window.getScreen, null, .{});
|
||||||
pub const visualViewport = bridge.accessor(Window.getVisualViewport, null, .{});
|
pub const visualViewport = bridge.accessor(Window.getVisualViewport, null, .{});
|
||||||
pub const performance = bridge.accessor(Window.getPerformance, null, .{});
|
pub const performance = bridge.accessor(Window.getPerformance, null, .{});
|
||||||
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{});
|
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{});
|
||||||
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{});
|
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{});
|
||||||
pub const document = bridge.accessor(Window.getDocument, null, .{});
|
|
||||||
pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{});
|
pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{});
|
||||||
pub const history = bridge.accessor(Window.getHistory, null, .{});
|
pub const history = bridge.accessor(Window.getHistory, null, .{});
|
||||||
pub const navigation = bridge.accessor(Window.getNavigation, null, .{});
|
pub const navigation = bridge.accessor(Window.getNavigation, null, .{});
|
||||||
|
|||||||
Reference in New Issue
Block a user