@@ -26,6 +26,31 @@ const predicates = @import("../builtins/predicates.zig");
2626const registry = @import ("../registry.zig" );
2727const NamespaceDef = registry .NamespaceDef ;
2828
29+ // Arena allocator for protocol registration data.
30+ // Protocol data (Protocol, PersistentArrayMap, HeapString, ProtocolFn, Value arrays)
31+ // lives for the entire process lifetime. Using an arena allows bulk deallocation
32+ // at shutdown, preventing GPA leak reports without individual tracking.
33+ var protocol_arena : ? std.heap.ArenaAllocator = null ;
34+
35+ /// Initialize the protocol arena. Must be called before any protocol registration.
36+ pub fn initArena (backing : Allocator ) void {
37+ protocol_arena = std .heap .ArenaAllocator .init (backing );
38+ }
39+
40+ /// Free all protocol registration data. Called at shutdown.
41+ pub fn deinitArena () void {
42+ if (protocol_arena ) | * arena | {
43+ arena .deinit ();
44+ protocol_arena = null ;
45+ }
46+ }
47+
48+ /// Get the protocol arena allocator. Falls back to the given allocator if arena not initialized.
49+ pub fn getArenaAllocator (fallback : Allocator ) Allocator {
50+ if (protocol_arena ) | * arena | return arena .allocator ();
51+ return fallback ;
52+ }
53+
2954// ============================================================
3055// Protocol method implementations (extend-type functions)
3156// ============================================================
@@ -151,8 +176,9 @@ pub fn createProtocol(
151176 sigs : []const MethodSig ,
152177 extend_via_meta : bool ,
153178) ! * Protocol {
154- const protocol = try allocator .create (Protocol );
155- const empty_map = try allocator .create (PersistentArrayMap );
179+ const alloc = getArenaAllocator (allocator );
180+ const protocol = try alloc .create (Protocol );
181+ const empty_map = try alloc .create (PersistentArrayMap );
156182 empty_map .* = .{ .entries = &.{} };
157183 protocol .* = .{
158184 .name = name ,
@@ -170,7 +196,7 @@ pub fn createProtocol(
170196 var i : usize = 0 ;
171197 while (i < sigs .len ) {
172198 const method_name = sigs [i ].name ;
173- const pf = try allocator .create (ProtocolFn );
199+ const pf = try alloc .create (ProtocolFn );
174200 pf .* = .{
175201 .protocol = protocol ,
176202 .method_name = method_name ,
@@ -195,27 +221,29 @@ pub fn extendType(
195221 type_key : []const u8 ,
196222 methods : []const struct { name : []const u8 , func : * const fn (Allocator , []const Value ) anyerror ! Value },
197223) ! void {
224+ const alloc = getArenaAllocator (allocator );
198225 // Build method map: [name1, fn1, name2, fn2, ...]
199- const method_entries = try allocator .alloc (Value , methods .len * 2 );
226+ const method_entries = try alloc .alloc (Value , methods .len * 2 );
200227 for (methods , 0.. ) | m , i | {
201- method_entries [i * 2 ] = Value .initString (allocator , m .name );
228+ method_entries [i * 2 ] = Value .initString (alloc , m .name );
202229 method_entries [i * 2 + 1 ] = Value .initBuiltinFn (m .func );
203230 }
204- const method_map = try allocator .create (PersistentArrayMap );
231+ const method_map = try alloc .create (PersistentArrayMap );
205232 method_map .* = .{ .entries = method_entries };
206233
207234 // Add to impls: grow the impls map
208235 const old_impls = protocol .impls ;
209236 const old_entries = old_impls .entries ;
210- const new_entries = try allocator .alloc (Value , old_entries .len + 2 );
237+ const new_entries = try alloc .alloc (Value , old_entries .len + 2 );
211238 @memcpy (new_entries [0.. old_entries .len ], old_entries );
212- new_entries [old_entries .len ] = Value .initString (allocator , type_key );
239+ new_entries [old_entries .len ] = Value .initString (alloc , type_key );
213240 new_entries [old_entries .len + 1 ] = Value .initMap (method_map );
214- const new_impls = try allocator .create (PersistentArrayMap );
241+ const new_impls = try alloc .create (PersistentArrayMap );
215242 new_impls .* = .{ .entries = new_entries };
216- // Free old impls (entries Values are copied by value, still referenced from new_entries)
217- if (old_entries .len > 0 ) allocator .free (old_entries );
218- allocator .destroy (old_impls );
243+ // With arena allocator, individual frees are no-ops (arena frees everything at once).
244+ // Keep the free calls for correctness when used without arena.
245+ if (old_entries .len > 0 ) alloc .free (old_entries );
246+ alloc .destroy (old_impls );
219247 protocol .impls = new_impls ;
220248 protocol .generation +%= 1 ;
221249}
0 commit comments