Skip to content
HN On Hacker News ↗

Shared-memory threads for JavaScriptCore (experimental, not working yet) by Jarred-Sumner · Pull Request #249 · oven-sh/WebKit

▲ 148 points 313 comments by gr4vityWall 2mo ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is a mix of AI-generated, AI-assisted, and human-written content

48 %

AI likelihood · overall

Mixed
41% human-written 37% AI-generated
SEGMENTS · HUMAN 2 of 8
SEGMENTS · AI 4 of 8
WORD COUNT 1,271
PEAK AI % 93% · §7
Analyzed
Jun 20
backend: pangram/v3.3
Segments scanned
8 windows
avg 159 words each
Distribution
41 / 37%
human / AI fraction
Verdict
Mixed
Pangram v3.3

Article text · 1,271 words · 8 segments analyzed

Human AI-generated
§1 Human · 4%

added 21 commits June 5, 2026 08:09

Design specs for shared-heap Thread support in JSC: heap server and per-thread allocators, shared VM state, TID/SW-tagged and segmented butterflies, JIT tiers under N mutators, and the Thread/Lock/Condition/ ThreadLocal API. Includes TSAN, race-amplifier, and bench-gate docs plus the design overview in THREAD.md.

…harness

Thread/Lock/Condition/ThreadLocal and Atomics-on-properties behind --useThreads, serialized by the VM's JSLock as a semantic oracle for the upcoming shared-heap implementation. Includes a 39-test corpus, a TSAN no-JIT build target (zero data races at idle, empty suppressions), a randomized-yield race amplifier, and a serial-perf bench gate with a recorded baseline. Also fixes ICU static-archive link order in WTF and two pre-existing no-JIT build breaks.

…rent object model, JIT support, and Thread API

Multi-mutator heap with per-thread allocators and N-thread safepoints, process-global sharded atom table and StructureID allocation locking, per-thread VM-lite execution state, TID/shared-write tagged butterflies with segmented fallback and TTL watchpoint elision, per-tier TID/SW checks with handler ICs in FTL and epoch-based CodeBlock reclamation, and real mutator threads behind the Thread/Lock/Condition/ThreadLocal API with Atomics on object properties. All behind --useJSThreads with the GIL retained as a --useThreadGIL fallback layer.

§2 Mixed · 42%

…ation, waiter lists

Six rounds of gate-driven fixes against the threads corpus: per-thread CLoop stacks replacing the shared-stack frame clobber, LocalAllocator and Heap shared-mode races, retired JIT artifact accounting, waiter list and condition wakeup fixes, LLInt call path initialization for spawned threads, butterfly regime dispatch in slow paths, and a watchpoint disarm before the flag-on JITData leak in ~CodeBlock. Corpus: 81/85 passing; adds per-test timeouts to the runner so hangs report as failures.

… on spawned threads

trySpreadFast reached the flat-only butterfly() accessor on arrays whose butterfly had segmented under a racing same-shape add storm; the spread path now dispatches on the regime and falls back to the generic slow path. Baseline-compiled callees invoked from spawned threads read per-thread JIT state that only LLInt entry initialized; the thread entry sequence now materializes it for all tiers. Threads corpus green.

…tion handout

SPEC-ungil.md: N-mutator execution model — JSLock GIL-off entered-token mode, per-thread microtask/task queues with keepalive lifetime, stop-the-world conductor protocol (seq_cst stop-bit/access Dekker pair), thread teardown state machine (TEARDOWN/COLLECTED/DETACHED under the lite registry lock), ~VM completion fence via registry condition wait, haveABadTime class-4 stops, lazy-init owner-reentry contract, termination model (VM-wide only). Includes executed inventory audits (K4/N7), full revision history with binding annexes, and the flattened 18-task implementation handout.

§3 AI · 83%

Workflow updates: ungil implementation runs DAG-scheduled parallel task waves with disjoint file ownership and per-task adversarial review; verification ladder covers GIL-on and flag-off regression arms; scanner/fuzz/CVE-audit workflows harden id/path sanitization.

No Source/ change landed (refuter discipline held). Evidence pack SHAREDHEAP-ALLOC-EVIDENCE.md is the round's contribution:

