DFG ArrayShift intrinsic node
DFG bytecode parser는 일반 JS 함수 호출을 직접 설계한 node로 대체할 수 있습니다. 이 node는 backend에서 고도로 최적화된 machine code로 변환됩니다. 새로운 intrinsic을 추가하려면 node를 다루는 모든 phase에 연동해야 합니다. type fixup, clobber analysis, abstract interpretation, GC interaction tracking, 각 backend의 code generator까지, 빠짐없이 대응 대상에 포함됩니다. Array.shift()는 Array.pop()보다 의미적으로 더 복잡합니다. 배열의 앞쪽 요소를 제거하는 특성상, length가 1보다 클 때는 전체 요소 이동이 필요하기 때문입니다.
이 commit은 ArrayShift를 새로운 DFG intrinsic node로 추가했습니다. 전체 DFG/FTL pipeline(fixup, clobberize, abstract interpreter, B3 lowering)에 걸쳐 연동됩니다. inline fast path는 length가 0 또는 1인 배열만 처리하며, length가 2 이상인 경우에는 runtime에서 operationArrayShift로 넘어갑니다. fast path에서는 storage[0]을 읽고, 해당 슬롯에 empty를 저장한 뒤, publicLength를 감소시킵니다. prototype-chain 처리는 우회됩니다.
Significance
storage의 앞쪽을 변경하는, 자주 사용되는 array operation에 JIT 컴파일된 machine code가 추가되었습니다. 기존에는 C++만으로 처리되던 경로가 이제 speculative machine code로 실행되며, 자체적인 GC interaction 및 OSR-exit 경계를 갖게 되었습니다.
Audit directions
- Hole detection on the fast path. length-1 fast path는
storage[0]의 empty slot을 감지하고 slow path로 빠져나가야 합니다. emptiness check가 올바르지 않거나 array-mode speculation이 잘못된 경우, stale하거나 초기화되지 않은 값이 JS로 반환될 가능성이 있습니다. 이는 type confusion으로 이어질 수 있습니다. - GC interaction window. fast path는
storage[0]을 읽고, 해당 슬롯에empty를 저장한 뒤publicLength를 감소시킵니다.doesGC가 이 node를 GC-free로 잘못 분류하면 문제가 발생합니다. 실제로 allocation을 유발할 수 있는 경로에서 GC가 실행될 경우, 부분적으로만 업데이트된 butterfly를 GC가 참조하게 될 가능성이 있습니다. - FTL
compileArrayShift. 새로 추가된 B3 lowering입니다. 모든 speculated array mode(Int32, Double, Contiguous, ArrayStorage)를 올바르게 처리하는지 확인해야 합니다. 또한publicLength쓰기가 값 읽기에 대해 올바르게 fence 처리되는지도 살펴볼 필요가 있습니다. - Speculation vs deopt boundary.
ArrayPop과 달리ArrayShift는 배열의 앞쪽을 변경합니다. prototype에 index 0에 대한 numeric setter가 존재하는데 speculative check에서 이를 놓치는 경우, 관찰되어야 할 변경이 조용히 건너뛰어질 가능성이 있습니다. - Length 1 → 0 transition. fast path 실행 후
storage[0]이 초기화되고publicLength가 0이 됩니다. 요소 초기화와 length 감소 사이의 window에서 배열을 참조하는 코드가 있다면, 일관성이 깨진 상태에 노출됩니다.