Skip to content

Commit a840f1f

Browse files
bmeurerCommit Bot
authored andcommitted
[async-await] Eliminate throwaway promise in async functions.
The ES2017 specification contains a so-called "throwaway" promise that is used to specify the behavior of await in terms of PerformPromiseThen, but it's actually not necessary and never exposed to user code. In addition to that, hooking up the promise in await required a context (to refer to the generator object) and two closures for the reject/fulfill handling, which would resume the generator corresponding to the async function. That meant, we had to allocate 4 additional objects for every await. Instead of using a JSPromise plus the callbacks, this CL adds logic to allow PromiseReaction and PromiseReactionJobTask to carry arbitrary payloads and Code handlers. We use this for await to avoid the additional 4 objects mentioned above, and instead just have simple Code handlers that resume the generator (for the async function), either by throwing (in case of a rejection) or by resuming normally (in case of fulfillment). For this to work properly the JSGeneratorObject has to have a link to the outer promise returned by the async function, so that the catch prediction can still figure out what to do in case of promise rejection. This is done by adding a new generator_outer_promise_symbol when the debugger is active, which refers from the generator to the outer promise. With this change the doxbee-async-es2017-native test goes from around 100.54ms to around 82.45ms, which corresponds to a ~18% reduction in execution time. Bug: v8:7253 Change-Id: Iae25b3300bac351c3417be5ae687eff469b0e61f Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng Reviewed-on: https://chromium-review.googlesource.com/924069 Reviewed-by: Yang Guo <[email protected]> Reviewed-by: Sathya Gunasekaran <[email protected]> Commit-Queue: Benedikt Meurer <[email protected]> Cr-Commit-Position: refs/heads/master@{#51334}
1 parent bcbdcea commit a840f1f

18 files changed

Lines changed: 276 additions & 258 deletions

src/bootstrapper.cc

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4164,20 +4164,6 @@ void Bootstrapper::ExportFromRuntime(Isolate* isolate,
41644164
native_context->set_async_function_await_uncaught(*function);
41654165
}
41664166

