[JSC] ArrayShift DFG node
Component: JSC DFG and FTL JIT | dfe5dc6
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/dfg/DFGOperations.cpp
ArrayPop은 이미 intrinsic 처리를 받고 있었습니다. Abstract interpreter의 effect 규칙과 clobberize의 memory 접근 규칙에 따라 machine code가 inline되는 방식입니다. 반면 Array.prototype.shift()는 그런 처리를 받지 못했습니다. 이번 commit은 여기에 전용 ArrayShift node를 부여하되, inline fast path의 범위는 더 좁게 잡았습니다. 길이가 0 또는 1인 배열만 inline으로 처리하며, 이 경우 값을 읽고 empty를 저장한 뒤 publicLength를 갱신합니다. 길이가 더 긴 배열에서는 요소를 앞으로 당기는 비용이 크고, JavaScript에서 prototype chain을 통해 상속될 수 있는 hole도 세심하게 다뤄야 하기 때문에 이 범위로 제한되었습니다. 나머지 경우는 모두 operationArrayShift로 fallback됩니다.
Significance
매우 흔한 array 연산이 직접적인 JIT codegen을 갖게 되었고, 그와 함께 DFG와 FTL 양쪽에서 array 길이와 storage invariant를 유지해야 하는 새로운 speculative fast path가 도입되었습니다. 이 node는 64비트 전용입니다. 32비트 백엔드는 DFG_CRASH stub에 도달하며, parser 단계에서 일찌감치 처리를 포기합니다.
Audit directions
좁게 보면, DFGSpeculativeJIT64.cpp의 codegen과 FTLLowerDFGToB3.cpp의 compileArrayShift를 살펴봐야 합니다. Fallback 이전에 길이-1 fast path와 hole/empty-value 감지가 올바르게 분리되어 있는지, 그리고 clobberize와 safeToExecute 규칙이 getter나 Proxy 기반 array를 통한 동시 array 변형에 대해 안전하지 않은 재정렬을 막고 있는지 확인할 필요가 있습니다. 넓게 보면, publicLength를 기록하는 모든 array 변형 JIT intrinsic — ArrayPop, ArrayPush, 그리고 이번에 추가된 ArrayShift — 를 대상으로 점검하는 방향입니다. 각각에 대해 length 저장과 element 저장이 다른 무언가에 의해 순서가 뒤바뀐 채 관찰될 수 있는지, 그리고 empty-value 검사가 store에 도달하는 모든 경로에서 적용되는지 확인해야 합니다. 찾아야 할 패턴은 짝을 이뤄야 할 element write 바로 옆에 붙어 있지 않은 store32(..., Butterfly::offsetOfPublicLength())입니다.