Skip to content

Commit f56c785

Browse files
committed
deps: V8: cherry-pick c0fceaa0669b
Original commit message: Reland "[api] JSFunction PromiseHook for v8::Context" This is a reland of d5457f5fb7ea05ca05a697599ffa50d35c1ae3c7 after a speculative revert. Additionally it fixes an issue with throwing promise hooks. Original change's description: > [api] JSFunction PromiseHook for v8::Context > > This will enable Node.js to get much better performance from async_hooks > as currently PromiseHook delegates to C++ for the hook function and then > Node.js delegates it right back to JavaScript, introducing several > unnecessary barrier hops in code that gets called very, very frequently > in modern, promise-heavy applications. > > This API mirrors the form of the original C++ function based PromiseHook > API, however it is intentionally separate to allow it to use JSFunctions > triggered within generated code to, as much as possible, avoid entering > runtime functions entirely. > > Because PromiseHook has internal use also, beyond just the Node.js use, > I have opted to leave the existing API intact and keep this separate to > avoid conflicting with any possible behaviour expectations of other API > users. > > The design ideas for this new API stemmed from discussion with some V8 > team members at a previous Node.js Diagnostics Summit hosted by Google > in Munich, and the relevant documentation of the discussion can be found > here: https://docs.google.com/document/d/1g8OrG5lMIUhRn1zbkutgY83MiTSMx-0NHDs8Bf-nXxM/edit#heading=h.w1bavzz80l1e > > A summary of the reasons for why this new design is important can be > found here: https://docs.google.com/document/d/1vtgoT4_kjgOr-Bl605HR2T6_SC-C8uWzYaOPDK5pmRo/edit?usp=sharing > > Bug: v8:11025 > Change-Id: I0b403b00c37d3020b5af07b654b860659d3a7697 > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2759188 > Reviewed-by: Marja Hölttä <marja@chromium.org> > Reviewed-by: Camillo Bruni <cbruni@chromium.org> > Reviewed-by: Anton Bikineev <bikineev@chromium.org> > Reviewed-by: Igor Sheludko <ishell@chromium.org> > Commit-Queue: Camillo Bruni <cbruni@chromium.org> > Cr-Commit-Position: refs/heads/master@{#73858} Bug: v8:11025 Bug: chromium:1197475 Change-Id: I73a71e97d9c3dff89a2b092c3fe4adff81ede8ef Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2823917 Reviewed-by: Marja Hölttä <marja@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Reviewed-by: Anton Bikineev <bikineev@chromium.org> Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#74071} Refs: v8/v8@c0fceaa PR-URL: #38273 Backport-PR-URL: #38991 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Mary Marchini <oss@mmarchini.me>
1 parent 7197fce commit f56c785

32 files changed

+782
-131
lines changed

common.gypi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
# Reset this number to 0 on major V8 upgrades.
3838
# Increment by one for each non-official patch applied to deps/v8.
39-
'v8_embedder_string': '-node.8',
39+
'v8_embedder_string': '-node.9',
4040

4141
##### V8 defaults for Node.js #####
4242

deps/v8/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ Seo Sanghyeon <sanxiyn@gmail.com>
211211
Shawn Anastasio <shawnanastasio@gmail.com>
212212
Shawn Presser <shawnpresser@gmail.com>
213213
Stefan Penner <stefan.penner@gmail.com>
214+
Stephen Belanger <stephen.belanger@datadoghq.com>
214215
Sylvestre Ledru <sledru@mozilla.com>
215216
Taketoshi Aono <brn@b6n.ch>
216217
Tao Liqiang <taolq@outlook.com>

