@@ -40,6 +40,7 @@ const StringCell = strcell.StringCell;
4040
4141var object_batch_cells_for_testing : std .atomic .Value (u64 ) = .init (0 );
4242var relocation_verifications_for_testing : std .atomic .Value (u64 ) = .init (0 );
43+ var plain_object_finalizer_skips_for_testing : std .atomic .Value (u64 ) = .init (0 );
4344
4445pub fn objectBatchCellsForTesting () u64 {
4546 return object_batch_cells_for_testing .load (.monotonic );
@@ -49,6 +50,10 @@ pub fn relocationVerificationsForTesting() u64 {
4950 return relocation_verifications_for_testing .load (.monotonic );
5051}
5152
53+ pub fn plainObjectFinalizerSkipsForTesting () u64 {
54+ return plain_object_finalizer_skips_for_testing .load (.monotonic );
55+ }
56+
5257/// The engine's GC cell taxonomy. Each `Heap.create(T, kind)` tags its cell so
5358/// `trace`/`finalize` dispatch without RTTI. AST nodes, bytecode chunks, and
5459/// `Shape`s are immutable and arena-permanent — they are *not* GC cells and
@@ -3330,6 +3335,21 @@ pub const Binding = struct {
33303335 /// slabs individually. A SharedArrayBuffer wrapper owns one realm retain
33313336 /// that must be released when the wrapper cell dies.
33323337 pub fn finalize (self : * Binding , cell : * anyopaque , kind : Kind ) void {
3338+ if (kind == .object ) {
3339+ const state = self .context .gc_state orelse return ;
3340+ const object : * Object = @ptrCast (@alignCast (cell ));
3341+ // An owner-only plain inline object has no side allocation, host
3342+ // owner, external resource, or rare state to release. Skip realm
3343+ // routing and six empty sidecar probes unless test telemetry needs
3344+ // the per-kind count. Shared heaps retain exact per-cell routing.
3345+ if (! state .realms .requiresCellRouting () and
3346+ self .context .gc_finalizer_stats_out == null and
3347+ object .storageState () == null )
3348+ {
3349+ if (builtin .is_test ) _ = plain_object_finalizer_skips_for_testing .fetchAdd (1 , .monotonic );
3350+ return ;
3351+ }
3352+ }
33333353 const ctx = self .realmForCell (cell ) orelse return ;
33343354 if (ctx .gc_finalizer_stats_out ) | stats | stats .addKind (kind );
33353355 switch (kind ) {
@@ -3419,6 +3439,35 @@ pub const Binding = struct {
34193439 }
34203440};
34213441
3442+ test "owner-only plain object finalization skips empty sidecar probes" {
3443+ const owner = try ContextMod .Context .createWith (std .testing .allocator , .{
3444+ .enable_gc = true ,
3445+ .enable_jit = false ,
3446+ });
3447+ defer owner .destroy ();
3448+ owner .collectGarbage ();
3449+ owner .gc .? .threshold_bytes = std .math .maxInt (usize );
3450+ owner .gc .? .nursery_threshold_bytes = std .math .maxInt (usize );
3451+
3452+ const skips_before = plainObjectFinalizerSkipsForTesting ();
3453+ _ = try owner .evaluate (
3454+ \\for (var i = 0; i < 64; i = i + 1) ({ value: i, stamp: i & 7, previous: i - 1 });
3455+ );
3456+ owner .collectGarbage ();
3457+ try std .testing .expect (plainObjectFinalizerSkipsForTesting () >= skips_before + 64 );
3458+
3459+ var stats = ContextMod.Context.GcFinalizerStats {};
3460+ owner .gc_finalizer_stats_out = & stats ;
3461+ defer owner .gc_finalizer_stats_out = null ;
3462+ const skips_with_stats = plainObjectFinalizerSkipsForTesting ();
3463+ _ = try owner .evaluate (
3464+ \\for (var i = 0; i < 64; i = i + 1) ({ value: i, stamp: i & 7, previous: i - 1 });
3465+ );
3466+ owner .collectGarbage ();
3467+ try std .testing .expect (stats .objects >= 64 );
3468+ try std .testing .expectEqual (skips_with_stats , plainObjectFinalizerSkipsForTesting ());
3469+ }
3470+
34223471test "precise heap realm registry traces relocates and retires one sibling exactly once" {
34233472 const owner = try ContextMod .Context .createWith (std .testing .allocator , .{ .enable_gc = true });
34243473 defer owner .destroy ();
0 commit comments