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 Dec 4, 2020
1 parent d1e4d34 commit 0eb6853
Show file tree
Hide file tree
Showing 20 changed files with 409 additions and 18 deletions.
11 changes: 11 additions & 0 deletions deps/v8/include/v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -10524,6 +10524,17 @@ 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<Value> init_hook, Local<Value> before_hook,
Local<Value> after_hook, Local<Value> resolve_hook);

/**
* Stack-allocated class which sets the execution context for all
* operations executed within a local scope.
Expand Down
32 changes: 32 additions & 0 deletions deps/v8/src/api/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6196,6 +6196,38 @@ void Context::SetContinuationPreservedEmbedderData(Local<Value> data) {
*i::Handle<i::HeapObject>::cast(Utils::OpenHandle(*data)));
}

void v8::Context::SetPromiseHooks(Local<Value> init_hook,
Local<Value> before_hook,
Local<Value> after_hook,
Local<Value> resolve_hook) {
i::Handle<i::Context> context = Utils::OpenHandle(this);
i::Isolate* isolate = context->GetIsolate();
isolate->UpdatePromiseHookProtector();

i::Handle<i::Object> init = Utils::OpenHandle(*init_hook);
i::Handle<i::Object> before = Utils::OpenHandle(*before_hook);
i::Handle<i::Object> after = Utils::OpenHandle(*after_hook);
i::Handle<i::Object> resolve = Utils::OpenHandle(*resolve_hook);

Utils::ApiCheck(init->IsJSFunction() || init->IsUndefined(),
"v8::Context::SetPromiseHooks",
"Init Promise hook must be a Function or undefined");
Utils::ApiCheck(before->IsJSFunction() || before->IsUndefined(),
"v8::Context::SetPromiseHooks",
"Before Promise hook must be a Function or undefined");
Utils::ApiCheck(after->IsJSFunction() || after->IsUndefined(),
"v8::Context::SetPromiseHooks",
"After Promise hook must be a Function or undefined");
Utils::ApiCheck(resolve->IsJSFunction() || resolve->IsUndefined(),
"v8::Context::SetPromiseHooks",
"Resolve Promise hook must be a Function or undefined");

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
33 changes: 29 additions & 4 deletions deps/v8/src/builtins/builtins-async-gen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,30 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOld(

TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());

RunContextPromiseHookInit(context, promise, outer_promise);

// 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);
Label if_debugging(this, Label::kDeferred),
if_promise_hook(this, Label::kDeferred),
not_debugging(this),
do_resolve_promise(this);
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_debugging, &do_resolve_promise);
&if_debugging, &not_debugging);
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(&not_debugging);

const TNode<Object> promise_hook_obj = LoadContextElement(
native_context, Context::PROMISE_HOOK_INIT_FUNCTION_INDEX);
Branch(IsUndefined(promise_hook_obj), &do_resolve_promise, &if_promise_hook);
BIND(&if_promise_hook);
var_throwaway = NewJSPromise(context, promise);
Goto(&do_resolve_promise);
BIND(&do_resolve_promise);

// Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
Expand Down Expand Up @@ -173,14 +186,26 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized(
// 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_perform_promise_then(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,
outer_promise, on_reject, is_predicted_as_caught));
Goto(&do_perform_promise_then);
BIND(&not_debugging);

const TNode<Object> promise_hook_obj = LoadContextElement(
native_context, Context::PROMISE_HOOK_INIT_FUNCTION_INDEX);
Branch(IsUndefined(promise_hook_obj), &do_perform_promise_then,
&if_promise_hook);
BIND(&if_promise_hook);
var_throwaway = NewJSPromise(context, promise);
Goto(&do_perform_promise_then);
BIND(&do_perform_promise_then);

return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
Expand Down
6 changes: 6 additions & 0 deletions deps/v8/src/builtins/builtins-microtask-queue-gen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
const TNode<Object> thenable = LoadObjectField(
microtask, PromiseResolveThenableJobTask::kThenableOffset);

RunContextPromiseHookBefore(microtask_context, promise_to_resolve);
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
CAST(promise_to_resolve));

Expand All @@ -207,6 +208,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
promise_to_resolve, thenable, then);
}

RunContextPromiseHookAfter(microtask_context, promise_to_resolve);
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
CAST(promise_to_resolve));

Expand Down Expand Up @@ -242,6 +244,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
BIND(&preserved_data_done);

// Run the promise before/debug hook if enabled.
RunContextPromiseHookBefore(microtask_context, promise_or_capability);
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
promise_or_capability);

Expand All @@ -252,6 +255,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
}

// Run the promise after/debug hook if enabled.
RunContextPromiseHookAfter(microtask_context, promise_or_capability);
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
promise_or_capability);

Expand Down Expand Up @@ -295,6 +299,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
BIND(&preserved_data_done);

// Run the promise before/debug hook if enabled.
RunContextPromiseHookBefore(microtask_context, promise_or_capability);
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
promise_or_capability);

Expand All @@ -305,6 +310,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
}

// Run the promise after/debug hook if enabled.
RunContextPromiseHookAfter(microtask_context, promise_or_capability);
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
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
56 changes: 55 additions & 1 deletion deps/v8/src/builtins/promise-misc.tq
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,59 @@ 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 (_err) {
}
}

@export
transitioning macro RunContextPromiseHookResolve(implicit context: Context)(
maybePromise: Object) {
RunContextPromiseHook(
ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, maybePromise);
}

@export
transitioning macro RunContextPromiseHookBefore(implicit context: Context)(
maybePromise: Object) {
RunContextPromiseHook(
ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, maybePromise);
}

@export
transitioning macro RunContextPromiseHookAfter(implicit context: Context)(
maybePromise: Object) {
RunContextPromiseHook(
ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, maybePromise);
}

transitioning macro RunContextPromiseHook(implicit context: Context)(
slot: Slot<NativeContext, Undefined|JSFunction>, maybePromise: Object) {
const maybeHook = *NativeContextSlot(slot);
if (IsUndefined(maybeHook)) return;
if (!Is<JSPromise>(maybePromise)) return;

const hook = Cast<JSFunction>(maybeHook) otherwise unreachable;
const promise = Cast<JSPromise>(maybePromise) otherwise unreachable;

try {
Call(context, hook, Undefined, promise);
} catch (_err) {
}
}

// These allocate and initialize a promise with pending state and
// undefined fields.
//
Expand All @@ -100,6 +153,7 @@ transitioning macro NewJSPromise(implicit context: Context)(parent: Object):
JSPromise {
const instance = InnerNewJSPromise();
PromiseInit(instance);
RunContextPromiseHookInit(instance, parent);
if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
runtime::PromiseHookInit(instance, parent);
}
Expand All @@ -124,7 +178,7 @@ transitioning macro NewJSPromise(implicit context: Context)(
instance.reactions_or_result = result;
instance.SetStatus(status);
promise_internal::ZeroOutEmbedderOffsets(instance);

RunContextPromiseHookInit(instance, Undefined);
if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
runtime::PromiseHookInit(instance, Undefined);
}
Expand Down
18 changes: 18 additions & 0 deletions deps/v8/src/d8/d8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,16 @@ 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], args[1], args[2], args[3]);

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());
Expand Down Expand Up @@ -2265,6 +2275,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;
}

Expand Down
2 changes: 2 additions & 0 deletions deps/v8/src/d8/d8.h
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,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);
Expand Down
20 changes: 16 additions & 4 deletions deps/v8/src/execution/isolate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4074,15 +4074,20 @@ void Isolate::FireCallCompletedCallback(MicrotaskQueue* microtask_queue) {
}
}

void Isolate::UpdatePromiseHookProtector() {
if (Protectors::IsPromiseHookIntact(this)) {
HandleScope scope(this);
Protectors::InvalidatePromiseHook(this);
}
}

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)) {
HandleScope scope(this);
Protectors::InvalidatePromiseHook(this);
if (promise_hook_or_debug_is_active_or_async_event_delegate) {
UpdatePromiseHookProtector();
}
promise_hook_or_async_event_delegate_ = promise_hook_or_async_event_delegate;
promise_hook_or_debug_is_active_or_async_event_delegate_ =
Expand Down Expand Up @@ -4272,6 +4277,13 @@ void Isolate::SetPromiseHook(PromiseHook hook) {
PromiseHookStateUpdated();
}

void Isolate::RunAllPromiseHooks(PromiseHookType type,
Handle<JSPromise> promise,
Handle<Object> parent) {
native_context()->RunPromiseHook(type, promise, parent);
RunPromiseHook(type, promise, parent);
}

void Isolate::RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
Handle<Object> parent) {
RunPromiseHookForAsyncEventDelegate(type, promise);
Expand Down
3 changes: 3 additions & 0 deletions deps/v8/src/execution/isolate.h
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,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);
Expand Down
3 changes: 2 additions & 1 deletion deps/v8/src/heap/factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3598,7 +3598,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;
}

Expand Down
Loading

0 comments on commit 0eb6853

Please sign in to comment.