← All issues

[JSC] Inline `NumericStrings` int cache lookup in DFG and FTL `ToString(Int32)`

186e2a9

NumericStrings는 VM 단위로 유지되는 캐시로, 정수를 미리 계산된 JSString 표현과 대응시킵니다. 두 계층으로 구성됩니다. 하나는 0..1023 범위의 small-int를 직접 인덱싱하는 배열이고, 다른 하나는 WTF::IntHash<int> (rapidHashMix64 mum mixer)를 키로 사용하는 hash 기반의 m_intCache입니다. DFG/FTL이 cache lookup을 인라인화할 때는, hash 연산·memory load·조건 분기를 native instruction으로 직접 내보냅니다. C++ call frame은 없으며, 명시적으로 내보낸 것 이외의 runtime guard도 존재하지 않습니다.

Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp

- slowCases.append(branch32(AboveOrEqual, valueGPR, TrustedImm32(NumericStrings::cacheSize)));
- move(valueGPR, resultGPR);
- lshiftPtr(TrustedImm32(WTF::fastLog2(static_cast<unsigned>(sizeof(NumericStrings::StringWithJSString)))), resultGPR);
- addPtr(TrustedImmPtr(vm().numericStrings.smallIntCache()), resultGPR);
- loadPtr(Address(resultGPR, NumericStrings::StringWithJSString::offsetOfJSString()), resultGPR);
- doneCases.append(branchTestPtr(NonZero, resultGPR));
- callOperation(operationInt32ToStringWithValidRadix, resultGPR, LinkableConstant::globalObject(*this, node), valueGPR, TrustedImm32(10));

Source/JavaScriptCore/b3/B3AbstractHeapRepository.h

+ macro(IntCache, 0, sizeof(NumericStrings::CacheEntryWithJSString<int>)) \

이 commit은 64-bit DFG와 FTL backend에서 NumericStrings int-cache hash lookup을 인라인화했습니다. 대상은 기존 0..1023 small-int 범위 밖의 값에 대한 radix-10 ToString(Int32) 처리입니다. rapidHashMix64를 JIT machine code로 직접 내보내 m_intCache를 인덱싱하고, key가 일치하면 캐시된 JSString을 반환합니다. miss인 경우에만 vmCall이 실행됩니다. 새롭게 추가된 IntCache B3 abstract heap은 optimizer의 load 재배치 방식을 제어합니다. 32-bit DFG backend는 기존 small-int 전용 경로를 유지합니다. rapidHashMix64에 필요한 128-bit 곱셈이 32-bit 환경에서는 실용적이지 않기 때문입니다.

이로써 hot JIT code에서 large integer를 string으로 변환하는 속도가 약 2.38배 향상됩니다. 단, 새롭게 내보내진 JIT code는 runtime hash table에서 GC가 관리하는 JSString 포인터를 직접 역참조합니다.

key 일치 여부와 jsString의 non-null 검사는 cache slot에서 각각 독립적으로 수행되는 두 번의 load입니다. 다른 ToString 호출이 충돌하는 key로 cache에 쓰는 동작이 이 두 load 사이에 끼어들면, key 검사는 통과하지만 jsString은 다른 정수에 속하는 상황이 발생할 수 있습니다.

rapidHashMix64의 JIT 내보내기 결과는 WTF::IntHash<int>와 비트 단위로 완전히 동일해야 합니다. 정수 너비, sign extension, 곱셈 잘림 중 어느 하나라도 차이가 생기면, runtime과 다른 slot을 인덱싱하게 됩니다. 결과적으로 잘못된 정수에 해당하는 JSString이 반환될 수 있습니다.

새롭게 추가된 B3 IntCache abstract heap은 주변 store에 대한 상대적인 slot load의 재배치 순서를 제어합니다. 선언된 size나 offset이 잘못되어 있으면, optimizer는 heap이 수정되지 않았다고 판단하면서 실제로는 stale한 slot 값을 사용하게 될 수 있습니다.

마지막으로, 64-bit와 32-bit 경로 간의 분리는 영구적인 유지보수 불일치를 만들어냅니다. 향후 cache 구조에 비대칭적인 변경이 이루어지면, 64-bit에서는 아무런 경고 없이 entry를 잘못 읽는 반면 32-bit는 정상 동작을 유지할 수 있습니다.