4167-
{
4168-
Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo(
4169-
isolate, Builtins::kAsyncFunctionAwaitRejectClosure,
4170-
factory->empty_string(), 1);
4171-
native_context->set_async_function_await_reject_shared_fun(*info);
4172-
}
4173-
4174-
{
4175-
Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo(
4176-
isolate, Builtins::kAsyncFunctionAwaitResolveClosure,
4177-
factory->empty_string(), 1);
4178-
native_context->set_async_function_await_resolve_shared_fun(*info);
4179-
}
4180-
41814167
{
41824168
Handle<JSFunction> function =
41834169
SimpleCreateFunction(isolate, factory->empty_string(),

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

Lines changed: 53 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -21,37 +21,18 @@ class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler {
2121
Node* const awaited, Node* const outer_promise,
2222
const bool is_predicted_as_caught);
2323

24-
void AsyncFunctionAwaitResumeClosure(
25-
Node* const context, Node* const sent_value,
26-
JSGeneratorObject::ResumeMode resume_mode);
24+
void AsyncFunctionAwaitResume(Node* const context, Node* const argument,
25+
Node* const generator,
26+
JSGeneratorObject::ResumeMode resume_mode);
2727
};
2828

29-
namespace {
30-
31-
// Describe fields of Context associated with AsyncFunctionAwait resume
32-
// closures.
33-
// TODO(jgruber): Refactor to reuse code for upcoming async-generators.
34-
class AwaitContext {
35-
public:
36-
enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength };
37-
};
38-
39-
} // anonymous namespace
40-
41-
void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
42-
Node* context, Node* sent_value,
29+
void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResume(
30+
Node* const context, Node* const argument, Node* const generator,
4331
JSGeneratorObject::ResumeMode resume_mode) {
32+
CSA_ASSERT(this, IsJSGeneratorObject(generator));
4433
DCHECK(resume_mode == JSGeneratorObject::kNext ||
4534
resume_mode == JSGeneratorObject::kThrow);
4635

47-
Node* const generator =
48-
LoadContextElement(context, AwaitContext::kGeneratorSlot);
49-
CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
50-
51-
// Inline version of GeneratorPrototypeNext / GeneratorPrototypeReturn with
52-
// unnecessary runtime checks removed.
53-
// TODO(jgruber): Refactor to reuse code from builtins-generator.cc.
54-
5536
// Ensure that the generator is neither closed nor running.
5637
CSA_SLOW_ASSERT(
5738
this,
@@ -66,30 +47,29 @@ void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
6647

6748
// Resume the {receiver} using our trampoline.
6849
Callable callable = CodeFactory::ResumeGenerator(isolate());
69-
CallStub(callable, context, sent_value, generator);
50+
CallStub(callable, context, argument, generator);
7051

7152
// The resulting Promise is a throwaway, so it doesn't matter what it
7253
// resolves to. What is important is that we don't end up keeping the
7354
// whole chain of intermediate Promises alive by returning the return value
7455
// of ResumeGenerator, as that would create a memory leak.
7556
}
7657

77-
TF_BUILTIN(AsyncFunctionAwaitRejectClosure, AsyncFunctionBuiltinsAssembler) {
78-
CSA_ASSERT_JS_ARGC_EQ(this, 1);
79-
Node* const sentError = Parameter(Descriptor::kSentError);
58+
TF_BUILTIN(AsyncFunctionAwaitFulfill, AsyncFunctionBuiltinsAssembler) {
59+
Node* const argument = Parameter(Descriptor::kArgument);
60+
Node* const generator = Parameter(Descriptor::kGenerator);
8061
Node* const context = Parameter(Descriptor::kContext);
81-
82-
AsyncFunctionAwaitResumeClosure(context, sentError,
83-
JSGeneratorObject::kThrow);
62+
AsyncFunctionAwaitResume(context, argument, generator,
63+
JSGeneratorObject::kNext);
8464
Return(UndefinedConstant());
8565
}
8666

87-
TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) {
88-
CSA_ASSERT_JS_ARGC_EQ(this, 1);
89-
Node* const sentValue = Parameter(Descriptor::kSentValue);
67+
TF_BUILTIN(AsyncFunctionAwaitReject, AsyncFunctionBuiltinsAssembler) {
68+
Node* const argument = Parameter(Descriptor::kArgument);
69+
Node* const generator = Parameter(Descriptor::kGenerator);
9070
Node* const context = Parameter(Descriptor::kContext);
91-
92-
AsyncFunctionAwaitResumeClosure(context, sentValue, JSGeneratorObject::kNext);
71+
AsyncFunctionAwaitResume(context, argument, generator,
72+
JSGeneratorObject::kThrow);
9373
Return(UndefinedConstant());
9474
}
9575

@@ -105,25 +85,42 @@ TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) {
10585
void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
10686
Node* const context, Node* const generator, Node* const awaited,
10787
Node* const outer_promise, const bool is_predicted_as_caught) {
108-
CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
109-
CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE));
110-
111-
ContextInitializer init_closure_context = [&](Node* context) {
112-
StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
113-
generator);
114-
};
115-
116-
// TODO(jgruber): AsyncBuiltinsAssembler::Await currently does not reuse
117-
// the awaited promise if it is already a promise. Reuse is non-spec compliant
118-
// but part of our old behavior gives us a couple of percent
119-
// performance boost.
120-
// TODO(jgruber): Use a faster specialized version of
121-
// InternalPerformPromiseThen.
122-
123-
Await(context, generator, awaited, outer_promise, AwaitContext::kLength,
124-
init_closure_context, Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
125-
Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN,
126-
is_predicted_as_caught);
88+
CSA_SLOW_ASSERT(this, IsJSGeneratorObject(generator));
89+
CSA_SLOW_ASSERT(this, IsJSPromise(outer_promise));
90+
91+
Node* const native_context = LoadNativeContext(context);
92+
Node* const promise = AllocateAndInitJSPromise(native_context);
93+
94+
Node* const promise_reactions =
95+
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
96+
Node* const fulfill_handler = HeapConstant(
97+
Builtins::CallableFor(isolate(), Builtins::kAsyncFunctionAwaitFulfill)
98+
.code());
99+
Node* const reject_handler = HeapConstant(
100+
Builtins::CallableFor(isolate(), Builtins::kAsyncFunctionAwaitReject)
101+
.code());
102+
Node* const reaction = AllocatePromiseReaction(
103+
promise_reactions, generator, fulfill_handler, reject_handler);
104+
StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction);
105+
PromiseSetHasHandler(promise);
106+
107+
// Perform ! Call(promiseCapability.[[Resolve]], undefined, « value »).
108+
CallBuiltin(Builtins::kResolvePromise, native_context, promise, awaited);
109+
110+
{
111+
Label done(this);
112+
GotoIfNot(IsDebugActive(), &done);
113+
CallRuntime(Runtime::kSetProperty, native_context, generator,
114+
LoadRoot(Heap::kgenerator_outer_promise_symbolRootIndex),
115+
outer_promise, SmiConstant(LanguageMode::kStrict));
116+
if (is_predicted_as_caught) {
117+
GotoIf(TaggedIsSmi(awaited), &done);
118+
GotoIfNot(IsJSPromise(awaited), &done);
119+
PromiseSetHandledHint(awaited);
120+
}
121+
Goto(&done);
122+
BIND(&done);
123+
}
127124

128125
// Return outer promise to avoid adding an load of the outer promise before
129126
// suspending in BytecodeGenerator.

