Cloned SymbolTable cache per JSGlobalObject fixes DFG constant-folding of mutable variables
Source/JavaScriptCore/bytecode/CodeBlock.cpp
Source/JavaScriptCore/runtime/SymbolTable.cpp
DFG JIT uses WatchpointSet objects to guard speculative optimizations: if a variable's WatchpointSet stays IsWatched (never fired), DFG is allowed to constant-fold its value. SymbolTable stores per-variable metadata for a lexical scope, including the WatchpointSetEntry that DFG consults. When a CodeBlock is jettisoned (invalidated due to a deoptimization), it may be fully recreated on the next execution. Each recreation previously called cloneScopePart(), which allocated a brand-new SymbolTable clone with a brand-new WatchpointSet.
The bug: op_put_to_scope bytecode correctly fires the new clone's WatchpointSet, but the live JSLexicalEnvironment still held the old clone. DFG, recompiling from the environment, read the old WatchpointSet, saw IsWatched, and emitted a constant load for a variable that had already been mutated. The fix introduces a WeakGCMap<SymbolTable*, SymbolTable> on JSGlobalObject that deduplicates clones — the same clone (and thus the same WatchpointSet) is reused for all CodeBlocks derived from the same original SymbolTable.
Before (bug):
CodeBlock v1 → clone_A (WatchpointSet_A: IsWatched)
JSLexicalEnv → holds clone_A
CodeBlock v1 jettisoned
CodeBlock v2 → clone_B (WatchpointSet_B: IsWatched)
op_put_to_scope fires WatchpointSet_B ← wrong clone
DFG reads env → clone_A → WatchpointSet_A still IsWatched → WRONG fold
After (fix):
CodeBlock v1 → cache miss → clone_A, stored in symbolTableCache
JSLexicalEnv → holds clone_A
CodeBlock v1 jettisoned
CodeBlock v2 → cache hit → reuse clone_A
op_put_to_scope fires WatchpointSet_A ← correct clone
DFG reads env → clone_A → WatchpointSet_A: Invalidated → correct deopt
The collectDebuggerInfo replacement for setRareDataCodeBlock copies debugger metadata (inferred name, source ID, line/column) from the first CodeBlock that visits the clone, then silently returns for all subsequent callers. This first-caller-wins semantics replaces the previous ASSERT(!m_codeBlock) pattern that could not survive clone reuse.
Significance
Incorrect constant-folding of mutable let variables in DFG-compiled closures is a textbook source of JIT type confusion bugs — the compiler operates on a stale assumption about a value it no longer controls. This fix closes a concrete watchpoint lifecycle hole where WatchpointSet identity diverged from live object state across CodeBlock jettison/recreation cycles.