1 // Copyright 2017 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/builtins/builtins-async-gen.h"
6 #include "src/builtins/builtins-utils-gen.h"
7 #include "src/builtins/builtins.h"
8 #include "src/code-stub-assembler.h"
9 #include "src/objects-inl.h"
10 #include "src/objects/js-generator.h"
11
12 namespace v8 {
13 namespace internal {
14
15 class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler {
16 public:
AsyncFunctionBuiltinsAssembler(compiler::CodeAssemblerState * state)17 explicit AsyncFunctionBuiltinsAssembler(compiler::CodeAssemblerState* state)
18 : AsyncBuiltinsAssembler(state) {}
19
20 protected:
21 void AsyncFunctionAwait(Node* const context, Node* const generator,
22 Node* const awaited, Node* const outer_promise,
23 const bool is_predicted_as_caught);
24 void AsyncFunctionAwaitOptimized(Node* const context, Node* const generator,
25 Node* const awaited,
26 Node* const outer_promise,
27 const bool is_predicted_as_caught);
28
29 void AsyncFunctionAwaitResumeClosure(
30 Node* const context, Node* const sent_value,
31 JSGeneratorObject::ResumeMode resume_mode);
32 };
33
34 namespace {
35
36 // Describe fields of Context associated with AsyncFunctionAwait resume
37 // closures.
38 // TODO(jgruber): Refactor to reuse code for upcoming async-generators.
39 class AwaitContext {
40 public:
41 enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength };
42 };
43
44 } // anonymous namespace
45
AsyncFunctionAwaitResumeClosure(Node * context,Node * sent_value,JSGeneratorObject::ResumeMode resume_mode)46 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
47 Node* context, Node* sent_value,
48 JSGeneratorObject::ResumeMode resume_mode) {
49 DCHECK(resume_mode == JSGeneratorObject::kNext ||
50 resume_mode == JSGeneratorObject::kThrow);
51
52 Node* const generator =
53 LoadContextElement(context, AwaitContext::kGeneratorSlot);
54 CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
55
56 // Inline version of GeneratorPrototypeNext / GeneratorPrototypeReturn with
57 // unnecessary runtime checks removed.
58 // TODO(jgruber): Refactor to reuse code from builtins-generator.cc.
59
60 // Ensure that the generator is neither closed nor running.
61 CSA_SLOW_ASSERT(
62 this,
63 SmiGreaterThan(CAST(LoadObjectField(
64 generator, JSGeneratorObject::kContinuationOffset)),
65 SmiConstant(JSGeneratorObject::kGeneratorClosed)));
66
67 // Remember the {resume_mode} for the {generator}.
68 StoreObjectFieldNoWriteBarrier(generator,
69 JSGeneratorObject::kResumeModeOffset,
70 SmiConstant(resume_mode));
71
72 // Resume the {receiver} using our trampoline.
73 Callable callable = CodeFactory::ResumeGenerator(isolate());
74 CallStub(callable, context, sent_value, generator);
75
76 // The resulting Promise is a throwaway, so it doesn't matter what it
77 // resolves to. What is important is that we don't end up keeping the
78 // whole chain of intermediate Promises alive by returning the return value
79 // of ResumeGenerator, as that would create a memory leak.
80 }
81
TF_BUILTIN(AsyncFunctionAwaitRejectClosure,AsyncFunctionBuiltinsAssembler)82 TF_BUILTIN(AsyncFunctionAwaitRejectClosure, AsyncFunctionBuiltinsAssembler) {
83 CSA_ASSERT_JS_ARGC_EQ(this, 1);
84 Node* const sentError = Parameter(Descriptor::kSentError);
85 Node* const context = Parameter(Descriptor::kContext);
86
87 AsyncFunctionAwaitResumeClosure(context, sentError,
88 JSGeneratorObject::kThrow);
89 Return(UndefinedConstant());
90 }
91
TF_BUILTIN(AsyncFunctionAwaitResolveClosure,AsyncFunctionBuiltinsAssembler)92 TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) {
93 CSA_ASSERT_JS_ARGC_EQ(this, 1);
94 Node* const sentValue = Parameter(Descriptor::kSentValue);
95 Node* const context = Parameter(Descriptor::kContext);
96
97 AsyncFunctionAwaitResumeClosure(context, sentValue, JSGeneratorObject::kNext);
98 Return(UndefinedConstant());
99 }
100
101 // ES#abstract-ops-async-function-await
102 // AsyncFunctionAwait ( value )
103 // Shared logic for the core of await. The parser desugars
104 // await awaited
105 // into
106 // yield AsyncFunctionAwait{Caught,Uncaught}(.generator, awaited, .promise)
107 // The 'awaited' parameter is the value; the generator stands in
108 // for the asyncContext, and .promise is the larger promise under
109 // construction by the enclosing async function.
AsyncFunctionAwait(Node * const context,Node * const generator,Node * const awaited,Node * const outer_promise,const bool is_predicted_as_caught)110 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
111 Node* const context, Node* const generator, Node* const awaited,
112 Node* const outer_promise, const bool is_predicted_as_caught) {
113 CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
114 CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE));
115
116 ContextInitializer init_closure_context = [&](Node* context) {
117 StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
118 generator);
119 };
120
121 // TODO(jgruber): AsyncBuiltinsAssembler::Await currently does not reuse
122 // the awaited promise if it is already a promise. Reuse is non-spec compliant
123 // but part of our old behavior gives us a couple of percent
124 // performance boost.
125 // TODO(jgruber): Use a faster specialized version of
126 // InternalPerformPromiseThen.
127
128 Label after_debug_hook(this), call_debug_hook(this, Label::kDeferred);
129 GotoIf(HasAsyncEventDelegate(), &call_debug_hook);
130 Goto(&after_debug_hook);
131 BIND(&after_debug_hook);
132
133 Await(context, generator, awaited, outer_promise, AwaitContext::kLength,
134 init_closure_context, Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
135 Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN,
136 is_predicted_as_caught);
137
138 // Return outer promise to avoid adding an load of the outer promise before
139 // suspending in BytecodeGenerator.
140 Return(outer_promise);
141
142 BIND(&call_debug_hook);
143 CallRuntime(Runtime::kDebugAsyncFunctionSuspended, context, outer_promise);
144 Goto(&after_debug_hook);
145 }
146
AsyncFunctionAwaitOptimized(Node * const context,Node * const generator,Node * const awaited,Node * const outer_promise,const bool is_predicted_as_caught)147 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitOptimized(
148 Node* const context, Node* const generator, Node* const awaited,
149 Node* const outer_promise, const bool is_predicted_as_caught) {
150 CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
151 CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE));
152
153 ContextInitializer init_closure_context = [&](Node* context) {
154 StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
155 generator);
156 };
157
158 // TODO(jgruber): AsyncBuiltinsAssembler::Await currently does not reuse
159 // the awaited promise if it is already a promise. Reuse is non-spec compliant
160 // but part of our old behavior gives us a couple of percent
161 // performance boost.
162 // TODO(jgruber): Use a faster specialized version of
163 // InternalPerformPromiseThen.
164
165 Label after_debug_hook(this), call_debug_hook(this, Label::kDeferred);
166 GotoIf(HasAsyncEventDelegate(), &call_debug_hook);
167 Goto(&after_debug_hook);
168 BIND(&after_debug_hook);
169
170 AwaitOptimized(
171 context, generator, awaited, outer_promise, AwaitContext::kLength,
172 init_closure_context, Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
173 Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN, is_predicted_as_caught);
174
175 // Return outer promise to avoid adding an load of the outer promise before
176 // suspending in BytecodeGenerator.
177 Return(outer_promise);
178
179 BIND(&call_debug_hook);
180 CallRuntime(Runtime::kDebugAsyncFunctionSuspended, context, outer_promise);
181 Goto(&after_debug_hook);
182 }
183
184 // Called by the parser from the desugaring of 'await' when catch
185 // prediction indicates that there is a locally surrounding catch block.
TF_BUILTIN(AsyncFunctionAwaitCaught,AsyncFunctionBuiltinsAssembler)186 TF_BUILTIN(AsyncFunctionAwaitCaught, AsyncFunctionBuiltinsAssembler) {
187 CSA_ASSERT_JS_ARGC_EQ(this, 3);
188 Node* const generator = Parameter(Descriptor::kGenerator);
189 Node* const awaited = Parameter(Descriptor::kAwaited);
190 Node* const outer_promise = Parameter(Descriptor::kOuterPromise);
191 Node* const context = Parameter(Descriptor::kContext);
192
193 static const bool kIsPredictedAsCaught = true;
194
195 AsyncFunctionAwait(context, generator, awaited, outer_promise,
196 kIsPredictedAsCaught);
197 }
198
TF_BUILTIN(AsyncFunctionAwaitCaughtOptimized,AsyncFunctionBuiltinsAssembler)199 TF_BUILTIN(AsyncFunctionAwaitCaughtOptimized, AsyncFunctionBuiltinsAssembler) {
200 CSA_ASSERT_JS_ARGC_EQ(this, 3);
201 Node* const generator = Parameter(Descriptor::kGenerator);
202 Node* const awaited = Parameter(Descriptor::kAwaited);
203 Node* const outer_promise = Parameter(Descriptor::kOuterPromise);
204 Node* const context = Parameter(Descriptor::kContext);
205
206 static const bool kIsPredictedAsCaught = true;
207
208 AsyncFunctionAwaitOptimized(context, generator, awaited, outer_promise,
209 kIsPredictedAsCaught);
210 }
211
212 // Called by the parser from the desugaring of 'await' when catch
213 // prediction indicates no locally surrounding catch block.
TF_BUILTIN(AsyncFunctionAwaitUncaught,AsyncFunctionBuiltinsAssembler)214 TF_BUILTIN(AsyncFunctionAwaitUncaught, AsyncFunctionBuiltinsAssembler) {
215 CSA_ASSERT_JS_ARGC_EQ(this, 3);
216 Node* const generator = Parameter(Descriptor::kGenerator);
217 Node* const awaited = Parameter(Descriptor::kAwaited);
218 Node* const outer_promise = Parameter(Descriptor::kOuterPromise);
219 Node* const context = Parameter(Descriptor::kContext);
220
221 static const bool kIsPredictedAsCaught = false;
222
223 AsyncFunctionAwait(context, generator, awaited, outer_promise,
224 kIsPredictedAsCaught);
225 }
226
TF_BUILTIN(AsyncFunctionAwaitUncaughtOptimized,AsyncFunctionBuiltinsAssembler)227 TF_BUILTIN(AsyncFunctionAwaitUncaughtOptimized,
228 AsyncFunctionBuiltinsAssembler) {
229 CSA_ASSERT_JS_ARGC_EQ(this, 3);
230 Node* const generator = Parameter(Descriptor::kGenerator);
231 Node* const awaited = Parameter(Descriptor::kAwaited);
232 Node* const outer_promise = Parameter(Descriptor::kOuterPromise);
233 Node* const context = Parameter(Descriptor::kContext);
234
235 static const bool kIsPredictedAsCaught = false;
236
237 AsyncFunctionAwaitOptimized(context, generator, awaited, outer_promise,
238 kIsPredictedAsCaught);
239 }
240
TF_BUILTIN(AsyncFunctionPromiseCreate,AsyncFunctionBuiltinsAssembler)241 TF_BUILTIN(AsyncFunctionPromiseCreate, AsyncFunctionBuiltinsAssembler) {
242 CSA_ASSERT_JS_ARGC_EQ(this, 0);
243 Node* const context = Parameter(Descriptor::kContext);
244
245 Node* const promise = AllocateAndInitJSPromise(context);
246
247 Label if_is_debug_active(this, Label::kDeferred);
248 GotoIf(IsDebugActive(), &if_is_debug_active);
249
250 // Early exit if debug is not active.
251 Return(promise);
252
253 BIND(&if_is_debug_active);
254 {
255 // Push the Promise under construction in an async function on
256 // the catch prediction stack to handle exceptions thrown before
257 // the first await.
258 CallRuntime(Runtime::kDebugPushPromise, context, promise);
259 Return(promise);
260 }
261 }
262
TF_BUILTIN(AsyncFunctionPromiseRelease,AsyncFunctionBuiltinsAssembler)263 TF_BUILTIN(AsyncFunctionPromiseRelease, AsyncFunctionBuiltinsAssembler) {
264 CSA_ASSERT_JS_ARGC_EQ(this, 2);
265 Node* const promise = Parameter(Descriptor::kPromise);
266 Node* const context = Parameter(Descriptor::kContext);
267
268 Label call_debug_instrumentation(this, Label::kDeferred);
269 GotoIf(HasAsyncEventDelegate(), &call_debug_instrumentation);
270 GotoIf(IsDebugActive(), &call_debug_instrumentation);
271
272 // Early exit if debug is not active.
273 Return(UndefinedConstant());
274
275 BIND(&call_debug_instrumentation);
276 {
277 // Pop the Promise under construction in an async function on
278 // from catch prediction stack.
279 CallRuntime(Runtime::kDebugAsyncFunctionFinished, context,
280 Parameter(Descriptor::kCanSuspend), promise);
281 Return(promise);
282 }
283 }
284
285 } // namespace internal
286 } // namespace v8
287