deps/v8/include/v8.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10585,6 +10585,18 @@ class V8_EXPORT Context : public Data {
1058510585
*/
1058610586
void SetContinuationPreservedEmbedderData(Local<Value> context);
1058710587

10588+
/**
10589+
* Set or clear hooks to be invoked for promise lifecycle operations.
10590+
* To clear a hook, set it to an empty v8::Function. Each function will
10591+
* receive the observed promise as the first argument. If a chaining
10592+
* operation is used on a promise, the init will additionally receive
10593+
* the parent promise as the second argument.
10594+
*/
10595+
void SetPromiseHooks(Local<Function> init_hook,
10596+
Local<Function> before_hook,
10597+
Local<Function> after_hook,
10598+
Local<Function> resolve_hook);
10599+
1058810600
/**
1058910601
* Stack-allocated class which sets the execution context for all
1059010602
* 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
@@ -6220,6 +6220,45 @@ void Context::SetContinuationPreservedEmbedderData(Local<Value> data) {
62206220
*i::Handle<i::HeapObject>::cast(Utils::OpenHandle(*data)));
62216221
}
62226222

6223+
void v8::Context::SetPromiseHooks(Local<Function> init_hook,
6224+
Local<Function> before_hook,
6225+
Local<Function> after_hook,
6226+
Local<Function> resolve_hook) {
6227+
i::Handle<i::Context> context = Utils::OpenHandle(this);
6228+
i::Isolate* isolate = context->GetIsolate();
6229+
6230+
i::Handle<i::Object> init = isolate->factory()->undefined_value();
6231+
i::Handle<i::Object> before = isolate->factory()->undefined_value();
6232+
i::Handle<i::Object> after = isolate->factory()->undefined_value();
6233+
i::Handle<i::Object> resolve = isolate->factory()->undefined_value();
6234+
6235+
bool has_hook = false;
6236+
6237+
if (!init_hook.IsEmpty()) {
6238+
init = Utils::OpenHandle(*init_hook);
6239+
has_hook = true;
6240+
}
6241+
if (!before_hook.IsEmpty()) {
6242+
before = Utils::OpenHandle(*before_hook);
6243+
has_hook = true;
6244+
}
6245+
if (!after_hook.IsEmpty()) {
6246+
after = Utils::OpenHandle(*after_hook);
6247+
has_hook = true;
6248+
}
6249+
if (!resolve_hook.IsEmpty()) {
6250+
resolve = Utils::OpenHandle(*resolve_hook);
6251+
has_hook = true;
6252+
}
6253+
6254+
isolate->SetHasContextPromiseHooks(has_hook);
6255+
6256+
context->native_context().set_promise_hook_init_function(*init);
6257+
context->native_context().set_promise_hook_before_function(*before);
6258+
context->native_context().set_promise_hook_after_function(*after);
6259+
context->native_context().set_promise_hook_resolve_function(*resolve);
6260+
}
6261+
62236262
MaybeLocal<Context> metrics::Recorder::GetContext(
62246263
Isolate* isolate, metrics::Recorder::ContextId id) {
62256264
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,14 @@ 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.
163165
Label if_instrumentation(this, Label::kDeferred),
164166
if_instrumentation_done(this);
165-
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
167+
Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
166168
&if_instrumentation, &if_instrumentation_done);
167169
BIND(&if_instrumentation);
168170
{

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

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,11 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOld(
9797

9898
TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
9999

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

113106
// Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
114107
CallBuiltin(Builtins::kResolvePromise, context, promise, value);
@@ -168,21 +161,46 @@ TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized(
168161

169162
TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
170163

164+
InitAwaitPromise(Runtime::kAwaitPromisesInit, context, promise, promise,
165+
outer_promise, on_reject, is_predicted_as_caught,
166+
&var_throwaway);
167+
168+
return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
169+
on_resolve, on_reject, var_throwaway.value());
170+
}
171+
172+
void AsyncBuiltinsAssembler::InitAwaitPromise(
173+
Runtime::FunctionId id, TNode<Context> context, TNode<Object> value,
174+
TNode<Object> promise, TNode<Object> outer_promise,
175+
TNode<HeapObject> on_reject, TNode<Oddball> is_predicted_as_caught,
176+
TVariable<HeapObject>* var_throwaway) {
171177
// Deal with PromiseHooks and debug support in the runtime. This
172178
// also allocates the throwaway promise, which is only needed in
173179
// case of PromiseHooks or debugging.
174-
Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this);
175-
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
176-
&if_debugging, &do_perform_promise_then);
180+
Label if_debugging(this, Label::kDeferred),
181+
if_promise_hook(this, Label::kDeferred),
182+
not_debugging(this),
183+
do_nothing(this);
184+
TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
185+
Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
186+
promiseHookFlags), &if_debugging, &not_debugging);
177187
BIND(&if_debugging);
178-
var_throwaway =
179-
CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise,
188+
*var_throwaway =
189+
CAST(CallRuntime(id, context, value, promise,
180190
outer_promise, on_reject, is_predicted_as_caught));
181-
Goto(&do_perform_promise_then);
182-
BIND(&do_perform_promise_then);
183-
184-
return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
185-
on_resolve, on_reject, var_throwaway.value());
191+
Goto(&do_nothing);
192+
BIND(&not_debugging);
193+
194+
// This call to NewJSPromise is to keep behaviour parity with what happens
195+
// in Runtime::kAwaitPromisesInit above if native hooks are set. It will
196+
// create a throwaway promise that will trigger an init event and will get
197+
// passed into Builtins::kPerformPromiseThen below.
198+
Branch(IsContextPromiseHookEnabled(promiseHookFlags), &if_promise_hook,
199+
&do_nothing);
200+
BIND(&if_promise_hook);
201+
*var_throwaway = NewJSPromise(context, promise);
202+
Goto(&do_nothing);
203+
BIND(&do_nothing);
186204
}
187205

188206
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-async-generator-gen.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ TF_BUILTIN(AsyncGeneratorResolve, AsyncGeneratorBuiltinsAssembler) {
518518
// the "promiseResolve" hook would not be fired otherwise.
519519
Label if_fast(this), if_slow(this, Label::kDeferred), return_promise(this);
520520
GotoIfForceSlowPath(&if_slow);
521-
GotoIf(IsPromiseHookEnabled(), &if_slow);
521+
GotoIf(IsIsolatePromiseHookEnabledOrHasAsyncEventDelegate(), &if_slow);
522522
Branch(IsPromiseThenProtectorCellInvalid(), &if_slow, &if_fast);
523523

524524
BIND(&if_fast);

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@ 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,
50-
TNode<HeapObject> promise_or_capability);
52+
TNode<HeapObject> promise_or_capability,
53+
TNode<Uint32T> promiseHookFlags);
5154
};
5255

5356
TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueue(
@@ -199,7 +202,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
199202
const TNode<Object> thenable = LoadObjectField(
200203
microtask, PromiseResolveThenableJobTask::kThenableOffset);
201204

202-
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
205+
RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
203206
CAST(promise_to_resolve));
204207

205208
{
@@ -208,7 +211,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
208211
promise_to_resolve, thenable, then);
209212
}
210213

211-
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
214+
RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
212215
CAST(promise_to_resolve));
213216

214217
RewindEnteredContext(saved_entered_context_count);
@@ -243,8 +246,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
243246
BIND(&preserved_data_done);
244247

245248
// Run the promise before/debug hook if enabled.
246-
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
247-
promise_or_capability);
249+
RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
250+
promise_or_capability);
248251

249252
{
250253
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
@@ -253,8 +256,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
253256
}
254257

255258
// Run the promise after/debug hook if enabled.
256-
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
257-
promise_or_capability);
259+
RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
260+
promise_or_capability);
258261

259262
Label preserved_data_reset_done(this);
260263
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
@@ -296,8 +299,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
296299
BIND(&preserved_data_done);
297300

298301
// Run the promise before/debug hook if enabled.
299-
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
300-
promise_or_capability);
302+
RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
303+
promise_or_capability);
301304

302305
{
303306
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
@@ -306,8 +309,8 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
306309
}
307310

308311
// Run the promise after/debug hook if enabled.
309-
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
310-
promise_or_capability);
312+
RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
313+
promise_or_capability);
311314

312315
Label preserved_data_reset_done(this);
313316
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
@@ -465,12 +468,43 @@ void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext(
465468
saved_entered_context_count);
466469
}
467470

471+
void MicrotaskQueueBuiltinsAssembler::RunAllPromiseHooks(
472+
PromiseHookType type, TNode<Context> context,
473+
TNode<HeapObject> promise_or_capability) {
474+
Label hook(this, Label::kDeferred), done_hook(this);
475+
TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
476+
Branch(IsAnyPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
477+
promiseHookFlags), &hook, &done_hook);
478+
BIND(&hook);
479+
{
480+
switch (type) {
481+
case PromiseHookType::kBefore:
482+
RunContextPromiseHookBefore(context, promise_or_capability,
483+
promiseHookFlags);
484+
RunPromiseHook(Runtime::kPromiseHookBefore, context,
485+
promise_or_capability, promiseHookFlags);
486+
break;
487+
case PromiseHookType::kAfter:
488+
RunContextPromiseHookAfter(context, promise_or_capability,
489+
promiseHookFlags);
490+
RunPromiseHook(Runtime::kPromiseHookAfter, context,
491+
promise_or_capability, promiseHookFlags);
492+
break;
493+
default:
494+
UNREACHABLE();
495+
}
496+
Goto(&done_hook);
497+
}
498+
BIND(&done_hook);
499+
}
500+
468501
void MicrotaskQueueBuiltinsAssembler::RunPromiseHook(
469502
Runtime::FunctionId id, TNode<Context> context,
470-
TNode<HeapObject> promise_or_capability) {
503+
TNode<HeapObject> promise_or_capability,
504+
TNode<Uint32T> promiseHookFlags) {
471505
Label hook(this, Label::kDeferred), done_hook(this);
472-
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), &hook,
473-
&done_hook);
506+
Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
507+
promiseHookFlags), &hook, &done_hook);
474508
BIND(&hook);
475509
{
476510
// Get to the underlying JSPromise instance.

deps/v8/src/builtins/cast.tq

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ Cast<Undefined|Callable>(o: HeapObject): Undefined|Callable
386386
return HeapObjectToCallable(o) otherwise CastError;
387387
}
388388

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

0 commit comments

Comments
 (0)