← All issues

[JSC] Map/Set iterator next must not touch JSMapIterator/JSSetIterator directly

7937fd7

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

- Node* storage = addToGraph(MapIteratorNext, Edge(iterator, MapIteratorObjectUse));
- setResult(storage);
+ Node* tuple = addToGraph(MapIteratorNext, Edge(storageField), Edge(iteratedObjectField, MapObjectUse), Edge(entryField));
+ Node* newStorage = addToGraph(ExtractFromTuple, OpInfo(0), tuple);
+ newStorage->setResult(NodeResultJS);
+ Node* newEntry = addToGraph(ExtractFromTuple, OpInfo(1), tuple);
+ newEntry->setResult(NodeResultInt32);
+ addToGraph(PutInternalField, OpInfo(storageFieldIndex), mapIterator, newStorage);
+ addToGraph(PutInternalField, OpInfo(entryFieldIndex), mapIterator, newEntry);
+ FrozenValue* sentinelConst = m_graph.freezeStrong(m_graph.m_vm.orderedHashTableSentinel());
+ Node* done = addToGraph(CompareEqPtr, OpInfo(sentinelConst), newStorage);
+ setResult(done);

이 리팩터링으로 MapIteratorNext(storage, entry) 튜플을 반환하는 stateless read로 전환되었습니다. ByteCodeParser는 관련된 key/value 로드가 전부 완료된 후에야 새로운 storage와 entry index를 확정하는 명시적인 PutInternalField 노드를 생성합니다. 이를 통해 전체 write set이 두 최적화 경로 모두에 노출됩니다.

Map/Set에 대한 hot for-of 루프에서 heap allocation을 제거하는 iterator sinking이 가능해졌습니다. 또한 deferred commit을 통해, 기존에 OSR exit 발생 시 iterator가 미확정 중간 상태로 남아 있던 correctness 문제가 해결되었습니다.

🔒

Several edge cases in the new deferred-commit and iterator-sinking paths are worth security investigation.

더 확인하려면 구독해 주세요