diff --git a/Source/JavaScriptCore/runtime/AsyncContextSwapScope.h b/Source/JavaScriptCore/runtime/AsyncContextSwapScope.h index 9d9a33c00687..2bcc7710d330 100644 --- a/Source/JavaScriptCore/runtime/AsyncContextSwapScope.h +++ b/Source/JavaScriptCore/runtime/AsyncContextSwapScope.h @@ -28,6 +28,8 @@ #if USE(BUN_JSC_ADDITIONS) #include "InternalFieldTuple.h" +#include "JSAsyncFunctionGenerator.h" +#include "JSAsyncGenerator.h" #include "JSCast.h" #include "JSGlobalObject.h" #include @@ -38,8 +40,8 @@ namespace JSC { // RAII helper for Bun's AsyncLocalStorage: swaps an async context value into // JSGlobalObject::m_asyncContextData field 0 for the lifetime of the scope and // restores the previous value on destruction. A no-op when the supplied context -// is empty or undefined, so the common path (no async context active) costs a -// single branch. Also provides helpers for the snapshot side (capturing the +// is empty, or undefined while async context tracking has never been enabled on +// the global, so the common path (no async context in use) costs a branch. Also provides helpers for the snapshot side (capturing the // current context and wrapping it into an InternalFieldTuple alongside a user // context) and for unwrapping such a tuple on the restore side. class AsyncContextSwapScope { @@ -49,13 +51,21 @@ class AsyncContextSwapScope { ALWAYS_INLINE AsyncContextSwapScope(VM& vm, JSGlobalObject* globalObject, JSValue asyncContext) : m_vm(vm) { - if (asyncContext.isEmpty() || asyncContext.isUndefined()) + if (asyncContext.isEmpty()) + return; + // Once anything uses async context, every continuation installs the + // context it captured -- including "none" -- so a context entered inside + // one continuation (AsyncLocalStorage.enterWith, an activated span) is + // scoped to that continuation instead of leaking into whichever job + // happens to run next. Until then this stays a single branch. + if (asyncContext.isUndefined() && !globalObject->isAsyncContextTrackingEnabled()) return; m_asyncContextData = globalObject->m_asyncContextData.get(); if (!m_asyncContextData) return; m_restoreAsyncContext = m_asyncContextData->getInternalField(0); - m_asyncContextData->putInternalField(vm, 0, asyncContext); + if (m_restoreAsyncContext != asyncContext) + m_asyncContextData->putInternalField(vm, 0, asyncContext); } ALWAYS_INLINE ~AsyncContextSwapScope() @@ -111,6 +121,54 @@ class AsyncContextSwapScope { return InternalFieldTuple::create(vm, globalObject->internalFieldTupleStructure(), userContext, asyncContext); } + // Await-side capture. When the suspending object is an async (generator) + // function, record the current async context in its AsyncContext slot — a + // suspended async function has exactly one outstanding await, so this is a + // single barriered store instead of an InternalFieldTuple allocation per + // await — and return the object itself. Other drivers (AsyncFromSyncIterator, + // top-level-await module records) are rare and keep the tuple path. + static ALWAYS_INLINE JSValue captureForAwait(VM& vm, JSGlobalObject* globalObject, JSValue driver) + { + auto* asyncContextData = globalObject->m_asyncContextData.get(); + if (!asyncContextData) + return driver; + JSValue asyncContext = asyncContextData->getInternalField(0); + if (driver.isCell()) { + JSCell* cell = driver.asCell(); + JSType type = cell->type(); + if (type == JSAsyncFunctionGeneratorType) { + auto* generator = uncheckedDowncast(cell); + if (generator->asyncContext() != asyncContext) + generator->setAsyncContext(vm, asyncContext); + return driver; + } + if (type == JSAsyncGeneratorType) { + auto* generator = uncheckedDowncast(cell); + if (generator->asyncContext() != asyncContext) + generator->setAsyncContext(vm, asyncContext); + return driver; + } + } + if (asyncContext.isUndefined()) + return driver; + return InternalFieldTuple::create(vm, globalObject->internalFieldTupleStructure(), driver, asyncContext); + } + + // Resume-side counterpart of captureForAwait(): yields the async context to + // restore and leaves contextArg pointing at the unwrapped driver. + static ALWAYS_INLINE JSValue contextForResume(JSValue& contextArg) + { + if (contextArg.isCell()) { + JSCell* cell = contextArg.asCell(); + JSType type = cell->type(); + if (type == JSAsyncFunctionGeneratorType) + return uncheckedDowncast(cell)->asyncContext(); + if (type == JSAsyncGeneratorType) + return uncheckedDowncast(cell)->asyncContext(); + } + return unwrapContextTuple(contextArg); + } + private: VM& m_vm; InternalFieldTuple* m_asyncContextData { nullptr }; diff --git a/Source/JavaScriptCore/runtime/AsyncFromSyncIteratorPrototype.cpp b/Source/JavaScriptCore/runtime/AsyncFromSyncIteratorPrototype.cpp index 7dd84c361498..c23e9cd624cd 100644 --- a/Source/JavaScriptCore/runtime/AsyncFromSyncIteratorPrototype.cpp +++ b/Source/JavaScriptCore/runtime/AsyncFromSyncIteratorPrototype.cpp @@ -26,6 +26,9 @@ #include "config.h" #include "AsyncFromSyncIteratorPrototype.h" +#if USE(BUN_JSC_ADDITIONS) +#include "AsyncContextSwapScope.h" +#endif #include "IteratorOperations.h" #include "JSArrayInlines.h" @@ -270,7 +273,11 @@ void driveAsyncFromSyncIteratorWithDriver(JSGlobalObject* globalObject, JSAsyncF JSValue error = catchScope.exception()->value(); if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] return; +#if USE(BUN_JSC_ADDITIONS) + JSPromise::rejectWithInternalMicrotask(vm, globalObject, error, InternalMicrotask::AsyncGeneratorDriverResume, AsyncContextSwapScope::captureForAwait(vm, globalObject, driver)); +#else JSPromise::rejectWithInternalMicrotask(vm, globalObject, error, InternalMicrotask::AsyncGeneratorDriverResume, driver); +#endif return; } } diff --git a/Source/JavaScriptCore/runtime/JSAsyncFunctionGenerator.h b/Source/JavaScriptCore/runtime/JSAsyncFunctionGenerator.h index 386468725505..7ac55cc971ca 100644 --- a/Source/JavaScriptCore/runtime/JSAsyncFunctionGenerator.h +++ b/Source/JavaScriptCore/runtime/JSAsyncFunctionGenerator.h @@ -30,9 +30,15 @@ namespace JSC { -class JSAsyncFunctionGenerator final : public JSInternalFieldObjectImpl<5> { +#if USE(BUN_JSC_ADDITIONS) +static constexpr unsigned jsAsyncFunctionGeneratorNumberOfInternalFields = 6; +#else +static constexpr unsigned jsAsyncFunctionGeneratorNumberOfInternalFields = 5; +#endif + +class JSAsyncFunctionGenerator final : public JSInternalFieldObjectImpl { public: - using Base = JSInternalFieldObjectImpl<5>; + using Base = JSInternalFieldObjectImpl; template static GCClient::IsoSubspace* subspaceFor(VM& vm) @@ -52,8 +58,14 @@ class JSAsyncFunctionGenerator final : public JSInternalFieldObjectImpl<5> { This, Frame, Context, +#if USE(BUN_JSC_ADDITIONS) + // Bun async context (AsyncLocalStorage) captured at the most recent await and + // restored when the function resumes. A suspended async function has exactly one + // outstanding await, so one slot replaces a per-await InternalFieldTuple. + AsyncContext, +#endif }; - static_assert(numberOfInternalFields == 5); + static_assert(numberOfInternalFields == jsAsyncFunctionGeneratorNumberOfInternalFields); static_assert(static_cast(Field::State) == static_cast(JSGenerator::Field::State)); static_assert(static_cast(Field::Next) == static_cast(JSGenerator::Field::Next)); static_assert(static_cast(Field::This) == static_cast(JSGenerator::Field::This)); @@ -67,6 +79,9 @@ class JSAsyncFunctionGenerator final : public JSInternalFieldObjectImpl<5> { jsUndefined(), jsUndefined(), jsUndefined(), +#if USE(BUN_JSC_ADDITIONS) + jsUndefined(), +#endif } }; } @@ -108,6 +123,18 @@ class JSAsyncFunctionGenerator final : public JSInternalFieldObjectImpl<5> { return Base::internalField(static_cast(Field::Context)).get(); } +#if USE(BUN_JSC_ADDITIONS) + JSValue asyncContext() const + { + return Base::internalField(static_cast(Field::AsyncContext)).get(); + } + + void setAsyncContext(VM& vm, JSValue value) + { + Base::internalField(static_cast(Field::AsyncContext)).set(vm, this, value); + } +#endif + DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; diff --git a/Source/JavaScriptCore/runtime/JSAsyncGenerator.h b/Source/JavaScriptCore/runtime/JSAsyncGenerator.h index 26de3dc50daf..5a7e20cd0094 100644 --- a/Source/JavaScriptCore/runtime/JSAsyncGenerator.h +++ b/Source/JavaScriptCore/runtime/JSAsyncGenerator.h @@ -31,9 +31,15 @@ namespace JSC { -class JSAsyncGenerator final : public JSInternalFieldObjectImpl<10> { +#if USE(BUN_JSC_ADDITIONS) +static constexpr unsigned jsAsyncGeneratorNumberOfInternalFields = 11; +#else +static constexpr unsigned jsAsyncGeneratorNumberOfInternalFields = 10; +#endif + +class JSAsyncGenerator final : public JSInternalFieldObjectImpl { public: - using Base = JSInternalFieldObjectImpl<10>; + using Base = JSInternalFieldObjectImpl; template static GCClient::IsoSubspace* subspaceFor(VM& vm) @@ -86,8 +92,12 @@ class JSAsyncGenerator final : public JSInternalFieldObjectImpl<10> { ResumePromise, CachedDriverResult, CachedDriverResultTarget, +#if USE(BUN_JSC_ADDITIONS) + // Bun async context captured at the most recent await; see JSAsyncFunctionGenerator. + AsyncContext, +#endif }; - static_assert(numberOfInternalFields == 10); + static_assert(numberOfInternalFields == jsAsyncGeneratorNumberOfInternalFields); static std::array initialValues() { return { { @@ -101,6 +111,9 @@ class JSAsyncGenerator final : public JSInternalFieldObjectImpl<10> { jsUndefined(), jsUndefined(), jsUndefined(), +#if USE(BUN_JSC_ADDITIONS) + jsUndefined(), +#endif } }; } @@ -197,6 +210,18 @@ class JSAsyncGenerator final : public JSInternalFieldObjectImpl<10> { Base::internalField(static_cast(Field::CachedDriverResultTarget)).set(vm, this, value); } +#if USE(BUN_JSC_ADDITIONS) + JSValue asyncContext() const + { + return Base::internalField(static_cast(Field::AsyncContext)).get(); + } + + void setAsyncContext(VM& vm, JSValue value) + { + Base::internalField(static_cast(Field::AsyncContext)).set(vm, this, value); + } +#endif + bool isQueueEmpty() const { return resumeMode() == static_cast(AsyncGeneratorResumeMode::Empty); diff --git a/Source/JavaScriptCore/runtime/JSMicrotask.cpp b/Source/JavaScriptCore/runtime/JSMicrotask.cpp index 1b15d8377a73..68cca2347450 100644 --- a/Source/JavaScriptCore/runtime/JSMicrotask.cpp +++ b/Source/JavaScriptCore/runtime/JSMicrotask.cpp @@ -307,9 +307,9 @@ static ALWAYS_INLINE void settleDriverWithIteratorResult(JSGlobalObject* globalO { JSGlobalObject* realm = producer->realm(); #if USE(BUN_JSC_ADDITIONS) - // Capture Bun's async context alongside the driver so AsyncGeneratorDriverResume can restore it. + // Capture Bun's async context on the driver so AsyncGeneratorDriverResume can restore it. // The unwrapped target is still used for the cached-result identity check below. - JSValue wrappedTarget = AsyncContextSwapScope::wrapWithCurrent(vm, globalObject, target); + JSValue wrappedTarget = AsyncContextSwapScope::captureForAwait(vm, globalObject, target); #else UNUSED_PARAM(globalObject); JSValue wrappedTarget = target; @@ -372,8 +372,13 @@ static void asyncFromSyncIteratorContinueOrDone(JSGlobalObject* globalObject, VM scope.release(); if (auto* promise = dynamicDowncast(target)) promise->reject(vm, result); - else + else { +#if USE(BUN_JSC_ADDITIONS) + JSPromise::rejectWithInternalMicrotask(vm, globalObject, result, InternalMicrotask::AsyncGeneratorDriverResume, AsyncContextSwapScope::captureForAwait(vm, globalObject, target)); +#else JSPromise::rejectWithInternalMicrotask(vm, globalObject, result, InternalMicrotask::AsyncGeneratorDriverResume, target); +#endif + } break; } case JSPromise::Status::Fulfilled: { @@ -551,7 +556,7 @@ static void asyncGeneratorCompleteStep(JSGlobalObject* globalObject, JSAsyncGene // resolveWithInternalMicrotask keeps resolvePromise's thenable check, matching a real Promise settlement. if (isThrow) { #if USE(BUN_JSC_ADDITIONS) - JSValue wrappedTarget = AsyncContextSwapScope::wrapWithCurrent(vm, globalObject, target); + JSValue wrappedTarget = AsyncContextSwapScope::captureForAwait(vm, globalObject, target); #else JSValue wrappedTarget = target; #endif @@ -1845,7 +1850,7 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas // context may be an InternalFieldTuple [userContext, asyncContext]; the resolving // functions keep the tuple as-is, so only peek at field 1 for the swap. JSValue peek = context; - AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(peek)); + AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::contextForResume(peek)); #endif auto [resolve, reject] = JSPromise::createResolvingFunctionsWithInternalMicrotask(vm, globalObject, task, context); @@ -1907,16 +1912,24 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas RELEASE_AND_RETURN(scope, promiseAnyResolveJob(resultPromise->realm(), vm, globalContext, arguments[1], static_cast(arguments[2].asAnyInt()), static_cast(payload))); } +#if USE(BUN_JSC_ADDITIONS) + case InternalMicrotask::PromiseReactionJobWithAsyncContext: +#endif case InternalMicrotask::PromiseReactionJob: { JSValue promiseOrCapability = arguments[0]; JSValue handler = arguments[1]; #if USE(BUN_JSC_ADDITIONS) - // arguments[3] is either an InternalFieldTuple [userContext, asyncContext] - // or userContext directly (legacy behavior). The scope stays active through - // resolvePromise/rejectPromise so thenables returned from the handler - // capture the correct async context, and restores on every return. + // arguments[3] is Bun's async context itself (PromiseReactionJobWithAsyncContext), an + // InternalFieldTuple [userContext, asyncContext] (performPromiseThenWithContext), or a + // bare userContext. The scope stays active through resolvePromise/rejectPromise so + // thenables returned from the handler capture the correct async context. JSValue userContext = arguments[3]; - AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(userContext)); + JSValue asyncContext; + if (task == InternalMicrotask::PromiseReactionJobWithAsyncContext) + asyncContext = std::exchange(userContext, JSValue()); + else + asyncContext = AsyncContextSwapScope::unwrapContextTuple(userContext); + AsyncContextSwapScope asyncContextScope(vm, globalObject, asyncContext); #endif JSValue result; @@ -2001,8 +2014,7 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas JSValue contextArg = arguments[2]; #if USE(BUN_JSC_ADDITIONS) - // contextArg may be an InternalFieldTuple [generator, asyncContext]. - AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(contextArg)); + AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::contextForResume(contextArg)); #endif auto* generator = uncheckedDowncast(contextArg); JSGlobalObject* generatorGlobalObject = generator->realm(); @@ -2026,7 +2038,7 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas case InternalMicrotask::AsyncGeneratorYieldAwaited: { JSValue contextArg = arguments[2]; #if USE(BUN_JSC_ADDITIONS) - AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(contextArg)); + AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::contextForResume(contextArg)); #endif auto* generator = uncheckedDowncast(contextArg); scope.release(); @@ -2037,7 +2049,7 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas case InternalMicrotask::AsyncGeneratorBodyCallNormal: { JSValue contextArg = arguments[2]; #if USE(BUN_JSC_ADDITIONS) - AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(contextArg)); + AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::contextForResume(contextArg)); #endif auto* generator = uncheckedDowncast(contextArg); scope.release(); @@ -2048,7 +2060,7 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas case InternalMicrotask::AsyncGeneratorBodyCallReturn: { JSValue contextArg = arguments[2]; #if USE(BUN_JSC_ADDITIONS) - AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(contextArg)); + AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::contextForResume(contextArg)); #endif auto* generator = uncheckedDowncast(contextArg); scope.release(); @@ -2059,7 +2071,7 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas case InternalMicrotask::AsyncGeneratorAwaitReturn: { JSValue contextArg = arguments[2]; #if USE(BUN_JSC_ADDITIONS) - AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(contextArg)); + AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::contextForResume(contextArg)); #endif auto* generator = uncheckedDowncast(contextArg); scope.release(); @@ -2070,7 +2082,7 @@ void runInternalMicrotask(JSGlobalObject* globalObject, VM& vm, InternalMicrotas case InternalMicrotask::AsyncGeneratorDriverResume: { JSValue contextArg = arguments[2]; #if USE(BUN_JSC_ADDITIONS) - AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::unwrapContextTuple(contextArg)); + AsyncContextSwapScope asyncContextScope(vm, globalObject, AsyncContextSwapScope::contextForResume(contextArg)); #endif scope.release(); asyncGeneratorDriverResume(vm, contextArg, arguments[1], static_cast(payload), microtaskCallCache); diff --git a/Source/JavaScriptCore/runtime/JSPromise.cpp b/Source/JavaScriptCore/runtime/JSPromise.cpp index ff34f32f3d6a..b4be994f7da2 100644 --- a/Source/JavaScriptCore/runtime/JSPromise.cpp +++ b/Source/JavaScriptCore/runtime/JSPromise.cpp @@ -345,8 +345,10 @@ void JSPromise::performPromiseThen(VM& vm, JSGlobalObject* globalObject, JSValue bool rejectedCallable = onRejected.isCallable(); #if USE(BUN_JSC_ADDITIONS) - // Capture async context for promise reaction as [userContext (undefined), asyncContext]. - JSValue context = AsyncContextSwapScope::wrapWithCurrent(vm, globalObject, jsUndefined()); + // Capture Bun's async context for the reaction. It travels as the reaction's context with the + // PromiseReactionJobWithAsyncContext tag so no [userContext, asyncContext] tuple is allocated. + JSValue context = AsyncContextSwapScope::current(globalObject); + InternalMicrotask reactionJob = context.isUndefined() ? InternalMicrotask::PromiseReactionJob : InternalMicrotask::PromiseReactionJobWithAsyncContext; #endif switch (status()) { @@ -375,7 +377,7 @@ void JSPromise::performPromiseThen(VM& vm, JSGlobalObject* globalObject, JSValue reaction = JSFullPromiseReaction::create(vm, promiseOrCapability, fulfilledCallable ? onFulfilled : jsUndefined(), rejectedCallable ? onRejected : jsUndefined(), - context, existing); + context, existing, InternalMicrotask::PromiseReactionJobWithAsyncContext); } else #endif if (onlyFulfill) @@ -396,7 +398,7 @@ void JSPromise::performPromiseThen(VM& vm, JSGlobalObject* globalObject, JSValue globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, this, JSPromiseRejectionOperation::Handle); if (rejectedCallable) #if USE(BUN_JSC_ADDITIONS) - globalObject->queueMicrotask(vm, InternalMicrotask::PromiseReactionJob, static_cast(Status::Rejected), promiseOrCapability, onRejected, settled, context); + globalObject->queueMicrotask(vm, reactionJob, static_cast(Status::Rejected), promiseOrCapability, onRejected, settled, context); #else globalObject->queueMicrotask(vm, InternalMicrotask::PromiseReactionJob, static_cast(Status::Rejected), promiseOrCapability, onRejected, settled); #endif @@ -409,7 +411,7 @@ void JSPromise::performPromiseThen(VM& vm, JSGlobalObject* globalObject, JSValue JSValue settled = settlementValue(); if (fulfilledCallable) #if USE(BUN_JSC_ADDITIONS) - globalObject->queueMicrotask(vm, InternalMicrotask::PromiseReactionJob, static_cast(Status::Fulfilled), promiseOrCapability, onFulfilled, settled, context); + globalObject->queueMicrotask(vm, reactionJob, static_cast(Status::Fulfilled), promiseOrCapability, onFulfilled, settled, context); #else globalObject->queueMicrotask(vm, InternalMicrotask::PromiseReactionJob, static_cast(Status::Fulfilled), promiseOrCapability, onFulfilled, settled); #endif @@ -918,6 +920,10 @@ void JSPromise::triggerPromiseReactions(VM& vm, JSGlobalObject* globalObject, St break; } JSValue context = fullReaction->context(); + if (fullReaction->internalMicrotask() == InternalMicrotask::PromiseReactionJobWithAsyncContext) { + globalObject->queueMicrotask(vm, InternalMicrotask::PromiseReactionJobWithAsyncContext, static_cast(status), promise, handler, arg, context); + return; + } if (!context.isUndefinedOrNull()) { globalObject->queueMicrotask(vm, task, static_cast(status), promise, handler, arg, context); return; @@ -965,10 +971,10 @@ void JSPromise::triggerPromiseReactions(VM& vm, JSGlobalObject* globalObject, St void JSPromise::resolveWithInternalMicrotaskForAsyncAwait(JSGlobalObject* globalObject, VM& vm, JSValue resolution, InternalMicrotask task, JSValue context) { #if USE(BUN_JSC_ADDITIONS) - // Capture Bun's async context at the point of await and wrap it with the generator context. - // This allows AsyncFunctionResume and related microtasks to restore the async context when - // resuming the async function. - JSValue wrappedContext = AsyncContextSwapScope::wrapWithCurrent(vm, globalObject, context); + // Capture Bun's async context at the point of await so AsyncFunctionResume and related + // microtasks restore it when resuming. For async (generator) functions this is a store into + // the generator; other drivers get an InternalFieldTuple [driver, asyncContext]. + JSValue wrappedContext = AsyncContextSwapScope::captureForAwait(vm, globalObject, context); #define BUN_CONTEXT wrappedContext #else #define BUN_CONTEXT context diff --git a/Source/JavaScriptCore/runtime/JSPromiseReaction.cpp b/Source/JavaScriptCore/runtime/JSPromiseReaction.cpp index 3e9117bd9dff..ff0371191c62 100644 --- a/Source/JavaScriptCore/runtime/JSPromiseReaction.cpp +++ b/Source/JavaScriptCore/runtime/JSPromiseReaction.cpp @@ -107,9 +107,14 @@ DEFINE_VISIT_CHILDREN(JSSlimPromiseReaction); const ClassInfo JSFullPromiseReaction::s_info = { "FullPromiseReaction"_s, &JSPromiseReaction::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSFullPromiseReaction) }; -JSFullPromiseReaction* JSFullPromiseReaction::create(VM& vm, JSValue promise, JSValue onFulfilled, JSValue onRejected, JSValue context, JSPromiseReaction* next) +JSFullPromiseReaction* JSFullPromiseReaction::create(VM& vm, JSValue promise, JSValue onFulfilled, JSValue onRejected, JSValue context, JSPromiseReaction* next, InternalMicrotask task) { - JSFullPromiseReaction* result = new (NotNull, allocateCell(vm)) JSFullPromiseReaction(vm, vm.fullPromiseReactionStructure.get(), promise, onFulfilled, onRejected, context, next); +#if USE(BUN_JSC_ADDITIONS) + ASSERT(task == InternalMicrotask::None || task == InternalMicrotask::PromiseReactionJobWithAsyncContext); +#else + ASSERT(task == InternalMicrotask::None); +#endif + JSFullPromiseReaction* result = new (NotNull, allocateCell(vm)) JSFullPromiseReaction(vm, vm.fullPromiseReactionStructure.get(), promise, onFulfilled, onRejected, context, next, task); result->finishCreation(vm); return result; } diff --git a/Source/JavaScriptCore/runtime/JSPromiseReaction.h b/Source/JavaScriptCore/runtime/JSPromiseReaction.h index ebd95df45cc0..18eed862cd62 100644 --- a/Source/JavaScriptCore/runtime/JSPromiseReaction.h +++ b/Source/JavaScriptCore/runtime/JSPromiseReaction.h @@ -121,7 +121,7 @@ class JSFullPromiseReaction final : public JSPromiseReaction { static Structure* createStructure(VM&, JSGlobalObject*, JSValue); - static JSFullPromiseReaction* create(VM&, JSValue promise, JSValue onFulfilled, JSValue onRejected, JSValue context, JSPromiseReaction* next); + static JSFullPromiseReaction* create(VM&, JSValue promise, JSValue onFulfilled, JSValue onRejected, JSValue context, JSPromiseReaction* next, InternalMicrotask = InternalMicrotask::None); JSValue onFulfilled() const { return m_onFulfilled.get(); } JSValue onRejected() const { return m_onRejected.get(); } @@ -133,8 +133,8 @@ class JSFullPromiseReaction final : public JSPromiseReaction { private: - JSFullPromiseReaction(VM& vm, Structure* structure, JSValue promise, JSValue onFulfilled, JSValue onRejected, JSValue context, JSPromiseReaction* next) - : Base(vm, structure, promise, next, static_cast(InternalMicrotask::None)) + JSFullPromiseReaction(VM& vm, Structure* structure, JSValue promise, JSValue onFulfilled, JSValue onRejected, JSValue context, JSPromiseReaction* next, InternalMicrotask task) + : Base(vm, structure, promise, next, static_cast(task)) , m_onFulfilled(onFulfilled, WriteBarrierEarlyInit) , m_onRejected(onRejected, WriteBarrierEarlyInit) , m_context(context, WriteBarrierEarlyInit) diff --git a/Source/JavaScriptCore/runtime/Microtask.h b/Source/JavaScriptCore/runtime/Microtask.h index 73b5f8d3114c..4179c387b5aa 100644 --- a/Source/JavaScriptCore/runtime/Microtask.h +++ b/Source/JavaScriptCore/runtime/Microtask.h @@ -91,6 +91,7 @@ enum class InternalMicrotask : uint8_t { #if USE(BUN_JSC_ADDITIONS) BunPerformMicrotaskJob, // Bun's performMicrotask function with async context BunInvokeJobWithArguments, // Invoke job function with up to 4 arguments + PromiseReactionJobWithAsyncContext, // PromiseReactionJob whose trailing argument is Bun's async context rather than a user context #endif };