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 
11 namespace v8 {
12 namespace internal {
13 
14 class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler {
15  public:
AsyncFunctionBuiltinsAssembler(compiler::CodeAssemblerState * state)16   explicit AsyncFunctionBuiltinsAssembler(compiler::CodeAssemblerState* state)
17       : AsyncBuiltinsAssembler(state) {}
18 
19  protected:
20   void AsyncFunctionAwait(Node* const context, Node* const generator,
21                           Node* const awaited, Node* const outer_promise,
22                           const bool is_predicted_as_caught);
23 
24   void AsyncFunctionAwaitResumeClosure(
25       Node* const context, Node* const sent_value,
26       JSGeneratorObject::ResumeMode resume_mode);
27 };
28 
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 
AsyncFunctionAwaitResumeClosure(Node * context,Node * sent_value,JSGeneratorObject::ResumeMode resume_mode)41 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
42     Node* context, Node* sent_value,
43     JSGeneratorObject::ResumeMode resume_mode) {
44   DCHECK(resume_mode == JSGeneratorObject::kNext ||
45          resume_mode == JSGeneratorObject::kThrow);
46 
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 
55   // Ensure that the generator is neither closed nor running.
56   CSA_SLOW_ASSERT(
57       this,
58       SmiGreaterThan(CAST(LoadObjectField(
59                          generator, JSGeneratorObject::kContinuationOffset)),
60                      SmiConstant(JSGeneratorObject::kGeneratorClosed)));
61 
62   // Remember the {resume_mode} for the {generator}.
63   StoreObjectFieldNoWriteBarrier(generator,
64                                  JSGeneratorObject::kResumeModeOffset,
65                                  SmiConstant(resume_mode));
66 
67   // Resume the {receiver} using our trampoline.
68   Callable callable = CodeFactory::ResumeGenerator(isolate());
69   CallStub(callable, context, sent_value, generator);
70 
71   // The resulting Promise is a throwaway, so it doesn't matter what it
72   // resolves to. What is important is that we don't end up keeping the
73   // whole chain of intermediate Promises alive by returning the return value
74   // of ResumeGenerator, as that would create a memory leak.
75 }
76 
TF_BUILTIN(AsyncFunctionAwaitRejectClosure,AsyncFunctionBuiltinsAssembler)77 TF_BUILTIN(AsyncFunctionAwaitRejectClosure, AsyncFunctionBuiltinsAssembler) {
78   CSA_ASSERT_JS_ARGC_EQ(this, 1);
79   Node* const sentError = Parameter(Descriptor::kSentError);
80   Node* const context = Parameter(Descriptor::kContext);
81 
82   AsyncFunctionAwaitResumeClosure(context, sentError,
83                                   JSGeneratorObject::kThrow);
84   Return(UndefinedConstant());
85 }
86 
TF_BUILTIN(AsyncFunctionAwaitResolveClosure,AsyncFunctionBuiltinsAssembler)87 TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) {
88   CSA_ASSERT_JS_ARGC_EQ(this, 1);
89   Node* const sentValue = Parameter(Descriptor::kSentValue);
90   Node* const context = Parameter(Descriptor::kContext);
91 
92   AsyncFunctionAwaitResumeClosure(context, sentValue, JSGeneratorObject::kNext);
93   Return(UndefinedConstant());
94 }
95 
96 // ES#abstract-ops-async-function-await
97 // AsyncFunctionAwait ( value )
98 // Shared logic for the core of await. The parser desugars
99 //   await awaited
100 // into
101 //   yield AsyncFunctionAwait{Caught,Uncaught}(.generator, awaited, .promise)
102 // The 'awaited' parameter is the value; the generator stands in
103 // for the asyncContext, and .promise is the larger promise under
104 // 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)105 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
106     Node* const context, Node* const generator, Node* const awaited,
107     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);
127 
128   // Return outer promise to avoid adding an load of the outer promise before
129   // suspending in BytecodeGenerator.
130   Return(outer_promise);
131 }
132 
133 // Called by the parser from the desugaring of 'await' when catch
134 // prediction indicates that there is a locally surrounding catch block.
TF_BUILTIN(AsyncFunctionAwaitCaught,AsyncFunctionBuiltinsAssembler)135 TF_BUILTIN(AsyncFunctionAwaitCaught, AsyncFunctionBuiltinsAssembler) {
136   CSA_ASSERT_JS_ARGC_EQ(this, 3);
137   Node* const generator = Parameter(Descriptor::kGenerator);
138   Node* const awaited = Parameter(Descriptor::kAwaited);
139   Node* const outer_promise = Parameter(Descriptor::kOuterPromise);
140   Node* const context = Parameter(Descriptor::kContext);
141 
142   static const bool kIsPredictedAsCaught = true;
143 
144   AsyncFunctionAwait(context, generator, awaited, outer_promise,
145                      kIsPredictedAsCaught);
146 }
147 
148 // Called by the parser from the desugaring of 'await' when catch
149 // prediction indicates no locally surrounding catch block.
TF_BUILTIN(AsyncFunctionAwaitUncaught,AsyncFunctionBuiltinsAssembler)150 TF_BUILTIN(AsyncFunctionAwaitUncaught, AsyncFunctionBuiltinsAssembler) {
151   CSA_ASSERT_JS_ARGC_EQ(this, 3);
152   Node* const generator = Parameter(Descriptor::kGenerator);
153   Node* const awaited = Parameter(Descriptor::kAwaited);
154   Node* const outer_promise = Parameter(Descriptor::kOuterPromise);
155   Node* const context = Parameter(Descriptor::kContext);
156 
157   static const bool kIsPredictedAsCaught = false;
158 
159   AsyncFunctionAwait(context, generator, awaited, outer_promise,
160                      kIsPredictedAsCaught);
161 }
162 
TF_BUILTIN(AsyncFunctionPromiseCreate,AsyncFunctionBuiltinsAssembler)163 TF_BUILTIN(AsyncFunctionPromiseCreate, AsyncFunctionBuiltinsAssembler) {
164   CSA_ASSERT_JS_ARGC_EQ(this, 0);
165   Node* const context = Parameter(Descriptor::kContext);
166 
167   Node* const promise = AllocateAndInitJSPromise(context);
168 
169   Label if_is_debug_active(this, Label::kDeferred);
170   GotoIf(IsDebugActive(), &if_is_debug_active);
171 
172   // Early exit if debug is not active.
173   Return(promise);
174 
175   BIND(&if_is_debug_active);
176   {
177     // Push the Promise under construction in an async function on
178     // the catch prediction stack to handle exceptions thrown before
179     // the first await.
180     // Assign ID and create a recurring task to save stack for future
181     // resumptions from await.
182     CallRuntime(Runtime::kDebugAsyncFunctionPromiseCreated, context, promise);
183     Return(promise);
184   }
185 }
186 
TF_BUILTIN(AsyncFunctionPromiseRelease,AsyncFunctionBuiltinsAssembler)187 TF_BUILTIN(AsyncFunctionPromiseRelease, AsyncFunctionBuiltinsAssembler) {
188   CSA_ASSERT_JS_ARGC_EQ(this, 1);
189   Node* const promise = Parameter(Descriptor::kPromise);
190   Node* const context = Parameter(Descriptor::kContext);
191 
192   Label if_is_debug_active(this, Label::kDeferred);
193   GotoIf(IsDebugActive(), &if_is_debug_active);
194 
195   // Early exit if debug is not active.
196   Return(UndefinedConstant());
197 
198   BIND(&if_is_debug_active);
199   {
200     // Pop the Promise under construction in an async function on
201     // from catch prediction stack.
202     CallRuntime(Runtime::kDebugPopPromise, context);
203     Return(promise);
204   }
205 }
206 
207 }  // namespace internal
208 }  // namespace v8
209