Skip to content

Commit

Permalink
v8: add Context PromiseHook API
Browse files Browse the repository at this point in the history
  • Loading branch information
Qard committed Feb 28, 2021
1 parent d1e4d34 commit 6cdc996
Show file tree
Hide file tree
Showing 26 changed files with 631 additions and 45 deletions.
1 change: 1 addition & 0 deletions deps/v8/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ Seo Sanghyeon <sanxiyn@gmail.com>
Shawn Anastasio <shawnanastasio@gmail.com>
Shawn Presser <shawnpresser@gmail.com>
Stefan Penner <stefan.penner@gmail.com>
Stephen Belanger <admin@stephenbelanger.com>
Sylvestre Ledru <sledru@mozilla.com>
Taketoshi Aono <brn@b6n.ch>
Teddy Katz <teddy.katz@gmail.com>
Expand Down
13 changes: 13 additions & 0 deletions deps/v8/include/v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -10524,6 +10524,19 @@ class V8_EXPORT Context {
*/
void SetContinuationPreservedEmbedderData(Local<Value> context);

/**
* Set or clear hooks to be invoked for promise lifecycle operations.
* The individual hooks can be either undefined (to disable the
* hooks) or some Function (to get notified). 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.
Expand Down
39 changes: 39 additions & 0 deletions deps/v8/src/api/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6196,6 +6196,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->SetContextPromiseHooks(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);
Expand Down
2 changes: 2 additions & 0 deletions deps/v8/src/builtins/builtins-async-function-gen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ 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.
Expand Down
62 changes: 41 additions & 21 deletions deps/v8/src/builtins/builtins-async-gen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -170,21 +163,48 @@ 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) {
const TNode<NativeContext> native_context = LoadNativeContext(context);

// 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);
Label if_debugging(this, Label::kDeferred),
if_promise_hook(this, Label::kDeferred),
not_debugging(this),
do_nothing(this);
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_debugging, &do_perform_promise_then);
&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.
const TNode<Object> promise_hook_obj = LoadContextElement(
native_context, Context::PROMISE_HOOK_INIT_FUNCTION_INDEX);
Branch(IsUndefined(promise_hook_obj), &do_nothing, &if_promise_hook);
BIND(&if_promise_hook);
*var_throwaway = NewJSPromise(context, promise);
Goto(&do_nothing);
BIND(&do_nothing);
}

TNode<Object> AsyncBuiltinsAssembler::Await(
Expand Down
6 changes: 6 additions & 0 deletions deps/v8/src/builtins/builtins-async-gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 38 additions & 10 deletions deps/v8/src/builtins/builtins-microtask-queue-gen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ 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);
};
Expand Down Expand Up @@ -198,7 +200,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));

{
Expand All @@ -207,7 +209,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);
Expand Down Expand Up @@ -242,8 +244,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);
Expand All @@ -252,8 +254,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);
Expand Down Expand Up @@ -295,8 +297,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);
Expand All @@ -305,8 +307,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);
Expand Down Expand Up @@ -464,6 +466,32 @@ 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);
Branch(IsAnyPromiseHookEnabled(), &hook, &done_hook);
BIND(&hook);
{
switch (type) {
case PromiseHookType::kBefore:
RunContextPromiseHookBefore(context, promise_or_capability);
RunPromiseHook(Runtime::kPromiseHookBefore, context,
promise_or_capability);
break;
case PromiseHookType::kAfter:
RunContextPromiseHookAfter(context, promise_or_capability);
RunPromiseHook(Runtime::kPromiseHookAfter, context,
promise_or_capability);
break;
default:
UNREACHABLE();
}
Goto(&done_hook);
}
BIND(&done_hook);
}

void MicrotaskQueueBuiltinsAssembler::RunPromiseHook(
Runtime::FunctionId id, TNode<Context> context,
TNode<HeapObject> promise_or_capability) {
Expand Down
6 changes: 6 additions & 0 deletions deps/v8/src/builtins/cast.tq
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,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;
Expand Down
4 changes: 4 additions & 0 deletions deps/v8/src/builtins/promise-abstract-operations.tq
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -233,6 +235,8 @@ RejectPromise(implicit context: Context)(
return runtime::RejectPromise(promise, reason, debugEvent);
}

RunContextPromiseHookResolve(promise);

// 2. Let reactions be promise.[[PromiseRejectReactions]].
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
Expand Down
2 changes: 2 additions & 0 deletions deps/v8/src/builtins/promise-constructor.tq
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ PromiseConstructor(
result = UnsafeCast<JSPromise>(
FastNewObject(context, promiseFun, UnsafeCast<JSReceiver>(newTarget)));
PromiseInit(result);
RunContextPromiseHookInit(result, Undefined);

if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
runtime::PromiseHookInit(result, Undefined);
}
Expand Down
Loading

0 comments on commit 6cdc996

Please sign in to comment.