src/builtins/builtins-definitions.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,10 @@ namespace internal {
365365
CPP(ArrayBufferPrototypeSlice) \
366366
\
367367
/* AsyncFunction */ \
368+
TFC(AsyncFunctionAwaitFulfill, PromiseReactionHandler, 1) \
369+
TFC(AsyncFunctionAwaitReject, PromiseReactionHandler, 1) \
368370
TFJ(AsyncFunctionAwaitCaught, 3, kGenerator, kAwaited, kOuterPromise) \
369371
TFJ(AsyncFunctionAwaitUncaught, 3, kGenerator, kAwaited, kOuterPromise) \
370-
TFJ(AsyncFunctionAwaitRejectClosure, 1, kSentError) \
371-
TFJ(AsyncFunctionAwaitResolveClosure, 1, kSentValue) \
372372
TFJ(AsyncFunctionPromiseCreate, 0) \
373373
TFJ(AsyncFunctionPromiseRelease, 1, kPromise) \
374374
\
@@ -820,8 +820,8 @@ namespace internal {
820820
/* ES #sec-promise.prototype.catch */ \
821821
TFJ(PromisePrototypeCatch, 1, kOnRejected) \
822822
/* ES #sec-promisereactionjob */ \
823-
TFS(PromiseRejectReactionJob, kReason, kHandler, kPromiseOrCapability) \
824-
TFS(PromiseFulfillReactionJob, kValue, kHandler, kPromiseOrCapability) \
823+
TFS(PromiseRejectReactionJob, kReason, kHandler, kPayload) \
824+
TFS(PromiseFulfillReactionJob, kValue, kHandler, kPayload) \
825825
/* ES #sec-promiseresolvethenablejob */ \
826826
TFS(PromiseResolveThenableJob, kPromiseToResolve, kThenable, kThen) \
827827
/* ES #sec-promise.resolve */ \

src/builtins/builtins-internal-gen.cc

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ class InternalBuiltinsAssembler : public CodeStubAssembler {
629629
void LeaveMicrotaskContext();
630630

631631
void RunPromiseHook(Runtime::FunctionId id, TNode<Context> context,
632-
SloppyTNode<HeapObject> promise_or_capability);
632+
SloppyTNode<HeapObject> payload);
633633

634634
TNode<Object> GetPendingException() {
635635
auto ref = ExternalReference(kPendingExceptionAddress, isolate());
@@ -750,21 +750,12 @@ void InternalBuiltinsAssembler::LeaveMicrotaskContext() {
750750

751751
void InternalBuiltinsAssembler::RunPromiseHook(
752752
Runtime::FunctionId id, TNode<Context> context,
753-
SloppyTNode<HeapObject> promise_or_capability) {
753+
SloppyTNode<HeapObject> payload) {
754754
Label hook(this, Label::kDeferred), done_hook(this);
755755
Branch(IsPromiseHookEnabledOrDebugIsActive(), &hook, &done_hook);
756756
BIND(&hook);
757757
{
758-
// Get to the underlying JSPromise instance.
759-
Node* const promise =
760-
Select(IsJSPromise(promise_or_capability),
761-
[=] { return promise_or_capability; },
762-
[=] {
763-
return LoadObjectField(promise_or_capability,
764-
PromiseCapability::kPromiseOffset);
765-
},
766-
MachineRepresentation::kTagged);
767-
CallRuntime(id, context, promise);
758+
CallRuntime(id, context, payload);
768759
Goto(&done_hook);
769760
}
770761
BIND(&done_hook);
@@ -979,21 +970,19 @@ TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
979970
LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
980971
Node* const handler =
981972
LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset);
982-
Node* const promise_or_capability = LoadObjectField(
983-
microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset);
973+
Node* const payload =
974+
LoadObjectField(microtask, PromiseReactionJobTask::kPayloadOffset);
984975

985976
// Run the promise before/debug hook if enabled.
986-
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
987-
promise_or_capability);
977+
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, payload);
988978

989979
Node* const result =
990980
CallBuiltin(Builtins::kPromiseFulfillReactionJob, microtask_context,
991-
argument, handler, promise_or_capability);
981+
argument, handler, payload);
992982
GotoIfException(result, &if_exception, &var_exception);
993983

994984
// Run the promise after/debug hook if enabled.
995-
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
996-
promise_or_capability);
985+
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, payload);
997986

998987
LeaveMicrotaskContext();
999988
SetCurrentContext(current_context);
@@ -1015,21 +1004,19 @@ TF_BUILTIN(RunMicrotasks, InternalBuiltinsAssembler) {
10151004
LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
10161005
Node* const handler =
10171006
LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset);
1018-
Node* const promise_or_capability = LoadObjectField(
1019-
microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset);
1007+
Node* const payload =
1008+
LoadObjectField(microtask, PromiseReactionJobTask::kPayloadOffset);
10201009

10211010
// Run the promise before/debug hook if enabled.
1022-
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
1023-
promise_or_capability);
1011+
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, payload);
10241012

10251013
Node* const result =
10261014
CallBuiltin(Builtins::kPromiseRejectReactionJob, microtask_context,
1027-
argument, handler, promise_or_capability);
1015+
argument, handler, payload);
10281016
GotoIfException(result, &if_exception, &var_exception);
10291017

10301018
// Run the promise after/debug hook if enabled.
1031-
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
1032-
promise_or_capability);
1019+
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, payload);
10331020

10341021
LeaveMicrotaskContext();
10351022
SetCurrentContext(current_context);

0 commit comments

Comments
 (0)