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.h"
6 #include "src/builtins/builtins-utils.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 typedef compiler::Node Node;
15 typedef CodeStubAssembler::ParameterMode ParameterMode;
16 typedef compiler::CodeAssemblerState CodeAssemblerState;
17
18 class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler {
19 public:
AsyncFunctionBuiltinsAssembler(CodeAssemblerState * state)20 explicit AsyncFunctionBuiltinsAssembler(CodeAssemblerState* state)
21 : AsyncBuiltinsAssembler(state) {}
22
23 protected:
24 void AsyncFunctionAwait(Node* const context, Node* const generator,
25 Node* const awaited, Node* const outer_promise,
26 const bool is_predicted_as_caught);
27
28 void AsyncFunctionAwaitResumeClosure(
29 Node* const context, Node* const sent_value,
30 JSGeneratorObject::ResumeMode resume_mode);
31 };
32
33 namespace {
34
35 // Describe fields of Context associated with AsyncFunctionAwait resume
36 // closures.
37 // TODO(jgruber): Refactor to reuse code for upcoming async-generators.
38 class AwaitContext {
39 public:
40 enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength };
41 };
42
43 } // anonymous namespace
44
AsyncFunctionAwaitResumeClosure(Node * context,Node * sent_value,JSGeneratorObject::ResumeMode resume_mode)45 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
46 Node* context, Node* sent_value,
47 JSGeneratorObject::ResumeMode resume_mode) {
48 DCHECK(resume_mode == JSGeneratorObject::kNext ||
49 resume_mode == JSGeneratorObject::kThrow);
50
51 Node* const generator =
52 LoadContextElement(context, AwaitContext::kGeneratorSlot);
53 CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
54
55 // Inline version of GeneratorPrototypeNext / GeneratorPrototypeReturn with
56 // unnecessary runtime checks removed.
57 // TODO(jgruber): Refactor to reuse code from builtins-generator.cc.
58
59 // Ensure that the generator is neither closed nor running.
60 CSA_SLOW_ASSERT(
61 this,
62 SmiGreaterThan(
63 LoadObjectField(generator, JSGeneratorObject::kContinuationOffset),
64 SmiConstant(JSGeneratorObject::kGeneratorClosed)));
65
66 // Resume the {receiver} using our trampoline.
67 Callable callable = CodeFactory::ResumeGenerator(isolate());
68 CallStub(callable, context, sent_value, generator, SmiConstant(resume_mode));
69
70 // The resulting Promise is a throwaway, so it doesn't matter what it
71 // resolves to. What is important is that we don't end up keeping the
72 // whole chain of intermediate Promises alive by returning the return value
73 // of ResumeGenerator, as that would create a memory leak.
74 }
75
TF_BUILTIN(AsyncFunctionAwaitRejectClosure,AsyncFunctionBuiltinsAssembler)76 TF_BUILTIN(AsyncFunctionAwaitRejectClosure, AsyncFunctionBuiltinsAssembler) {
77 CSA_ASSERT_JS_ARGC_EQ(this, 1);
78 Node* const sentError = Parameter(1);
79 Node* const context = Parameter(4);
80
81 AsyncFunctionAwaitResumeClosure(context, sentError,
82 JSGeneratorObject::kThrow);
83 Return(UndefinedConstant());
84 }
85
TF_BUILTIN(AsyncFunctionAwaitResolveClosure,AsyncFunctionBuiltinsAssembler)86 TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) {
87 CSA_ASSERT_JS_ARGC_EQ(this, 1);
88 Node* const sentValue = Parameter(1);
89 Node* const context = Parameter(4);
90
91 AsyncFunctionAwaitResumeClosure(context, sentValue, JSGeneratorObject::kNext);
92 Return(UndefinedConstant());
93 }
94
95 // ES#abstract-ops-async-function-await
96 // AsyncFunctionAwait ( value )
97 // Shared logic for the core of await. The parser desugars
98 // await awaited
99 // into
100 // yield AsyncFunctionAwait{Caught,Uncaught}(.generator, awaited, .promise)
101 // The 'awaited' parameter is the value; the generator stands in
102 // for the asyncContext, and .promise is the larger promise under
103 // 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)104 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
105 Node* const context, Node* const generator, Node* const awaited,
106 Node* const outer_promise, const bool is_predicted_as_caught) {
107 CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
108 CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE));
109
110 NodeGenerator1 create_closure_context = [&](Node* native_context) -> Node* {
111 Node* const context =
112 CreatePromiseContext(native_context, AwaitContext::kLength);
113 StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
114 generator);
115 return context;
116 };
117
118 // TODO(jgruber): AsyncBuiltinsAssembler::Await currently does not reuse
119 // the awaited promise if it is already a promise. Reuse is non-spec compliant
120 // but part of our old behavior gives us a couple of percent
121 // performance boost.
122 // TODO(jgruber): Use a faster specialized version of
123 // InternalPerformPromiseThen.
124
125 Node* const result = Await(
126 context, generator, awaited, outer_promise, create_closure_context,
127 Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
128 Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN, is_predicted_as_caught);
129
130 Return(result);
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(1);
138 Node* const awaited = Parameter(2);
139 Node* const outer_promise = Parameter(3);
140 Node* const context = Parameter(6);
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(1);
153 Node* const awaited = Parameter(2);
154 Node* const outer_promise = Parameter(3);
155 Node* const context = Parameter(6);
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(3);
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(1);
190 Node* const context = Parameter(4);
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