JSC: cache Symbol.prototype.toString result
JSC's optimization pipeline has three relevant tiers here: DFG (typed IR with speculation), FTL (B3 backend), and a GC that must trace every live heap pointer. An intrinsic is a built-in function the JIT recognizes by identity and replaces with a hand-coded IR node. This commit adds a per-Symbol cached JSString field and a SymbolToString DFG/FTL intrinsic node. DFGByteCodeParser emits the specialized node, DFGFixupPhase constrains its input to SymbolUse, and SpeculativeJIT/FTL lower it to a raw memory load of the new cached-string field.
Significance
A new JIT intrinsic touching a GC-traced field creates a multi-layered attack surface — type speculation, GC liveness, and slow-path write-back all have to be correct simultaneously.
Audit directions
-
GC tracing of the new field.
Symbol::visitChildrenImplmust trace the new cachedJSStringfield. An incorrect field offset, a missingDECLARE_VISIT_CHILDRENmacro expansion, or a stale pointer after GC compaction yields a live dangling string reference exploitable through the fast-path load. -
Type speculation boundary.
FixupPhaseconstrains input toSymbolUse, butSymbol.prototype.toStringis callable with aSymbolObject(boxed wrapper fromObject(Symbol())) asthis. If the intrinsic emitsSymbolUsewhere it should emitSymbolObjectUseor handle both, the speculation guard is wrong, producing an unchecked type confusion downstream. -
Cache write-back race. The slow path that initializes
m_cachedStringis callable from JIT-compiled code on multiple tiers. If the store is not visible across concurrent compiles or a second slow-path entry races with the first, a thread could read a partially-written pointer. -
StringConstructor consistency.
StringConstructor.cppindependently uses the same cache via theString(symbol)call path. Any divergence in cache-validity assumptions between this path and the intrinsic is a consistency bug.