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
11namespace v8 {
12namespace internal {
13
14typedef compiler::Node Node;
15typedef CodeStubAssembler::ParameterMode ParameterMode;
16typedef compiler::CodeAssemblerState CodeAssemblerState;
17
18class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler {
19 public:
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
33namespace {
34
35// Describe fields of Context associated with AsyncFunctionAwait resume
36// closures.
37// TODO(jgruber): Refactor to reuse code for upcoming async-generators.
38class AwaitContext {
39 public:
40  enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength };
41};
42
43}  // anonymous namespace
44
45void 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
76TF_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
86TF_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.
104void 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.
135TF_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.
150TF_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
163TF_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
187TF_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