[JSC] Map/Set iterator next must not touch JSMapIterator/JSSetIterator directly
ObjectAllocationSinking은 객체가 외부로 노출되지 않을 때 heap에 할당된 객체를 virtual SSA 필드로 대체하는 DFG optimization입니다. 이 최적화가 정상적으로 동작하려면 모든 쓰기 연산이 명시적인 PutInternalField 노드로 드러나야 합니다. OSR exit는 JIT 실행 도중 interpreter로 제어를 넘기는 시점이며, 이미 변경된 객체 상태는 그 순간 영구적으로 반영됩니다. 기존 MapIteratorNext는 iterator의 internal storage pointer와 bucket index를 노드 내부에서 암묵적인 side effect로 업데이트하고 있었습니다.
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
이 리팩터링으로 MapIteratorNext는 (storage, entry) 튜플을 반환하는 stateless read로 전환되었습니다. ByteCodeParser는 관련된 key/value 로드가 전부 완료된 후에야 새로운 storage와 entry index를 확정하는 명시적인 PutInternalField 노드를 생성합니다. 이를 통해 전체 write set이 두 최적화 경로 모두에 노출됩니다.
Significance
Map/Set에 대한 hot for-of 루프에서 heap allocation을 제거하는 iterator sinking이 가능해졌습니다. 또한 deferred commit을 통해, 기존에 OSR exit 발생 시 iterator가 미확정 중간 상태로 남아 있던 correctness 문제가 해결되었습니다.
Audit directions
ObjectAllocationSinking이 JSMapIterator를 sink한 뒤 escape가 발생하면, runtime은 virtual SSA 필드로부터 live 객체를 재구성해야 합니다. 이때 재구성되는 상태는 정확한 advancement 지점이어야 합니다. 새로운 materialization 경로에서 필드 순서가 잘못되거나 write가 누락되거나 storage pointer가 틀릴 경우, iterator는 stale하거나 해제된 bucket 메모리를 가리키게 됩니다. 이는 type confusion 또는 UAF primitive로 이어질 가능성이 있습니다.
한편 deferred commit 방식에서는, PutInternalField 실행 전에 OSR exit가 발생하면 re-entry 시 해당 단계를 다시 실행하게 됩니다. commit 이전과 이후 중 어느 시점에 로드를 보호할지 기준에 off-by-one이 존재한다면, 항목을 조용히 건너뛰거나 중복 방문하는 문제가 발생할 가능성이 있습니다.
Map/Set의 value type speculation은 prediction type에 의존합니다. JSStringIterator가 항상 String으로 예측되는 것과 달리, sentinel/done 케이스를 처리하기가 더 까다롭습니다. deferred-commit window 안에서 tuple extraction 도중 misprediction으로 OSR exit가 발생하는 경우를 살펴볼 필요가 있습니다. 이 경로는 DFG speculative JIT와 FTL B3 lowering 양쪽 모두 추적해야 합니다.
DFGSpeculativeJIT32_64.cpp에는 별도의 compileMapIteratorNext 구현이 있습니다. tuple layout과 commit 순서에 대한 32비트/64비트 구현의 일치 여부를 점검해야 합니다. 또한 sink된 iteration 도중 Map mutation(항목 삭제)을 fuzzing하는 것도 중요합니다. rehash 시 bucket pointer가 이동하는 상황을 집중적으로 살펴볼 필요가 있습니다.