DFG/FTL PerformPromiseThen node for species-check-free Promise.then()
c1df92d
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
+ case PromiseThen: {
+ JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic);
+ auto& promise = m_state.forNode(node->child1());
+ if (promise.isType(SpecPromiseObject)) {
+ auto& structureSet = promise.m_structure;
+ if (structureSet.isFinite()) {
+ if (auto structure = structureSet.onlyStructure()) {
+ if (structure.get() == globalObject->promiseStructure()) {
+ if (m_graph.isWatchingPromiseSpeciesWatchpoint(node)) {
+ m_interpreter.execute(indexInBlock);
+ alreadyHandled = true;
+
+ auto* resultPromise = m_insertionSet.insertNode(
+ indexInBlock, SpecPromiseObject, NewInternalFieldObject,
+ node->origin, OpInfo(m_graph.registerStructure(globalObject->promiseStructure())));
+ m_insertionSet.insertNode(indexInBlock, SpecNone, ExitOK, node->origin);
+
+ unsigned firstChild = m_graph.m_varArgChildren.size();
+ m_graph.m_varArgChildren.append(Edge(node->child1().node(), KnownCellUse));
+ m_graph.m_varArgChildren.append(node->child2());
+ m_graph.m_varArgChildren.append(node->child3());
+ m_graph.m_varArgChildren.append(Edge(resultPromise, KnownCellUse));
+ m_insertionSet.insertNode(indexInBlock, SpecNone, PerformPromiseThen,
+ node->origin, AdjacencyList(AdjacencyList::Variable, firstChild, 4));
+ m_insertionSet.insertNode(indexInBlock, SpecNone, ExitOK, node->origin);
+
+ node->convertToIdentityOn(resultPromise);
+ changed = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
ECMAScript 사양은 Promise.prototype.then이 결과 promise를 생성할 때 반드시 SpeciesConstructor(promise, %Promise%)를 호출하도록 규정합니다. 서브클래스가 Symbol.species를 통해 결과 타입을 재정의할 수 있도록 하기 위한 사양 요건입니다. 다만 이 constructor 호출은 비용이 크고 inlining을 방해합니다. JSC는 Promise species 프로퍼티에 watchpoint를 유지하며, 커스텀 Symbol.species가 설정된 이력이 없는 한 이 watchpoint는 유효 상태로 존재합니다. 여기에 더해, DFG abstract interpreter는 특정 값이 서브클래스 인스턴스가 아닌 정확히 base Promise 구조를 가짐을 증명할 수도 있습니다.
ConstantFoldingPhase 수행 중 두 조건이 동시에 성립하면, 컴파일러는 species check가 no-op임을 정적으로 증명합니다. 그 결과, 단일 PromiseThen node는 두 가지 더 간단한 연산으로 대체됩니다. 먼저 NewInternalFieldObject를 통해 결과 promise를 직접 할당하고, 이후 PerformPromiseThen으로 callback을 연결합니다.
Before (unoptimized PromiseThen):
promise.then(f, r)
└─► SpeciesConstructor(promise) ← may call subclass ctor
└─► PerformPromiseThen(promise, f, r, resultPromise)
After (ConstantFoldingPhase, watchpoint valid + exact base structure):
PromiseThen
├─► NewInternalFieldObject(promiseStructure) → resultPromise
├─► ExitOK
├─► PerformPromiseThen(promise, f, r, resultPromise)
├─► ExitOK
└─► node→convertToIdentityOn(resultPromise)
Significance
Promise.then()은 현대 JavaScript에서 가장 빈번히 실행되는 async code path 중 하나입니다. species constructor 호출을 제거함으로써 처리량이 측정 가능한 수준으로 향상되며, 보안에 민감하고 사양이 강제하는 알고리즘에 새로운 JIT 컴파일 fast path가 도입됩니다.
Audit directions
Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aa Aaa Aaaaa Aa Aa Aa Aaaaaaaa Aaaaaa a Aaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaaaa Aaaaa Aaa a Aaaa Aaaa Aaa Aaaa Aaaaa Aa Aaaaaaa Aaa Aaa Aaaa Aaa Aaa a Aaaaa Aaaaaaaaaa Aa Aaaa Aaa Aaa Aa Aa Aaa Aaa Aaa Aaa Aaaa Aaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaa Aaaaaaaa Aaaa Aaaaaaaa Aaa Aaaaaaaaaaaaaaaa Aaaa Aaaaa Aa Aaa Aaaaa Aa Aa Aa Aaaa Aaa Aaaaa a Aa Aaa Aaaa Aaaaa Aaa Aaaaaa Aaaaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaa Aa Aaaaa a Aa Aa Aaaaaaaaa Aaaaaaaaa Aaaa a Aaa Aaa a Aaaaaaa Aa Aaa Aaa Aaaa Aaaaaaaaaaaaaaaaaaaaa Aa a Aaa Aaaa Aaaa
🔒New JIT fast path for Promise.then — the split allocation sequence and watchpoint gating introduce several edge cases worth security investigation.
더 확인하려면 구독해 주세요