From b13e57f66f78a8a4696d23f0548ade619aa714be Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Tue, 20 Apr 2021 18:10:05 -0700 Subject: [PATCH 1/6] deps: V8: backport c0fceaa0669b MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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ä > Reviewed-by: Camillo Bruni > Reviewed-by: Anton Bikineev > Reviewed-by: Igor Sheludko > Commit-Queue: Camillo Bruni > 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ä Reviewed-by: Igor Sheludko Reviewed-by: Anton Bikineev Reviewed-by: Camillo Bruni Commit-Queue: Camillo Bruni Cr-Commit-Position: refs/heads/master@{#74071} Refs: https://github.com/v8/v8/commit/c0fceaa0669b39136c9e780f278e2596d71b4e8a --- common.gypi | 2 +- deps/v8/AUTHORS | 1 + deps/v8/include/v8.h | 12 + deps/v8/src/api/api.cc | 39 +++ .../builtins/builtins-async-function-gen.cc | 4 +- deps/v8/src/builtins/builtins-async-gen.cc | 62 +++-- deps/v8/src/builtins/builtins-async-gen.h | 6 + .../builtins/builtins-async-generator-gen.cc | 2 +- .../builtins/builtins-microtask-queue-gen.cc | 62 ++++- deps/v8/src/builtins/cast.tq | 6 + .../builtins/promise-abstract-operations.tq | 15 +- deps/v8/src/builtins/promise-all.tq | 2 +- deps/v8/src/builtins/promise-constructor.tq | 7 +- deps/v8/src/builtins/promise-jobs.tq | 2 +- deps/v8/src/builtins/promise-misc.tq | 122 ++++++++- deps/v8/src/builtins/promise-resolve.tq | 2 +- deps/v8/src/codegen/code-stub-assembler.cc | 61 +++-- deps/v8/src/codegen/code-stub-assembler.h | 40 ++- deps/v8/src/codegen/external-reference.cc | 20 +- deps/v8/src/codegen/external-reference.h | 6 +- deps/v8/src/d8/d8.cc | 22 ++ deps/v8/src/d8/d8.h | 2 + deps/v8/src/execution/isolate.cc | 41 ++- deps/v8/src/execution/isolate.h | 47 +++- deps/v8/src/heap/factory.cc | 3 +- deps/v8/src/objects/contexts.cc | 48 ++++ deps/v8/src/objects/contexts.h | 8 + deps/v8/src/objects/contexts.tq | 6 + deps/v8/src/objects/objects.cc | 8 +- deps/v8/src/runtime/runtime-promise.cc | 8 +- .../test/cctest/test-code-stub-assembler.cc | 3 +- deps/v8/test/mjsunit/promise-hooks.js | 244 ++++++++++++++++++ 32 files changed, 782 insertions(+), 131 deletions(-) create mode 100644 deps/v8/test/mjsunit/promise-hooks.js diff --git a/common.gypi b/common.gypi index be7c06d4c81882..e5f01195e63850 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.11', + 'v8_embedder_string': '-node.12', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/AUTHORS b/deps/v8/AUTHORS index aa6d32302b3954..a27cf5ef0a1074 100644 --- a/deps/v8/AUTHORS +++ b/deps/v8/AUTHORS @@ -209,6 +209,7 @@ Seo Sanghyeon Shawn Anastasio Shawn Presser Stefan Penner +Stephen Belanger Sylvestre Ledru Taketoshi Aono Tao Liqiang diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index 083bf5e12323c2..e4448db1910069 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -10794,6 +10794,18 @@ class V8_EXPORT Context : public Data { */ void SetContinuationPreservedEmbedderData(Local context); + /** + * Set or clear hooks to be invoked for promise lifecycle operations. + * To clear a hook, set it to an empty v8::Function. Each function will + * receive the observed promise as the first argument. If a chaining + * operation is used on a promise, the init will additionally receive + * the parent promise as the second argument. + */ + void SetPromiseHooks(Local init_hook, + Local before_hook, + Local after_hook, + Local resolve_hook); + /** * Stack-allocated class which sets the execution context for all * operations executed within a local scope. diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index a56b7e1a7d7922..ca73b58a60ba42 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -6147,6 +6147,45 @@ void Context::SetContinuationPreservedEmbedderData(Local data) { *i::Handle::cast(Utils::OpenHandle(*data))); } +void v8::Context::SetPromiseHooks(Local init_hook, + Local before_hook, + Local after_hook, + Local resolve_hook) { + i::Handle context = Utils::OpenHandle(this); + i::Isolate* isolate = context->GetIsolate(); + + i::Handle init = isolate->factory()->undefined_value(); + i::Handle before = isolate->factory()->undefined_value(); + i::Handle after = isolate->factory()->undefined_value(); + i::Handle resolve = isolate->factory()->undefined_value(); + + bool has_hook = false; + + if (!init_hook.IsEmpty()) { + init = Utils::OpenHandle(*init_hook); + has_hook = true; + } + if (!before_hook.IsEmpty()) { + before = Utils::OpenHandle(*before_hook); + has_hook = true; + } + if (!after_hook.IsEmpty()) { + after = Utils::OpenHandle(*after_hook); + has_hook = true; + } + if (!resolve_hook.IsEmpty()) { + resolve = Utils::OpenHandle(*resolve_hook); + has_hook = true; + } + + isolate->SetHasContextPromiseHooks(has_hook); + + context->native_context().set_promise_hook_init_function(*init); + context->native_context().set_promise_hook_before_function(*before); + context->native_context().set_promise_hook_after_function(*after); + context->native_context().set_promise_hook_resolve_function(*resolve); +} + MaybeLocal metrics::Recorder::GetContext( Isolate* isolate, metrics::Recorder::ContextId id) { i::Isolate* i_isolate = reinterpret_cast(isolate); diff --git a/deps/v8/src/builtins/builtins-async-function-gen.cc b/deps/v8/src/builtins/builtins-async-function-gen.cc index 49b00caa048b4a..1644997ed0101d 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 fa05e9b32ad795..1a660abece17d2 100644 --- a/deps/v8/src/builtins/builtins-async-gen.cc +++ b/deps/v8/src/builtins/builtins-async-gen.cc @@ -99,18 +99,11 @@ TNode 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 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, TNode value, + TNode promise, TNode outer_promise, + TNode on_reject, TNode is_predicted_as_caught, + TVariable* 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 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 AsyncBuiltinsAssembler::Await( diff --git a/deps/v8/src/builtins/builtins-async-gen.h b/deps/v8/src/builtins/builtins-async-gen.h index 833e78d45d5be0..34b7a0ce1d654c 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 on_resolve_sfi, TNode on_reject_sfi, TNode is_predicted_as_caught); + + void InitAwaitPromise( + Runtime::FunctionId id, TNode context, TNode value, + TNode promise, TNode outer_promise, + TNode on_reject, TNode is_predicted_as_caught, + TVariable* 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 374b13dd63fc9a..5d053063ff9d8f 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 9f16186d13b8e9..1ec9e350f672d6 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 native_context); void RewindEnteredContext(TNode saved_entered_context_count); + void RunAllPromiseHooks(PromiseHookType type, TNode context, + TNode promise_or_capability); void RunPromiseHook(Runtime::FunctionId id, TNode context, - TNode promise_or_capability); + TNode promise_or_capability, + TNode promiseHookFlags); }; TNode MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueue( @@ -199,7 +202,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( const TNode 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, + TNode promise_or_capability) { + Label hook(this, Label::kDeferred), done_hook(this); + TNode 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, - TNode promise_or_capability) { + TNode promise_or_capability, + TNode 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 b490055a19e894..2bec3d86be062d 100644 --- a/deps/v8/src/builtins/cast.tq +++ b/deps/v8/src/builtins/cast.tq @@ -386,6 +386,12 @@ Cast(o: HeapObject): Undefined|Callable return HeapObjectToCallable(o) otherwise CastError; } +Cast(o: HeapObject): Undefined|JSFunction + labels CastError { + if (o == Undefined) return Undefined; + return Cast(o) otherwise CastError; +} + macro Cast(o: Symbol): T labels CastError; Cast(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 b7a1b571e6418b..0e435afad9b182 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 41dee8b9e7682e..294c5e911c9094 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(nextValue) || !IsPromiseThenLookupChainIntact( nativeContext, UnsafeCast(nextValue).map)) { diff --git a/deps/v8/src/builtins/promise-constructor.tq b/deps/v8/src/builtins/promise-constructor.tq index 3c5a5e560d49de..b5f7292a77cc70 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( FastNewObject(context, promiseFun, UnsafeCast(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 80e98f373b9665..6fa81dcd28634a 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 67e5e38687d76c..c6661e371732cb 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(maybeHook) otherwise unreachable; + const parentObject = Is(parent) ? Cast(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, + promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) { + if (!IsContextPromiseHookEnabled(flags)) return; + const maybeHook = *NativeContextSlot(slot); + if (IsUndefined(maybeHook)) return; + + const hook = Cast(maybeHook) otherwise unreachable; + + let promise: JSPromise; + typeswitch (promiseOrCapability) { + case (jspromise: JSPromise): { + promise = jspromise; + } + case (capability: PromiseCapability): { + promise = Cast(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 e933dfbae0abf8..3125054e87a313 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); diff --git a/deps/v8/src/codegen/code-stub-assembler.cc b/deps/v8/src/codegen/code-stub-assembler.cc index 76ee8c2d065666..415291dd736f20 100644 --- a/deps/v8/src/codegen/code-stub-assembler.cc +++ b/deps/v8/src/codegen/code-stub-assembler.cc @@ -13391,35 +13391,56 @@ TNode CodeStubAssembler::IsDebugActive() { return Word32NotEqual(is_debug_active, Int32Constant(0)); } -TNode CodeStubAssembler::IsPromiseHookEnabled() { - const TNode promise_hook = Load( - ExternalConstant(ExternalReference::promise_hook_address(isolate()))); - return WordNotEqual(promise_hook, IntPtrConstant(0)); -} - TNode CodeStubAssembler::HasAsyncEventDelegate() { const TNode async_event_delegate = Load(ExternalConstant( ExternalReference::async_event_delegate_address(isolate()))); return WordNotEqual(async_event_delegate, IntPtrConstant(0)); } -TNode CodeStubAssembler::IsPromiseHookEnabledOrHasAsyncEventDelegate() { - const TNode promise_hook_or_async_event_delegate = - Load(ExternalConstant( - ExternalReference::promise_hook_or_async_event_delegate_address( - isolate()))); - return Word32NotEqual(promise_hook_or_async_event_delegate, Int32Constant(0)); +TNode CodeStubAssembler::PromiseHookFlags() { + return Load(ExternalConstant( + ExternalReference::promise_hook_flags_address(isolate()))); +} + +TNode CodeStubAssembler::IsAnyPromiseHookEnabled(TNode flags) { + uint32_t mask = Isolate::PromiseHookFields::HasContextPromiseHook::kMask | + Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask; + return IsSetWord32(flags, mask); +} + +TNode CodeStubAssembler::IsContextPromiseHookEnabled( + TNode flags) { + return IsSetWord32(flags); +} + +TNode CodeStubAssembler:: + IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(TNode flags) { + uint32_t mask = Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask | + Isolate::PromiseHookFields::HasAsyncEventDelegate::kMask; + return IsSetWord32(flags, mask); +} + +TNode CodeStubAssembler:: + IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + TNode flags) { + uint32_t mask = Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask | + Isolate::PromiseHookFields::HasAsyncEventDelegate::kMask | + Isolate::PromiseHookFields::IsDebugActive::kMask; + return IsSetWord32(flags, mask); +} + +TNode CodeStubAssembler:: + IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + TNode flags) { + return Word32NotEqual(flags, Int32Constant(0)); } TNode CodeStubAssembler:: - IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() { - const TNode promise_hook_or_debug_is_active_or_async_event_delegate = - Load(ExternalConstant( - ExternalReference:: - promise_hook_or_debug_is_active_or_async_event_delegate_address( - isolate()))); - return Word32NotEqual(promise_hook_or_debug_is_active_or_async_event_delegate, - Int32Constant(0)); + IsAnyPromiseHookEnabledOrHasAsyncEventDelegate(TNode flags) { + uint32_t mask = Isolate::PromiseHookFields::HasContextPromiseHook::kMask | + Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask | + Isolate::PromiseHookFields::HasAsyncEventDelegate::kMask; + return IsSetWord32(flags, mask); } TNode CodeStubAssembler::LoadBuiltin(TNode builtin_id) { diff --git a/deps/v8/src/codegen/code-stub-assembler.h b/deps/v8/src/codegen/code-stub-assembler.h index 03af2cc5e2db5b..702792d7888aad 100644 --- a/deps/v8/src/codegen/code-stub-assembler.h +++ b/deps/v8/src/codegen/code-stub-assembler.h @@ -3497,10 +3497,44 @@ class V8_EXPORT_PRIVATE CodeStubAssembler TNode context); // Promise helpers - TNode IsPromiseHookEnabled(); + TNode PromiseHookFlags(); TNode HasAsyncEventDelegate(); - TNode IsPromiseHookEnabledOrHasAsyncEventDelegate(); - TNode IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(); + TNode IsContextPromiseHookEnabled(TNode flags); + TNode IsContextPromiseHookEnabled() { + return IsContextPromiseHookEnabled(PromiseHookFlags()); + } + TNode IsAnyPromiseHookEnabled(TNode flags); + TNode IsAnyPromiseHookEnabled() { + return IsAnyPromiseHookEnabled(PromiseHookFlags()); + } + TNode IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate( + TNode flags); + TNode IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate() { + return IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate( + PromiseHookFlags()); + } + TNode + IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + TNode flags); + TNode + IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() { + return IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + PromiseHookFlags()); + } + TNode IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + TNode flags); + TNode + IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() { + return IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( + PromiseHookFlags()); + } + TNode IsAnyPromiseHookEnabledOrHasAsyncEventDelegate( + TNode flags); + TNode + IsAnyPromiseHookEnabledOrHasAsyncEventDelegate() { + return IsAnyPromiseHookEnabledOrHasAsyncEventDelegate( + PromiseHookFlags()); + } // for..in helpers void CheckPrototypeEnumCache(TNode receiver, diff --git a/deps/v8/src/codegen/external-reference.cc b/deps/v8/src/codegen/external-reference.cc index bf75ff3d122015..88ce90f4fdc61a 100644 --- a/deps/v8/src/codegen/external-reference.cc +++ b/deps/v8/src/codegen/external-reference.cc @@ -925,6 +925,11 @@ ExternalReference ExternalReference::cpu_features() { return ExternalReference(&CpuFeatures::supported_); } +ExternalReference ExternalReference::promise_hook_flags_address( + Isolate* isolate) { + return ExternalReference(isolate->promise_hook_flags_address()); +} + ExternalReference ExternalReference::promise_hook_address(Isolate* isolate) { return ExternalReference(isolate->promise_hook_address()); } @@ -934,21 +939,6 @@ ExternalReference ExternalReference::async_event_delegate_address( return ExternalReference(isolate->async_event_delegate_address()); } -ExternalReference -ExternalReference::promise_hook_or_async_event_delegate_address( - Isolate* isolate) { - return ExternalReference( - isolate->promise_hook_or_async_event_delegate_address()); -} - -ExternalReference ExternalReference:: - promise_hook_or_debug_is_active_or_async_event_delegate_address( - Isolate* isolate) { - return ExternalReference( - isolate - ->promise_hook_or_debug_is_active_or_async_event_delegate_address()); -} - ExternalReference ExternalReference::debug_execution_mode_address( Isolate* isolate) { return ExternalReference(isolate->debug_execution_mode_address()); diff --git a/deps/v8/src/codegen/external-reference.h b/deps/v8/src/codegen/external-reference.h index d44b8e801c2f47..0cd80ca6f1bd08 100644 --- a/deps/v8/src/codegen/external-reference.h +++ b/deps/v8/src/codegen/external-reference.h @@ -50,13 +50,9 @@ class StatsCounter; V(handle_scope_limit_address, "HandleScope::limit") \ V(scheduled_exception_address, "Isolate::scheduled_exception") \ V(address_of_pending_message_obj, "address_of_pending_message_obj") \ + V(promise_hook_flags_address, "Isolate::promise_hook_flags_address()") \ V(promise_hook_address, "Isolate::promise_hook_address()") \ V(async_event_delegate_address, "Isolate::async_event_delegate_address()") \ - V(promise_hook_or_async_event_delegate_address, \ - "Isolate::promise_hook_or_async_event_delegate_address()") \ - V(promise_hook_or_debug_is_active_or_async_event_delegate_address, \ - "Isolate::promise_hook_or_debug_is_active_or_async_event_delegate_" \ - "address()") \ V(debug_execution_mode_address, "Isolate::debug_execution_mode_address()") \ V(debug_is_active_address, "Debug::is_active_address()") \ V(debug_hook_on_function_call_address, \ diff --git a/deps/v8/src/d8/d8.cc b/deps/v8/src/d8/d8.cc index 999e8c2b964446..63bcdf871f1db0 100644 --- a/deps/v8/src/d8/d8.cc +++ b/deps/v8/src/d8/d8.cc @@ -1792,6 +1792,20 @@ void Shell::AsyncHooksTriggerAsyncId( PerIsolateData::Get(isolate)->GetAsyncHooks()->GetTriggerAsyncId())); } +void Shell::SetPromiseHooks(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + HandleScope handle_scope(isolate); + + context->SetPromiseHooks( + args[0]->IsFunction() ? args[0].As() : Local(), + args[1]->IsFunction() ? args[1].As() : Local(), + args[2]->IsFunction() ? args[2].As() : Local(), + args[3]->IsFunction() ? args[3].As() : Local()); + + args.GetReturnValue().Set(v8::Undefined(isolate)); +} + void WriteToFile(FILE* file, const v8::FunctionCallbackInfo& args) { for (int i = 0; i < args.Length(); i++) { HandleScope handle_scope(args.GetIsolate()); @@ -2582,6 +2596,14 @@ Local Shell::CreateD8Template(Isolate* isolate) { d8_template->Set(isolate, "log", log_template); } + { + Local promise_template = ObjectTemplate::New(isolate); + promise_template->Set( + isolate, "setHooks", + FunctionTemplate::New(isolate, SetPromiseHooks, Local(), + Local(), 4)); + d8_template->Set(isolate, "promise", promise_template); + } return d8_template; } diff --git a/deps/v8/src/d8/d8.h b/deps/v8/src/d8/d8.h index a9f6f3bc8b867b..6ea9681d6997b7 100644 --- a/deps/v8/src/d8/d8.h +++ b/deps/v8/src/d8/d8.h @@ -478,6 +478,8 @@ class Shell : public i::AllStatic { static void AsyncHooksTriggerAsyncId( const v8::FunctionCallbackInfo& args); + static void SetPromiseHooks(const v8::FunctionCallbackInfo& args); + static void Print(const v8::FunctionCallbackInfo& args); static void PrintErr(const v8::FunctionCallbackInfo& args); static void Write(const v8::FunctionCallbackInfo& args); diff --git a/deps/v8/src/execution/isolate.cc b/deps/v8/src/execution/isolate.cc index 6f7efe39869f67..7fa6d7774da6a2 100644 --- a/deps/v8/src/execution/isolate.cc +++ b/deps/v8/src/execution/isolate.cc @@ -4045,19 +4045,23 @@ void Isolate::FireCallCompletedCallback(MicrotaskQueue* microtask_queue) { } } -void Isolate::PromiseHookStateUpdated() { - bool promise_hook_or_async_event_delegate = - promise_hook_ || async_event_delegate_; - bool promise_hook_or_debug_is_active_or_async_event_delegate = - promise_hook_or_async_event_delegate || debug()->is_active(); - if (promise_hook_or_debug_is_active_or_async_event_delegate && - Protectors::IsPromiseHookIntact(this)) { +void Isolate::UpdatePromiseHookProtector() { + if (Protectors::IsPromiseHookIntact(this)) { HandleScope scope(this); Protectors::InvalidatePromiseHook(this); } - promise_hook_or_async_event_delegate_ = promise_hook_or_async_event_delegate; - promise_hook_or_debug_is_active_or_async_event_delegate_ = - promise_hook_or_debug_is_active_or_async_event_delegate; +} + +void Isolate::PromiseHookStateUpdated() { + promise_hook_flags_ = + (promise_hook_flags_ & PromiseHookFields::HasContextPromiseHook::kMask) | + PromiseHookFields::HasIsolatePromiseHook::encode(promise_hook_) | + PromiseHookFields::HasAsyncEventDelegate::encode(async_event_delegate_) | + PromiseHookFields::IsDebugActive::encode(debug()->is_active()); + + if (promise_hook_flags_ != 0) { + UpdatePromiseHookProtector(); + } } namespace { @@ -4357,17 +4361,30 @@ void Isolate::SetPromiseHook(PromiseHook hook) { PromiseHookStateUpdated(); } +void Isolate::RunAllPromiseHooks(PromiseHookType type, + Handle promise, + Handle parent) { + if (HasContextPromiseHooks()) { + native_context()->RunPromiseHook(type, promise, parent); + } + if (HasIsolatePromiseHooks() || HasAsyncEventDelegate()) { + RunPromiseHook(type, promise, parent); + } +} + void Isolate::RunPromiseHook(PromiseHookType type, Handle promise, Handle parent) { RunPromiseHookForAsyncEventDelegate(type, promise); - if (promise_hook_ == nullptr) return; + if (!HasIsolatePromiseHooks()) return; + DCHECK(promise_hook_ != nullptr); promise_hook_(type, v8::Utils::PromiseToLocal(promise), v8::Utils::ToLocal(parent)); } void Isolate::RunPromiseHookForAsyncEventDelegate(PromiseHookType type, Handle promise) { - if (!async_event_delegate_) return; + if (!HasAsyncEventDelegate()) return; + DCHECK(async_event_delegate_ != nullptr); switch (type) { case PromiseHookType::kResolve: return; diff --git a/deps/v8/src/execution/isolate.h b/deps/v8/src/execution/isolate.h index cd819c5087f272..e08195674fde25 100644 --- a/deps/v8/src/execution/isolate.h +++ b/deps/v8/src/execution/isolate.h @@ -1428,21 +1428,27 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { } #endif - Address promise_hook_address() { - return reinterpret_cast
(&promise_hook_); + void SetHasContextPromiseHooks(bool context_promise_hook) { + promise_hook_flags_ = PromiseHookFields::HasContextPromiseHook::update( + promise_hook_flags_, context_promise_hook); + PromiseHookStateUpdated(); } - Address async_event_delegate_address() { - return reinterpret_cast
(&async_event_delegate_); + bool HasContextPromiseHooks() const { + return PromiseHookFields::HasContextPromiseHook::decode( + promise_hook_flags_); } - Address promise_hook_or_async_event_delegate_address() { - return reinterpret_cast
(&promise_hook_or_async_event_delegate_); + Address promise_hook_flags_address() { + return reinterpret_cast
(&promise_hook_flags_); } - Address promise_hook_or_debug_is_active_or_async_event_delegate_address() { - return reinterpret_cast
( - &promise_hook_or_debug_is_active_or_async_event_delegate_); + Address promise_hook_address() { + return reinterpret_cast
(&promise_hook_); + } + + Address async_event_delegate_address() { + return reinterpret_cast
(&async_event_delegate_); } Address handle_scope_implementer_address() { @@ -1460,6 +1466,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { void SetPromiseHook(PromiseHook hook); void RunPromiseHook(PromiseHookType type, Handle promise, Handle parent); + void RunAllPromiseHooks(PromiseHookType type, Handle promise, + Handle parent); + void UpdatePromiseHookProtector(); void PromiseHookStateUpdated(); void AddDetachedContext(Handle context); @@ -1697,6 +1706,13 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { } #endif + struct PromiseHookFields { + using HasContextPromiseHook = base::BitField; + using HasIsolatePromiseHook = HasContextPromiseHook::Next; + using HasAsyncEventDelegate = HasIsolatePromiseHook::Next; + using IsDebugActive = HasAsyncEventDelegate::Next; + }; + private: explicit Isolate(std::unique_ptr isolate_allocator); ~Isolate(); @@ -1780,6 +1796,16 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { void RunPromiseHookForAsyncEventDelegate(PromiseHookType type, Handle promise); + bool HasIsolatePromiseHooks() const { + return PromiseHookFields::HasIsolatePromiseHook::decode( + promise_hook_flags_); + } + + bool HasAsyncEventDelegate() const { + return PromiseHookFields::HasAsyncEventDelegate::decode( + promise_hook_flags_); + } + const char* RAILModeName(RAILMode rail_mode) const { switch (rail_mode) { case PERFORMANCE_RESPONSE: @@ -2032,8 +2058,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { debug::ConsoleDelegate* console_delegate_ = nullptr; debug::AsyncEventDelegate* async_event_delegate_ = nullptr; - bool promise_hook_or_async_event_delegate_ = false; - bool promise_hook_or_debug_is_active_or_async_event_delegate_ = false; + uint32_t promise_hook_flags_ = 0; int async_task_count_ = 0; std::unique_ptr main_thread_local_isolate_; diff --git a/deps/v8/src/heap/factory.cc b/deps/v8/src/heap/factory.cc index bd7ec8915ca307..ca9d316e2bbf1e 100644 --- a/deps/v8/src/heap/factory.cc +++ b/deps/v8/src/heap/factory.cc @@ -3377,7 +3377,8 @@ Handle Factory::NewJSPromiseWithoutHook() { Handle Factory::NewJSPromise() { Handle promise = NewJSPromiseWithoutHook(); - isolate()->RunPromiseHook(PromiseHookType::kInit, promise, undefined_value()); + isolate()->RunAllPromiseHooks(PromiseHookType::kInit, promise, + undefined_value()); return promise; } diff --git a/deps/v8/src/objects/contexts.cc b/deps/v8/src/objects/contexts.cc index af73bf02568721..771fbea40bb3bf 100644 --- a/deps/v8/src/objects/contexts.cc +++ b/deps/v8/src/objects/contexts.cc @@ -511,5 +511,53 @@ STATIC_ASSERT(NativeContext::kSize == (Context::SizeFor(NativeContext::NATIVE_CONTEXT_SLOTS) + kSystemPointerSize)); +void NativeContext::RunPromiseHook(PromiseHookType type, + Handle promise, + Handle parent) { + Isolate* isolate = promise->GetIsolate(); + DCHECK(isolate->HasContextPromiseHooks()); + int contextSlot; + + switch (type) { + case PromiseHookType::kInit: + contextSlot = PROMISE_HOOK_INIT_FUNCTION_INDEX; + break; + case PromiseHookType::kResolve: + contextSlot = PROMISE_HOOK_RESOLVE_FUNCTION_INDEX; + break; + case PromiseHookType::kBefore: + contextSlot = PROMISE_HOOK_BEFORE_FUNCTION_INDEX; + break; + case PromiseHookType::kAfter: + contextSlot = PROMISE_HOOK_AFTER_FUNCTION_INDEX; + break; + default: + UNREACHABLE(); + } + + Handle hook(isolate->native_context()->get(contextSlot), isolate); + if (hook->IsUndefined()) return; + + int argc = type == PromiseHookType::kInit ? 2 : 1; + Handle argv[2] = { + Handle::cast(promise), + parent + }; + + Handle receiver = isolate->global_proxy(); + + if (Execution::Call(isolate, hook, receiver, argc, argv).is_null()) { + DCHECK(isolate->has_pending_exception()); + Handle exception(isolate->pending_exception(), isolate); + + MessageLocation* no_location = nullptr; + Handle message = + isolate->CreateMessageOrAbort(exception, no_location); + MessageHandler::ReportMessage(isolate, no_location, message); + + isolate->clear_pending_exception(); + } +} + } // namespace internal } // namespace v8 diff --git a/deps/v8/src/objects/contexts.h b/deps/v8/src/objects/contexts.h index 47784cf40511f5..0a67d52d5ed343 100644 --- a/deps/v8/src/objects/contexts.h +++ b/deps/v8/src/objects/contexts.h @@ -198,6 +198,11 @@ enum ContextLookupFlags { V(NUMBER_FUNCTION_INDEX, JSFunction, number_function) \ V(OBJECT_FUNCTION_INDEX, JSFunction, object_function) \ V(OBJECT_FUNCTION_PROTOTYPE_MAP_INDEX, Map, object_function_prototype_map) \ + V(PROMISE_HOOK_INIT_FUNCTION_INDEX, Object, promise_hook_init_function) \ + V(PROMISE_HOOK_BEFORE_FUNCTION_INDEX, Object, promise_hook_before_function) \ + V(PROMISE_HOOK_AFTER_FUNCTION_INDEX, Object, promise_hook_after_function) \ + V(PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, Object, \ + promise_hook_resolve_function) \ V(PROXY_CALLABLE_MAP_INDEX, Map, proxy_callable_map) \ V(PROXY_CONSTRUCTOR_MAP_INDEX, Map, proxy_constructor_map) \ V(PROXY_FUNCTION_INDEX, JSFunction, proxy_function) \ @@ -692,6 +697,9 @@ class NativeContext : public Context { void IncrementErrorsThrown(); int GetErrorsThrown(); + void RunPromiseHook(PromiseHookType type, Handle promise, + Handle parent); + private: STATIC_ASSERT(OffsetOfElementAt(EMBEDDER_DATA_INDEX) == Internals::kNativeContextEmbedderDataOffset); diff --git a/deps/v8/src/objects/contexts.tq b/deps/v8/src/objects/contexts.tq index 604852c24eaeb0..ff427629abe06c 100644 --- a/deps/v8/src/objects/contexts.tq +++ b/deps/v8/src/objects/contexts.tq @@ -124,6 +124,12 @@ extern enum ContextSlot extends intptr constexpr 'Context::Field' { PROMISE_PROTOTYPE_INDEX: Slot, STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX: Slot, + PROMISE_HOOK_INIT_FUNCTION_INDEX: Slot, + PROMISE_HOOK_BEFORE_FUNCTION_INDEX: Slot, + PROMISE_HOOK_AFTER_FUNCTION_INDEX: Slot, + PROMISE_HOOK_RESOLVE_FUNCTION_INDEX: + Slot, + CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX: Slot, BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX: Slot, diff --git a/deps/v8/src/objects/objects.cc b/deps/v8/src/objects/objects.cc index d9cb7486be8f85..697ffdec088adb 100644 --- a/deps/v8/src/objects/objects.cc +++ b/deps/v8/src/objects/objects.cc @@ -5254,8 +5254,8 @@ Handle JSPromise::Reject(Handle promise, if (isolate->debug()->is_active()) MoveMessageToPromise(isolate, promise); if (debug_event) isolate->debug()->OnPromiseReject(promise, reason); - isolate->RunPromiseHook(PromiseHookType::kResolve, promise, - isolate->factory()->undefined_value()); + isolate->RunAllPromiseHooks(PromiseHookType::kResolve, promise, + isolate->factory()->undefined_value()); // 1. Assert: The value of promise.[[PromiseState]] is "pending". CHECK_EQ(Promise::kPending, promise->status()); @@ -5290,8 +5290,8 @@ MaybeHandle JSPromise::Resolve(Handle promise, DCHECK( !reinterpret_cast(isolate)->GetCurrentContext().IsEmpty()); - isolate->RunPromiseHook(PromiseHookType::kResolve, promise, - isolate->factory()->undefined_value()); + isolate->RunAllPromiseHooks(PromiseHookType::kResolve, promise, + isolate->factory()->undefined_value()); // 7. If SameValue(resolution, promise) is true, then if (promise.is_identical_to(resolution)) { diff --git a/deps/v8/src/runtime/runtime-promise.cc b/deps/v8/src/runtime/runtime-promise.cc index c1ee96faccad09..54adc4c920f838 100644 --- a/deps/v8/src/runtime/runtime-promise.cc +++ b/deps/v8/src/runtime/runtime-promise.cc @@ -29,8 +29,8 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) { // undefined, which we interpret as being a caught exception event. rejected_promise = isolate->GetPromiseOnStackOnThrow(); } - isolate->RunPromiseHook(PromiseHookType::kResolve, promise, - isolate->factory()->undefined_value()); + isolate->RunAllPromiseHooks(PromiseHookType::kResolve, promise, + isolate->factory()->undefined_value()); isolate->debug()->OnPromiseReject(rejected_promise, value); // Report only if we don't actually have a handler. @@ -142,7 +142,7 @@ Handle AwaitPromisesInitCommon(Isolate* isolate, // hook for the throwaway promise (passing the {promise} as its // parent). Handle throwaway = isolate->factory()->NewJSPromiseWithoutHook(); - isolate->RunPromiseHook(PromiseHookType::kInit, throwaway, promise); + isolate->RunAllPromiseHooks(PromiseHookType::kInit, throwaway, promise); // On inspector side we capture async stack trace and store it by // outer_promise->async_task_id when async function is suspended first time. @@ -204,7 +204,7 @@ RUNTIME_FUNCTION(Runtime_AwaitPromisesInitOld) { // Fire the init hook for the wrapper promise (that we created for the // {value} previously). - isolate->RunPromiseHook(PromiseHookType::kInit, promise, outer_promise); + isolate->RunAllPromiseHooks(PromiseHookType::kInit, promise, outer_promise); return *AwaitPromisesInitCommon(isolate, value, promise, outer_promise, reject_handler, is_predicted_as_caught); } diff --git a/deps/v8/test/cctest/test-code-stub-assembler.cc b/deps/v8/test/cctest/test-code-stub-assembler.cc index 07e6c768f543e3..1ba26a81b86df0 100644 --- a/deps/v8/test/cctest/test-code-stub-assembler.cc +++ b/deps/v8/test/cctest/test-code-stub-assembler.cc @@ -2595,7 +2595,8 @@ TEST(IsPromiseHookEnabled) { CodeStubAssembler m(asm_tester.state()); m.Return( - m.SelectBooleanConstant(m.IsPromiseHookEnabledOrHasAsyncEventDelegate())); + m.SelectBooleanConstant( + m.IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate())); FunctionTester ft(asm_tester.GenerateCode(), kNumParams); Handle result = diff --git a/deps/v8/test/mjsunit/promise-hooks.js b/deps/v8/test/mjsunit/promise-hooks.js new file mode 100644 index 00000000000000..9e13206a525f95 --- /dev/null +++ b/deps/v8/test/mjsunit/promise-hooks.js @@ -0,0 +1,244 @@ +// Copyright 2020 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Flags: --allow-natives-syntax --opt --no-always-opt --no-stress-opt --deopt-every-n-times=0 --ignore-unhandled-promises + +let log = []; +let asyncId = 0; + +function logEvent (type, args) { + const promise = args[0]; + promise.asyncId = promise.asyncId || ++asyncId; + log.push({ + type, + promise, + parent: args[1], + argsLength: args.length + }) +} +function initHook(...args) { + logEvent('init', args); +} +function resolveHook(...args) { + logEvent('resolve', args); +} +function beforeHook(...args) { + logEvent('before', args); +} +function afterHook(...args) { + logEvent('after', args); +} + +function printLog(message) { + console.log(` --- ${message} --- `) + for (const event of log) { + console.log(JSON.stringify(event)) + } +} + +function assertNextEvent(type, args) { + const [ promiseOrId, parentOrId ] = args; + const nextEvent = log.shift(); + + assertEquals(type, nextEvent.type); + assertEquals(type === 'init' ? 2 : 1, nextEvent.argsLength); + + assertTrue(nextEvent.promise instanceof Promise); + if (promiseOrId instanceof Promise) { + assertEquals(promiseOrId, nextEvent.promise); + } else { + assertTrue(typeof promiseOrId === 'number'); + assertEquals(promiseOrId, nextEvent.promise?.asyncId); + } + + if (parentOrId instanceof Promise) { + assertEquals(parentOrId, nextEvent.parent); + assertTrue(nextEvent.parent instanceof Promise); + } else if (typeof parentOrId === 'number') { + assertEquals(parentOrId, nextEvent.parent?.asyncId); + assertTrue(nextEvent.parent instanceof Promise); + } else { + assertEquals(undefined, parentOrId); + assertEquals(undefined, nextEvent.parent); + } +} +function assertEmptyLog() { + assertEquals(0, log.length); + asyncId = 0; + log = []; +} + +// Verify basic log structure of different promise behaviours +function basicTest() { + d8.promise.setHooks(initHook, beforeHook, afterHook, resolveHook); + + // `new Promise(...)` triggers init event with correct promise + var done, p1 = new Promise(r => done = r); + %PerformMicrotaskCheckpoint(); + assertNextEvent('init', [ p1 ]); + assertEmptyLog(); + + // `promise.then(...)` triggers init event with correct promise and parent + var p2 = p1.then(() => { }); + %PerformMicrotaskCheckpoint(); + assertNextEvent('init', [ p2, p1 ]); + assertEmptyLog(); + + // `resolve(...)` triggers resolve event and any already attached continuations + done(); + %PerformMicrotaskCheckpoint(); + assertNextEvent('resolve', [ p1 ]); + assertNextEvent('before', [ p2 ]); + assertNextEvent('resolve', [ p2 ]); + assertNextEvent('after', [ p2 ]); + assertEmptyLog(); + + // `reject(...)` triggers the resolve event + var done, p3 = new Promise((_, r) => done = r); + done(); + %PerformMicrotaskCheckpoint(); + assertNextEvent('init', [ p3 ]); + assertNextEvent('resolve', [ p3 ]); + assertEmptyLog(); + + // `promise.catch(...)` triggers init event with correct promise and parent + // When the promise is already completed, the continuation should also run + // immediately at the next checkpoint. + var p4 = p3.catch(() => { }); + %PerformMicrotaskCheckpoint(); + assertNextEvent('init', [ p4, p3 ]); + assertNextEvent('before', [ p4 ]); + assertNextEvent('resolve', [ p4 ]); + assertNextEvent('after', [ p4 ]); + assertEmptyLog(); + + // Detach hooks + d8.promise.setHooks(); +} + +// Exceptions thrown in hook handlers should not raise or reject +function exceptions() { + function thrower() { + throw new Error('unexpected!'); + } + + // Init hook + d8.promise.setHooks(thrower); + assertDoesNotThrow(() => { + Promise.resolve() + .catch(assertUnreachable); + }); + %PerformMicrotaskCheckpoint(); + d8.promise.setHooks(); + + // Before hook + d8.promise.setHooks(undefined, thrower); + assertDoesNotThrow(() => { + Promise.resolve() + .then(() => {}) + .catch(assertUnreachable); + }); + %PerformMicrotaskCheckpoint(); + d8.promise.setHooks(); + + // After hook + d8.promise.setHooks(undefined, undefined, thrower); + assertDoesNotThrow(() => { + Promise.resolve() + .then(() => {}) + .catch(assertUnreachable); + }); + %PerformMicrotaskCheckpoint(); + d8.promise.setHooks(); + + // Resolve hook + d8.promise.setHooks(undefined, undefined, undefined, thrower); + assertDoesNotThrow(() => { + Promise.resolve() + .catch(assertUnreachable); + }); + %PerformMicrotaskCheckpoint(); + d8.promise.setHooks(); + + // Resolve hook for a reject + d8.promise.setHooks(undefined, undefined, undefined, thrower); + assertDoesNotThrow(() => { + Promise.reject() + .then(assertUnreachable) + .catch(); + }); + %PerformMicrotaskCheckpoint(); + d8.promise.setHooks(); +} + +// For now, expect the optimizer to bail out on async functions +// when context promise hooks are attached. +function optimizerBailout(test, verify) { + // Warm up test method + %PrepareFunctionForOptimization(test); + assertUnoptimized(test); + test(); + test(); + test(); + %PerformMicrotaskCheckpoint(); + + // Prove transition to optimized code when no hooks are present + assertUnoptimized(test); + %OptimizeFunctionOnNextCall(test); + test(); + assertOptimized(test); + %PerformMicrotaskCheckpoint(); + + // Verify that attaching hooks deopts the async function + d8.promise.setHooks(initHook, beforeHook, afterHook, resolveHook); + // assertUnoptimized(test); + + // Verify log structure of deoptimized call + %PrepareFunctionForOptimization(test); + test(); + %PerformMicrotaskCheckpoint(); + + verify(); + + // Optimize and verify log structure again + %OptimizeFunctionOnNextCall(test); + test(); + assertOptimized(test); + %PerformMicrotaskCheckpoint(); + + verify(); + + d8.promise.setHooks(); +} + +optimizerBailout(async () => { + await Promise.resolve(); +}, () => { + assertNextEvent('init', [ 1 ]); + assertNextEvent('init', [ 2 ]); + assertNextEvent('resolve', [ 2 ]); + assertNextEvent('init', [ 3, 2 ]); + assertNextEvent('before', [ 3 ]); + assertNextEvent('resolve', [ 1 ]); + assertNextEvent('resolve', [ 3 ]); + assertNextEvent('after', [ 3 ]); + assertEmptyLog(); +}); +optimizerBailout(async () => { + await { then (cb) { cb() } }; +}, () => { + assertNextEvent('init', [ 1 ]); + assertNextEvent('init', [ 2, 1 ]); + assertNextEvent('init', [ 3, 2 ]); + assertNextEvent('before', [ 2 ]); + assertNextEvent('resolve', [ 2 ]); + assertNextEvent('after', [ 2 ]); + assertNextEvent('before', [ 3 ]); + assertNextEvent('resolve', [ 1 ]); + assertNextEvent('resolve', [ 3 ]); + assertNextEvent('after', [ 3 ]); + assertEmptyLog(); +}); +basicTest(); +exceptions(); From a81249b8809a0a89a7c6e6c3ebdb164343b7deee Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Thu, 22 Apr 2021 08:59:36 -0700 Subject: [PATCH 2/6] deps: V8: cherry-pick 272445f10927 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [runtime] Fix promise hooks promiseCapability can be undefined. Bug: v8:11025 Bug: chromium:1201113 Change-Id: I9da8764820cee0db1f0c38ed2fff0e3afeb9a80e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2844649 Reviewed-by: Marja Hölttä Commit-Queue: Camillo Bruni Cr-Commit-Position: refs/heads/master@{#74117} Refs: https://github.com/v8/v8/commit/272445f109273dcdf81d37b1f91f146cfd78ec41 --- common.gypi | 2 +- deps/v8/src/builtins/promise-misc.tq | 13 ++++++++----- deps/v8/test/mjsunit/promise-hooks.js | 12 +++++++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/common.gypi b/common.gypi index e5f01195e63850..67862ba1b187dc 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.12', + 'v8_embedder_string': '-node.13', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/builtins/promise-misc.tq b/deps/v8/src/builtins/promise-misc.tq index c6661e371732cb..50e2c8f0de1419 100644 --- a/deps/v8/src/builtins/promise-misc.tq +++ b/deps/v8/src/builtins/promise-misc.tq @@ -134,7 +134,7 @@ transitioning macro RunContextPromiseHookResolve(implicit context: Context)( @export transitioning macro RunContextPromiseHookBefore(implicit context: Context)( - promiseOrCapability: JSPromise|PromiseCapability) { + promiseOrCapability: JSPromise|PromiseCapability|Undefined) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability, PromiseHookFlags()); @@ -142,7 +142,7 @@ transitioning macro RunContextPromiseHookBefore(implicit context: Context)( @export transitioning macro RunContextPromiseHookBefore(implicit context: Context)( - promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) { + promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability, flags); @@ -150,7 +150,7 @@ transitioning macro RunContextPromiseHookBefore(implicit context: Context)( @export transitioning macro RunContextPromiseHookAfter(implicit context: Context)( - promiseOrCapability: JSPromise|PromiseCapability) { + promiseOrCapability: JSPromise|PromiseCapability|Undefined) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability, PromiseHookFlags()); @@ -158,7 +158,7 @@ transitioning macro RunContextPromiseHookAfter(implicit context: Context)( @export transitioning macro RunContextPromiseHookAfter(implicit context: Context)( - promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) { + promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32) { RunContextPromiseHook( ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability, flags); @@ -166,7 +166,7 @@ transitioning macro RunContextPromiseHookAfter(implicit context: Context)( transitioning macro RunContextPromiseHook(implicit context: Context)( slot: Slot, - promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) { + promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32) { if (!IsContextPromiseHookEnabled(flags)) return; const maybeHook = *NativeContextSlot(slot); if (IsUndefined(maybeHook)) return; @@ -181,6 +181,9 @@ transitioning macro RunContextPromiseHook(implicit context: Context)( case (capability: PromiseCapability): { promise = Cast(capability.promise) otherwise return; } + case (Undefined): { + return; + } } try { diff --git a/deps/v8/test/mjsunit/promise-hooks.js b/deps/v8/test/mjsunit/promise-hooks.js index 9e13206a525f95..db7041a8f5de97 100644 --- a/deps/v8/test/mjsunit/promise-hooks.js +++ b/deps/v8/test/mjsunit/promise-hooks.js @@ -1,7 +1,7 @@ // Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// + // Flags: --allow-natives-syntax --opt --no-always-opt --no-stress-opt --deopt-every-n-times=0 --ignore-unhandled-promises let log = []; @@ -242,3 +242,13 @@ optimizerBailout(async () => { }); basicTest(); exceptions(); + +(function regress1126309() { + function __f_16(test) { + test(); + d8.promise.setHooks( undefined, () => {}); + %PerformMicrotaskCheckpoint(); + d8.promise.setHooks(); + } + __f_16(async () => { await Promise.resolve()}); +})(); From b9f79a6209ee6b6a0b6ab2dbbaefc075c96f0d4e Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Tue, 27 Apr 2021 10:05:32 -0700 Subject: [PATCH 3/6] deps: V8: cherry-pick 5f4413194480 Original commit message: [promises] Change context promise hooks to Callable The previously added perf-context Promise-hooks take a v8::Function as arguments. However, the builtin code was only accepting JSFunctions which causes cast errors. Drive-by-fix: Directly pass nativeContext in more places. Bug: chromium:1201465 Change-Id: Ic8bed11253a1f18a84e71eb9ea809b1ec1c3f428 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2850162 Reviewed-by: Jakob Gruber Commit-Queue: Camillo Bruni Cr-Commit-Position: refs/heads/master@{#74223} Refs: https://github.com/v8/v8/commit/5f44131944800f16c4dc6768acb67561e3746384 --- common.gypi | 2 +- deps/v8/src/builtins/promise-misc.tq | 10 +++------- deps/v8/src/builtins/promise-resolve.tq | 14 +++++++++----- deps/v8/src/objects/contexts.tq | 9 ++++----- deps/v8/test/mjsunit/promise-hooks.js | 10 ++++++++++ 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/common.gypi b/common.gypi index 67862ba1b187dc..2eb8664697d990 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.13', + 'v8_embedder_string': '-node.14', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/builtins/promise-misc.tq b/deps/v8/src/builtins/promise-misc.tq index 50e2c8f0de1419..0eae717b3fcfb3 100644 --- a/deps/v8/src/builtins/promise-misc.tq +++ b/deps/v8/src/builtins/promise-misc.tq @@ -104,9 +104,7 @@ 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(maybeHook) otherwise unreachable; + const hook = Cast(maybeHook) otherwise return; const parentObject = Is(parent) ? Cast(parent) otherwise unreachable: Undefined; @@ -165,13 +163,11 @@ transitioning macro RunContextPromiseHookAfter(implicit context: Context)( } transitioning macro RunContextPromiseHook(implicit context: Context)( - slot: Slot, + slot: Slot, promiseOrCapability: JSPromise|PromiseCapability|Undefined, flags: uint32) { if (!IsContextPromiseHookEnabled(flags)) return; const maybeHook = *NativeContextSlot(slot); - if (IsUndefined(maybeHook)) return; - - const hook = Cast(maybeHook) otherwise unreachable; + const hook = Cast(maybeHook) otherwise return; let promise: JSPromise; typeswitch (promiseOrCapability) { diff --git a/deps/v8/src/builtins/promise-resolve.tq b/deps/v8/src/builtins/promise-resolve.tq index 3125054e87a313..fa3d19411fc8aa 100644 --- a/deps/v8/src/builtins/promise-resolve.tq +++ b/deps/v8/src/builtins/promise-resolve.tq @@ -30,7 +30,8 @@ transitioning builtin PromiseResolve(implicit context: Context)( constructor: JSReceiver, value: JSAny): JSAny { const nativeContext = LoadNativeContext(context); - const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX); + const promiseFun = *NativeContextSlot( + nativeContext, ContextSlot::PROMISE_FUNCTION_INDEX); try { // Check if {value} is a JSPromise. const value = Cast(value) otherwise NeedToAllocate; @@ -40,7 +41,8 @@ PromiseResolve(implicit context: Context)( // intact, as that guards the lookup path for "constructor" on // JSPromise instances which have the (initial) Promise.prototype. const promisePrototype = - *NativeContextSlot(ContextSlot::PROMISE_PROTOTYPE_INDEX); + *NativeContextSlot( + nativeContext, ContextSlot::PROMISE_PROTOTYPE_INDEX); // Check that Torque load elimination works. static_assert(nativeContext == LoadNativeContext(context)); if (value.map.prototype != promisePrototype) { @@ -139,7 +141,8 @@ ResolvePromise(implicit context: Context)( assert(IsJSReceiverMap(resolutionMap)); assert(!IsPromiseThenProtectorCellInvalid()); if (resolutionMap == - *NativeContextSlot(ContextSlot::ITERATOR_RESULT_MAP_INDEX)) { + *NativeContextSlot( + nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX)) { return FulfillPromise(promise, resolution); } else { goto Slow; @@ -147,10 +150,11 @@ ResolvePromise(implicit context: Context)( } const promisePrototype = - *NativeContextSlot(ContextSlot::PROMISE_PROTOTYPE_INDEX); + *NativeContextSlot( + nativeContext, ContextSlot::PROMISE_PROTOTYPE_INDEX); if (resolutionMap.prototype == promisePrototype) { // The {resolution} is a native Promise in this case. - then = *NativeContextSlot(ContextSlot::PROMISE_THEN_INDEX); + then = *NativeContextSlot(nativeContext, ContextSlot::PROMISE_THEN_INDEX); // Check that Torque load elimination works. static_assert(nativeContext == LoadNativeContext(context)); goto Enqueue; diff --git a/deps/v8/src/objects/contexts.tq b/deps/v8/src/objects/contexts.tq index ff427629abe06c..28ea1300ee00b0 100644 --- a/deps/v8/src/objects/contexts.tq +++ b/deps/v8/src/objects/contexts.tq @@ -124,11 +124,10 @@ extern enum ContextSlot extends intptr constexpr 'Context::Field' { PROMISE_PROTOTYPE_INDEX: Slot, STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX: Slot, - PROMISE_HOOK_INIT_FUNCTION_INDEX: Slot, - PROMISE_HOOK_BEFORE_FUNCTION_INDEX: Slot, - PROMISE_HOOK_AFTER_FUNCTION_INDEX: Slot, - PROMISE_HOOK_RESOLVE_FUNCTION_INDEX: - Slot, + PROMISE_HOOK_INIT_FUNCTION_INDEX: Slot, + PROMISE_HOOK_BEFORE_FUNCTION_INDEX: Slot, + PROMISE_HOOK_AFTER_FUNCTION_INDEX: Slot, + PROMISE_HOOK_RESOLVE_FUNCTION_INDEX: Slot, CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX: Slot, diff --git a/deps/v8/test/mjsunit/promise-hooks.js b/deps/v8/test/mjsunit/promise-hooks.js index db7041a8f5de97..bf51777dcecd8d 100644 --- a/deps/v8/test/mjsunit/promise-hooks.js +++ b/deps/v8/test/mjsunit/promise-hooks.js @@ -252,3 +252,13 @@ exceptions(); } __f_16(async () => { await Promise.resolve()}); })(); + +(function boundFunction() { + function hook() {}; + const bound = hook.bind(this); + d8.promise.setHooks(bound, bound, bound, bound); + Promise.resolve(); + Promise.reject(); + %PerformMicrotaskCheckpoint(); + d8.promise.setHooks(); +})(); From ae8b9512456142c5d74d5ac54997d9f7b0c276a4 Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Thu, 29 Apr 2021 09:18:17 -0700 Subject: [PATCH 4/6] deps: V8: cherry-pick 4c074516397b MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [promises] Fix slow path when context promise hooks are present Bug: chromium:1201936 Change-Id: I1ee545e33587ddf4a5c7e1cbd64b53d36c75a146 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2850936 Reviewed-by: Marja Hölttä Reviewed-by: Jakob Gruber Commit-Queue: Camillo Bruni Cr-Commit-Position: refs/heads/master@{#74267} Refs: https://github.com/v8/v8/commit/4c074516397b89c5cfe9de9857018484f73445ef --- common.gypi | 2 +- deps/v8/src/builtins/promise-jobs.tq | 3 ++- deps/v8/src/codegen/code-stub-assembler.cc | 6 +++--- deps/v8/src/codegen/code-stub-assembler.h | 10 ++++------ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/common.gypi b/common.gypi index 2eb8664697d990..2a82642e350028 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.14', + 'v8_embedder_string': '-node.15', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/builtins/promise-jobs.tq b/deps/v8/src/builtins/promise-jobs.tq index 6fa81dcd28634a..77d2e7cf9c4813 100644 --- a/deps/v8/src/builtins/promise-jobs.tq +++ b/deps/v8/src/builtins/promise-jobs.tq @@ -7,6 +7,7 @@ // https://tc39.es/ecma262/#sec-promise-jobs namespace promise { extern macro IsJSPromiseMap(Map): bool; +extern macro NeedsAnyPromiseHooks(): bool; // https://tc39.es/ecma262/#sec-promiseresolvethenablejob transitioning builtin @@ -25,7 +26,7 @@ PromiseResolveThenableJob(implicit context: Context)( const promiseThen = *NativeContextSlot(ContextSlot::PROMISE_THEN_INDEX); const thenableMap = thenable.map; if (TaggedEqual(then, promiseThen) && IsJSPromiseMap(thenableMap) && - !IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() && + !NeedsAnyPromiseHooks() && 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/codegen/code-stub-assembler.cc b/deps/v8/src/codegen/code-stub-assembler.cc index 415291dd736f20..90e34d4dbdcbf0 100644 --- a/deps/v8/src/codegen/code-stub-assembler.cc +++ b/deps/v8/src/codegen/code-stub-assembler.cc @@ -13435,11 +13435,11 @@ TNode CodeStubAssembler:: return Word32NotEqual(flags, Int32Constant(0)); } -TNode CodeStubAssembler:: - IsAnyPromiseHookEnabledOrHasAsyncEventDelegate(TNode flags) { +TNode CodeStubAssembler::NeedsAnyPromiseHooks(TNode flags) { uint32_t mask = Isolate::PromiseHookFields::HasContextPromiseHook::kMask | Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask | - Isolate::PromiseHookFields::HasAsyncEventDelegate::kMask; + Isolate::PromiseHookFields::HasAsyncEventDelegate::kMask | + Isolate::PromiseHookFields::IsDebugActive::kMask; return IsSetWord32(flags, mask); } diff --git a/deps/v8/src/codegen/code-stub-assembler.h b/deps/v8/src/codegen/code-stub-assembler.h index 702792d7888aad..ba331624c6fecf 100644 --- a/deps/v8/src/codegen/code-stub-assembler.h +++ b/deps/v8/src/codegen/code-stub-assembler.h @@ -3528,12 +3528,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler return IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate( PromiseHookFlags()); } - TNode IsAnyPromiseHookEnabledOrHasAsyncEventDelegate( - TNode flags); - TNode - IsAnyPromiseHookEnabledOrHasAsyncEventDelegate() { - return IsAnyPromiseHookEnabledOrHasAsyncEventDelegate( - PromiseHookFlags()); + + TNode NeedsAnyPromiseHooks(TNode flags); + TNode NeedsAnyPromiseHooks() { + return NeedsAnyPromiseHooks(PromiseHookFlags()); } // for..in helpers From eed9e5ea84a1bc40253cdf900676974a2c245288 Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Mon, 3 May 2021 10:43:25 -0700 Subject: [PATCH 5/6] deps: V8: cherry-pick fa4cb172cde2 Original commit message: [runtime] Fix Promise.all context promise hooks We have to take the slow path in Promise.all if context promise hooks are set. The fast-path doesn't create intermediate promises by default. Bug: chromium:1204132, v8:11025 Change-Id: Ide92de00a4f6df05e0ddbc8814f6673bd667f426 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2866771 Reviewed-by: Victor Gomes Commit-Queue: Camillo Bruni Cr-Commit-Position: refs/heads/master@{#74326} Refs: https://github.com/v8/v8/commit/fa4cb172cde256a1e71d675d81fbb4b85d1e5f66 --- common.gypi | 2 +- deps/v8/src/builtins/promise-all.tq | 3 +-- deps/v8/test/mjsunit/promise-hooks.js | 13 ++++++++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/common.gypi b/common.gypi index 2a82642e350028..d81bac25e30fb6 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.15', + 'v8_embedder_string': '-node.16', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/builtins/promise-all.tq b/deps/v8/src/builtins/promise-all.tq index 294c5e911c9094..5ab64a167d3dff 100644 --- a/deps/v8/src/builtins/promise-all.tq +++ b/deps/v8/src/builtins/promise-all.tq @@ -231,8 +231,7 @@ Reject(Object) { // the PromiseReaction (aka we can pass undefined to // PerformPromiseThen), since this is only necessary for DevTools and // PromiseHooks. - if (promiseResolveFunction != Undefined || - IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() || + if (promiseResolveFunction != Undefined || NeedsAnyPromiseHooks() || IsPromiseSpeciesProtectorCellInvalid() || Is(nextValue) || !IsPromiseThenLookupChainIntact( nativeContext, UnsafeCast(nextValue).map)) { diff --git a/deps/v8/test/mjsunit/promise-hooks.js b/deps/v8/test/mjsunit/promise-hooks.js index bf51777dcecd8d..f7c1558c1d2e20 100644 --- a/deps/v8/test/mjsunit/promise-hooks.js +++ b/deps/v8/test/mjsunit/promise-hooks.js @@ -246,7 +246,7 @@ exceptions(); (function regress1126309() { function __f_16(test) { test(); - d8.promise.setHooks( undefined, () => {}); + d8.promise.setHooks(undefined, () => {}); %PerformMicrotaskCheckpoint(); d8.promise.setHooks(); } @@ -262,3 +262,14 @@ exceptions(); %PerformMicrotaskCheckpoint(); d8.promise.setHooks(); })(); + + +(function promiseAll() { + let initCount = 0; + d8.promise.setHooks(() => { initCount++}); + Promise.all([Promise.resolve(1)]); + %PerformMicrotaskCheckpoint(); + assertEquals(initCount, 3); + + d8.promise.setHooks(); +})(); From 023447d7fb6a28d0c48189c40bf0f6a48c59ba10 Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Fri, 4 Dec 2020 10:24:05 -0800 Subject: [PATCH 6/6] async_hooks: use new v8::Context PromiseHook API --- lib/internal/async_hooks.js | 96 ++++++++++++++++++------------------- src/async_wrap.cc | 11 +++++ 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/lib/internal/async_hooks.js b/lib/internal/async_hooks.js index b8955f644d15d7..eac2471ff79fb2 100644 --- a/lib/internal/async_hooks.js +++ b/lib/internal/async_hooks.js @@ -5,7 +5,6 @@ const { ErrorCaptureStackTrace, ObjectPrototypeHasOwnProperty, ObjectDefineProperty, - Promise, Symbol, } = primordials; @@ -53,7 +52,7 @@ const { clearAsyncIdStack, } = async_wrap; // For performance reasons, only track Promises when a hook is enabled. -const { enablePromiseHook, disablePromiseHook } = async_wrap; +const { enablePromiseHook, disablePromiseHook, setPromiseHooks } = async_wrap; // Properties in active_hooks are used to keep track of the set of hooks being // executed in case another hook is enabled/disabled. The new set of hooks is // then restored once the active set of hooks is finished executing. @@ -303,53 +302,50 @@ function restoreActiveHooks() { active_hooks.tmp_fields = null; } -function trackPromise(promise, parent, silent) { - const asyncId = getOrSetAsyncId(promise); +function trackPromise(promise, parent) { + if (promise[async_id_symbol]) { + return; + } + promise[async_id_symbol] = newAsyncId(); promise[trigger_async_id_symbol] = parent ? getOrSetAsyncId(parent) : getDefaultTriggerAsyncId(); +} - if (!silent && initHooksExist()) { - const triggerId = promise[trigger_async_id_symbol]; - emitInitScript(asyncId, 'PROMISE', triggerId, promise); - } +function promiseInitHook(promise, parent) { + trackPromise(promise, parent); + const asyncId = promise[async_id_symbol]; + const triggerAsyncId = promise[trigger_async_id_symbol]; + emitInitScript(asyncId, 'PROMISE', triggerAsyncId, promise); } -function fastPromiseHook(type, promise, parent) { - if (type === kInit || !promise[async_id_symbol]) { - const silent = type !== kInit; - if (parent instanceof Promise) { - trackPromise(promise, parent, silent); - } else { - trackPromise(promise, null, silent); - } +function promiseBeforeHook(promise) { + trackPromise(promise); + const asyncId = promise[async_id_symbol]; + const triggerId = promise[trigger_async_id_symbol]; + emitBeforeScript(asyncId, triggerId, promise); +} - if (!silent) return; +function promiseAfterHook(promise) { + trackPromise(promise); + const asyncId = promise[async_id_symbol]; + if (hasHooks(kAfter)) { + emitAfterNative(asyncId); } + if (asyncId === executionAsyncId()) { + // This condition might not be true if async_hooks was enabled during + // the promise callback execution. + // Popping it off the stack can be skipped in that case, because it is + // known that it would correspond to exactly one call with + // PromiseHookType::kBefore that was not witnessed by the PromiseHook. + popAsyncContext(asyncId); + } +} +function promiseResolveHook(promise) { + trackPromise(promise); const asyncId = promise[async_id_symbol]; - switch (type) { - case kBefore: - const triggerId = promise[trigger_async_id_symbol]; - emitBeforeScript(asyncId, triggerId, promise); - break; - case kAfter: - if (hasHooks(kAfter)) { - emitAfterNative(asyncId); - } - if (asyncId === executionAsyncId()) { - // This condition might not be true if async_hooks was enabled during - // the promise callback execution. - // Popping it off the stack can be skipped in that case, because it is - // known that it would correspond to exactly one call with - // PromiseHookType::kBefore that was not witnessed by the PromiseHook. - popAsyncContext(asyncId); - } - break; - case kPromiseResolve: - emitPromiseResolveNative(asyncId); - break; - } + emitPromiseResolveNative(asyncId); } let wantPromiseHook = false; @@ -357,17 +353,17 @@ function enableHooks() { async_hook_fields[kCheck] += 1; } -let promiseHookMode = -1; function updatePromiseHookMode() { wantPromiseHook = true; if (destroyHooksExist()) { - if (promiseHookMode !== 1) { - promiseHookMode = 1; - enablePromiseHook(); - } - } else if (promiseHookMode !== 0) { - promiseHookMode = 0; - enablePromiseHook(fastPromiseHook); + enablePromiseHook(); + } else { + setPromiseHooks( + initHooksExist() ? promiseInitHook : undefined, + promiseBeforeHook, + promiseAfterHook, + promiseResolveHooksExist() ? promiseResolveHook : undefined, + ); } } @@ -383,8 +379,8 @@ function disableHooks() { function disablePromiseHookIfNecessary() { if (!wantPromiseHook) { - promiseHookMode = -1; disablePromiseHook(); + setPromiseHooks(undefined, undefined, undefined, undefined); } } @@ -458,6 +454,10 @@ function destroyHooksExist() { return hasHooks(kDestroy); } +function promiseResolveHooksExist() { + return hasHooks(kPromiseResolve); +} + function emitInitScript(asyncId, type, triggerAsyncId, resource) { // Short circuit all checks for the common case. Which is that no hooks have diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 0baf8010907043..a1c76b94138762 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -452,6 +452,15 @@ static void EnablePromiseHook(const FunctionCallbackInfo& args) { } } +static void SetPromiseHooks(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Local ctx = env->context(); + ctx->SetPromiseHooks( + args[0]->IsFunction() ? args[0].As() : Local(), + args[1]->IsFunction() ? args[1].As() : Local(), + args[2]->IsFunction() ? args[2].As() : Local(), + args[3]->IsFunction() ? args[3].As() : Local()); +} static void DisablePromiseHook(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -631,6 +640,7 @@ void AsyncWrap::Initialize(Local target, env->SetMethod(target, "clearAsyncIdStack", ClearAsyncIdStack); env->SetMethod(target, "queueDestroyAsyncId", QueueDestroyAsyncId); env->SetMethod(target, "enablePromiseHook", EnablePromiseHook); + env->SetMethod(target, "setPromiseHooks", SetPromiseHooks); env->SetMethod(target, "disablePromiseHook", DisablePromiseHook); env->SetMethod(target, "registerDestroyHook", RegisterDestroyHook); @@ -725,6 +735,7 @@ void AsyncWrap::RegisterExternalReferences( registry->Register(ClearAsyncIdStack); registry->Register(QueueDestroyAsyncId); registry->Register(EnablePromiseHook); + registry->Register(SetPromiseHooks); registry->Register(DisablePromiseHook); registry->Register(RegisterDestroyHook); registry->Register(AsyncWrap::GetAsyncId);