← All issues

[JSC] New DFG node for StringIteratorPrototype.next

93518ed

JSC의 DFG JIT는 알려진 host 함수 호출을 "intrinsic" 노드로 대체할 수 있는데, 이 노드는 inline으로 최적화된 기계어로 확장됩니다. StringIteratorPrototype.next()는 이전까지 JS builtin으로 호출 지점에서 inline 처리되던 함수였습니다. 이 commit에서는 이를 JSStringIteratorNextIntrinsic으로 태그된 C++ host 함수로 전환했습니다. 결과적으로 DFGByteCodeParser가 해당 호출 지점을 인식하고 custom node graph를 생성하는 경로가 마련되었습니다.

Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp

+ case JSStringIteratorNextIntrinsic: {
+ if (!is64Bit())
+ return CallOptimizationResult::DidNothing;
+
+ Node* iterator = get(virtualRegisterForArgumentIncludingThis(0, registerOffset));
+ addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(globalObject->stringIteratorStructure())), iterator);
+
+ Node* position = addToGraph(GetInternalField, OpInfo(static_cast<uint32_t>(JSStringIterator::Field::Index)), OpInfo(SpecInt32Only), iterator);
+ Node* string = addToGraph(GetInternalField, OpInfo(static_cast<uint32_t>(JSStringIterator::Field::IteratedString)), OpInfo(SpecString), iterator);
+
+ Node* tuple = addToGraph(StringIteratorNextWithUndefined, Edge(string), Edge(position));
+ Node* value = addToGraph(ExtractFromTuple, OpInfo(0), tuple);
+ value->setResult(NodeResultJS);
+ Node* nextPosition = addToGraph(ExtractFromTuple, OpInfo(1), tuple);
+ nextPosition->setResult(NodeResultInt32);
+ ...
+ // OSR exit 가능한 모든 노드 이후에 iterator를 전진시킴
+ addToGraph(PutInternalField, OpInfo(static_cast<uint32_t>(JSStringIterator::Field::Index)), iterator, nextPosition);
+ setResult(resultObject);
+ return CallOptimizationResult::Inlined;
+ }

DFG는 이 호출을 직렬 노드 시퀀스로 확장합니다. 먼저 CheckStructure guard로 타입을 확인하고, GetInternalField를 두 차례 실행하여 내부 필드를 조회합니다. 이어서 새 StringIteratorNextWithUndefined 노드가 (char|undefined, nextIndex) tuple을 생성하고, NewObject/PutByOffset으로 결과 객체를 구성한 다음, PutInternalField가 index를 전진시키는 순서로 확장됩니다. 이 PutInternalField는 OSR exit 가능한 모든 노드보다 뒤에 생성되므로, deopt가 이미 전진된 index로 next()를 재실행하는 상황이 차단됩니다. 단, 이 경로는 64비트 환경에서만 동작합니다. StringIteratorNextWithUndefined는 기존 StringIteratorNext(for-of에서 사용)의 형제 노드로, 소진 시점에 빈 문자열 sentinel 대신 undefined를 반환한다는 점이 유일한 차이입니다. 이로써 for-of 경로에서의 SpecString 타입 추론이 유지됩니다.

babel로 트랜스파일된 코드에서 흔히 볼 수 있는 iterator.next() 직접 호출이, 이제는 interpret되는 builtin 대신 최적화된 JIT fast path로 컴파일됩니다. 벤치마크 기준으로 약 1.7배 빠른 성능을 보였습니다(105ms → 61ms). 또한 이번 변경으로 boxed JSValue 요소를 포함한 최초의 DFG tuple 노드가 도입되었습니다. 이 인프라는 향후 다른 intrinsic을 구현할 때 재사용될 것입니다.

🔒

New JIT-expanded intrinsic with OSR exit ordering constraints and novel tuple node infrastructure — several audit directions included.

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