mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-27 10:07:15 +02:00
LibJS: Add fast path in async function await for non-thenable values
Per spec, every `await` goes through PromiseResolve (which wraps the value in a new Promise via NewPromiseCapability) and then PerformPromiseThen (which creates PromiseReaction and JobCallback objects). This results in 13-16 GC cell allocations per await. Add a fast path that detects two common cases: 1. Primitive values: These can never have a "then" property, so we can skip all promise wrapping and directly schedule the async function's continuation as a microtask. 2. Already-settled native Promises: If the promise has no own properties and its prototype is the intrinsic %Promise.prototype%, we can extract the result directly and schedule continuation. For these cases, we bypass promise_resolve(), new_promise_capability(), create_resolving_functions(), perform_then(), PromiseReaction creation, and JobCallback creation -- replacing ~13 GC allocations with 1 (the GC::Function for the microtask job).
This commit is contained in:
committed by
Andreas Kling
parent
b34274a2a0
commit
3a2f2f3926
Notes:
github-actions[bot]
2026-03-16 17:02:59 +00:00
Author: https://github.com/awesomekling Commit: https://github.com/LadybirdBrowser/ladybird/commit/3a2f2f39263 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8449 Reviewed-by: https://github.com/shannonbooth
@@ -46,6 +46,37 @@ ThrowCompletionOr<void> AsyncFunctionDriverWrapper::await(JS::Value value)
|
||||
if (!m_suspended_execution_context)
|
||||
m_suspended_execution_context = vm.running_execution_context().copy();
|
||||
|
||||
// OPTIMIZATION: Fast path for non-thenable values.
|
||||
//
|
||||
// Per spec, PromiseResolve wraps non-Promise values in a new resolved promise,
|
||||
// then PerformPromiseThen attaches reaction handlers and schedules a microtask.
|
||||
// This creates 10+ GC objects per await.
|
||||
//
|
||||
// Since primitives can never have a "then" property, and already-settled native
|
||||
// Promises with the %Promise% constructor don't need wrapping, we can skip all
|
||||
// of that machinery and directly schedule the async function's continuation.
|
||||
//
|
||||
// For pending promises, or promises with a non-standard constructor, we fall
|
||||
// through to the spec-compliant slow path.
|
||||
if (!value.is_object()) {
|
||||
// Primitive values are never thenable -- schedule resume directly.
|
||||
schedule_resume(value, true);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto promise = value.as_if<Promise>()) {
|
||||
// Already-settled native Promises whose constructor is the intrinsic %Promise%.
|
||||
auto* promise_prototype = realm.intrinsics().promise_prototype().ptr();
|
||||
if (promise->state() != Promise::State::Pending
|
||||
&& promise->shape().property_count() == 0
|
||||
&& promise->shape().prototype() == promise_prototype
|
||||
&& promise_prototype->get_without_side_effects(vm.names.constructor) == Value(realm.intrinsics().promise_constructor())) {
|
||||
schedule_resume(promise->result(), promise->state() == Promise::State::Fulfilled);
|
||||
promise->set_is_handled();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Let promise be ? PromiseResolve(%Promise%, value).
|
||||
auto* promise_object = TRY(promise_resolve(vm, realm.intrinsics().promise_constructor(), value));
|
||||
|
||||
@@ -105,6 +136,19 @@ ThrowCompletionOr<void> AsyncFunctionDriverWrapper::await(JS::Value value)
|
||||
return {};
|
||||
}
|
||||
|
||||
void AsyncFunctionDriverWrapper::schedule_resume(Value value, bool is_fulfilled)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
vm.host_enqueue_promise_job(
|
||||
GC::create_function(vm.heap(), [this, value, is_fulfilled, &vm]() -> ThrowCompletionOr<Value> {
|
||||
TRY(vm.push_execution_context(*m_suspended_execution_context, {}));
|
||||
continue_async_execution(vm, value, is_fulfilled);
|
||||
vm.pop_execution_context();
|
||||
return js_undefined();
|
||||
}),
|
||||
vm.current_realm());
|
||||
}
|
||||
|
||||
void AsyncFunctionDriverWrapper::continue_async_execution(VM& vm, Value value, bool is_successful)
|
||||
{
|
||||
auto generator_result = is_successful
|
||||
|
||||
Reference in New Issue
Block a user