• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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