- 99.67% of 70.9M cells ALREADY hit interval-bump (Riptide's bump-in- fresh-block path). Refills are 0.33% of allocs / ~4.1% of wall. The 'per-thread fresh-block cache' candidate targets the wrong lever and carries RSS risk. - The measurable per-cell tax is the 3-hop allocator LOOKUP (allocationClientForCurrentThread -> allocatorForSizeStep -> allocateForClient, ~250ms). Higher-leverage zero-RSS candidate: cache LocalAllocator* per (thread, size-class). - Decomposition: of intcs W=1 +5889ms gap vs Java, only ~1912ms (33%) is sharedGCHeap+gilOff tax.

§4 Mixed · 56%

~3937ms (67%) is plain-JSC floor (WTF::equal Map-key compare, lockProtoFuncHold, rope-resolve, IC-miss, CellLock/DeferTermination/traps). <6000ms needed BOTH; allocator-only was never going to clear it. - intcs W=16 RSS noise is mode-correlated (slow-mode rep = low-RSS rep).

§41: clean-tree re-baseline, all within ±3% of §40, all gates green, RSS within +10%.

… 36.5% of tax)

H-VMLITE-TLCPTR: bake a process-constant TLC slot index at JIT-compile time and load the per-thread LocalAllocator* lite-relative (VMLite::{tlcTable,tlcTableBound}) instead of a null Allocator constant. H-TLS-TABLE: collapse the C++ CompleteSubspace::allocate sharedGCHeap arm to two IE-TLS loads + one indexed load. H-TLC-FIXEDTABLE-NOREALLOC: pre-grow the TLC table so cached pointers never go stale. defer-hoist- lazyslow: hoist DeferGCForAWhile out of the gilOff operationCompileFTLLazySlowPath steady-state. GCClient::CompleteSubspaceView infra (staging).

uprobe-verified: CompleteSubspace::allocateForClient 27.8M -> 0 (3-hop fully eliminated). operationCompileFTLLazySlowPath 46.6M -> 36.4M (-22% only — stringSpace is iso, not table-addressable; tlcSlotForConcurrently <JSRopeString> returns nullopt so MakeRope still bakes null Allocator; JSRopeString+JSString = 71% of cells).

§42: intcs W=1 7788 -> 7142 (-646ms, tax 1912 -> 1214); nomap W=1 -428; default W=1 -1120; flat W=16 -22.

§5 AI · 77%

RSS: intcs W=1 -2.3%, W=16 -10.4%. Corpus 94+95/0, identity 40/0, all checksums stable. Residual ~75% one mechanism: 36.4M MakeRope thunk traversals (iso-subspace TLC-slot extension + thin-thunk are the named follow-ups).

…unk (76.3% of tax cumulative)

H-ISO-TLCSLOT (IsoSubspace.{h,cpp}, GCThreadLocalCache.cpp, DFGSpeculativeJIT.cpp, FTLLowerDFGToB3.cpp, AssemblyHelpers.h): per-type IsoSubspace TLC slot stamped at GCClient::Heap creation; tlcSlotForConcurrentlyWithIso<T>() resolves via the stamped index. JSArray EXCLUDED (returns nullopt): JIT inline allocateObject/emitAllocateJSObject stores butterfly word UNTAGGED -> fresh inline JSArray reads as foreign at §4.2 ensureLength -> segments on first growth (measured 182,339 convertToSegmentedButterfly + 19M operationArrayPush -> +3,472ms). Under §42 JSArray cell allocator was always null GIL-off so path went to operationNewArrayWithSize (TID-tags in C++); §43 iso arm would be FIRST time JSArray inline path fires GIL-off.

§6 Human · 15%

Gated on Task-8 (TID-tag every JIT inline butterfly install). All other iso ClassTypes either no-butterfly (JSRopeString/JSString) or null-butterfly inline path.

Thin-thunk (FTLThunks.cpp, FTLLazySlowPath.h): gilOff steady state does the T8 acquire-load m_stubCodePtr IN JIT code, tail-jump if non-null; no saveAllRegisters/restoreAllRegisters dump, no C call. Null falls through to today's full thunk.

