[JSC] Inline `NumericStrings` int cache lookup in DFG and FTL `ToString(Int32)`
NumericStrings is a per-VM cache mapping integers to their precomputed JSString representations, with two tiers: a direct-mapped small-int array (indices 0..1023) and a hash-based m_intCache for arbitrary 32-bit values, keyed by WTF::IntHash<int> (the rapidHashMix64 mum mixer). When DFG/FTL inlines a cache lookup, it emits the hash arithmetic, memory loads, and conditional branches as native instructions — no C++ call frame, no runtime guards beyond what is explicitly emitted.
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/b3/B3AbstractHeapRepository.h
This commit inlines the NumericStrings int-cache hash lookup for radix-10 ToString(Int32) in the 64-bit DFG and FTL backends, covering values outside the existing 0..1023 small-int range. It emits rapidHashMix64 directly as JIT machine code to index into m_intCache, returning the cached JSString on a key match and only falling through to the vmCall on a miss. A new IntCache B3 abstract heap controls how the optimizer may reorder those loads. The 32-bit DFG backend keeps the old small-int-only path since the 128-bit multiply in rapidHashMix64 is not viable there.
Significance
The result is a ~2.38x speedup for large-integer-to-string conversions in hot JIT code, at the cost of new JIT-emitted code that directly dereferences GC-managed JSString pointers out of a runtime hash table.
Audit directions
The key-match and jsString-non-null check are two separate loads from the cache slot — if a write to the cache (from another ToString on a colliding key) races between the two loads, the key check can pass on one value while jsString belongs to a different integer. The rapidHashMix64 JIT emission must be bit-for-bit identical to WTF::IntHash<int>; any difference in integer width, sign extension, or multiply truncation would index a different slot than the runtime, returning a JSString for the wrong integer. The new B3 IntCache abstract heap governs reordering of the slot load relative to surrounding stores — if its declared size or offset is wrong, the optimizer could use a stale slot value while believing the heap was unmodified. Finally, the 64/32-bit split creates a permanent maintenance divergence: an asymmetric future change to the cache structure could silently misread entries on 64-bit while 32-bit stays correct.