← All reports

`JSString` gains a per-cell atom bit for concurrent profiling

Component: JSC value profiling | 24f0a33

Source/JavaScriptCore/bytecode/SpeculatedType.h

+inline SpeculatedType speculationFromValueForProfiling(JSValue value)
+{
+ return speculationFromValueImpl<true>(value);
+}

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]

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.

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?