Async function promise optimization for no-await bodies
19dc01a
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
+ if (isEmptyBody()) {
+ ASSERT(!usingDeclarationCount());
+ ASSERT(!hasAwaitUsingDeclaration());
+ RefPtr<RegisterID> newResolvedPromise = generator.moveLinkTimeConstant(nullptr, LinkTimeConstant::newResolvedPromise);
+ CallArguments resolveArgs(generator, nullptr, 1);
+ generator.emitLoad(resolveArgs.thisRegister(), jsUndefined());
+ generator.emitLoad(resolveArgs.argumentRegister(0), jsUndefined());
+ RefPtr<RegisterID> result = generator.newTemporary();
+ generator.emitCall(result.get(), newResolvedPromise.get(), NoExpectedFunction, resolveArgs, divot, divot, divot, DebuggableCall::No);
+ generator.emitWillLeaveCallFrameDebugHook();
+ generator.emitReturn(result.get());
+ break;
+ }
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
+ case NewResolvedPromiseIntrinsic: {
+ if (argumentCountIncludingThis < 2)
+ return CallOptimizationResult::DidNothing;
+ insertChecks();
+ Node* argument = get(virtualRegisterForArgumentIncludingThis(1, registerOffset));
+ setResult(addToGraph(NewResolvedPromise, Edge(argument)));
+ return CallOptimizationResult::Inlined;
+ }
+
+ case NewRejectedPromiseIntrinsic: {
+ if (argumentCountIncludingThis < 2)
+ return CallOptimizationResult::DidNothing;
+ insertChecks();
+ Node* argument = get(virtualRegisterForArgumentIncludingThis(1, registerOffset));
+ setResult(addToGraph(NewRejectedPromise, Edge(argument)));
+ return CallOptimizationResult::Inlined;
+ }
In JSC, every async function is desugared to a state machine that wraps its body in an implicit promise. Historically, this promise is allocated at function entry; when the body finishes, fulfillPromiseWithFirstResolvingFunctionCallCheck is called to settle it — handling thenable unwrapping, spec-mandated identity checks, and PerformPromiseThen. For no-await async functions, the promise is guaranteed to settle synchronously at the first return point, so the pre-allocation and full settle path are pure overhead.
This commit detects no-await bodies at compile time via isEmptyBody() and emits a deferred newResolvedPromise/newRejectedPromise call at return time instead. New DFG and FTL IR nodes (NewResolvedPromise, NewRejectedPromise) back this path so the JIT can fold it into a direct fulfilled-promise allocation when the return value is proven non-object.
Before (all async functions):
Entry
└─► allocate Promise (heap) ─────────────────────────────────┐
└─► run body │
└─► fulfillPromiseWithFirstResolvingFunctionCallCheck ──┘
(thenable check, identity guard)
└─► return pre-allocated Promise
After (no-await async functions):
Entry
└─► run body (no promise allocated)
├── normal return ──► NewResolvedPromise(value)
│ (DFG: direct fulfilled-promise alloc if non-object)
└── throw ──► NewRejectedPromise(error)
└─► return freshly-settled Promise
Significance
Async functions with no await are common in real codebases (adapters, delegation wrappers, trivial async APIs), and eliminating the upfront promise allocation and full settle path reduces both GC pressure and throughput overhead for callers that chain .then() on such functions.
Audit directions
Aaaaaaa Aaaaaa Aaaaa Aaaaaaaaa Aaaaaa Aaaaaaaaaaaaa Aaaaaaaaaaaaaa Aaaaaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaa Aaaa Aaa Aaaaaa Aaaa a Aaaaaaaa Aa Aaaaaa Aa Aaaaaaaa Aaa Aaaaaa Aaaaaa Aaaaaaaaaaaa Aaaa Aa Aaaaaaaaaaaaaaaaa Aaaa Aaa Aaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaa Aa Aaa a Aaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaa Aaaaa Aaaaaaaaa Aaaaa Aaaa Aa Aaaaaaaaaaaaa Aaaaaaaaa Aa Aaaaaaaaaaaaa Aaa Aaa Aaaaaaaaaa Aaaa Aaaaaaa Aa Aaaa Aa a Aaaaaaaaaaaaaaaaaaaa Aaaa
Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaa Aaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aaaaaaaaaaaaaaaa Aaa Aaaa Aaaaaa Aaa Aaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaaa a Aaaaaaaa Aa Aaaaaaaaaaaa Aaaaaaaa a Aaaaa Aaaaaaa Aa Aa Aaaaaaaaa Aaaaaaaa Aaaaaaaaaa Aaa Aaaaa Aaaaaaa Aaaaaaaa Aa Aaaaaaaaaa Aa Aaaaaaaaaa a Aaaaaaa Aaa Aaaaaaaaaaa Aaaa Aaaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaa
Aaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaaaaaaaa Aaaaa Aaa Aaa Aaaaa Aaa Aaaa Aaaaaaaa Aaaa Aa Aaaaa Aaaaaaaaaa Aaaaa Aaaaaaa Aa Aaaaa Aaaaa Aa Aaa Aaaaaaaa Aaaaa Aaaaaaaa Aaa Aaa Aaaa Aaaaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaa Aaaa Aaaaaaa Aaa Aaaa Aaaaaaaaa Aaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaa Aaaaaaaaa Aaaaaa Aaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaa Aaaaaa
Aaaaaaa Aaa Aaaa Aaaaaaa Aaaaaaaaaa Aaaa Aaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaa Aaaaa Aaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaa Aaaaaa Aaaaaaa a Aaa Aaaaaa Aaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaa Aaaaaa Aaa Aaa Aaaaaaaa Aaaaaaaa Aaaa Aa Aaaaa Aaaaaaa Aaaaaaaaa Aa Aaaaaa Aaa Aaaaaaaaa Aaaaa Aaaa Aaaaaaaaa Aaaaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaa Aa Aaaaaaaaaaaaaaaaaaaaa
🔒New JIT nodes and fast-path classification logic for async functions — several edge cases are worth security investigation.
Subscribe to read more