[JSC] Unify JS Allocators
Component: JavaScriptCore Heap | aa19dc6
Source/JavaScriptCore/heap/BlockDirectory.cpp
Source/JavaScriptCore/heap/AlignedMemoryAllocator.cpp
JSC's heap partitions cells by type into subspaces (JSObject, Structure, JSString, ...), each with a BlockDirectory managing 16KB MarkedBlocks. Whether two directories can steal empty blocks from each other is governed by whether they share an AlignedMemoryAllocator; stealing between directories on one allocator already existed behind Options::stealEmptyBlocksFromOtherAllocators, but each IsoSubspace previously owned its own private allocator, so no IsoSubspace could steal from another. This commit removes IsoSubspace::m_allocator and groups subspaces by memory-region/alignment class instead (fastMalloc-backed, primitive gigacage, and a new shared Structure allocator), replacing the old subspace-walk bookkeeping (Subspace::findEmptyBlockToSteal, registerDirectory/registerSubspace) with a lock-protected linked list of directories currently holding empty blocks. To make the broader stealing safe it adds two per-block exclusions the old path lacked — it previously masked only ~inUseBits — namely stealableBits() (emptyBits & ~destructibleBits & ~inUseBits), which skips blocks still owing a destructor pass, and a check inside the new search loop skipping any block whose weakSet().head() is non-null.
Before (private allocator per IsoSubspace):
IsoSubspace A IsoSubspace B
private AlignedMemoryAllocator private AlignedMemoryAllocator
BlockDirectory A BlockDirectory B
(no shared allocator => A and B can never steal from each other)
After (allocators shared by memory region):
Shared AlignedMemoryAllocator (e.g. structureAllocator)
BlockDirectory A <──steal──> BlockDirectory B
still excluded per-block: destructor-owing blocks, blocks with a live WeakSet
Significance
Unifying allocators lets empty MarkedBlocks migrate between far more cell-type subspaces than before, widening a heap block-reuse mechanism that JS heap-grooming techniques care about. Without the two new exclusions, handing off either kind of block would also charge one cell type's destructor or WeakBlock-sweep cost to another type's allocation.
Audit directions
This rewires a core heap block-reuse invariant in JSC's collector, so it is a high-value target. Narrow: audit findEmptyBlockToSteal and noteBlockMayBeStealable for races between the relaxed-atomic fast-path check on m_isOnEmptyBlocksList and the locked list mutation, and check whether a block's stealability can change between the check and the actual steal — a WeakSet becoming empty, or a destructor flag going stale — since a missed edge case lets a block be stolen while still owing destructor work or holding live WeakImpls, producing a use-after-free or type confusion between unrelated cell types. Wider: every other place that reasons about which directories share an allocator now reasons over a much larger equivalence class, so re-examine the remaining consumers of alignedMemoryAllocator() and the Options::stealEmptyBlocksFromOtherAllocators gate for assumptions that a shared allocator implies related cell types. Widest: the pattern is "a pooling optimization that merges previously-disjoint equivalence classes" — every invariant that was accidentally upheld by the partition becomes an explicit exclusion check that must be complete. Code-review tell: a new *Bits() accessor composed of several bitvector masks, introduced in the same diff that widens the set of participants the mask applies to.