← 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;
+ }

The ECMAScript spec requires Promise.prototype.then to use SpeciesConstructor(promise, %Promise%) when creating the result promise, allowing subclasses to override result type via Symbol.species. This constructor call is expensive and prevents inlining. JSC maintains a watchpoint on the Promise species property; as long as no code has set a custom Symbol.species, the watchpoint stays valid. The DFG abstract interpreter can additionally prove that a given value has exactly the base Promise structure (not a subclass instance).

When both conditions hold during ConstantFoldingPhase, the compiler statically proves the species check is a no-op and replaces the monolithic PromiseThen node with two cheaper operations: directly allocate the result promise via NewInternalFieldObject, then wire the callbacks with PerformPromiseThen.

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() is one of the hottest async code paths in modern JavaScript; eliminating the species constructor call is a measurable throughput win and introduces a new JIT-compiled fast path for a security-sensitive, spec-mandated algorithm.

🔒

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

Subscribe to read more