From 6530f55f34e0dc6ad362d07cbc7d4cbbe4347929 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 9 May 2025 04:57:02 +0300 Subject: [PATCH] LibJS: Cache rejected/fulfilled callbacks in AsyncFunctionDriverWrapper Saves us two NativeFunction allocations per each `await`. With this change, following function goes 80% faster on my computer: ```js (async () => { const resolved = Promise.resolve(); for (let i = 0; i < 5_000_000; i++) { await resolved; } })(); ``` --- Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp | 10 +++++++--- Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.h | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp b/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp index 0007ee97d1b..8f3a67216c1 100644 --- a/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp +++ b/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp @@ -75,7 +75,8 @@ ThrowCompletionOr AsyncFunctionDriverWrapper::await(JS::Value value) }; // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). - auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1); + if (!m_on_fulfilled) + m_on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1); // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the // following steps when called: @@ -103,11 +104,12 @@ ThrowCompletionOr AsyncFunctionDriverWrapper::await(JS::Value value) }; // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). - auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1); + if (!m_on_rejected) + m_on_rejected = NativeFunction::create(realm, move(rejected_closure), 1); // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected). m_current_promise = as(promise_object); - m_current_promise->perform_then(on_fulfilled, on_rejected, {}); + m_current_promise->perform_then(m_on_fulfilled, m_on_rejected, {}); // NOTE: None of these are necessary. 8-12 are handled by step d of the above lambdas. // 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the @@ -176,6 +178,8 @@ void AsyncFunctionDriverWrapper::visit_edges(Cell::Visitor& visitor) visitor.visit(m_current_promise); if (m_suspended_execution_context) m_suspended_execution_context->visit_edges(visitor); + visitor.visit(m_on_fulfilled); + visitor.visit(m_on_rejected); } } diff --git a/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.h b/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.h index ba93f94f5ee..54b0d67d8cd 100644 --- a/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.h +++ b/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.h @@ -34,6 +34,9 @@ private: GC::Ref m_top_level_promise; GC::Ptr m_current_promise { nullptr }; OwnPtr m_suspended_execution_context; + + GC::Ptr m_on_fulfilled; + GC::Ptr m_on_rejected; }; }