Skip to content

Commit 6cdc996

Browse files
committed
v8: add Context PromiseHook API
1 parent d1e4d34 commit 6cdc996

26 files changed

Lines changed: 631 additions & 45 deletions

deps/v8/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ Seo Sanghyeon <sanxiyn@gmail.com>
197197
Shawn Anastasio <shawnanastasio@gmail.com>
198198
Shawn Presser <shawnpresser@gmail.com>
199199
Stefan Penner <stefan.penner@gmail.com>
200+
Stephen Belanger <admin@stephenbelanger.com>
200201
Sylvestre Ledru <sledru@mozilla.com>
201202
Taketoshi Aono <brn@b6n.ch>
202203
Teddy Katz <teddy.katz@gmail.com>

deps/v8/include/v8.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10524,6 +10524,19 @@ 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<Function> init_hook,
10536+
Local<Function> before_hook,
10537+
Local<Function> after_hook,
10538+
Local<Function> resolve_hook);
10539+
1052710540
/**
1052810541
* Stack-allocated class which sets the execution context for all
1052910542
* operations executed within a local scope.

deps/v8/src/api/api.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6196,6 +6196,45 @@ void Context::SetContinuationPreservedEmbedderData(Local<Value> data) {
61966196
*i::Handle<i::HeapObject>::cast(Utils::OpenHandle(*data)));
61976197
}
61986198

6199+
void v8::Context::SetPromiseHooks(Local<Function> init_hook,
6200+
Local<Function> before_hook,
6201+
Local<Function> after_hook,
6202+
Local<Function> resolve_hook) {
6203+
i::Handle<i::Context> context = Utils::OpenHandle(this);
6204+
i::Isolate* isolate = context->GetIsolate();
6205+
6206+
i::Handle<i::Object> init = isolate->factory()->undefined_value();
6207+
i::Handle<i::Object> before = isolate->factory()->undefined_value();
6208+
i::Handle<i::Object> after = isolate->factory()->undefined_value();
6209+
i::Handle<i::Object> resolve = isolate->factory()->undefined_value();
6210+
6211+
bool has_hook = false;
6212+
6213+
if (!init_hook.IsEmpty()) {
6214+
init = Utils::OpenHandle(*init_hook);
6215+
has_hook = true;
6216+
}
6217+
if (!before_hook.IsEmpty()) {
6218+
before = Utils::OpenHandle(*before_hook);
6219+
has_hook = true;
6220+
}
6221+
if (!after_hook.IsEmpty()) {
6222+
after = Utils::OpenHandle(*after_hook);
6223+
has_hook = true;
6224+
}
6225+
if (!resolve_hook.IsEmpty()) {
6226+
resolve = Utils::OpenHandle(*resolve_hook);
6227+
has_hook = true;
6228+
}
6229+
6230+
isolate->SetContextPromiseHooks(has_hook);
6231+
6232+
context->native_context().set_promise_hook_init_function(*init);
6233+
context->native_context().set_promise_hook_before_function(*before);
6234+
context->native_context().set_promise_hook_after_function(*after);
6235+
context->native_context().set_promise_hook_resolve_function(*resolve);
6236+
}
6237+
61996238
MaybeLocal<Context> metrics::Recorder::GetContext(
62006239
Isolate* isolate, metrics::Recorder::ContextId id) {
62016240
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);

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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,11 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOld(
9999

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

102-
// Deal with PromiseHooks and debug support in the runtime. This
103-
// also allocates the throwaway promise, which is only needed in
104-
// case of PromiseHooks or debugging.
105-
Label if_debugging(this, Label::kDeferred), do_resolve_promise(this);
106-
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
107-
&if_debugging, &do_resolve_promise);
108-
BIND(&if_debugging);
109-
var_throwaway =
110-
CAST(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value, promise,
111-
outer_promise, on_reject, is_predicted_as_caught));
112-
Goto(&do_resolve_promise);
113-
BIND(&do_resolve_promise);
102+
RunContextPromiseHookInit(context, promise, outer_promise);
103+
104+
InitAwaitPromise(Runtime::kAwaitPromisesInitOld, context, value, promise,
105+
outer_promise, on_reject, is_predicted_as_caught,
106+
&var_throwaway);
114107

115108
// Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
116109
CallBuiltin(Builtins::kResolvePromise, context, promise, value);
@@ -170,21 +163,48 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized(
170163

171164
TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
172165

166+
InitAwaitPromise(Runtime::kAwaitPromisesInit, context, promise, promise,
167+
outer_promise, on_reject, is_predicted_as_caught,
168+
&var_throwaway);
169+
170+
return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
171+
on_resolve, on_reject, var_throwaway.value());
172+
}
173+
174+
void AsyncBuiltinsAssembler::InitAwaitPromise(
175+
Runtime::FunctionId id, TNode<Context> context, TNode<Object> value,
176+
TNode<Object> promise, TNode<Object> outer_promise,
177+
TNode<HeapObject> on_reject, TNode<Oddball> is_predicted_as_caught,
178+
TVariable<HeapObject>* var_throwaway) {
179+
const TNode<NativeContext> native_context = LoadNativeContext(context);
180+
173181
// Deal with PromiseHooks and debug support in the runtime. This
174182
// also allocates the throwaway promise, which is only needed in
175183
// case of PromiseHooks or debugging.
176-
Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this);
184+
Label if_debugging(this, Label::kDeferred),
185+
if_promise_hook(this, Label::kDeferred),
186+
not_debugging(this),
187+
do_nothing(this);
177188
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
178-
&if_debugging, &do_perform_promise_then);
189+
&if_debugging, &not_debugging);
179190
BIND(&if_debugging);
180-
var_throwaway =
181-
CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise,
191+
*var_throwaway =
192+
CAST(CallRuntime(id, context, value, promise,
182193
outer_promise, on_reject, is_predicted_as_caught));
183-
Goto(&do_perform_promise_then);
184-
BIND(&do_perform_promise_then);
185-
186-
return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
187-
on_resolve, on_reject, var_throwaway.value());
194+
Goto(&do_nothing);
195+
BIND(&not_debugging);
196+
197+
// This call to NewJSPromise is to keep behaviour parity with what happens
198+
// in Runtime::kAwaitPromisesInit above if native hooks are set. It will
199+
// create a throwaway promise that will trigger an init event and will get
200+
// passed into Builtins::kPerformPromiseThen below.
201+
const TNode<Object> promise_hook_obj = LoadContextElement(
202+
native_context, Context::PROMISE_HOOK_INIT_FUNCTION_INDEX);
203+
Branch(IsUndefined(promise_hook_obj), &do_nothing, &if_promise_hook);
204+
BIND(&if_promise_hook);
205+
*var_throwaway = NewJSPromise(context, promise);
206+
Goto(&do_nothing);
207+
BIND(&do_nothing);
188208
}
189209

190210
TNode<Object> AsyncBuiltinsAssembler::Await(

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ class AsyncBuiltinsAssembler : public PromiseBuiltinsAssembler {
6262
TNode<SharedFunctionInfo> on_resolve_sfi,
6363
TNode<SharedFunctionInfo> on_reject_sfi,
6464
TNode<Oddball> is_predicted_as_caught);
65+
66+
void InitAwaitPromise(
67+
Runtime::FunctionId id, TNode<Context> context, TNode<Object> value,
68+
TNode<Object> promise, TNode<Object> outer_promise,
69+
TNode<HeapObject> on_reject, TNode<Oddball> is_predicted_as_caught,
70+
TVariable<HeapObject>* var_throwaway);
6571
};
6672

6773
} // namespace internal

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

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler {
4646
void EnterMicrotaskContext(TNode<Context> native_context);
4747
void RewindEnteredContext(TNode<IntPtrT> saved_entered_context_count);
4848

49+
void RunAllPromiseHooks(PromiseHookType type, TNode<Context> context,
50+
TNode<HeapObject> promise_or_capability);
4951
void RunPromiseHook(Runtime::FunctionId id, TNode<Context> context,
5052
TNode<HeapObject> promise_or_capability);
5153
};
@@ -198,7 +200,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
198200
const TNode<Object> thenable = LoadObjectField(
199201
microtask, PromiseResolveThenableJobTask::kThenableOffset);
200202

201-
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
203+
RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
202204
CAST(promise_to_resolve));
203205

204206
{
@@ -207,7 +209,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
207209
promise_to_resolve, thenable, then);
208210
}
209211

210-
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
212+
RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
211213
CAST(promise_to_resolve));
212214

213215
RewindEnteredContext(saved_entered_context_count);
@@ -242,8 +244,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
242244
BIND(&preserved_data_done);
243245

244246
// Run the promise before/debug hook if enabled.
245-
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
246-
promise_or_capability);
247+
RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
248+
promise_or_capability);
247249

248250
{
249251
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
@@ -252,8 +254,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
252254
}
253255

254256
// Run the promise after/debug hook if enabled.
255-
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
256-
promise_or_capability);
257+
RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
258+
promise_or_capability);
257259

258260
Label preserved_data_reset_done(this);
259261
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
@@ -295,8 +297,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
295297
BIND(&preserved_data_done);
296298

297299
// Run the promise before/debug hook if enabled.
298-
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
299-
promise_or_capability);
300+
RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
301+
promise_or_capability);
300302

301303
{
302304
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
@@ -305,8 +307,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
305307
}
306308

307309
// Run the promise after/debug hook if enabled.
308-
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
309-
promise_or_capability);
310+
RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
311+
promise_or_capability);
310312

311313
Label preserved_data_reset_done(this);
312314
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
@@ -464,6 +466,32 @@ void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext(
464466
saved_entered_context_count);
465467
}
466468

469+
void MicrotaskQueueBuiltinsAssembler::RunAllPromiseHooks(
470+
PromiseHookType type, TNode<Context> context,
471+
TNode<HeapObject> promise_or_capability) {
472+
Label hook(this, Label::kDeferred), done_hook(this);
473+
Branch(IsAnyPromiseHookEnabled(), &hook, &done_hook);
474+
BIND(&hook);
475+
{
476+
switch (type) {
477+
case PromiseHookType::kBefore:
478+
RunContextPromiseHookBefore(context, promise_or_capability);
479+
RunPromiseHook(Runtime::kPromiseHookBefore, context,
480+
promise_or_capability);
481+
break;
482+
case PromiseHookType::kAfter:
483+
RunContextPromiseHookAfter(context, promise_or_capability);
484+
RunPromiseHook(Runtime::kPromiseHookAfter, context,
485+
promise_or_capability);
486+
break;
487+
default:
488+
UNREACHABLE();
489+
}
490+
Goto(&done_hook);
491+
}
492+
BIND(&done_hook);
493+
}
494+
467495
void MicrotaskQueueBuiltinsAssembler::RunPromiseHook(
468496
Runtime::FunctionId id, TNode<Context> context,
469497
TNode<HeapObject> promise_or_capability) {

deps/v8/src/builtins/cast.tq

Lines changed: 6 additions & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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
}

0 commit comments

Comments
 (0)