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:
parent
6e4769bf9f
commit
c0fceaa066
1
AUTHORS
1
AUTHORS
@ -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>
|
||||
|
12
include/v8.h
12
include/v8.h
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
{
|
||||
|
@ -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, ¬_debugging);
|
||||
BIND(&if_debugging);
|
||||
*var_throwaway =
|
||||
CAST(CallRuntime(id, context, value, promise,
|
||||
outer_promise, on_reject, is_predicted_as_caught));
|
||||
Goto(&do_nothing);
|
||||
BIND(¬_debugging);
|
||||
|
||||
// This call to NewJSPromise is to keep behaviour parity with what happens
|
||||
// in Runtime::kAwaitPromisesInit above if native hooks are set. It will
|
||||
// create a throwaway promise that will trigger an init event and will get
|
||||
// passed into Builtins::kPerformPromiseThen below.
|
||||
Branch(IsContextPromiseHookEnabled(promiseHookFlags), &if_promise_hook,
|
||||
&do_nothing);
|
||||
BIND(&if_promise_hook);
|
||||
*var_throwaway = NewJSPromise(context, promise);
|
||||
Goto(&do_nothing);
|
||||
BIND(&do_nothing);
|
||||
}
|
||||
|
||||
TNode<Object> AsyncBuiltinsAssembler::Await(
|
||||
TNode<Context> context, TNode<JSGeneratorObject> generator,
|
||||
TNode<Object> value, TNode<JSPromise> outer_promise,
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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)) {
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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());
|
||||
|
@ -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, \
|
||||
|
22
src/d8/d8.cc
22
src/d8/d8.cc
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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>,
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 =
|
||||
|
244
test/mjsunit/promise-hooks.js
Normal file
244
test/mjsunit/promise-hooks.js
Normal 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();
|
Loading…
Reference in New Issue
Block a user