Reland "[api] JSFunction PromiseHook for v8::Context"

This is a reland of d5457f5fb7
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}
This commit is contained in:
Stephen Belanger 2021-04-14 15:44:39 -07:00 committed by Commit Bot
parent 6e4769bf9f
commit c0fceaa066
31 changed files with 784 additions and 133 deletions

View File

@ -211,6 +211,7 @@ Seo Sanghyeon <sanxiyn@gmail.com>
Shawn Anastasio <shawnanastasio@gmail.com>
Shawn Presser <shawnpresser@gmail.com>
Stefan Penner <stefan.penner@gmail.com>
Stephen Belanger <stephen.belanger@datadoghq.com>
Sylvestre Ledru <sledru@mozilla.com>
Taketoshi Aono <brn@b6n.ch>
Tao Liqiang <taolq@outlook.com>

View File

@ -10589,6 +10589,18 @@ class V8_EXPORT Context : public Data {
*/
void SetContinuationPreservedEmbedderData(Local<Value> 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<Function> init_hook,
Local<Function> before_hook,
Local<Function> after_hook,
Local<Function> resolve_hook);
/**
* Stack-allocated class which sets the execution context for all
* operations executed within a local scope.

View File

@ -6262,6 +6262,45 @@ void Context::SetContinuationPreservedEmbedderData(Local<Value> data) {
*i::Handle<i::HeapObject>::cast(Utils::OpenHandle(*data)));
}
void v8::Context::SetPromiseHooks(Local<Function> init_hook,
Local<Function> before_hook,
Local<Function> after_hook,
Local<Function> resolve_hook) {
i::Handle<i::Context> context = Utils::OpenHandle(this);
i::Isolate* isolate = context->GetIsolate();
i::Handle<i::Object> init = isolate->factory()->undefined_value();
i::Handle<i::Object> before = isolate->factory()->undefined_value();
i::Handle<i::Object> after = isolate->factory()->undefined_value();
i::Handle<i::Object> 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<Context> metrics::Recorder::GetContext(
Isolate* isolate, metrics::Recorder::ContextId id) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);

View File

@ -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);
{

View File

@ -97,18 +97,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);
@ -168,23 +161,48 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized(
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_perform_promise_then(this);
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_debugging, &do_perform_promise_then);
BIND(&if_debugging);
var_throwaway =
CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise,
outer_promise, on_reject, is_predicted_as_caught));
Goto(&do_perform_promise_then);
BIND(&do_perform_promise_then);
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),
if_promise_hook(this, Label::kDeferred),
not_debugging(this),
do_nothing(this);
TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
promiseHookFlags), &if_debugging, &not_debugging);
BIND(&if_debugging);
*var_throwaway =
CAST(CallRuntime(id, context, value, promise,
outer_promise, on_reject, is_predicted_as_caught));
Goto(&do_nothing);
BIND(&not_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(
TNode<Context> context, TNode<JSGeneratorObject> generator,
TNode<Object> value, TNode<JSPromise> outer_promise,

View File

@ -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

View File

@ -518,7 +518,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);

View File

@ -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::RunPromiseHook(
Runtime::FunctionId id, TNode<Context> context,
void MicrotaskQueueBuiltinsAssembler::RunAllPromiseHooks(
PromiseHookType type, TNode<Context> context,
TNode<HeapObject> promise_or_capability) {
Label hook(this, Label::kDeferred), done_hook(this);
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), &hook,
&done_hook);
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<Uint32T> promiseHookFlags) {
Label hook(this, Label::kDeferred), done_hook(this);
Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
promiseHookFlags), &hook, &done_hook);
BIND(&hook);
{
// Get to the underlying JSPromise instance.

View File

@ -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;

View File

@ -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);

View File

@ -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)) {

View File

@ -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();

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -13855,35 +13855,56 @@ TNode<BoolT> CodeStubAssembler::IsDebugActive() {
return Word32NotEqual(is_debug_active, Int32Constant(0));
}
TNode<BoolT> CodeStubAssembler::IsPromiseHookEnabled() {
const TNode<RawPtrT> promise_hook = Load<RawPtrT>(
ExternalConstant(ExternalReference::promise_hook_address(isolate())));
return WordNotEqual(promise_hook, IntPtrConstant(0));
}
TNode<BoolT> CodeStubAssembler::HasAsyncEventDelegate() {
const TNode<RawPtrT> async_event_delegate = Load<RawPtrT>(ExternalConstant(
ExternalReference::async_event_delegate_address(isolate())));
return WordNotEqual(async_event_delegate, IntPtrConstant(0));
}
TNode<BoolT> CodeStubAssembler::IsPromiseHookEnabledOrHasAsyncEventDelegate() {
const TNode<Uint8T> promise_hook_or_async_event_delegate =
Load<Uint8T>(ExternalConstant(
ExternalReference::promise_hook_or_async_event_delegate_address(
isolate())));
return Word32NotEqual(promise_hook_or_async_event_delegate, Int32Constant(0));
TNode<Uint32T> CodeStubAssembler::PromiseHookFlags() {
return Load<Uint32T>(ExternalConstant(
ExternalReference::promise_hook_flags_address(isolate())));
}
TNode<BoolT> CodeStubAssembler::IsAnyPromiseHookEnabled(TNode<Uint32T> flags) {
uint32_t mask = Isolate::PromiseHookFields::HasContextPromiseHook::kMask |
Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask;
return IsSetWord32(flags, mask);
}
TNode<BoolT> CodeStubAssembler::IsContextPromiseHookEnabled(
TNode<Uint32T> flags) {
return IsSetWord32<Isolate::PromiseHookFields::HasContextPromiseHook>(flags);
}
TNode<BoolT> CodeStubAssembler::
IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() {
const TNode<Uint8T> promise_hook_or_debug_is_active_or_async_event_delegate =
Load<Uint8T>(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));
IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(TNode<Uint32T> flags) {
uint32_t mask = Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask |
Isolate::PromiseHookFields::HasAsyncEventDelegate::kMask;
return IsSetWord32(flags, mask);
}
TNode<BoolT> CodeStubAssembler::
IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
TNode<Uint32T> flags) {
uint32_t mask = Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask |
Isolate::PromiseHookFields::HasAsyncEventDelegate::kMask |
Isolate::PromiseHookFields::IsDebugActive::kMask;
return IsSetWord32(flags, mask);
}
TNode<BoolT> CodeStubAssembler::
IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
TNode<Uint32T> flags) {
return Word32NotEqual(flags, Int32Constant(0));
}
TNode<BoolT> CodeStubAssembler::
IsAnyPromiseHookEnabledOrHasAsyncEventDelegate(TNode<Uint32T> flags) {
uint32_t mask = Isolate::PromiseHookFields::HasContextPromiseHook::kMask |
Isolate::PromiseHookFields::HasIsolatePromiseHook::kMask |
Isolate::PromiseHookFields::HasAsyncEventDelegate::kMask;
return IsSetWord32(flags, mask);
}
TNode<Code> CodeStubAssembler::LoadBuiltin(TNode<Smi> builtin_id) {

View File

@ -3534,10 +3534,44 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<Context> context);
// Promise helpers
TNode<BoolT> IsPromiseHookEnabled();
TNode<Uint32T> PromiseHookFlags();
TNode<BoolT> HasAsyncEventDelegate();
TNode<BoolT> IsPromiseHookEnabledOrHasAsyncEventDelegate();
TNode<BoolT> IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate();
TNode<BoolT> IsContextPromiseHookEnabled(TNode<Uint32T> flags);
TNode<BoolT> IsContextPromiseHookEnabled() {
return IsContextPromiseHookEnabled(PromiseHookFlags());
}
TNode<BoolT> IsAnyPromiseHookEnabled(TNode<Uint32T> flags);
TNode<BoolT> IsAnyPromiseHookEnabled() {
return IsAnyPromiseHookEnabled(PromiseHookFlags());
}
TNode<BoolT> IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(
TNode<Uint32T> flags);
TNode<BoolT> IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate() {
return IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(
PromiseHookFlags());
}
TNode<BoolT>
IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
TNode<Uint32T> flags);
TNode<BoolT>
IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() {
return IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
PromiseHookFlags());
}
TNode<BoolT> IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
TNode<Uint32T> flags);
TNode<BoolT>
IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() {
return IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
PromiseHookFlags());
}
TNode<BoolT> IsAnyPromiseHookEnabledOrHasAsyncEventDelegate(
TNode<Uint32T> flags);
TNode<BoolT>
IsAnyPromiseHookEnabledOrHasAsyncEventDelegate() {
return IsAnyPromiseHookEnabledOrHasAsyncEventDelegate(
PromiseHookFlags());
}
// for..in helpers
void CheckPrototypeEnumCache(TNode<JSReceiver> receiver,

View File

@ -965,6 +965,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());
}
@ -974,21 +979,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());

View File

@ -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, \

View File

@ -1940,6 +1940,20 @@ void Shell::AsyncHooksTriggerAsyncId(
PerIsolateData::Get(isolate)->GetAsyncHooks()->GetTriggerAsyncId()));
}
void Shell::SetPromiseHooks(const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
HandleScope handle_scope(isolate);
context->SetPromiseHooks(
args[0]->IsFunction() ? args[0].As<Function>() : Local<Function>(),
args[1]->IsFunction() ? args[1].As<Function>() : Local<Function>(),
args[2]->IsFunction() ? args[2].As<Function>() : Local<Function>(),
args[3]->IsFunction() ? args[3].As<Function>() : Local<Function>());
args.GetReturnValue().Set(v8::Undefined(isolate));
}
void WriteToFile(FILE* file, const v8::FunctionCallbackInfo<v8::Value>& args) {
for (int i = 0; i < args.Length(); i++) {
HandleScope handle_scope(args.GetIsolate());
@ -2781,6 +2795,14 @@ Local<ObjectTemplate> Shell::CreateD8Template(Isolate* isolate) {
d8_template->Set(isolate, "test", test_template);
}
{
Local<ObjectTemplate> promise_template = ObjectTemplate::New(isolate);
promise_template->Set(
isolate, "setHooks",
FunctionTemplate::New(isolate, SetPromiseHooks, Local<Value>(),
Local<Signature>(), 4));
d8_template->Set(isolate, "promise", promise_template);
}
return d8_template;
}

View File

@ -489,6 +489,8 @@ class Shell : public i::AllStatic {
static void AsyncHooksTriggerAsyncId(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetPromiseHooks(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PrintErr(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Write(const v8::FunctionCallbackInfo<v8::Value>& args);

View File

@ -4171,19 +4171,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 {
@ -4483,17 +4487,30 @@ void Isolate::SetPromiseHook(PromiseHook hook) {
PromiseHookStateUpdated();
}
void Isolate::RunAllPromiseHooks(PromiseHookType type,
Handle<JSPromise> promise,
Handle<Object> parent) {
if (HasContextPromiseHooks()) {
native_context()->RunPromiseHook(type, promise, parent);
}
if (HasIsolatePromiseHooks() || HasAsyncEventDelegate()) {
RunPromiseHook(type, promise, parent);
}
}
void Isolate::RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
Handle<Object> 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<JSPromise> promise) {
if (!async_event_delegate_) return;
if (!HasAsyncEventDelegate()) return;
DCHECK(async_event_delegate_ != nullptr);
switch (type) {
case PromiseHookType::kResolve:
return;

View File

@ -1455,6 +1455,21 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
}
#endif
void SetHasContextPromiseHooks(bool context_promise_hook) {
promise_hook_flags_ = PromiseHookFields::HasContextPromiseHook::update(
promise_hook_flags_, context_promise_hook);
PromiseHookStateUpdated();
}
bool HasContextPromiseHooks() const {
return PromiseHookFields::HasContextPromiseHook::decode(
promise_hook_flags_);
}
Address promise_hook_flags_address() {
return reinterpret_cast<Address>(&promise_hook_flags_);
}
Address promise_hook_address() {
return reinterpret_cast<Address>(&promise_hook_);
}
@ -1463,15 +1478,6 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
return reinterpret_cast<Address>(&async_event_delegate_);
}
Address promise_hook_or_async_event_delegate_address() {
return reinterpret_cast<Address>(&promise_hook_or_async_event_delegate_);
}
Address promise_hook_or_debug_is_active_or_async_event_delegate_address() {
return reinterpret_cast<Address>(
&promise_hook_or_debug_is_active_or_async_event_delegate_);
}
Address handle_scope_implementer_address() {
return reinterpret_cast<Address>(&handle_scope_implementer_);
}
@ -1487,6 +1493,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
void SetPromiseHook(PromiseHook hook);
void RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
Handle<Object> parent);
void RunAllPromiseHooks(PromiseHookType type, Handle<JSPromise> promise,
Handle<Object> parent);
void UpdatePromiseHookProtector();
void PromiseHookStateUpdated();
void AddDetachedContext(Handle<Context> context);
@ -1736,6 +1745,13 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
}
#endif
struct PromiseHookFields {
using HasContextPromiseHook = base::BitField<bool, 0, 1>;
using HasIsolatePromiseHook = HasContextPromiseHook::Next<bool, 1>;
using HasAsyncEventDelegate = HasIsolatePromiseHook::Next<bool, 1>;
using IsDebugActive = HasAsyncEventDelegate::Next<bool, 1>;
};
private:
explicit Isolate(std::unique_ptr<IsolateAllocator> isolate_allocator);
~Isolate();
@ -1819,6 +1835,16 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
void RunPromiseHookForAsyncEventDelegate(PromiseHookType type,
Handle<JSPromise> 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:
@ -2075,8 +2101,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<LocalIsolate> main_thread_local_isolate_;

View File

@ -3508,7 +3508,8 @@ Handle<JSPromise> Factory::NewJSPromiseWithoutHook() {
Handle<JSPromise> Factory::NewJSPromise() {
Handle<JSPromise> promise = NewJSPromiseWithoutHook();
isolate()->RunPromiseHook(PromiseHookType::kInit, promise, undefined_value());
isolate()->RunAllPromiseHooks(PromiseHookType::kInit, promise,
undefined_value());
return promise;
}

View File

@ -511,5 +511,53 @@ STATIC_ASSERT(NativeContext::kSize ==
(Context::SizeFor(NativeContext::NATIVE_CONTEXT_SLOTS) +
kSystemPointerSize));
void NativeContext::RunPromiseHook(PromiseHookType type,
Handle<JSPromise> promise,
Handle<Object> 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<Object> hook(isolate->native_context()->get(contextSlot), isolate);
if (hook->IsUndefined()) return;
int argc = type == PromiseHookType::kInit ? 2 : 1;
Handle<Object> argv[2] = {
Handle<Object>::cast(promise),
parent
};
Handle<Object> receiver = isolate->global_proxy();
if (Execution::Call(isolate, hook, receiver, argc, argv).is_null()) {
DCHECK(isolate->has_pending_exception());
Handle<Object> exception(isolate->pending_exception(), isolate);
MessageLocation* no_location = nullptr;
Handle<JSMessageObject> message =
isolate->CreateMessageOrAbort(exception, no_location);
MessageHandler::ReportMessage(isolate, no_location, message);
isolate->clear_pending_exception();
}
}
} // namespace internal
} // namespace v8

View File

@ -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) \
@ -696,6 +701,9 @@ class NativeContext : public Context {
void IncrementErrorsThrown();
int GetErrorsThrown();
void RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
Handle<Object> parent);
private:
STATIC_ASSERT(OffsetOfElementAt(EMBEDDER_DATA_INDEX) ==
Internals::kNativeContextEmbedderDataOffset);

View File

@ -124,6 +124,12 @@ extern enum ContextSlot extends intptr constexpr 'Context::Field' {
PROMISE_PROTOTYPE_INDEX: Slot<NativeContext, JSObject>,
STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX: Slot<NativeContext, Map>,
PROMISE_HOOK_INIT_FUNCTION_INDEX: Slot<NativeContext, Undefined|JSFunction>,
PROMISE_HOOK_BEFORE_FUNCTION_INDEX: Slot<NativeContext, Undefined|JSFunction>,
PROMISE_HOOK_AFTER_FUNCTION_INDEX: Slot<NativeContext, Undefined|JSFunction>,
PROMISE_HOOK_RESOLVE_FUNCTION_INDEX:
Slot<NativeContext, Undefined|JSFunction>,
CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX: Slot<NativeContext, HeapObject>,
BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX: Slot<NativeContext, Map>,

View File

@ -5393,8 +5393,8 @@ Handle<Object> JSPromise::Reject(Handle<JSPromise> 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());
@ -5429,8 +5429,8 @@ MaybeHandle<Object> JSPromise::Resolve(Handle<JSPromise> promise,
DCHECK(
!reinterpret_cast<v8::Isolate*>(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)) {

View File

@ -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<JSPromise> AwaitPromisesInitCommon(Isolate* isolate,
// hook for the throwaway promise (passing the {promise} as its
// parent).
Handle<JSPromise> 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);
}

View File

@ -2688,7 +2688,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<Object> result =

View File

@ -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();