`JSString` gains a per-cell atom bit for concurrent profiling
Component: JSC value profiling | 24f0a33
Source/JavaScriptCore/bytecode/SpeculatedType.h
JSC's DFG/FTL compiler threads run concurrently with the mutator and speculate on runtime types by inspecting values recorded in ValueProfile buckets — this is how the JIT decides which fast-path type checks to emit. Determining whether a JSString is an atom (interned, hash-consed string used as property keys) previously required following its internal StringImpl* and checking StringImpl::isAtom(), but that store isn't fenced against the ValueProfile write that exposes the cell to compiler threads.
This commit adds a per-cell isDefinitelyAtom bit to JSString, set whenever a string is atomized, including in-place upgrades in swapToAtomString and JSRopeString::convertToNonRope. The bit is deliberately one-sided — set implies atom, clear means unknown — so it can be read racily without dereferencing anything beyond the cell itself, at the cost of occasionally under-predicting. speculationFromCell/speculationFromValue are refactored into templates but keep their existing dereferencing behavior; a new speculationFromValueForProfiling variant reads only the per-cell bit, and ValueProfileBase::computeUpdatedPrediction/computeUpdatedPredictionForExtraValue are switched to use it:
Before: After:
Mutator thread Compiler thread Mutator thread Compiler thread
create JSString create JSString
store StringImpl* into cell store StringImpl* into cell
write value into write value into
ValueProfile bucket ValueProfile bucket
(no fence vs. above store) ── races with ──► (no fence vs. above store)
speculationFromValue() speculationFromValueForProfiling()
tryGetValueImpl() reads cell->isDefinitelyAtom()
dereferences StringImpl* (per-cell bit, no ptr chase)
[possible stale/ [worst case: stale 'false',
unpublished ptr deref] never a bad dereference]
Significance
A ValueProfile bucket can be written with no ordering guarantee against the stores that initialize a JSString, so a compiler thread could observe the cell before its StringImpl* was published and dereference it. Restricting the profiling path to an in-cell bit removes that dereference entirely, trading a possible false-negative prediction for the elimination of a potential crash.
Audit directions
This touches JSC's concurrent JIT type-profiling path, where memory-ordering mistakes turn into type confusion or wild pointer dereferences read by compiler threads. Narrow: check whether markAsAtom's plain setPerCellBit(true) plus the preceding storeStoreFence in swapToAtomString/convertToNonRope is sufficient on all supported architectures — the match tell is a publication pattern where the fence orders two stores but the consumer's load of the flag has no matching acquire. Then verify that the avoidStringDereference=false call sites (speculationFromCell/speculationFromValue) are genuinely restricted to contexts safe from this same profiling race. Wider: the reusable pattern is a concurrent reader that follows a pointer out of an object it reached through an unfenced publication — audit whether any other ValueProfile/ArrayProfile consumer still dereferences a cell-internal pointer without the publication guarantee this patch adds specifically for atomness. The general question to carry: for each field a compiler thread reads off a mutator-created cell, what orders the field's initialization against the store that made the cell visible?