Summary
#7834 introduced a process-global zero/nonzero mirror for the per-thread layout side tables. It currently uses two independent atomics:
PER_OBJECT_LAYOUT_ARMED_THREADS: AtomicIsize
- exported
PERRY_PER_OBJECT_LAYOUTS_ANY: AtomicU8, read directly by generated allocation sites
The disarm path reads the count and then separately stores 0 to the byte. The comment says a race can only leave a conservative stale 1, but this interleaving leaves the authoritative fast-path byte at false zero while a thread is armed:
initial: count=1, any=1
A: count.fetch_sub(1) -> count=0
A: count.load() == 0
B: count.fetch_add(1) -> count=1
B: any.store(1)
A: any.store(0)
final: count=1, any=0
Even SeqCst on the existing two atomics would permit this interleaving; the state needs one atomic source of truth or a synchronization protocol that cannot publish a false zero.
Separately, the source explicitly documents that a thread exiting while armed leaks its count permanently, leaving all future allocations in the process on the pre-#7834 call path.
Impact
0 is documented as a proof that no per-object record exists, and generated code uses it to skip js_gc_forget_object_layout entirely. A false zero can therefore skip removal of an address-keyed record at object death/reuse, allowing a recycled object address to inherit stale layout metadata. That is a GC scanning/representation correctness risk, not just a performance miss.
The TLS-exit leak is conservative for correctness but process-wide for performance: one short-lived perry/thread agent that exits with a record can make every subsequent inline construction pay the runtime branch/call path forever.
Relevant source:
This is a focused successor to the broader layout-performance umbrella #5094.
Acceptance criteria
- Make the generated-code zero test read a single authoritative atomic state (for example an exported atomic count with a safe zero/nonzero transition), so no interleaving can publish
0 while any thread is armed.
- Disarm the process-global state from the owning TLS value's destructor when a thread exits armed; do not depend on destructor order between separate TLS keys.
- Add a deterministic synchronization-hook test for the interleaving above. A probabilistic thread race is not enough.
- Add a thread-exit test that arms a side table, exits without emptying it, and proves the process-global state returns to zero.
- Add/retain a recycled-address correctness witness proving the gate never hides a live/stale per-object layout record.
Summary
#7834 introduced a process-global zero/nonzero mirror for the per-thread layout side tables. It currently uses two independent atomics:
PER_OBJECT_LAYOUT_ARMED_THREADS: AtomicIsizePERRY_PER_OBJECT_LAYOUTS_ANY: AtomicU8, read directly by generated allocation sitesThe disarm path reads the count and then separately stores
0to the byte. The comment says a race can only leave a conservative stale1, but this interleaving leaves the authoritative fast-path byte at false zero while a thread is armed:Even
SeqCston the existing two atomics would permit this interleaving; the state needs one atomic source of truth or a synchronization protocol that cannot publish a false zero.Separately, the source explicitly documents that a thread exiting while armed leaks its count permanently, leaving all future allocations in the process on the pre-#7834 call path.
Impact
0is documented as a proof that no per-object record exists, and generated code uses it to skipjs_gc_forget_object_layoutentirely. A false zero can therefore skip removal of an address-keyed record at object death/reuse, allowing a recycled object address to inherit stale layout metadata. That is a GC scanning/representation correctness risk, not just a performance miss.The TLS-exit leak is conservative for correctness but process-wide for performance: one short-lived
perry/threadagent that exits with a record can make every subsequent inline construction pay the runtime branch/call path forever.Relevant source:
This is a focused successor to the broader layout-performance umbrella #5094.
Acceptance criteria
0while any thread is armed.