Merge pull request #1125 from lightpanda-io/call_arena

Move the call_arena to the page.
This commit is contained in:
Karl Seguin
2025-10-06 17:22:41 +08:00
committed by GitHub
8 changed files with 30 additions and 40 deletions

View File

@@ -39,6 +39,7 @@ pub const Browser = struct {
session: ?Session, session: ?Session,
allocator: Allocator, allocator: Allocator,
http_client: *HttpClient, http_client: *HttpClient,
call_arena: ArenaAllocator,
page_arena: ArenaAllocator, page_arena: ArenaAllocator,
session_arena: ArenaAllocator, session_arena: ArenaAllocator,
transfer_arena: ArenaAllocator, transfer_arena: ArenaAllocator,
@@ -63,6 +64,7 @@ pub const Browser = struct {
.allocator = allocator, .allocator = allocator,
.notification = notification, .notification = notification,
.http_client = app.http.client, .http_client = app.http.client,
.call_arena = ArenaAllocator.init(allocator),
.page_arena = ArenaAllocator.init(allocator), .page_arena = ArenaAllocator.init(allocator),
.session_arena = ArenaAllocator.init(allocator), .session_arena = ArenaAllocator.init(allocator),
.transfer_arena = ArenaAllocator.init(allocator), .transfer_arena = ArenaAllocator.init(allocator),
@@ -73,6 +75,7 @@ pub const Browser = struct {
pub fn deinit(self: *Browser) void { pub fn deinit(self: *Browser) void {
self.closeSession(); self.closeSession();
self.env.deinit(); self.env.deinit();
self.call_arena.deinit();
self.page_arena.deinit(); self.page_arena.deinit();
self.session_arena.deinit(); self.session_arena.deinit();
self.transfer_arena.deinit(); self.transfer_arena.deinit();

View File

@@ -35,12 +35,11 @@ templates: []v8.FunctionTemplate,
// references the Env.meta_lookup // references the Env.meta_lookup
meta_lookup: []types.Meta, meta_lookup: []types.Meta,
// An arena for the lifetime of a call-group. Gets reset whenever // Arena for the lifetime of the context
// call_depth reaches 0. arena: Allocator,
call_arena: Allocator,
// An arena for the lifetime of the context // The page.call_arena
context_arena: Allocator, call_arena: Allocator,
// Because calls can be nested (i.e.a function calling a callback), // Because calls can be nested (i.e.a function calling a callback),
// we can only reset the call_arena when call_depth == 0. If we were // we can only reset the call_arena when call_depth == 0. If we were
@@ -179,7 +178,7 @@ pub fn deinit(self: *Context) void {
} }
fn trackCallback(self: *Context, pf: PersistentFunction) !void { fn trackCallback(self: *Context, pf: PersistentFunction) !void {
return self.callbacks.append(self.context_arena, pf); return self.callbacks.append(self.arena, pf);
} }
// Given an anytype, turns it into a v8.Object. The anytype could be: // Given an anytype, turns it into a v8.Object. The anytype could be:
@@ -236,7 +235,7 @@ pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url:
const m = try compileModule(self.isolate, src, url); const m = try compileModule(self.isolate, src, url);
const arena = self.context_arena; const arena = self.arena;
const owned_url = try arena.dupe(u8, url); const owned_url = try arena.dupe(u8, url);
try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url); try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url);
@@ -258,9 +257,9 @@ pub fn module(self: *Context, comptime want_result: bool, src: []const u8, url:
owned_url, owned_url,
.{ .alloc = .if_needed, .null_terminated = true }, .{ .alloc = .if_needed, .null_terminated = true },
); );
const gop = try self.module_cache.getOrPut(self.context_arena, normalized_specifier); const gop = try self.module_cache.getOrPut(self.arena, normalized_specifier);
if (!gop.found_existing) { if (!gop.found_existing) {
const owned_specifier = try self.context_arena.dupeZ(u8, normalized_specifier); const owned_specifier = try self.arena.dupeZ(u8, normalized_specifier);
gop.key_ptr.* = owned_specifier; gop.key_ptr.* = owned_specifier;
gop.value_ptr.* = .{}; gop.value_ptr.* = .{};
try self.script_manager.?.getModule(owned_specifier); try self.script_manager.?.getModule(owned_specifier);
@@ -522,18 +521,18 @@ pub fn zigValueToJs(self: *Context, value: anytype) !v8.Value {
// we can just grab it from the identity_map) // we can just grab it from the identity_map)
pub fn mapZigInstanceToJs(self: *Context, js_obj_or_template: anytype, value: anytype) !PersistentObject { pub fn mapZigInstanceToJs(self: *Context, js_obj_or_template: anytype, value: anytype) !PersistentObject {
const v8_context = self.v8_context; const v8_context = self.v8_context;
const context_arena = self.context_arena; const arena = self.arena;
const T = @TypeOf(value); const T = @TypeOf(value);
switch (@typeInfo(T)) { switch (@typeInfo(T)) {
.@"struct" => { .@"struct" => {
// Struct, has to be placed on the heap // Struct, has to be placed on the heap
const heap = try context_arena.create(T); const heap = try arena.create(T);
heap.* = value; heap.* = value;
return self.mapZigInstanceToJs(js_obj_or_template, heap); return self.mapZigInstanceToJs(js_obj_or_template, heap);
}, },
.pointer => |ptr| { .pointer => |ptr| {
const gop = try self.identity_map.getOrPut(context_arena, @intFromPtr(value)); const gop = try self.identity_map.getOrPut(arena, @intFromPtr(value));
if (gop.found_existing) { if (gop.found_existing) {
// we've seen this instance before, return the same // we've seen this instance before, return the same
// PersistentObject. // PersistentObject.
@@ -541,7 +540,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_or_template: anytype, value: an
} }
if (comptime @hasDecl(ptr.child, "destructor")) { if (comptime @hasDecl(ptr.child, "destructor")) {
try self.destructor_callbacks.append(context_arena, DestructorCallback.init(value)); try self.destructor_callbacks.append(arena, DestructorCallback.init(value));
} }
// Sometimes we're creating a new v8.Object, like when // Sometimes we're creating a new v8.Object, like when
@@ -563,7 +562,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_or_template: anytype, value: an
// The TAO contains the pointer ot our Zig instance as // The TAO contains the pointer ot our Zig instance as
// well as any meta data we'll need to use it later. // well as any meta data we'll need to use it later.
// See the TaggedAnyOpaque struct for more details. // See the TaggedAnyOpaque struct for more details.
const tao = try context_arena.create(TaggedAnyOpaque); const tao = try arena.create(TaggedAnyOpaque);
const meta_index = @field(types.LOOKUP, @typeName(ptr.child)); const meta_index = @field(types.LOOKUP, @typeName(ptr.child));
const meta = self.meta_lookup[meta_index]; const meta = self.meta_lookup[meta_index];
@@ -768,7 +767,7 @@ fn jsValueToStruct(self: *Context, comptime named_function: NamedFunction, compt
} }
if (T == js.String) { if (T == js.String) {
return .{ .string = try self.valueToString(js_value, .{ .allocator = self.context_arena }) }; return .{ .string = try self.valueToString(js_value, .{ .allocator = self.arena }) };
} }
const js_obj = js_value.castTo(v8.Object); const js_obj = js_value.castTo(v8.Object);
@@ -1062,7 +1061,7 @@ pub fn createPromiseResolver(self: *Context, comptime lifetime: PromiseResolverL
const persisted = v8.Persistent(v8.PromiseResolver).init(self.isolate, resolver); const persisted = v8.Persistent(v8.PromiseResolver).init(self.isolate, resolver);
if (comptime lifetime == .page) { if (comptime lifetime == .page) {
try self.persisted_promise_resolvers.append(self.context_arena, persisted); try self.persisted_promise_resolvers.append(self.arena, persisted);
} }
return .{ return .{
@@ -1122,7 +1121,7 @@ pub fn dynamicModuleCallback(
}; };
const normalized_specifier = @import("../../url.zig").stitch( const normalized_specifier = @import("../../url.zig").stitch(
self.context_arena, // might need to survive until the module is loaded self.arena, // might need to survive until the module is loaded
specifier, specifier,
resource, resource,
.{ .alloc = .if_needed, .null_terminated = true }, .{ .alloc = .if_needed, .null_terminated = true },
@@ -1172,7 +1171,7 @@ fn _resolveModuleCallback(self: *Context, referrer: v8.Module, specifier: []cons
.{ .alloc = .if_needed, .null_terminated = true }, .{ .alloc = .if_needed, .null_terminated = true },
); );
const gop = try self.module_cache.getOrPut(self.context_arena, normalized_specifier); const gop = try self.module_cache.getOrPut(self.arena, normalized_specifier);
if (gop.found_existing) { if (gop.found_existing) {
if (gop.value_ptr.module) |m| { if (gop.value_ptr.module) |m| {
return m.handle; return m.handle;
@@ -1229,7 +1228,7 @@ const DynamicModuleResolveState = struct {
fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8) !v8.Promise { fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8) !v8.Promise {
const isolate = self.isolate; const isolate = self.isolate;
const gop = try self.module_cache.getOrPut(self.context_arena, specifier); const gop = try self.module_cache.getOrPut(self.arena, specifier);
if (gop.found_existing and gop.value_ptr.resolver_promise != null) { if (gop.found_existing and gop.value_ptr.resolver_promise != null) {
// This is easy, there's already something responsible // This is easy, there's already something responsible
// for loading the module. Maybe it's still loading, maybe // for loading the module. Maybe it's still loading, maybe
@@ -1238,10 +1237,10 @@ fn _dynamicModuleCallback(self: *Context, specifier: [:0]const u8) !v8.Promise {
} }
const persistent_resolver = v8.Persistent(v8.PromiseResolver).init(isolate, v8.PromiseResolver.init(self.v8_context)); const persistent_resolver = v8.Persistent(v8.PromiseResolver).init(isolate, v8.PromiseResolver.init(self.v8_context));
try self.persisted_promise_resolvers.append(self.context_arena, persistent_resolver); try self.persisted_promise_resolvers.append(self.arena, persistent_resolver);
var resolver = persistent_resolver.castToPromiseResolver(); var resolver = persistent_resolver.castToPromiseResolver();
const state = try self.context_arena.create(DynamicModuleResolveState); const state = try self.arena.create(DynamicModuleResolveState);
state.* = .{ state.* = .{
.module = null, .module = null,
.context = self, .context = self,

View File

@@ -178,7 +178,6 @@ pub fn newExecutionWorld(self: *Env) !ExecutionWorld {
return .{ return .{
.env = self, .env = self,
.context = null, .context = null,
.call_arena = ArenaAllocator.init(self.allocator),
.context_arena = ArenaAllocator.init(self.allocator), .context_arena = ArenaAllocator.init(self.allocator),
}; };
} }

View File

@@ -21,13 +21,6 @@ const CONTEXT_ARENA_RETAIN = 1024 * 64;
const ExecutionWorld = @This(); const ExecutionWorld = @This();
env: *Env, env: *Env,
// Arena whose lifetime is for a single getter/setter/function/etc.
// Largely used to get strings out of V8, like a stack trace from
// a TryCatch. The allocator will be owned by the Context, but the
// arena itself is owned by the ExecutionWorld so that we can re-use it
// from context to context.
call_arena: ArenaAllocator,
// Arena whose lifetime is for a single page load. Where // Arena whose lifetime is for a single page load. Where
// the call_arena lives for a single function call, the context_arena // the call_arena lives for a single function call, the context_arena
// lives for the lifetime of the entire page. The allocator will be // lives for the lifetime of the entire page. The allocator will be
@@ -48,7 +41,6 @@ pub fn deinit(self: *ExecutionWorld) void {
self.removeContext(); self.removeContext();
} }
self.call_arena.deinit();
self.context_arena.deinit(); self.context_arena.deinit();
} }
@@ -178,8 +170,8 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal
.meta_lookup = &env.meta_lookup, .meta_lookup = &env.meta_lookup,
.handle_scope = handle_scope, .handle_scope = handle_scope,
.script_manager = &page.script_manager, .script_manager = &page.script_manager,
.call_arena = self.call_arena.allocator(), .call_arena = page.call_arena,
.context_arena = self.context_arena.allocator(), .arena = self.context_arena.allocator(),
.global_callback = global_callback, .global_callback = global_callback,
}; };
@@ -191,8 +183,6 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool, global_cal
v8_context.setEmbedderData(1, data); v8_context.setEmbedderData(1, data);
} }
page.call_arena = context.call_arena;
// Custom exception // Custom exception
// NOTE: there is no way in v8 to subclass the Error built-in type // NOTE: there is no way in v8 to subclass the Error built-in type
// TODO: this is an horrible hack // TODO: this is an horrible hack

View File

@@ -22,7 +22,7 @@ pub fn setIndex(self: Object, index: u32, value: anytype, opts: SetOpts) !void {
@setEvalBranchQuota(10000); @setEvalBranchQuota(10000);
const key = switch (index) { const key = switch (index) {
inline 0...20 => |i| std.fmt.comptimePrint("{d}", .{i}), inline 0...20 => |i| std.fmt.comptimePrint("{d}", .{i}),
else => try std.fmt.allocPrint(self.context.context_arena, "{d}", .{index}), else => try std.fmt.allocPrint(self.context.arena, "{d}", .{index}),
}; };
return self.set(key, value, opts); return self.set(key, value, opts);
} }
@@ -76,7 +76,7 @@ pub fn persist(self: Object) !Object {
const js_obj = self.js_obj; const js_obj = self.js_obj;
const persisted = PersistentObject.init(context.isolate, js_obj); const persisted = PersistentObject.init(context.isolate, js_obj);
try context.js_object_list.append(context.context_arena, persisted); try context.js_object_list.append(context.arena, persisted);
return .{ return .{
.context = context, .context = context,

View File

@@ -121,7 +121,7 @@ pub const Page = struct {
complete, complete,
}; };
pub fn init(self: *Page, arena: Allocator, session: *Session) !void { pub fn init(self: *Page, arena: Allocator, call_arena: Allocator, session: *Session) !void {
const browser = session.browser; const browser = session.browser;
const script_manager = ScriptManager.init(browser, self); const script_manager = ScriptManager.init(browser, self);
@@ -131,7 +131,7 @@ pub const Page = struct {
.window = try Window.create(null, null), .window = try Window.create(null, null),
.arena = arena, .arena = arena,
.session = session, .session = session,
.call_arena = undefined, .call_arena = call_arena,
.renderer = Renderer.init(arena), .renderer = Renderer.init(arena),
.state_pool = &browser.state_pool, .state_pool = &browser.state_pool,
.cookie_jar = &session.cookie_jar, .cookie_jar = &session.cookie_jar,

View File

@@ -106,7 +106,7 @@ pub const Session = struct {
self.page = @as(Page, undefined); self.page = @as(Page, undefined);
const page = &self.page.?; const page = &self.page.?;
try Page.init(page, page_arena.allocator(), self); try Page.init(page, page_arena.allocator(), self.browser.call_arena.allocator(), self);
log.debug(.browser, "create page", .{}); log.debug(.browser, "create page", .{});
// start JS env // start JS env

View File

@@ -443,7 +443,6 @@ const LineWriter = struct {
} }
}; };
pub fn debugCallback(_: *c.CURL, msg_type: c.curl_infotype, raw: [*c]u8, len: usize, _: *anyopaque) callconv(.c) void { pub fn debugCallback(_: *c.CURL, msg_type: c.curl_infotype, raw: [*c]u8, len: usize, _: *anyopaque) callconv(.c) void {
const data = raw[0..len]; const data = raw[0..len];
switch (msg_type) { switch (msg_type) {