uprobe: operationCompileFTLLazySlowPath 36.4M -> 56 (-99.9998%).

§43: intcs W=1 7142 -> 6381 (-761); nomap W=1 -1018; default W=1 -976. Cumulative §42+§43 = 1459ms = 76.3% of original 1912ms tax (now 453ms). RSS: intcs W=1 -2.3%, W=16 -10.6%. Corpus 94+95/0, identity 40/0, 34/34 checksums stable. Residual: JSArray iso-TLC ~400-500ms gated on Task-8.

…n race (corrects §40)

Mechanism: String.fromCharCode is a lazy static property on StringConstructor (initial structure {prototype,length,name}, butterfly Flat TID=0).

§7 AI · 93%

intcs/ noconcat have no main-thread termOf() before workers, so first access is a 16-thread race from phaseAI -> termOf. If a WORKER (TID!=0) wins: foreign-TID structure transition on a Flat butterfly -> convertToSegmentedButterfly -> StringConstructor butterfly Segmented for process lifetime -> DFG compileGetButterfly emits segmented check as speculationCheck(BadIndexingType) -> every String.fromCharCode get_by_id in termOf bc#22 / tokenize bc#266 / genDocTextI bc#254 OSR-exits -> handleGetById doesn't consult hasExitSite(BadIndexingType) -> recompile re-emits SAME body -> termOf 15x / genDocTextI 8x / tokenize 9x recompile loop = 4600ms slow-mode.

§40's verdict ('Map<string> is the trigger') was WRONG: nomap is monomodal because its nmShardOf[] precompute incidentally calls termOf() on main at module init, reifying at TID=0. Removing Map was incidental.

Discriminating tests: force main reify -> 15/15 fast; force worker reify -> 12/12 slow; reportDFGCompileTimes fast termOf=1 vs slow termOf=15; verboseOSR exit kinds = BadIndexingType at GetButterfly(String). Explains §34(C): logGC adds main-thread dataLog latency -> main loses race -> 0/15 fast.

Fix (bench-level): String.fromCharCode(97); at module init. 30-rep phaseA before max/min 3.14 (18/30 slow) -> after 1.11 (0/30 slow). intcs W=16 median total 3359 [3050,3754]. Corpus 94+95/0, identity 40/0, all checksums stable.

§8 AI · 84%

ENGINE-SIDE BUG remains: any GIL-off program first-touching a lazy static property (String.fromCharCode/fromCodePoint/raw, Array.of/from/isArray, Object.assign/keys/...) from a worker segments that constructor's butterfly and DFG GetButterfly OSR-exits forever. Candidate fixes: (a) handleGetById checks hasExitSite(BadIndexingType) and falls back to getById IC; (b) foreign-transition rule special-cases property-only NonArray butterflies.

…adIndexingType backstop

ConcurrentButterfly.cpp §4.2 trySegmentedTransition StayFlatShared gate: foreign-TID/SW=1 property transition on a Flat butterfly with NO indexing header AND NO outOfLineCapacity growth reuses the existing flat allocation under cell lock — release-store value into live slot, nuke + DCAS {newStructure, (installerTID,SW=1)}. R7 read protocol via same M2/M5 ordering as owner StayFlat reuse. I12 holds via step-0 F2 fire. Gated !useThreadGIL.

DFGByteCodeParser handleGetById: under useJSThreads, before the simple CheckStructure+GetButterfly+GetByOffset / MultiGetByOffset lowering, consult m_exitProfile.hasExitSite(m_currentIndex, BadIndexingType) and fall back to GetById IC node on hit. Mirrors BadCache idiom. Converges in one recompile when gate cannot apply (capacity grows). useJSThreads- gated; flag-off byte-identical.

bench.js: §44 prewarm removed (no longer needed). JSTests/threads/jit/foreign-reify-getbyid-converges.js: worker-reifies Array.from/Object.keys/String.raw, asserts numberOfDFGCompiles<=4.

§45: force-worker-reify 12/12 SLOW -> 12/12 FAST [1563,1650].