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)
Significance
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.
Audit directions
Aaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaa Aaa Aaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaa Aa Aa Aaaa Aaaaaaa Aaaaa Aaa Aaaaaaaa Aaaaa Aaa Aaaaaaa Aaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aaaaaaa a Aaaaaaaaa Aa a Aaaaaaaaa Aaaaaaa Aaaaaa Aaaaaaaaaaa Aaa Aaaaaa Aaaaa Aa Aaaaaaaa Aa a Aaaaaaaaa Aaaaaaaaaaa Aaaaaa Aa Aaa Aaa Aaaaa Aaaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaaaaaaa Aa Aaaa Aaa Aaa Aaaaaaaa Aaaaaa Aaa Aaaaaaaaaaaaa a Aaaaa Aaaaaaa Aaa Aaaa Aaaaaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaa Aaaaaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaa a Aaaaaa Aaaa a Aaaaaaaaaaaaaaa Aaaaaa Aaaaaa Aaaaa Aaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaa Aaaaa Aaa Aaaaaaaaaaaa Aaaaaa Aaaaaa Aaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaa
🔒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