summaryrefslogtreecommitdiff
path: root/deps/v8
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8')
-rw-r--r--deps/v8/AUTHORS1
-rw-r--r--deps/v8/include/v8.h12
-rw-r--r--deps/v8/src/api/api.cc39
-rw-r--r--deps/v8/src/builtins/builtins-async-function-gen.cc4
-rw-r--r--deps/v8/src/builtins/builtins-async-gen.cc62
-rw-r--r--deps/v8/src/builtins/builtins-async-gen.h6
-rw-r--r--deps/v8/src/builtins/builtins-async-generator-gen.cc2
-rw-r--r--deps/v8/src/builtins/builtins-microtask-queue-gen.cc62
-rw-r--r--deps/v8/src/builtins/cast.tq6
-rw-r--r--deps/v8/src/builtins/promise-abstract-operations.tq15
-rw-r--r--deps/v8/src/builtins/promise-all.tq2
-rw-r--r--deps/v8/src/builtins/promise-constructor.tq7
-rw-r--r--deps/v8/src/builtins/promise-jobs.tq2
-rw-r--r--deps/v8/src/builtins/promise-misc.tq122
-rw-r--r--deps/v8/src/builtins/promise-resolve.tq2
-rw-r--r--deps/v8/src/codegen/code-stub-assembler.cc61
-rw-r--r--deps/v8/src/codegen/code-stub-assembler.h40
-rw-r--r--deps/v8/src/codegen/external-reference.cc20
-rw-r--r--deps/v8/src/codegen/external-reference.h6
-rw-r--r--deps/v8/src/d8/d8.cc22
-rw-r--r--deps/v8/src/d8/d8.h2
-rw-r--r--deps/v8/src/execution/isolate.cc41
-rw-r--r--deps/v8/src/execution/isolate.h47
-rw-r--r--deps/v8/src/heap/factory.cc3
-rw-r--r--deps/v8/src/objects/contexts.cc48
-rw-r--r--deps/v8/src/objects/contexts.h8
-rw-r--r--deps/v8/src/objects/contexts.tq6
-rw-r--r--deps/v8/src/objects/objects.cc8
-rw-r--r--deps/v8/src/runtime/runtime-promise.cc8
-rw-r--r--deps/v8/test/cctest/test-code-stub-assembler.cc3
-rw-r--r--deps/v8/test/mjsunit/promise-hooks.js244
31 files changed, 781 insertions, 130 deletions
diff --git a/deps/v8/AUTHORS b/deps/v8/AUTHORS
index aa6d32302b..a27cf5ef0a 100644
--- a/deps/v8/AUTHORS
+++ b/deps/v8/AUTHORS
@@ -209,6 +209,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>
diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h
index 083bf5e123..e4448db191 100644
--- a/deps/v8/include/v8.h
+++ b/deps/v8/include/v8.h
@@ -10795,6 +10795,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.
*/
diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc
index a56b7e1a7d..ca73b58a60 100644
--- a/deps/v8/src/api/api.cc
+++ b/deps/v8/src/api/api.cc
@@ -6147,6 +6147,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);
diff --git a/deps/v8/src/builtins/builtins-async-function-gen.cc b/deps/v8/src/builtins/builtins-async-function-gen.cc
index 49b00caa04..1644997ed0 100644
--- a/deps/v8/src/builtins/builtins-async-function-gen.cc
+++ b/deps/v8/src/builtins/builtins-async-function-gen.cc
@@ -157,12 +157,14 @@ TF_BUILTIN(AsyncFunctionEnter, AsyncFunctionBuiltinsAssembler) {
StoreObjectFieldNoWriteBarrier(
async_function_object, JSAsyncFunctionObject::kPromiseOffset, promise);
+ RunContextPromiseHookInit(context, promise, UndefinedConstant());
+
// Fire promise hooks if enabled and push the Promise under construction
// in an async function on the catch prediction stack to handle exceptions
// thrown before the first await.
Label if_instrumentation(this, Label::kDeferred),
if_instrumentation_done(this);
- Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
+ Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_instrumentation, &if_instrumentation_done);
BIND(&if_instrumentation);
{
diff --git a/deps/v8/src/builtins/builtins-async-gen.cc b/deps/v8/src/builtins/builtins-async-gen.cc
index fa05e9b32a..1a660abece 100644
--- a/deps/v8/src/builtins/builtins-async-gen.cc
+++ b/deps/v8/src/builtins/builtins-async-gen.cc
@@ -99,18 +99,11 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOld(
TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
- // Deal with PromiseHooks and debug support in the runtime. This
- // also allocates the throwaway promise, which is only needed in
- // case of PromiseHooks or debugging.
- Label if_debugging(this, Label::kDeferred), do_resolve_promise(this);
- Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
- &if_debugging, &do_resolve_promise);
- BIND(&if_debugging);
- var_throwaway =
- CAST(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value, promise,
- outer_promise, on_reject, is_predicted_as_caught));
- Goto(&do_resolve_promise);
- BIND(&do_resolve_promise);
+ RunContextPromiseHookInit(context, promise, outer_promise);
+
+ InitAwaitPromise(Runtime::kAwaitPromisesInitOld, context, value, promise,
+ outer_promise, on_reject, is_predicted_as_caught,
+ &var_throwaway);
// Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
CallBuiltin(Builtins::kResolvePromise, context, promise, value);
@@ -170,21 +163,46 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized(
TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
+ InitAwaitPromise(Runtime::kAwaitPromisesInit, context, promise, promise,
+ outer_promise, on_reject, is_predicted_as_caught,
+ &var_throwaway);
+
+ return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
+ on_resolve, on_reject, var_throwaway.value());
+}
+
+void AsyncBuiltinsAssembler::InitAwaitPromise(
+ Runtime::FunctionId id, TNode<Context> context, TNode<Object> value,
+ TNode<Object> promise, TNode<Object> outer_promise,
+ TNode<HeapObject> on_reject, TNode<Oddball> is_predicted_as_caught,
+ TVariable<HeapObject>* var_throwaway) {
// Deal with PromiseHooks and debug support in the runtime. This
// also allocates the throwaway promise, which is only needed in
// case of PromiseHooks or debugging.
- Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this);
- Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
- &if_debugging, &do_perform_promise_then);
+ Label if_debugging(this, Label::kDeferred),
+ if_promise_hook(this, Label::kDeferred),
+ not_debugging(this),
+ do_nothing(this);
+ TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
+ Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
+ promiseHookFlags), &if_debugging, &not_debugging);
BIND(&if_debugging);
- var_throwaway =
- CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise,
+ *var_throwaway =
+ CAST(CallRuntime(id, context, value, promise,
outer_promise, on_reject, is_predicted_as_caught));
- Goto(&do_perform_promise_then);
- BIND(&do_perform_promise_then);
-
- return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
- on_resolve, on_reject, var_throwaway.value());
+ Goto(&do_nothing);
+ BIND(&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(
diff --git a/deps/v8/src/builtins/builtins-async-gen.h b/deps/v8/src/builtins/builtins-async-gen.h
index 833e78d45d..34b7a0ce1d 100644
--- a/deps/v8/src/builtins/builtins-async-gen.h
+++ b/deps/v8/src/builtins/builtins-async-gen.h
@@ -62,6 +62,12 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
TNode<SharedFunctionInfo> on_resolve_sfi,
TNode<SharedFunctionInfo> on_reject_sfi,
TNode<Oddball> is_predicted_as_caught);
+
+ void InitAwaitPromise(
+ Runtime::FunctionId id, TNode<Context> context, TNode<Object> value,
+ TNode<Object> promise, TNode<Object> outer_promise,
+ TNode<HeapObject> on_reject, TNode<Oddball> is_predicted_as_caught,
+ TVariable<HeapObject>* var_throwaway);
};
} // namespace internal
diff --git a/deps/v8/src/builtins/builtins-async-generator-gen.cc b/deps/v8/src/builtins/builtins-async-generator-gen.cc
index 374b13dd63..5d053063ff 100644
--- a/deps/v8/src/builtins/builtins-async-generator-gen.cc
+++ b/deps/v8/src/builtins/builtins-async-generator-gen.cc
@@ -520,7 +520,7 @@ TF_BUILTIN(AsyncGeneratorResolve, AsyncGeneratorBuiltinsAssembler) {
// the "promiseResolve" hook would not be fired otherwise.
Label if_fast(this), if_slow(this, Label::kDeferred), return_promise(this);
GotoIfForceSlowPath(&if_slow);
- GotoIf(IsPromiseHookEnabled(), &if_slow);
+ GotoIf(IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(), &if_slow);
Branch(IsPromiseThenProtectorCellInvalid(), &if_slow, &if_fast);
BIND(&if_fast);
diff --git a/deps/v8/src/builtins/builtins-microtask-queue-gen.cc b/deps/v8/src/builtins/builtins-microtask-queue-gen.cc
index 9f16186d13..1ec9e350f6 100644
--- a/deps/v8/src/builtins/builtins-microtask-queue-gen.cc
+++ b/deps/v8/src/builtins/builtins-microtask-queue-gen.cc
@@ -46,8 +46,11 @@ class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler {
void EnterMicrotaskContext(TNode<Context> native_context);
void RewindEnteredContext(TNode<IntPtrT> saved_entered_context_count);
+ void RunAllPromiseHooks(PromiseHookType type, TNode<Context> context,
+ TNode<HeapObject> promise_or_capability);
void RunPromiseHook(Runtime::FunctionId id, TNode<Context> context,
- TNode<HeapObject> promise_or_capability);
+ TNode<HeapObject> promise_or_capability,
+ TNode<Uint32T> promiseHookFlags);
};
TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueue(
@@ -199,7 +202,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
const TNode<Object> thenable = LoadObjectField(
microtask, PromiseResolveThenableJobTask::kThenableOffset);
- RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
+ RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
CAST(promise_to_resolve));
{
@@ -208,7 +211,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
promise_to_resolve, thenable, then);
}
- RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
+ RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
CAST(promise_to_resolve));
RewindEnteredContext(saved_entered_context_count);
@@ -243,8 +246,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
BIND(&preserved_data_done);
// Run the promise before/debug hook if enabled.
- RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
- promise_or_capability);
+ RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
+ promise_or_capability);
{
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
@@ -253,8 +256,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
}
// Run the promise after/debug hook if enabled.
- RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
- promise_or_capability);
+ RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
+ promise_or_capability);
Label preserved_data_reset_done(this);
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
@@ -296,8 +299,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
BIND(&preserved_data_done);
// Run the promise before/debug hook if enabled.
- RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
- promise_or_capability);
+ RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
+ promise_or_capability);
{
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
@@ -306,8 +309,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
}
// Run the promise after/debug hook if enabled.
- RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
- promise_or_capability);
+ RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
+ promise_or_capability);
Label preserved_data_reset_done(this);
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
@@ -465,12 +468,43 @@ void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext(
saved_entered_context_count);
}
+void MicrotaskQueueBuiltinsAssembler::RunAllPromiseHooks(
+ PromiseHookType type, TNode<Context> context,
+ TNode<HeapObject> promise_or_capability) {
+ Label hook(this, Label::kDeferred), done_hook(this);
+ TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
+ Branch(IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
+ promiseHookFlags), &hook, &done_hook);
+ BIND(&hook);
+ {
+ switch (type) {
+ case PromiseHookType::kBefore:
+ RunContextPromiseHookBefore(context, promise_or_capability,
+ promiseHookFlags);
+ RunPromiseHook(Runtime::kPromiseHookBefore, context,
+ promise_or_capability, promiseHookFlags);
+ break;
+ case PromiseHookType::kAfter:
+ RunContextPromiseHookAfter(context, promise_or_capability,
+ promiseHookFlags);
+ RunPromiseHook(Runtime::kPromiseHookAfter, context,
+ promise_or_capability, promiseHookFlags);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ Goto(&done_hook);
+ }
+ BIND(&done_hook);
+}
+
void MicrotaskQueueBuiltinsAssembler::RunPromiseHook(
Runtime::FunctionId id, TNode<Context> context,
- TNode<HeapObject> promise_or_capability) {
+ TNode<HeapObject> promise_or_capability,
+ TNode<Uint32T> promiseHookFlags) {
Label hook(this, Label::kDeferred), done_hook(this);
- Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), &hook,
- &done_hook);
+ Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
+ promiseHookFlags), &hook, &done_hook);
BIND(&hook);
{
// Get to the underlying JSPromise instance.
diff --git a/deps/v8/src/builtins/cast.tq b/deps/v8/src/builtins/cast.tq
index b490055a19..2bec3d86be 100644
--- a/deps/v8/src/builtins/cast.tq
+++ b/deps/v8/src/builtins/cast.tq
@@ -386,6 +386,12 @@ Cast<Undefined|Callable>(o: HeapObject): Undefined|Callable
return HeapObjectToCallable(o) otherwise CastError;
}
+Cast<Undefined|JSFunction>(o: HeapObject): Undefined|JSFunction
+ labels CastError {
+ if (o == Undefined) return Undefined;
+ return Cast<JSFunction>(o) otherwise CastError;
+}
+
macro Cast<T : type extends Symbol>(o: Symbol): T labels CastError;
Cast<PublicSymbol>(s: Symbol): PublicSymbol labels CastError {
if (s.flags.is_private) goto CastError;
diff --git a/deps/v8/src/builtins/promise-abstract-operations.tq b/deps/v8/src/builtins/promise-abstract-operations.tq
index b7a1b571e6..0e435afad9 100644
--- a/deps/v8/src/builtins/promise-abstract-operations.tq
+++ b/deps/v8/src/builtins/promise-abstract-operations.tq
@@ -196,6 +196,8 @@ FulfillPromise(implicit context: Context)(
// Assert: The value of promise.[[PromiseState]] is "pending".
assert(promise.Status() == PromiseState::kPending);
+ RunContextPromiseHookResolve(promise);
+
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
@@ -214,17 +216,24 @@ FulfillPromise(implicit context: Context)(
}
extern macro PromiseBuiltinsAssembler::
- IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(): bool;
+ IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(): bool;
+
+extern macro PromiseBuiltinsAssembler::
+ IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(uint32):
+ bool;
// https://tc39.es/ecma262/#sec-rejectpromise
transitioning builtin
RejectPromise(implicit context: Context)(
promise: JSPromise, reason: JSAny, debugEvent: Boolean): JSAny {
+ const promiseHookFlags = PromiseHookFlags();
+
// If promise hook is enabled or the debugger is active, let
// the runtime handle this operation, which greatly reduces
// the complexity here and also avoids a couple of back and
// forth between JavaScript and C++ land.
- if (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
+ if (IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
+ promiseHookFlags) ||
!promise.HasHandler()) {
// 7. If promise.[[PromiseIsHandled]] is false, perform
// HostPromiseRejectionTracker(promise, "reject").
@@ -233,6 +242,8 @@ RejectPromise(implicit context: Context)(
return runtime::RejectPromise(promise, reason, debugEvent);
}
+ RunContextPromiseHookResolve(promise, promiseHookFlags);
+
// 2. Let reactions be promise.[[PromiseRejectReactions]].
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
diff --git a/deps/v8/src/builtins/promise-all.tq b/deps/v8/src/builtins/promise-all.tq
index 41dee8b9e7..294c5e911c 100644
--- a/deps/v8/src/builtins/promise-all.tq
+++ b/deps/v8/src/builtins/promise-all.tq
@@ -232,7 +232,7 @@ Reject(Object) {
// PerformPromiseThen), since this is only necessary for DevTools and
// PromiseHooks.
if (promiseResolveFunction != Undefined ||
- IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
+ IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
IsPromiseSpeciesProtectorCellInvalid() || Is<Smi>(nextValue) ||
!IsPromiseThenLookupChainIntact(
nativeContext, UnsafeCast<HeapObject>(nextValue).map)) {
diff --git a/deps/v8/src/builtins/promise-constructor.tq b/deps/v8/src/builtins/promise-constructor.tq
index 3c5a5e560d..b5f7292a77 100644
--- a/deps/v8/src/builtins/promise-constructor.tq
+++ b/deps/v8/src/builtins/promise-constructor.tq
@@ -40,7 +40,8 @@ extern macro ConstructorBuiltinsAssembler::FastNewObject(
Context, JSFunction, JSReceiver): JSObject;
extern macro
-PromiseBuiltinsAssembler::IsPromiseHookEnabledOrHasAsyncEventDelegate(): bool;
+PromiseBuiltinsAssembler::IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(
+ uint32): bool;
// https://tc39.es/ecma262/#sec-promise-executor
transitioning javascript builtin
@@ -73,9 +74,7 @@ PromiseConstructor(
result = UnsafeCast<JSPromise>(
FastNewObject(context, promiseFun, UnsafeCast<JSReceiver>(newTarget)));
PromiseInit(result);
- if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
- runtime::PromiseHookInit(result, Undefined);
- }
+ RunAnyPromiseHookInit(result, Undefined);
}
const isDebugActive = IsDebugActive();
diff --git a/deps/v8/src/builtins/promise-jobs.tq b/deps/v8/src/builtins/promise-jobs.tq
index 80e98f373b..6fa81dcd28 100644
--- a/deps/v8/src/builtins/promise-jobs.tq
+++ b/deps/v8/src/builtins/promise-jobs.tq
@@ -25,7 +25,7 @@ PromiseResolveThenableJob(implicit context: Context)(
const promiseThen = *NativeContextSlot(ContextSlot::PROMISE_THEN_INDEX);
const thenableMap = thenable.map;
if (TaggedEqual(then, promiseThen) && IsJSPromiseMap(thenableMap) &&
- !IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() &&
+ !IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() &&
IsPromiseSpeciesLookupChainIntact(nativeContext, thenableMap)) {
// We know that the {thenable} is a JSPromise, which doesn't require
// any special treatment and that {then} corresponds to the initial
diff --git a/deps/v8/src/builtins/promise-misc.tq b/deps/v8/src/builtins/promise-misc.tq
index 67e5e38687..c6661e3717 100644
--- a/deps/v8/src/builtins/promise-misc.tq
+++ b/deps/v8/src/builtins/promise-misc.tq
@@ -8,6 +8,9 @@
namespace runtime {
extern transitioning runtime
AllowDynamicFunction(implicit context: Context)(JSAny): JSAny;
+
+extern transitioning runtime
+ReportMessageFromMicrotask(implicit context: Context)(JSAny): JSAny;
}
// Unsafe functions that should be used very carefully.
@@ -17,6 +20,12 @@ extern macro PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets(JSPromise): void;
extern macro PromiseBuiltinsAssembler::AllocateJSPromise(Context): HeapObject;
}
+extern macro
+PromiseBuiltinsAssembler::IsContextPromiseHookEnabled(uint32): bool;
+
+extern macro
+PromiseBuiltinsAssembler::PromiseHookFlags(): uint32;
+
namespace promise {
extern macro IsFunctionWithPrototypeSlotMap(Map): bool;
@@ -90,6 +99,110 @@ macro NewPromiseRejectReactionJobTask(implicit context: Context)(
};
}
+@export
+transitioning macro RunContextPromiseHookInit(implicit context: Context)(
+ promise: JSPromise, parent: Object) {
+ const maybeHook = *NativeContextSlot(
+ ContextSlot::PROMISE_HOOK_INIT_FUNCTION_INDEX);
+ if (IsUndefined(maybeHook)) return;
+
+ const hook = Cast<JSFunction>(maybeHook) otherwise unreachable;
+ const parentObject = Is<JSPromise>(parent) ? Cast<JSPromise>(parent)
+ otherwise unreachable: Undefined;
+
+ try {
+ Call(context, hook, Undefined, promise, parentObject);
+ } catch (e) {
+ runtime::ReportMessageFromMicrotask(e);
+ }
+}
+
+@export
+transitioning macro RunContextPromiseHookResolve(implicit context: Context)(
+ promise: JSPromise) {
+ RunContextPromiseHook(
+ ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise,
+ PromiseHookFlags());
+}
+
+@export
+transitioning macro RunContextPromiseHookResolve(implicit context: Context)(
+ promise: JSPromise, flags: uint32) {
+ RunContextPromiseHook(
+ ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, promise, flags);
+}
+
+@export
+transitioning macro RunContextPromiseHookBefore(implicit context: Context)(
+ promiseOrCapability: JSPromise|PromiseCapability) {
+ RunContextPromiseHook(
+ ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability,
+ PromiseHookFlags());
+}
+
+@export
+transitioning macro RunContextPromiseHookBefore(implicit context: Context)(
+ promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) {
+ RunContextPromiseHook(
+ ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, promiseOrCapability,
+ flags);
+}
+
+@export
+transitioning macro RunContextPromiseHookAfter(implicit context: Context)(
+ promiseOrCapability: JSPromise|PromiseCapability) {
+ RunContextPromiseHook(
+ ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability,
+ PromiseHookFlags());
+}
+
+@export
+transitioning macro RunContextPromiseHookAfter(implicit context: Context)(
+ promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) {
+ RunContextPromiseHook(
+ ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, promiseOrCapability,
+ flags);
+}
+
+transitioning macro RunContextPromiseHook(implicit context: Context)(
+ slot: Slot<NativeContext, Undefined|JSFunction>,
+ promiseOrCapability: JSPromise|PromiseCapability, flags: uint32) {
+ if (!IsContextPromiseHookEnabled(flags)) return;
+ const maybeHook = *NativeContextSlot(slot);
+ if (IsUndefined(maybeHook)) return;
+
+ const hook = Cast<JSFunction>(maybeHook) otherwise unreachable;
+
+ let promise: JSPromise;
+ typeswitch (promiseOrCapability) {
+ case (jspromise: JSPromise): {
+ promise = jspromise;
+ }
+ case (capability: PromiseCapability): {
+ promise = Cast<JSPromise>(capability.promise) otherwise return;
+ }
+ }
+
+ try {
+ Call(context, hook, Undefined, promise);
+ } catch (e) {
+ runtime::ReportMessageFromMicrotask(e);
+ }
+}
+
+transitioning macro RunAnyPromiseHookInit(implicit context: Context)(
+ promise: JSPromise, parent: Object) {
+ const promiseHookFlags = PromiseHookFlags();
+ // Fast return if no hooks are set.
+ if (promiseHookFlags == 0) return;
+ if (IsContextPromiseHookEnabled(promiseHookFlags)) {
+ RunContextPromiseHookInit(promise, parent);
+ }
+ if (IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(promiseHookFlags)) {
+ runtime::PromiseHookInit(promise, parent);
+ }
+}
+
// These allocate and initialize a promise with pending state and
// undefined fields.
//
@@ -100,9 +213,7 @@ transitioning macro NewJSPromise(implicit context: Context)(parent: Object):
JSPromise {
const instance = InnerNewJSPromise();
PromiseInit(instance);
- if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
- runtime::PromiseHookInit(instance, parent);
- }
+ RunAnyPromiseHookInit(instance, parent);
return instance;
}
@@ -124,10 +235,7 @@ transitioning macro NewJSPromise(implicit context: Context)(
instance.reactions_or_result = result;
instance.SetStatus(status);
promise_internal::ZeroOutEmbedderOffsets(instance);
-
- if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
- runtime::PromiseHookInit(instance, Undefined);
- }
+ RunAnyPromiseHookInit(instance, Undefined);
return instance;
}
diff --git a/deps/v8/src/builtins/promise-resolve.tq b/deps/v8/src/builtins/promise-resolve.tq
index e933dfbae0..3125054e87 100644
--- a/deps/v8/src/builtins/promise-resolve.tq
+++ b/deps/v8/src/builtins/promise-resolve.tq
@@ -97,7 +97,7 @@ ResolvePromise(implicit context: Context)(
// We also let the runtime handle it if promise == resolution.
// We can use pointer comparison here, since the {promise} is guaranteed
// to be a JSPromise inside this function and thus is reference comparable.
- if (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
+ if (IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
TaggedEqual(promise, resolution))
deferred {
return runtime::ResolvePromise(promise, resolution);
diff --git a/deps/v8/src/codegen/code-stub-assembler.cc b/deps/v8/src/codegen/code-stub-assembler.cc
index 76ee8c2d06..415291dd73 100644
--- a/deps/v8/src/codegen/code-stub-assembler.cc
+++ b/deps/v8/src/codegen/code-stub-assembler.cc
@@ -13391,35 +13391,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::
+ 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::
- 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));
+ 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) {
diff --git a/deps/v8/src/codegen/code-stub-assembler.h b/deps/v8/src/codegen/code-stub-assembler.h
index 03af2cc5e2..702792d788 100644
--- a/deps/v8/src/codegen/code-stub-assembler.h
+++ b/deps/v8/src/codegen/code-stub-assembler.h
@@ -3497,10 +3497,44 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<Context> 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,
diff --git a/deps/v8/src/codegen/external-reference.cc b/deps/v8/src/codegen/external-reference.cc
index bf75ff3d12..88ce90f4fd 100644
--- a/deps/v8/src/codegen/external-reference.cc
+++ b/deps/v8/src/codegen/external-reference.cc
@@ -925,6 +925,11 @@ ExternalReference ExternalReference::cpu_features() {
return ExternalReference(&CpuFeatures::supported_);
}
+ExternalReference ExternalReference::promise_hook_flags_address(
+ Isolate* isolate) {
+ return ExternalReference(isolate->promise_hook_flags_address());
+}
+
ExternalReference ExternalReference::promise_hook_address(Isolate* isolate) {
return ExternalReference(isolate->promise_hook_address());
}
@@ -934,21 +939,6 @@ ExternalReference ExternalReference::async_event_delegate_address(
return ExternalReference(isolate->async_event_delegate_address());
}
-ExternalReference
-ExternalReference::promise_hook_or_async_event_delegate_address(
- Isolate* isolate) {
- return ExternalReference(
- isolate->promise_hook_or_async_event_delegate_address());
-}
-
-ExternalReference ExternalReference::
- promise_hook_or_debug_is_active_or_async_event_delegate_address(
- Isolate* isolate) {
- return ExternalReference(
- isolate
- ->promise_hook_or_debug_is_active_or_async_event_delegate_address());
-}
-
ExternalReference ExternalReference::debug_execution_mode_address(
Isolate* isolate) {
return ExternalReference(isolate->debug_execution_mode_address());
diff --git a/deps/v8/src/codegen/external-reference.h b/deps/v8/src/codegen/external-reference.h
index d44b8e801c..0cd80ca6f1 100644
--- a/deps/v8/src/codegen/external-reference.h
+++ b/deps/v8/src/codegen/external-reference.h
@@ -50,13 +50,9 @@ class StatsCounter;
V(handle_scope_limit_address, "HandleScope::limit") \
V(scheduled_exception_address, "Isolate::scheduled_exception") \
V(address_of_pending_message_obj, "address_of_pending_message_obj") \
+ V(promise_hook_flags_address, "Isolate::promise_hook_flags_address()") \
V(promise_hook_address, "Isolate::promise_hook_address()") \
V(async_event_delegate_address, "Isolate::async_event_delegate_address()") \
- V(promise_hook_or_async_event_delegate_address, \
- "Isolate::promise_hook_or_async_event_delegate_address()") \
- V(promise_hook_or_debug_is_active_or_async_event_delegate_address, \
- "Isolate::promise_hook_or_debug_is_active_or_async_event_delegate_" \
- "address()") \
V(debug_execution_mode_address, "Isolate::debug_execution_mode_address()") \
V(debug_is_active_address, "Debug::is_active_address()") \
V(debug_hook_on_function_call_address, \
diff --git a/deps/v8/src/d8/d8.cc b/deps/v8/src/d8/d8.cc
index 999e8c2b96..63bcdf871f 100644
--- a/deps/v8/src/d8/d8.cc
+++ b/deps/v8/src/d8/d8.cc
@@ -1792,6 +1792,20 @@ void Shell::AsyncHooksTriggerAsyncId(
PerIsolateData::Get(isolate)->GetAsyncHooks()->GetTriggerAsyncId()));
}
+void Shell::SetPromiseHooks(const v8::FunctionCallbackInfo<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());
@@ -2582,6 +2596,14 @@ Local<ObjectTemplate> Shell::CreateD8Template(Isolate* isolate) {
d8_template->Set(isolate, "log", log_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;
}
diff --git a/deps/v8/src/d8/d8.h b/deps/v8/src/d8/d8.h
index a9f6f3bc8b..6ea9681d69 100644
--- a/deps/v8/src/d8/d8.h
+++ b/deps/v8/src/d8/d8.h
@@ -478,6 +478,8 @@ class Shell : public i::AllStatic {
static void AsyncHooksTriggerAsyncId(
const v8::FunctionCallbackInfo<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);
diff --git a/deps/v8/src/execution/isolate.cc b/deps/v8/src/execution/isolate.cc
index 6f7efe3986..7fa6d7774d 100644
--- a/deps/v8/src/execution/isolate.cc
+++ b/deps/v8/src/execution/isolate.cc
@@ -4045,19 +4045,23 @@ void Isolate::FireCallCompletedCallback(MicrotaskQueue* microtask_queue) {
}
}
-void Isolate::PromiseHookStateUpdated() {
- bool promise_hook_or_async_event_delegate =
- promise_hook_ || async_event_delegate_;
- bool promise_hook_or_debug_is_active_or_async_event_delegate =
- promise_hook_or_async_event_delegate || debug()->is_active();
- if (promise_hook_or_debug_is_active_or_async_event_delegate &&
- Protectors::IsPromiseHookIntact(this)) {
+void Isolate::UpdatePromiseHookProtector() {
+ if (Protectors::IsPromiseHookIntact(this)) {
HandleScope scope(this);
Protectors::InvalidatePromiseHook(this);
}
- promise_hook_or_async_event_delegate_ = promise_hook_or_async_event_delegate;
- promise_hook_or_debug_is_active_or_async_event_delegate_ =
- promise_hook_or_debug_is_active_or_async_event_delegate;
+}
+
+void Isolate::PromiseHookStateUpdated() {
+ promise_hook_flags_ =
+ (promise_hook_flags_ & PromiseHookFields::HasContextPromiseHook::kMask) |
+ PromiseHookFields::HasIsolatePromiseHook::encode(promise_hook_) |
+ PromiseHookFields::HasAsyncEventDelegate::encode(async_event_delegate_) |
+ PromiseHookFields::IsDebugActive::encode(debug()->is_active());
+
+ if (promise_hook_flags_ != 0) {
+ UpdatePromiseHookProtector();
+ }
}
namespace {
@@ -4357,17 +4361,30 @@ void Isolate::SetPromiseHook(PromiseHook hook) {
PromiseHookStateUpdated();
}
+void Isolate::RunAllPromiseHooks(PromiseHookType type,
+ Handle<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;
diff --git a/deps/v8/src/execution/isolate.h b/deps/v8/src/execution/isolate.h
index cd819c5087..e08195674f 100644
--- a/deps/v8/src/execution/isolate.h
+++ b/deps/v8/src/execution/isolate.h
@@ -1428,21 +1428,27 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
}
#endif
- Address promise_hook_address() {
- return reinterpret_cast<Address>(&promise_hook_);
+ void SetHasContextPromiseHooks(bool context_promise_hook) {
+ promise_hook_flags_ = PromiseHookFields::HasContextPromiseHook::update(
+ promise_hook_flags_, context_promise_hook);
+ PromiseHookStateUpdated();
}
- Address async_event_delegate_address() {
- return reinterpret_cast<Address>(&async_event_delegate_);
+ bool HasContextPromiseHooks() const {
+ return PromiseHookFields::HasContextPromiseHook::decode(
+ promise_hook_flags_);
}
- Address promise_hook_or_async_event_delegate_address() {
- return reinterpret_cast<Address>(&promise_hook_or_async_event_delegate_);
+ Address promise_hook_flags_address() {
+ return reinterpret_cast<Address>(&promise_hook_flags_);
}
- 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 promise_hook_address() {
+ return reinterpret_cast<Address>(&promise_hook_);
+ }
+
+ Address async_event_delegate_address() {
+ return reinterpret_cast<Address>(&async_event_delegate_);
}
Address handle_scope_implementer_address() {
@@ -1460,6 +1466,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
void SetPromiseHook(PromiseHook hook);
void RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
Handle<Object> parent);
+ void RunAllPromiseHooks(PromiseHookType type, Handle<JSPromise> promise,
+ Handle<Object> parent);
+ void UpdatePromiseHookProtector();
void PromiseHookStateUpdated();
void AddDetachedContext(Handle<Context> context);
@@ -1697,6 +1706,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();
@@ -1780,6 +1796,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:
@@ -2032,8 +2058,7 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
debug::ConsoleDelegate* console_delegate_ = nullptr;
debug::AsyncEventDelegate* async_event_delegate_ = nullptr;
- bool promise_hook_or_async_event_delegate_ = false;
- bool promise_hook_or_debug_is_active_or_async_event_delegate_ = false;
+ uint32_t promise_hook_flags_ = 0;
int async_task_count_ = 0;
std::unique_ptr<LocalIsolate> main_thread_local_isolate_;
diff --git a/deps/v8/src/heap/factory.cc b/deps/v8/src/heap/factory.cc
index bd7ec8915c..ca9d316e2b 100644
--- a/deps/v8/src/heap/factory.cc
+++ b/deps/v8/src/heap/factory.cc
@@ -3377,7 +3377,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;
}
diff --git a/deps/v8/src/objects/contexts.cc b/deps/v8/src/objects/contexts.cc
index af73bf0256..771fbea40b 100644
--- a/deps/v8/src/objects/contexts.cc
+++ b/deps/v8/src/objects/contexts.cc
@@ -511,5 +511,53 @@ STATIC_ASSERT(NativeContext::kSize ==
(Context::SizeFor(NativeContext::NATIVE_CONTEXT_SLOTS) +
kSystemPointerSize));
+void NativeContext::RunPromiseHook(PromiseHookType type,
+ Handle<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
diff --git a/deps/v8/src/objects/contexts.h b/deps/v8/src/objects/contexts.h
index 47784cf405..0a67d52d5e 100644
--- a/deps/v8/src/objects/contexts.h
+++ b/deps/v8/src/objects/contexts.h
@@ -198,6 +198,11 @@ enum ContextLookupFlags {
V(NUMBER_FUNCTION_INDEX, JSFunction, number_function) \
V(OBJECT_FUNCTION_INDEX, JSFunction, object_function) \
V(OBJECT_FUNCTION_PROTOTYPE_MAP_INDEX, Map, object_function_prototype_map) \
+ V(PROMISE_HOOK_INIT_FUNCTION_INDEX, Object, promise_hook_init_function) \
+ V(PROMISE_HOOK_BEFORE_FUNCTION_INDEX, Object, promise_hook_before_function) \
+ V(PROMISE_HOOK_AFTER_FUNCTION_INDEX, Object, promise_hook_after_function) \
+ V(PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, Object, \
+ promise_hook_resolve_function) \
V(PROXY_CALLABLE_MAP_INDEX, Map, proxy_callable_map) \
V(PROXY_CONSTRUCTOR_MAP_INDEX, Map, proxy_constructor_map) \
V(PROXY_FUNCTION_INDEX, JSFunction, proxy_function) \
@@ -692,6 +697,9 @@ class NativeContext : public Context {
void IncrementErrorsThrown();
int GetErrorsThrown();
+ void RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
+ Handle<Object> parent);
+
private:
STATIC_ASSERT(OffsetOfElementAt(EMBEDDER_DATA_INDEX) ==
Internals::kNativeContextEmbedderDataOffset);
diff --git a/deps/v8/src/objects/contexts.tq b/deps/v8/src/objects/contexts.tq
index 604852c24e..ff427629ab 100644
--- a/deps/v8/src/objects/contexts.tq
+++ b/deps/v8/src/objects/contexts.tq
@@ -124,6 +124,12 @@ extern enum ContextSlot extends intptr constexpr 'Context::Field' {
PROMISE_PROTOTYPE_INDEX: Slot<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>,
diff --git a/deps/v8/src/objects/objects.cc b/deps/v8/src/objects/objects.cc
index d9cb7486be..697ffdec08 100644
--- a/deps/v8/src/objects/objects.cc
+++ b/deps/v8/src/objects/objects.cc
@@ -5254,8 +5254,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());
@@ -5290,8 +5290,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)) {
diff --git a/deps/v8/src/runtime/runtime-promise.cc b/deps/v8/src/runtime/runtime-promise.cc
index c1ee96facc..54adc4c920 100644
--- a/deps/v8/src/runtime/runtime-promise.cc
+++ b/deps/v8/src/runtime/runtime-promise.cc
@@ -29,8 +29,8 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) {
// undefined, which we interpret as being a caught exception event.
rejected_promise = isolate->GetPromiseOnStackOnThrow();
}
- isolate->RunPromiseHook(PromiseHookType::kResolve, promise,
- isolate->factory()->undefined_value());
+ isolate->RunAllPromiseHooks(PromiseHookType::kResolve, promise,
+ isolate->factory()->undefined_value());
isolate->debug()->OnPromiseReject(rejected_promise, value);
// Report only if we don't actually have a handler.
@@ -142,7 +142,7 @@ Handle<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);
}
diff --git a/deps/v8/test/cctest/test-code-stub-assembler.cc b/deps/v8/test/cctest/test-code-stub-assembler.cc
index 07e6c768f5..1ba26a81b8 100644
--- a/deps/v8/test/cctest/test-code-stub-assembler.cc
+++ b/deps/v8/test/cctest/test-code-stub-assembler.cc
@@ -2595,7 +2595,8 @@ TEST(IsPromiseHookEnabled) {
CodeStubAssembler m(asm_tester.state());
m.Return(
- m.SelectBooleanConstant(m.IsPromiseHookEnabledOrHasAsyncEventDelegate()));
+ m.SelectBooleanConstant(
+ m.IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate()));
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
Handle<Object> result =
diff --git a/deps/v8/test/mjsunit/promise-hooks.js b/deps/v8/test/mjsunit/promise-hooks.js
new file mode 100644
index 0000000000..9e13206a52
--- /dev/null
+++ b/deps/v8/test/mjsunit/promise-hooks.js
@@ -0,0 +1,244 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Flags: --allow-natives-syntax --opt --no-always-opt --no-stress-opt --deopt-every-n-times=0 --ignore-unhandled-promises
+
+let log = [];
+let asyncId = 0;
+
+function logEvent (type, args) {
+ const promise = args[0];
+ promise.asyncId = promise.asyncId || ++asyncId;
+ log.push({
+ type,
+ promise,
+ parent: args[1],
+ argsLength: args.length
+ })
+}
+function initHook(...args) {
+ logEvent('init', args);
+}
+function resolveHook(...args) {
+ logEvent('resolve', args);
+}
+function beforeHook(...args) {
+ logEvent('before', args);
+}
+function afterHook(...args) {
+ logEvent('after', args);
+}
+
+function printLog(message) {
+ console.log(` --- ${message} --- `)
+ for (const event of log) {
+ console.log(JSON.stringify(event))
+ }
+}
+
+function assertNextEvent(type, args) {
+ const [ promiseOrId, parentOrId ] = args;
+ const nextEvent = log.shift();
+
+ assertEquals(type, nextEvent.type);
+ assertEquals(type === 'init' ? 2 : 1, nextEvent.argsLength);
+
+ assertTrue(nextEvent.promise instanceof Promise);
+ if (promiseOrId instanceof Promise) {
+ assertEquals(promiseOrId, nextEvent.promise);
+ } else {
+ assertTrue(typeof promiseOrId === 'number');
+ assertEquals(promiseOrId, nextEvent.promise?.asyncId);
+ }
+
+ if (parentOrId instanceof Promise) {
+ assertEquals(parentOrId, nextEvent.parent);
+ assertTrue(nextEvent.parent instanceof Promise);
+ } else if (typeof parentOrId === 'number') {
+ assertEquals(parentOrId, nextEvent.parent?.asyncId);
+ assertTrue(nextEvent.parent instanceof Promise);
+ } else {
+ assertEquals(undefined, parentOrId);
+ assertEquals(undefined, nextEvent.parent);
+ }
+}
+function assertEmptyLog() {
+ assertEquals(0, log.length);
+ asyncId = 0;
+ log = [];
+}
+
+// Verify basic log structure of different promise behaviours
+function basicTest() {
+ d8.promise.setHooks(initHook, beforeHook, afterHook, resolveHook);
+
+ // `new Promise(...)` triggers init event with correct promise
+ var done, p1 = new Promise(r => done = r);
+ %PerformMicrotaskCheckpoint();
+ assertNextEvent('init', [ p1 ]);
+ assertEmptyLog();
+
+ // `promise.then(...)` triggers init event with correct promise and parent
+ var p2 = p1.then(() => { });
+ %PerformMicrotaskCheckpoint();
+ assertNextEvent('init', [ p2, p1 ]);
+ assertEmptyLog();
+
+ // `resolve(...)` triggers resolve event and any already attached continuations
+ done();
+ %PerformMicrotaskCheckpoint();
+ assertNextEvent('resolve', [ p1 ]);
+ assertNextEvent('before', [ p2 ]);
+ assertNextEvent('resolve', [ p2 ]);
+ assertNextEvent('after', [ p2 ]);
+ assertEmptyLog();
+
+ // `reject(...)` triggers the resolve event
+ var done, p3 = new Promise((_, r) => done = r);
+ done();
+ %PerformMicrotaskCheckpoint();
+ assertNextEvent('init', [ p3 ]);
+ assertNextEvent('resolve', [ p3 ]);
+ assertEmptyLog();
+
+ // `promise.catch(...)` triggers init event with correct promise and parent
+ // When the promise is already completed, the continuation should also run
+ // immediately at the next checkpoint.
+ var p4 = p3.catch(() => { });
+ %PerformMicrotaskCheckpoint();
+ assertNextEvent('init', [ p4, p3 ]);
+ assertNextEvent('before', [ p4 ]);
+ assertNextEvent('resolve', [ p4 ]);
+ assertNextEvent('after', [ p4 ]);
+ assertEmptyLog();
+
+ // Detach hooks
+ d8.promise.setHooks();
+}
+
+// Exceptions thrown in hook handlers should not raise or reject
+function exceptions() {
+ function thrower() {
+ throw new Error('unexpected!');
+ }
+
+ // Init hook
+ d8.promise.setHooks(thrower);
+ assertDoesNotThrow(() => {
+ Promise.resolve()
+ .catch(assertUnreachable);
+ });
+ %PerformMicrotaskCheckpoint();
+ d8.promise.setHooks();
+
+ // Before hook
+ d8.promise.setHooks(undefined, thrower);
+ assertDoesNotThrow(() => {
+ Promise.resolve()
+ .then(() => {})
+ .catch(assertUnreachable);
+ });
+ %PerformMicrotaskCheckpoint();
+ d8.promise.setHooks();
+
+ // After hook
+ d8.promise.setHooks(undefined, undefined, thrower);
+ assertDoesNotThrow(() => {
+ Promise.resolve()
+ .then(() => {})
+ .catch(assertUnreachable);
+ });
+ %PerformMicrotaskCheckpoint();
+ d8.promise.setHooks();
+
+ // Resolve hook
+ d8.promise.setHooks(undefined, undefined, undefined, thrower);
+ assertDoesNotThrow(() => {
+ Promise.resolve()
+ .catch(assertUnreachable);
+ });
+ %PerformMicrotaskCheckpoint();
+ d8.promise.setHooks();
+
+ // Resolve hook for a reject
+ d8.promise.setHooks(undefined, undefined, undefined, thrower);
+ assertDoesNotThrow(() => {
+ Promise.reject()
+ .then(assertUnreachable)
+ .catch();
+ });
+ %PerformMicrotaskCheckpoint();
+ d8.promise.setHooks();
+}
+
+// For now, expect the optimizer to bail out on async functions
+// when context promise hooks are attached.
+function optimizerBailout(test, verify) {
+ // Warm up test method
+ %PrepareFunctionForOptimization(test);
+ assertUnoptimized(test);
+ test();
+ test();
+ test();
+ %PerformMicrotaskCheckpoint();
+
+ // Prove transition to optimized code when no hooks are present
+ assertUnoptimized(test);
+ %OptimizeFunctionOnNextCall(test);
+ test();
+ assertOptimized(test);
+ %PerformMicrotaskCheckpoint();
+
+ // Verify that attaching hooks deopts the async function
+ d8.promise.setHooks(initHook, beforeHook, afterHook, resolveHook);
+ // assertUnoptimized(test);
+
+ // Verify log structure of deoptimized call
+ %PrepareFunctionForOptimization(test);
+ test();
+ %PerformMicrotaskCheckpoint();
+
+ verify();
+
+ // Optimize and verify log structure again
+ %OptimizeFunctionOnNextCall(test);
+ test();
+ assertOptimized(test);
+ %PerformMicrotaskCheckpoint();
+
+ verify();
+
+ d8.promise.setHooks();
+}
+
+optimizerBailout(async () => {
+ await Promise.resolve();
+}, () => {
+ assertNextEvent('init', [ 1 ]);
+ assertNextEvent('init', [ 2 ]);
+ assertNextEvent('resolve', [ 2 ]);
+ assertNextEvent('init', [ 3, 2 ]);
+ assertNextEvent('before', [ 3 ]);
+ assertNextEvent('resolve', [ 1 ]);
+ assertNextEvent('resolve', [ 3 ]);
+ assertNextEvent('after', [ 3 ]);
+ assertEmptyLog();
+});
+optimizerBailout(async () => {
+ await { then (cb) { cb() } };
+}, () => {
+ assertNextEvent('init', [ 1 ]);
+ assertNextEvent('init', [ 2, 1 ]);
+ assertNextEvent('init', [ 3, 2 ]);
+ assertNextEvent('before', [ 2 ]);
+ assertNextEvent('resolve', [ 2 ]);
+ assertNextEvent('after', [ 2 ]);
+ assertNextEvent('before', [ 3 ]);
+ assertNextEvent('resolve', [ 1 ]);
+ assertNextEvent('resolve', [ 3 ]);
+ assertNextEvent('after', [ 3 ]);
+ assertEmptyLog();
+});
+basicTest();
+exceptions();