← All issues

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)

Promise.then()은 현대 JavaScript에서 가장 빈번히 실행되는 async code path 중 하나입니다. species constructor 호출을 제거함으로써 처리량이 측정 가능한 수준으로 향상되며, 보안에 민감하고 사양이 강제하는 알고리즘에 새로운 JIT 컴파일 fast path가 도입됩니다.

🔒

New JIT fast path for Promise.then — the split allocation sequence and watchpoint gating introduce several edge cases worth security investigation.

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