Skip to content

Commit 0eb6853

Browse files
committed
v8: add Context PromiseHook API
1 parent d1e4d34 commit 0eb6853

20 files changed

+409
-18
lines changed

deps/v8/include/v8.h

+11
Original file line numberDiff line numberDiff line change
@@ -10524,6 +10524,17 @@ class V8_EXPORT Context {
1052410524
*/
1052510525
void SetContinuationPreservedEmbedderData(Local<Value> context);
1052610526

10527+
/**
10528+
* Set or clear hooks to be invoked for promise lifecycle operations.
10529+
* The individual hooks can be either undefined (to disable the
10530+
* hooks) or some Function (to get notified). Each function will receive
10531+
* the observed promise as the first argument. If a chaining operation
10532+
* is used on a promise, the init will additionally receive the parent
10533+
* promise as the second argument.
10534+
*/
10535+
void SetPromiseHooks(Local<Value> init_hook, Local<Value> before_hook,
10536+
Local<Value> after_hook, Local<Value> resolve_hook);
10537+
1052710538
/**
1052810539
* Stack-allocated class which sets the execution context for all
1052910540
* operations executed within a local scope.

deps/v8/src/api/api.cc

+32
Original file line numberDiff line numberDiff line change
@@ -6196,6 +6196,38 @@ void Context::SetContinuationPreservedEmbedderData(Local<Value> data) {
61966196
*i::Handle<i::HeapObject>::cast(Utils::OpenHandle(*data)));
61976197
}
61986198

6199+
void v8::Context::SetPromiseHooks(Local<Value> init_hook,
6200+
Local<Value> before_hook,
6201+
Local<Value> after_hook,
6202+
Local<Value> resolve_hook) {
6203+
i::Handle<i::Context> context = Utils::OpenHandle(this);
6204+
i::Isolate* isolate = context->GetIsolate();
6205+
isolate->UpdatePromiseHookProtector();
6206+
6207+
i::Handle<i::Object> init = Utils::OpenHandle(*init_hook);
6208+
i::Handle<i::Object> before = Utils::OpenHandle(*before_hook);
6209+
i::Handle<i::Object> after = Utils::OpenHandle(*after_hook);
6210+
i::Handle<i::Object> resolve = Utils::OpenHandle(*resolve_hook);
6211+
6212+
Utils::ApiCheck(init->IsJSFunction() || init->IsUndefined(),
6213+
"v8::Context::SetPromiseHooks",
6214+
"Init Promise hook must be a Function or undefined");
6215+
Utils::ApiCheck(before->IsJSFunction() || before->IsUndefined(),
6216+
"v8::Context::SetPromiseHooks",
6217+
"Before Promise hook must be a Function or undefined");
6218+
Utils::ApiCheck(after->IsJSFunction() || after->IsUndefined(),
6219+
"v8::Context::SetPromiseHooks",
6220+
"After Promise hook must be a Function or undefined");
6221+
Utils::ApiCheck(resolve->IsJSFunction() || resolve->IsUndefined(),
6222+
"v8::Context::SetPromiseHooks",
6223+
"Resolve Promise hook must be a Function or undefined");
6224+
6225+
context->native_context().set_promise_hook_init_function(*init);
6226+
context->native_context().set_promise_hook_before_function(*before);
6227+
context->native_context().set_promise_hook_after_function(*after);
6228+
context->native_context().set_promise_hook_resolve_function(*resolve);
6229+
}
6230+
61996231
MaybeLocal<Context> metrics::Recorder::GetContext(
62006232
Isolate* isolate, metrics::Recorder::ContextId id) {
62016233
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);

deps/v8/src/builtins/builtins-async-function-gen.cc

+2
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ TF_BUILTIN(AsyncFunctionEnter, AsyncFunctionBuiltinsAssembler) {
157157
StoreObjectFieldNoWriteBarrier(
158158
async_function_object, JSAsyncFunctionObject::kPromiseOffset, promise);
159159

160+
RunContextPromiseHookInit(context, promise, UndefinedConstant());
161+
160162
// Fire promise hooks if enabled and push the Promise under construction
161163
// in an async function on the catch prediction stack to handle exceptions
162164
// thrown before the first await.

deps/v8/src/builtins/builtins-async-gen.cc

+29-4
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,30 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOld(
9999

100100
TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
101101

102+
RunContextPromiseHookInit(context, promise, outer_promise);
103+
102104
// Deal with PromiseHooks and debug support in the runtime. This
103105
// also allocates the throwaway promise, which is only needed in
104106
// case of PromiseHooks or debugging.
105-
Label if_debugging(this, Label::kDeferred), do_resolve_promise(this);
107+
Label if_debugging(this, Label::kDeferred),
108+
if_promise_hook(this, Label::kDeferred),
109+
not_debugging(this),
110+
do_resolve_promise(this);
106111
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
107-
&if_debugging, &do_resolve_promise);
112+
&if_debugging, &not_debugging);
108113
BIND(&if_debugging);
109114
var_throwaway =
110115
CAST(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value, promise,
111116
outer_promise, on_reject, is_predicted_as_caught));
112117
Goto(&do_resolve_promise);
118+
BIND(&not_debugging);
119+
120+
const TNode<Object> promise_hook_obj = LoadContextElement(
121+
native_context, Context::PROMISE_HOOK_INIT_FUNCTION_INDEX);
122+
Branch(IsUndefined(promise_hook_obj), &do_resolve_promise, &if_promise_hook);
123+
BIND(&if_promise_hook);
124+
var_throwaway = NewJSPromise(context, promise);
125+
Goto(&do_resolve_promise);
113126
BIND(&do_resolve_promise);
114127

115128
// Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
@@ -173,14 +186,26 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized(
173186
// Deal with PromiseHooks and debug support in the runtime. This
174187
// also allocates the throwaway promise, which is only needed in
175188
// case of PromiseHooks or debugging.
176-
Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this);
189+
Label if_debugging(this, Label::kDeferred),
190+
if_promise_hook(this, Label::kDeferred),
191+
not_debugging(this),
192+
do_perform_promise_then(this);
177193
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
178-
&if_debugging, &do_perform_promise_then);
194+
&if_debugging, &not_debugging);
179195
BIND(&if_debugging);
180196
var_throwaway =
181197
CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise,
182198
outer_promise, on_reject, is_predicted_as_caught));
183199
Goto(&do_perform_promise_then);
200+
BIND(&not_debugging);
201+
202+
const TNode<Object> promise_hook_obj = LoadContextElement(
203+
native_context, Context::PROMISE_HOOK_INIT_FUNCTION_INDEX);
204+
Branch(IsUndefined(promise_hook_obj), &do_perform_promise_then,
205+
&if_promise_hook);
206+
BIND(&if_promise_hook);
207+
var_throwaway = NewJSPromise(context, promise);
208+
Goto(&do_perform_promise_then);
184209
BIND(&do_perform_promise_then);
185210

186211
return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,

deps/v8/src/builtins/builtins-microtask-queue-gen.cc

+6
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
198198
const TNode<Object> thenable = LoadObjectField(
199199
microtask, PromiseResolveThenableJobTask::kThenableOffset);
200200

201+
RunContextPromiseHookBefore(microtask_context, promise_to_resolve);
201202
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
202203
CAST(promise_to_resolve));
203204

@@ -207,6 +208,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
207208
promise_to_resolve, thenable, then);
208209
}
209210

211+
RunContextPromiseHookAfter(microtask_context, promise_to_resolve);
210212
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
211213
CAST(promise_to_resolve));
212214

@@ -242,6 +244,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
242244
BIND(&preserved_data_done);
243245

244246
// Run the promise before/debug hook if enabled.
247+
RunContextPromiseHookBefore(microtask_context, promise_or_capability);
245248
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
246249
promise_or_capability);
247250

@@ -252,6 +255,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
252255
}
253256

254257
// Run the promise after/debug hook if enabled.
258+
RunContextPromiseHookAfter(microtask_context, promise_or_capability);
255259
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
256260
promise_or_capability);
257261

@@ -295,6 +299,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
295299
BIND(&preserved_data_done);
296300

297301
// Run the promise before/debug hook if enabled.
302+
RunContextPromiseHookBefore(microtask_context, promise_or_capability);
298303
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
299304
promise_or_capability);
300305

@@ -305,6 +310,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
305310
}
306311

307312
// Run the promise after/debug hook if enabled.
313+
RunContextPromiseHookAfter(microtask_context, promise_or_capability);
308314
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
309315
promise_or_capability);
310316

deps/v8/src/builtins/cast.tq

+6
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,12 @@ Cast<Undefined|Callable>(o: HeapObject): Undefined|Callable
385385
return HeapObjectToCallable(o) otherwise CastError;
386386
}
387387

388+
Cast<Undefined|JSFunction>(o: HeapObject): Undefined|JSFunction
389+
labels CastError {
390+
if (o == Undefined) return Undefined;
391+
return Cast<JSFunction>(o) otherwise CastError;
392+
}
393+
388394
macro Cast<T : type extends Symbol>(o: Symbol): T labels CastError;
389395
Cast<PublicSymbol>(s: Symbol): PublicSymbol labels CastError {
390396
if (s.flags.is_private) goto CastError;

deps/v8/src/builtins/promise-abstract-operations.tq

+4
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ FulfillPromise(implicit context: Context)(
196196
// Assert: The value of promise.[[PromiseState]] is "pending".
197197
assert(promise.Status() == PromiseState::kPending);
198198

199+
RunContextPromiseHookResolve(promise);
200+
199201
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
200202
const reactions =
201203
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
@@ -233,6 +235,8 @@ RejectPromise(implicit context: Context)(
233235
return runtime::RejectPromise(promise, reason, debugEvent);
234236
}
235237

238+
RunContextPromiseHookResolve(promise);
239+
236240
// 2. Let reactions be promise.[[PromiseRejectReactions]].
237241
const reactions =
238242
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);

deps/v8/src/builtins/promise-constructor.tq

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ PromiseConstructor(
7373
result = UnsafeCast<JSPromise>(
7474
FastNewObject(context, promiseFun, UnsafeCast<JSReceiver>(newTarget)));
7575
PromiseInit(result);
76+
RunContextPromiseHookInit(result, Undefined);
77+
7678
if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
7779
runtime::PromiseHookInit(result, Undefined);
7880
}

deps/v8/src/builtins/promise-misc.tq

+55-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,59 @@ macro NewPromiseRejectReactionJobTask(implicit context: Context)(
9090
};
9191
}
9292

93+
@export
94+
transitioning macro RunContextPromiseHookInit(implicit context: Context)(
95+
promise: JSPromise, parent: Object) {
96+
const maybeHook = *NativeContextSlot(
97+
ContextSlot::PROMISE_HOOK_INIT_FUNCTION_INDEX);
98+
if (IsUndefined(maybeHook)) return;
99+
100+
const hook = Cast<JSFunction>(maybeHook) otherwise unreachable;
101+
const parentObject = Is<JSPromise>(parent) ? Cast<JSPromise>(parent)
102+
otherwise unreachable: Undefined;
103+
104+
try {
105+
Call(context, hook, Undefined, promise, parentObject);
106+
} catch (_err) {
107+
}
108+
}
109+
110+
@export
111+
transitioning macro RunContextPromiseHookResolve(implicit context: Context)(
112+
maybePromise: Object) {
113+
RunContextPromiseHook(
114+
ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, maybePromise);
115+
}
116+
117+
@export
118+
transitioning macro RunContextPromiseHookBefore(implicit context: Context)(
119+
maybePromise: Object) {
120+
RunContextPromiseHook(
121+
ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, maybePromise);
122+
}
123+
124+
@export
125+
transitioning macro RunContextPromiseHookAfter(implicit context: Context)(
126+
maybePromise: Object) {
127+
RunContextPromiseHook(
128+
ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, maybePromise);
129+
}
130+
131+
transitioning macro RunContextPromiseHook(implicit context: Context)(
132+
slot: Slot<NativeContext, Undefined|JSFunction>, maybePromise: Object) {
133+
const maybeHook = *NativeContextSlot(slot);
134+
if (IsUndefined(maybeHook)) return;
135+
if (!Is<JSPromise>(maybePromise)) return;
136+
137+
const hook = Cast<JSFunction>(maybeHook) otherwise unreachable;
138+
const promise = Cast<JSPromise>(maybePromise) otherwise unreachable;
139+
140+
try {
141+
Call(context, hook, Undefined, promise);
142+
} catch (_err) {
143+
}
144+
}
145+
93146
// These allocate and initialize a promise with pending state and
94147
// undefined fields.
95148
//
@@ -100,6 +153,7 @@ transitioning macro NewJSPromise(implicit context: Context)(parent: Object):
100153
JSPromise {
101154
const instance = InnerNewJSPromise();
102155
PromiseInit(instance);
156+
RunContextPromiseHookInit(instance, parent);
103157
if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
104158
runtime::PromiseHookInit(instance, parent);
105159
}
@@ -124,7 +178,7 @@ transitioning macro NewJSPromise(implicit context: Context)(
124178
instance.reactions_or_result = result;
125179
instance.SetStatus(status);
126180
promise_internal::ZeroOutEmbedderOffsets(instance);
127-
181+
RunContextPromiseHookInit(instance, Undefined);
128182
if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
129183
runtime::PromiseHookInit(instance, Undefined);
130184
}

deps/v8/src/d8/d8.cc

+18
Original file line numberDiff line numberDiff line change
@@ -1572,6 +1572,16 @@ void Shell::AsyncHooksTriggerAsyncId(
15721572
PerIsolateData::Get(isolate)->GetAsyncHooks()->GetTriggerAsyncId()));
15731573
}
15741574

1575+
void Shell::SetPromiseHooks(const v8::FunctionCallbackInfo<v8::Value>& args) {
1576+
Isolate* isolate = args.GetIsolate();
1577+
Local<Context> context = isolate->GetCurrentContext();
1578+
HandleScope handle_scope(isolate);
1579+
1580+
context->SetPromiseHooks(args[0], args[1], args[2], args[3]);
1581+
1582+
args.GetReturnValue().Set(v8::Undefined(isolate));
1583+
}
1584+
15751585
void WriteToFile(FILE* file, const v8::FunctionCallbackInfo<v8::Value>& args) {
15761586
for (int i = 0; i < args.Length(); i++) {
15771587
HandleScope handle_scope(args.GetIsolate());
@@ -2265,6 +2275,14 @@ Local<ObjectTemplate> Shell::CreateD8Template(Isolate* isolate) {
22652275

22662276
d8_template->Set(isolate, "log", log_template);
22672277
}
2278+
{
2279+
Local<ObjectTemplate> promise_template = ObjectTemplate::New(isolate);
2280+
promise_template->Set(
2281+
isolate, "setHooks",
2282+
FunctionTemplate::New(isolate, SetPromiseHooks, Local<Value>(),
2283+
Local<Signature>(), 4));
2284+
d8_template->Set(isolate, "promise", promise_template);
2285+
}
22682286
return d8_template;
22692287
}
22702288

deps/v8/src/d8/d8.h

+2
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,8 @@ class Shell : public i::AllStatic {
462462
static void AsyncHooksTriggerAsyncId(
463463
const v8::FunctionCallbackInfo<v8::Value>& args);
464464

465+
static void SetPromiseHooks(const v8::FunctionCallbackInfo<v8::Value>& args);
466+
465467
static void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
466468
static void PrintErr(const v8::FunctionCallbackInfo<v8::Value>& args);
467469
static void Write(const v8::FunctionCallbackInfo<v8::Value>& args);

deps/v8/src/execution/isolate.cc

+16-4
Original file line numberDiff line numberDiff line change
@@ -4074,15 +4074,20 @@ void Isolate::FireCallCompletedCallback(MicrotaskQueue* microtask_queue) {
40744074
}
40754075
}
40764076

4077+
void Isolate::UpdatePromiseHookProtector() {
4078+
if (Protectors::IsPromiseHookIntact(this)) {
4079+
HandleScope scope(this);
4080+
Protectors::InvalidatePromiseHook(this);
4081+
}
4082+
}
4083+
40774084
void Isolate::PromiseHookStateUpdated() {
40784085
bool promise_hook_or_async_event_delegate =
40794086
promise_hook_ || async_event_delegate_;
40804087
bool promise_hook_or_debug_is_active_or_async_event_delegate =
40814088
promise_hook_or_async_event_delegate || debug()->is_active();
4082-
if (promise_hook_or_debug_is_active_or_async_event_delegate &&
4083-
Protectors::IsPromiseHookIntact(this)) {
4084-
HandleScope scope(this);
4085-
Protectors::InvalidatePromiseHook(this);
4089+
if (promise_hook_or_debug_is_active_or_async_event_delegate) {
4090+
UpdatePromiseHookProtector();
40864091
}
40874092
promise_hook_or_async_event_delegate_ = promise_hook_or_async_event_delegate;
40884093
promise_hook_or_debug_is_active_or_async_event_delegate_ =
@@ -4272,6 +4277,13 @@ void Isolate::SetPromiseHook(PromiseHook hook) {
42724277
PromiseHookStateUpdated();
42734278
}
42744279

4280+
void Isolate::RunAllPromiseHooks(PromiseHookType type,
4281+
Handle<JSPromise> promise,
4282+
Handle<Object> parent) {
4283+
native_context()->RunPromiseHook(type, promise, parent);
4284+
RunPromiseHook(type, promise, parent);
4285+
}
4286+
42754287
void Isolate::RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
42764288
Handle<Object> parent) {
42774289
RunPromiseHookForAsyncEventDelegate(type, promise);

deps/v8/src/execution/isolate.h

+3
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
13661366
void SetPromiseHook(PromiseHook hook);
13671367
void RunPromiseHook(PromiseHookType type, Handle<JSPromise> promise,
13681368
Handle<Object> parent);
1369+
void RunAllPromiseHooks(PromiseHookType type, Handle<JSPromise> promise,
1370+
Handle<Object> parent);
1371+
void UpdatePromiseHookProtector();
13691372
void PromiseHookStateUpdated();
13701373

13711374
void AddDetachedContext(Handle<Context> context);

deps/v8/src/heap/factory.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -3598,7 +3598,8 @@ Handle<JSPromise> Factory::NewJSPromiseWithoutHook() {
35983598

35993599
Handle<JSPromise> Factory::NewJSPromise() {
36003600
Handle<JSPromise> promise = NewJSPromiseWithoutHook();
3601-
isolate()->RunPromiseHook(PromiseHookType::kInit, promise, undefined_value());
3601+
isolate()->RunAllPromiseHooks(PromiseHookType::kInit, promise,
3602+
undefined_value());
36023603
return promise;
36033604
}
36043605

0 commit comments

Comments
 (0)