diff options
author | Stephen Belanger <stephen.belanger@datadoghq.com> | 2021-04-20 18:10:05 -0700 |
---|---|---|
committer | Stephen Belanger <stephen.belanger@datadoghq.com> | 2021-05-06 15:17:35 -0700 |
commit | 50dd62ed96e745a087c9289a76721e5f5e0fecbb (patch) | |
tree | 6b52857a9e737dc9cc6304bd2c44e7c61ad18c9d /deps/v8/src/builtins | |
parent | 2465062c45bb73025ecf06f475339bcf6e783de2 (diff) | |
download | node-new-50dd62ed96e745a087c9289a76721e5f5e0fecbb.tar.gz |
deps: V8: backport c0fceaa0669b
Original commit message:
Reland "[api] JSFunction PromiseHook for v8::Context"
This is a reland of d5457f5fb7ea05ca05a697599ffa50d35c1ae3c7
after a speculative revert.
Additionally it fixes an issue with throwing promise hooks.
Original change's description:
> [api] JSFunction PromiseHook for v8::Context
>
> This will enable Node.js to get much better performance from async_hooks
> as currently PromiseHook delegates to C++ for the hook function and then
> Node.js delegates it right back to JavaScript, introducing several
> unnecessary barrier hops in code that gets called very, very frequently
> in modern, promise-heavy applications.
>
> This API mirrors the form of the original C++ function based PromiseHook
> API, however it is intentionally separate to allow it to use JSFunctions
> triggered within generated code to, as much as possible, avoid entering
> runtime functions entirely.
>
> Because PromiseHook has internal use also, beyond just the Node.js use,
> I have opted to leave the existing API intact and keep this separate to
> avoid conflicting with any possible behaviour expectations of other API
> users.
>
> The design ideas for this new API stemmed from discussion with some V8
> team members at a previous Node.js Diagnostics Summit hosted by Google
> in Munich, and the relevant documentation of the discussion can be found
> here: https://docs.google.com/document/d/1g8OrG5lMIUhRn1zbkutgY83MiTSMx-0NHDs8Bf-nXxM/edit#heading=h.w1bavzz80l1e
>
> A summary of the reasons for why this new design is important can be
> found here: https://docs.google.com/document/d/1vtgoT4_kjgOr-Bl605HR2T6_SC-C8uWzYaOPDK5pmRo/edit?usp=sharing
>
> Bug: v8:11025
> Change-Id: I0b403b00c37d3020b5af07b654b860659d3a7697
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2759188
> Reviewed-by: Marja Hölttä <marja@chromium.org>
> Reviewed-by: Camillo Bruni <cbruni@chromium.org>
> Reviewed-by: Anton Bikineev <bikineev@chromium.org>
> Reviewed-by: Igor Sheludko <ishell@chromium.org>
> Commit-Queue: Camillo Bruni <cbruni@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#73858}
Bug: v8:11025
Bug: chromium:1197475
Change-Id: I73a71e97d9c3dff89a2b092c3fe4adff81ede8ef
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2823917
Reviewed-by: Marja Hölttä <marja@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Anton Bikineev <bikineev@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74071}
Refs: https://github.com/v8/v8/commit/c0fceaa0669b39136c9e780f278e2596d71b4e8a
PR-URL: https://github.com/nodejs/node/pull/36394
Reviewed-By: Bryan English <bryan@bryanenglish.com>
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com>
Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'deps/v8/src/builtins')
-rw-r--r-- | deps/v8/src/builtins/builtins-async-function-gen.cc | 4 | ||||
-rw-r--r-- | deps/v8/src/builtins/builtins-async-gen.cc | 62 | ||||
-rw-r--r-- | deps/v8/src/builtins/builtins-async-gen.h | 6 | ||||
-rw-r--r-- | deps/v8/src/builtins/builtins-async-generator-gen.cc | 2 | ||||
-rw-r--r-- | deps/v8/src/builtins/builtins-microtask-queue-gen.cc | 62 | ||||
-rw-r--r-- | deps/v8/src/builtins/cast.tq | 6 | ||||
-rw-r--r-- | deps/v8/src/builtins/promise-abstract-operations.tq | 15 | ||||
-rw-r--r-- | deps/v8/src/builtins/promise-all.tq | 2 | ||||
-rw-r--r-- | deps/v8/src/builtins/promise-constructor.tq | 7 | ||||
-rw-r--r-- | deps/v8/src/builtins/promise-jobs.tq | 2 | ||||
-rw-r--r-- | deps/v8/src/builtins/promise-misc.tq | 122 | ||||
-rw-r--r-- | deps/v8/src/builtins/promise-resolve.tq | 2 |
12 files changed, 238 insertions, 54 deletions
diff --git a/deps/v8/src/builtins/builtins-async-function-gen.cc b/deps/v8/src/builtins/builtins-async-function-gen.cc index 49b00caa04..1644997ed0 100644 --- a/deps/v8/src/builtins/builtins-async-function-gen.cc +++ b/deps/v8/src/builtins/builtins-async-function-gen.cc @@ -157,12 +157,14 @@ TF_BUILTIN(AsyncFunctionEnter, AsyncFunctionBuiltinsAssembler) { StoreObjectFieldNoWriteBarrier( async_function_object, JSAsyncFunctionObject::kPromiseOffset, promise); + RunContextPromiseHookInit(context, promise, UndefinedConstant()); + // Fire promise hooks if enabled and push the Promise under construction // in an async function on the catch prediction stack to handle exceptions // thrown before the first await. Label if_instrumentation(this, Label::kDeferred), if_instrumentation_done(this); - Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), + Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), &if_instrumentation, &if_instrumentation_done); BIND(&if_instrumentation); { diff --git a/deps/v8/src/builtins/builtins-async-gen.cc b/deps/v8/src/builtins/builtins-async-gen.cc index fa05e9b32a..1a660abece 100644 --- a/deps/v8/src/builtins/builtins-async-gen.cc +++ b/deps/v8/src/builtins/builtins-async-gen.cc @@ -99,18 +99,11 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOld( TVARIABLE(HeapObject, var_throwaway, UndefinedConstant()); - // Deal with PromiseHooks and debug support in the runtime. This - // also allocates the throwaway promise, which is only needed in - // case of PromiseHooks or debugging. - Label if_debugging(this, Label::kDeferred), do_resolve_promise(this); - Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), - &if_debugging, &do_resolve_promise); - BIND(&if_debugging); - var_throwaway = - CAST(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value, promise, - outer_promise, on_reject, is_predicted_as_caught)); - Goto(&do_resolve_promise); - BIND(&do_resolve_promise); + RunContextPromiseHookInit(context, promise, outer_promise); + + InitAwaitPromise(Runtime::kAwaitPromisesInitOld, context, value, promise, + outer_promise, on_reject, is_predicted_as_caught, + &var_throwaway); // Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »). CallBuiltin(Builtins::kResolvePromise, context, promise, value); @@ -170,21 +163,46 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized( TVARIABLE(HeapObject, var_throwaway, UndefinedConstant()); + InitAwaitPromise(Runtime::kAwaitPromisesInit, context, promise, promise, + outer_promise, on_reject, is_predicted_as_caught, + &var_throwaway); + + return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise, + on_resolve, on_reject, var_throwaway.value()); +} + +void AsyncBuiltinsAssembler::InitAwaitPromise( + Runtime::FunctionId id, TNode<Context> context, TNode<Object> value, + TNode<Object> promise, TNode<Object> outer_promise, + TNode<HeapObject> on_reject, TNode<Oddball> is_predicted_as_caught, + TVariable<HeapObject>* var_throwaway) { // Deal with PromiseHooks and debug support in the runtime. This // also allocates the throwaway promise, which is only needed in // case of PromiseHooks or debugging. - Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this); - Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), - &if_debugging, &do_perform_promise_then); + Label if_debugging(this, Label::kDeferred), + if_promise_hook(this, Label::kDeferred), + not_debugging(this), + do_nothing(this); + TNode<Uint32T> promiseHookFlags = PromiseHookFlags(); + Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + promiseHookFlags), &if_debugging, ¬_debugging); BIND(&if_debugging); - var_throwaway = - CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise, + *var_throwaway = + CAST(CallRuntime(id, context, value, promise, outer_promise, on_reject, is_predicted_as_caught)); - Goto(&do_perform_promise_then); - BIND(&do_perform_promise_then); - - return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise, - on_resolve, on_reject, var_throwaway.value()); + Goto(&do_nothing); + BIND(¬_debugging); + + // This call to NewJSPromise is to keep behaviour parity with what happens + // in Runtime::kAwaitPromisesInit above if native hooks are set. It will + // create a throwaway promise that will trigger an init event and will get + // passed into Builtins::kPerformPromiseThen below. + Branch(IsContextPromiseHookEnabled(promiseHookFlags), &if_promise_hook, + &do_nothing); + BIND(&if_promise_hook); + *var_throwaway = NewJSPromise(context, promise); + Goto(&do_nothing); + BIND(&do_nothing); } TNode<Object> AsyncBuiltinsAssembler::Await( diff --git a/deps/v8/src/builtins/builtins-async-gen.h b/deps/v8/src/builtins/builtins-async-gen.h index 833e78d45d..34b7a0ce1d 100644 --- a/deps/v8/src/builtins/builtins-async-gen.h +++ b/deps/v8/src/builtins/builtins-async-gen.h @@ -62,6 +62,12 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler { TNode<SharedFunctionInfo> on_resolve_sfi, TNode<SharedFunctionInfo> on_reject_sfi, TNode<Oddball> is_predicted_as_caught); + + void InitAwaitPromise( + Runtime::FunctionId id, TNode<Context> context, TNode<Object> value, + TNode<Object> promise, TNode<Object> outer_promise, + TNode<HeapObject> on_reject, TNode<Oddball> is_predicted_as_caught, + TVariable<HeapObject>* var_throwaway); }; } // namespace internal diff --git a/deps/v8/src/builtins/builtins-async-generator-gen.cc b/deps/v8/src/builtins/builtins-async-generator-gen.cc index 374b13dd63..5d053063ff 100644 --- a/deps/v8/src/builtins/builtins-async-generator-gen.cc +++ b/deps/v8/src/builtins/builtins-async-generator-gen.cc @@ -520,7 +520,7 @@ TF_BUILTIN(AsyncGeneratorResolve, AsyncGeneratorBuiltinsAssembler) { // the "promiseResolve" hook would not be fired otherwise. Label if_fast(this), if_slow(this, Label::kDeferred), return_promise(this); GotoIfForceSlowPath(&if_slow); - GotoIf(IsPromiseHookEnabled(), &if_slow); + GotoIf(IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(), &if_slow); Branch(IsPromiseThenProtectorCellInvalid(), &if_slow, &if_fast); BIND(&if_fast); diff --git a/deps/v8/src/builtins/builtins-microtask-queue-gen.cc b/deps/v8/src/builtins/builtins-microtask-queue-gen.cc index 9f16186d13..1ec9e350f6 100644 --- a/deps/v8/src/builtins/builtins-microtask-queue-gen.cc +++ b/deps/v8/src/builtins/builtins-microtask-queue-gen.cc @@ -46,8 +46,11 @@ class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler { void EnterMicrotaskContext(TNode<Context> native_context); void RewindEnteredContext(TNode<IntPtrT> saved_entered_context_count); + void RunAllPromiseHooks(PromiseHookType type, TNode<Context> context, + TNode<HeapObject> promise_or_capability); void RunPromiseHook(Runtime::FunctionId id, TNode<Context> context, - TNode<HeapObject> promise_or_capability); + TNode<HeapObject> promise_or_capability, + TNode<Uint32T> promiseHookFlags); }; TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueue( @@ -199,7 +202,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( const TNode<Object> thenable = LoadObjectField( microtask, PromiseResolveThenableJobTask::kThenableOffset); - RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, + RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context, CAST(promise_to_resolve)); { @@ -208,7 +211,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( promise_to_resolve, thenable, then); } - RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, + RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context, CAST(promise_to_resolve)); RewindEnteredContext(saved_entered_context_count); @@ -243,8 +246,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( BIND(&preserved_data_done); // Run the promise before/debug hook if enabled. - RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, - promise_or_capability); + RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context, + promise_or_capability); { ScopedExceptionHandler handler(this, &if_exception, &var_exception); @@ -253,8 +256,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( } // Run the promise after/debug hook if enabled. - RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, - promise_or_capability); + RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context, + promise_or_capability); Label preserved_data_reset_done(this); GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done); @@ -296,8 +299,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( BIND(&preserved_data_done); // Run the promise before/debug hook if enabled. - RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, - promise_or_capability); + RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context, + promise_or_capability); { ScopedExceptionHandler handler(this, &if_exception, &var_exception); @@ -306,8 +309,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( } // Run the promise after/debug hook if enabled. - RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, - promise_or_capability); + RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context, + promise_or_capability); Label preserved_data_reset_done(this); GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done); @@ -465,12 +468,43 @@ void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext( saved_entered_context_count); } +void MicrotaskQueueBuiltinsAssembler::RunAllPromiseHooks( + PromiseHookType type, TNode<Context> context, + TNode<HeapObject> promise_or_capability) { + Label hook(this, Label::kDeferred), done_hook(this); + TNode<Uint32T> promiseHookFlags = PromiseHookFlags(); + Branch(IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + promiseHookFlags), &hook, &done_hook); + BIND(&hook); + { + switch (type) { + case PromiseHookType::kBefore: + RunContextPromiseHookBefore(context, promise_or_capability, + promiseHookFlags); + RunPromiseHook(Runtime::kPromiseHookBefore, context, + promise_or_capability, promiseHookFlags); + break; + case PromiseHookType::kAfter: + RunContextPromiseHookAfter(context, promise_or_capability, + promiseHookFlags); + RunPromiseHook(Runtime::kPromiseHookAfter, context, + promise_or_capability, promiseHookFlags); + break; + default: + UNREACHABLE(); + } + Goto(&done_hook); + } + BIND(&done_hook); +} + void MicrotaskQueueBuiltinsAssembler::RunPromiseHook( Runtime::FunctionId id, TNode<Context> context, - TNode<HeapObject> promise_or_capability) { + TNode<HeapObject> promise_or_capability, + TNode<Uint32T> promiseHookFlags) { Label hook(this, Label::kDeferred), done_hook(this); - Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), &hook, - &done_hook); + Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + promiseHookFlags), &hook, &done_hook); BIND(&hook); { // Get to the underlying JSPromise instance. diff --git a/deps/v8/src/builtins/cast.tq b/deps/v8/src/builtins/cast.tq index b490055a19..2bec3d86be 100644 --- a/deps/v8/src/builtins/cast.tq +++ b/deps/v8/src/builtins/cast.tq @@ -386,6 +386,12 @@ Cast<Undefined|Callable>(o: HeapObject): Undefined|Callable return HeapObjectToCallable(o) otherwise CastError; } +Cast<Undefined|JSFunction>(o: HeapObject): Undefined|JSFunction + labels CastError { + if (o == Undefined) return Undefined; + return Cast<JSFunction>(o) otherwise CastError; +} + macro Cast<T : type extends Symbol>(o: Symbol): T labels CastError; Cast<PublicSymbol>(s: Symbol): PublicSymbol labels CastError { if (s.flags.is_private) goto CastError; diff --git a/deps/v8/src/builtins/promise-abstract-operations.tq b/deps/v8/src/builtins/promise-abstract-operations.tq index b7a1b571e6..0e435afad9 100644 --- a/deps/v8/src/builtins/promise-abstract-operations.tq +++ b/deps/v8/src/builtins/promise-abstract-operations.tq @@ -196,6 +196,8 @@ FulfillPromise(implicit context: Context)( // Assert: The value of promise.[[PromiseState]] is "pending". assert(promise.Status() == PromiseState::kPending); + RunContextPromiseHookResolve(promise); + // 2. Let reactions be promise.[[PromiseFulfillReactions]]. const reactions = UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result); @@ -214,17 +216,24 @@ FulfillPromise(implicit context: Context)( } extern macro PromiseBuiltinsAssembler:: - IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(): bool; + IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(): bool; + +extern macro PromiseBuiltinsAssembler:: + IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(uint32): + bool; // https://tc39.es/ecma262/#sec-rejectpromise transitioning builtin RejectPromise(implicit context: Context)( promise: JSPromise, reason: JSAny, debugEvent: Boolean): JSAny { + const promiseHookFlags = PromiseHookFlags(); + // If promise hook is enabled or the debugger is active, let // the runtime handle this operation, which greatly reduces // the complexity here and also avoids a couple of back and // forth between JavaScript and C++ land. - if (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() || + if (IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + promiseHookFlags) || !promise.HasHandler()) { // 7. If promise.[[PromiseIsHandled]] is false, perform // HostPromiseRejectionTracker(promise, "reject"). @@ -233,6 +242,8 @@ RejectPromise(implicit context: Context)( return runtime::RejectPromise(promise, reason, debugEvent); } + RunContextPromiseHookResolve(promise, promiseHookFlags); + // 2. Let reactions be promise.[[PromiseRejectReactions]]. const reactions = UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result); diff --git a/deps/v8/src/builtins/promise-all.tq b/deps/v8/src/builtins/promise-all.tq index 41dee8b9e7..294c5e911c 100644 --- a/deps/v8/src/builtins/promise-all.tq +++ b/deps/v8/src/builtins/promise-all.tq @@ -232,7 +232,7 @@ Reject(Object) { // PerformPromiseThen), since this is only necessary for DevTools and // PromiseHooks. if (promiseResolveFunction != Undefined || - IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() || + IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() || IsPromiseSpeciesProtectorCellInvalid() || Is<Smi>(nextValue) || !IsPromiseThenLookupChainIntact( nativeContext, UnsafeCast<HeapObject>(nextValue).map)) { diff --git a/deps/v8/src/builtins/promise-constructor.tq b/deps/v8/src/builtins/promise-constructor.tq index 3c5a5e560d..b5f7292a77 100644 --- a/deps/v8/src/builtins/promise-constructor.tq +++ b/deps/v8/src/builtins/promise-constructor.tq @@ -40,7 +40,8 @@ extern macro ConstructorBuiltinsAssembler::FastNewObject( Context, JSFunction, JSReceiver): JSObject; extern macro -PromiseBuiltinsAssembler::IsPromiseHookEnabledOrHasAsyncEventDelegate(): bool; +PromiseBuiltinsAssembler::IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate( + uint32): bool; // https://tc39.es/ecma262/#sec-promise-executor transitioning javascript builtin @@ -73,9 +74,7 @@ PromiseConstructor( result = UnsafeCast<JSPromise>( FastNewObject(context, promiseFun, UnsafeCast<JSReceiver>(newTarget))); PromiseInit(result); - if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) { - runtime::PromiseHookInit(result, Undefined); - } + RunAnyPromiseHookInit(result, Undefined); } const isDebugActive = IsDebugActive(); diff --git a/deps/v8/src/builtins/promise-jobs.tq b/deps/v8/src/builtins/promise-jobs.tq index 80e98f373b..6fa81dcd28 100644 --- a/deps/v8/src/builtins/promise-jobs.tq +++ b/deps/v8/src/builtins/promise-jobs.tq @@ -25,7 +25,7 @@ PromiseResolveThenableJob(implicit context: Context)( const promiseThen = *NativeContextSlot(ContextSlot::PROMISE_THEN_INDEX); const thenableMap = thenable.map; if (TaggedEqual(then, promiseThen) && IsJSPromiseMap(thenableMap) && - !IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() && + !IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() && IsPromiseSpeciesLookupChainIntact(nativeContext, thenableMap)) { // We know that the {thenable} is a JSPromise, which doesn't require // any special treatment and that {then} corresponds to the initial diff --git a/deps/v8/src/builtins/promise-misc.tq b/deps/v8/src/builtins/promise-misc.tq index 67e5e38687..c6661e3717 100644 --- a/deps/v8/src/builtins/promise-misc.tq +++ b/deps/v8/src/builtins/promise-misc.tq @@ -8,6 +8,9 @@ namespace runtime { extern transitioning runtime AllowDynamicFunction(implicit context: Context)(JSAny): JSAny; + +extern transitioning runtime +ReportMessageFromMicrotask(implicit context: Context)(JSAny): JSAny; } // Unsafe functions that should be used very carefully. @@ -17,6 +20,12 @@ extern macro PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets(JSPromise): void; extern macro PromiseBuiltinsAssembler::AllocateJSPromise(Context): HeapObject; } +extern macro +PromiseBuiltinsAssembler::IsContextPromiseHookEnabled(uint32): bool; + +extern macro +PromiseBuiltinsAssembler::PromiseHookFlags(): uint32; + namespace promise { extern macro IsFunctionWithPrototypeSlotMap(Map): bool; @@ -90,6 +99,110 @@ macro NewPromiseRejectReactionJobTask(implicit context: Context)( }; } +@export +transitioning macro RunContextPromiseHookInit(implicit context: Context)( + promise: JSPromise, parent: Object) { + const maybeHook = *NativeContextSlot( + ContextSlot::PROMISE_HOOK_INIT_FUNCTION_INDEX); + if (IsUndefined(maybeHook)) return; + + const hook = Cast<JSFunction>(maybeHook) otherwise unreachable; + const parentObject = Is<JSPromise>(parent) ? Cast<JSPromise>(parent) + otherwise unreachable: Undefined; + + try { + Call(context, hook, Undefined, promise, parentObject); + } catch (e) { + runtime::ReportMessageFromMicrotask(e); + } +} + +@export +transitioning macro RunContextPromiseHookResolve(implicit context: Context)( + promise: JSPromise) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise, + PromiseHookFlags()); +} + +@export +transitioning macro RunContextPromiseHookResolve(implicit context: Context)( + promise: JSPromise, flags: uint32) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise, flags); +} + +@export +transitioning macro RunContextPromiseHookBefore(implicit context: Context)( + promiseOrCapability: JSPromise|PromiseCapability) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability, + PromiseHookFlags()); +} + +@export +transitioning macro RunContextPromiseHookBefore(implicit context: Context)( + promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability, + flags); +} + +@export +transitioning macro RunContextPromiseHookAfter(implicit context: Context)( + promiseOrCapability: JSPromise|PromiseCapability) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability, + PromiseHookFlags()); +} + +@export +transitioning macro RunContextPromiseHookAfter(implicit context: Context)( + promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability, + flags); +} + +transitioning macro RunContextPromiseHook(implicit context: Context)( + slot: Slot<NativeContext, Undefined|JSFunction>, + promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) { + if (!IsContextPromiseHookEnabled(flags)) return; + const maybeHook = *NativeContextSlot(slot); + if (IsUndefined(maybeHook)) return; + + const hook = Cast<JSFunction>(maybeHook) otherwise unreachable; + + let promise: JSPromise; + typeswitch (promiseOrCapability) { + case (jspromise: JSPromise): { + promise = jspromise; + } + case (capability: PromiseCapability): { + promise = Cast<JSPromise>(capability.promise) otherwise return; + } + } + + try { + Call(context, hook, Undefined, promise); + } catch (e) { + runtime::ReportMessageFromMicrotask(e); + } +} + +transitioning macro RunAnyPromiseHookInit(implicit context: Context)( + promise: JSPromise, parent: Object) { + const promiseHookFlags = PromiseHookFlags(); + // Fast return if no hooks are set. + if (promiseHookFlags == 0) return; + if (IsContextPromiseHookEnabled(promiseHookFlags)) { + RunContextPromiseHookInit(promise, parent); + } + if (IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(promiseHookFlags)) { + runtime::PromiseHookInit(promise, parent); + } +} + // These allocate and initialize a promise with pending state and // undefined fields. // @@ -100,9 +213,7 @@ transitioning macro NewJSPromise(implicit context: Context)(parent: Object): JSPromise { const instance = InnerNewJSPromise(); PromiseInit(instance); - if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) { - runtime::PromiseHookInit(instance, parent); - } + RunAnyPromiseHookInit(instance, parent); return instance; } @@ -124,10 +235,7 @@ transitioning macro NewJSPromise(implicit context: Context)( instance.reactions_or_result = result; instance.SetStatus(status); promise_internal::ZeroOutEmbedderOffsets(instance); - - if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) { - runtime::PromiseHookInit(instance, Undefined); - } + RunAnyPromiseHookInit(instance, Undefined); return instance; } diff --git a/deps/v8/src/builtins/promise-resolve.tq b/deps/v8/src/builtins/promise-resolve.tq index e933dfbae0..3125054e87 100644 --- a/deps/v8/src/builtins/promise-resolve.tq +++ b/deps/v8/src/builtins/promise-resolve.tq @@ -97,7 +97,7 @@ ResolvePromise(implicit context: Context)( // We also let the runtime handle it if promise == resolution. // We can use pointer comparison here, since the {promise} is guaranteed // to be a JSPromise inside this function and thus is reference comparable. - if (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() || + if (IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() || TaggedEqual(promise, resolution)) deferred { return runtime::ResolvePromise(promise, resolution); |