1// Copyright 2016 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
7#include "src/builtins/builtins-utils-gen.h"
8#include "src/heap/factory-inl.h"
9#include "src/objects/js-generator.h"
10#include "src/objects/js-promise.h"
11#include "src/objects/shared-function-info.h"
12
13namespace v8 {
14namespace internal {
15
16namespace {
17// Describe fields of Context associated with the AsyncIterator unwrap closure.
18class ValueUnwrapContext {
19 public:
20  enum Fields { kDoneSlot = Context::MIN_CONTEXT_SLOTS, kLength };
21};
22
23}  // namespace
24
25TNode<Object> AsyncBuiltinsAssembler::Await(
26    TNode<Context> context, TNode<JSGeneratorObject> generator,
27    TNode<Object> value, TNode<JSPromise> outer_promise,
28    TNode<SharedFunctionInfo> on_resolve_sfi,
29    TNode<SharedFunctionInfo> on_reject_sfi,
30    TNode<Oddball> is_predicted_as_caught) {
31  const TNode<NativeContext> native_context = LoadNativeContext(context);
32
33  // We do the `PromiseResolve(%Promise%,value)` avoiding to unnecessarily
34  // create wrapper promises. Now if {value} is already a promise with the
35  // intrinsics %Promise% constructor as its "constructor", we don't need
36  // to allocate the wrapper promise.
37  {
38    TVARIABLE(Object, var_value, value);
39    Label if_slow_path(this, Label::kDeferred), if_done(this),
40        if_slow_constructor(this, Label::kDeferred);
41    GotoIf(TaggedIsSmi(value), &if_slow_path);
42    TNode<HeapObject> value_object = CAST(value);
43    const TNode<Map> value_map = LoadMap(value_object);
44    GotoIfNot(IsJSPromiseMap(value_map), &if_slow_path);
45    // We can skip the "constructor" lookup on {value} if it's [[Prototype]]
46    // is the (initial) Promise.prototype and the @@species protector is
47    // intact, as that guards the lookup path for "constructor" on
48    // JSPromise instances which have the (initial) Promise.prototype.
49    const TNode<Object> promise_prototype =
50        LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
51    GotoIfNot(TaggedEqual(LoadMapPrototype(value_map), promise_prototype),
52              &if_slow_constructor);
53    Branch(IsPromiseSpeciesProtectorCellInvalid(), &if_slow_constructor,
54           &if_done);
55
56    // At this point, {value} doesn't have the initial promise prototype or
57    // the promise @@species protector was invalidated, but {value} could still
58    // have the %Promise% as its "constructor", so we need to check that as
59    // well.
60    BIND(&if_slow_constructor);
61    {
62      const TNode<Object> value_constructor = GetProperty(
63          context, value, isolate()->factory()->constructor_string());
64      const TNode<Object> promise_function =
65          LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
66      Branch(TaggedEqual(value_constructor, promise_function), &if_done,
67             &if_slow_path);
68    }
69
70    BIND(&if_slow_path);
71    {
72      // We need to mark the {value} wrapper as having {outer_promise}
73      // as its parent, which is why we need to inline a good chunk of
74      // logic from the `PromiseResolve` builtin here.
75      var_value = NewJSPromise(native_context, outer_promise);
76      CallBuiltin(Builtin::kResolvePromise, native_context, var_value.value(),
77                  value);
78      Goto(&if_done);
79    }
80
81    BIND(&if_done);
82    value = var_value.value();
83  }
84
85  static const int kClosureContextSize =
86      FixedArray::SizeFor(Context::MIN_CONTEXT_EXTENDED_SLOTS);
87  TNode<Context> closure_context =
88      UncheckedCast<Context>(AllocateInNewSpace(kClosureContextSize));
89  {
90    // Initialize the await context, storing the {generator} as extension.
91    TNode<Map> map = CAST(
92        LoadContextElement(native_context, Context::AWAIT_CONTEXT_MAP_INDEX));
93    StoreMapNoWriteBarrier(closure_context, map);
94    StoreObjectFieldNoWriteBarrier(
95        closure_context, Context::kLengthOffset,
96        SmiConstant(Context::MIN_CONTEXT_EXTENDED_SLOTS));
97    const TNode<Object> empty_scope_info =
98        LoadContextElement(native_context, Context::SCOPE_INFO_INDEX);
99    StoreContextElementNoWriteBarrier(
100        closure_context, Context::SCOPE_INFO_INDEX, empty_scope_info);
101    StoreContextElementNoWriteBarrier(closure_context, Context::PREVIOUS_INDEX,
102                                      native_context);
103    StoreContextElementNoWriteBarrier(closure_context, Context::EXTENSION_INDEX,
104                                      generator);
105  }
106
107  // Allocate and initialize resolve handler
108  TNode<HeapObject> on_resolve =
109      AllocateInNewSpace(JSFunction::kSizeWithoutPrototype);
110  InitializeNativeClosure(closure_context, native_context, on_resolve,
111                          on_resolve_sfi);
112
113  // Allocate and initialize reject handler
114  TNode<HeapObject> on_reject =
115      AllocateInNewSpace(JSFunction::kSizeWithoutPrototype);
116  InitializeNativeClosure(closure_context, native_context, on_reject,
117                          on_reject_sfi);
118
119  // Deal with PromiseHooks and debug support in the runtime. This
120  // also allocates the throwaway promise, which is only needed in
121  // case of PromiseHooks or debugging.
122  TVARIABLE(Object, var_throwaway, UndefinedConstant());
123  Label if_instrumentation(this, Label::kDeferred),
124      if_instrumentation_done(this);
125  TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
126  GotoIf(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
127             promiseHookFlags),
128         &if_instrumentation);
129#ifdef V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
130  // This call to NewJSPromise is to keep behaviour parity with what happens
131  // in Runtime::kDebugAsyncFunctionSuspended below if native hooks are set.
132  // It creates a throwaway promise that will trigger an init event and get
133  // passed into Builtin::kPerformPromiseThen below.
134  GotoIfNot(IsContextPromiseHookEnabled(promiseHookFlags),
135            &if_instrumentation_done);
136  var_throwaway = NewJSPromise(context, value);
137#endif  // V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
138  Goto(&if_instrumentation_done);
139  BIND(&if_instrumentation);
140  {
141    var_throwaway = CallRuntime(Runtime::kDebugAsyncFunctionSuspended,
142                                native_context, value, outer_promise, on_reject,
143                                generator, is_predicted_as_caught);
144    Goto(&if_instrumentation_done);
145  }
146  BIND(&if_instrumentation_done);
147
148  return CallBuiltin(Builtin::kPerformPromiseThen, native_context, value,
149                     on_resolve, on_reject, var_throwaway.value());
150}
151
152void AsyncBuiltinsAssembler::InitializeNativeClosure(
153    TNode<Context> context, TNode<NativeContext> native_context,
154    TNode<HeapObject> function, TNode<SharedFunctionInfo> shared_info) {
155  TNode<Map> function_map = CAST(LoadContextElement(
156      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
157  // Ensure that we don't have to initialize prototype_or_initial_map field of
158  // JSFunction.
159  CSA_DCHECK(this,
160             IntPtrEqual(LoadMapInstanceSizeInWords(function_map),
161                         IntPtrConstant(JSFunction::kSizeWithoutPrototype /
162                                        kTaggedSize)));
163  STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kTaggedSize);
164  StoreMapNoWriteBarrier(function, function_map);
165  StoreObjectFieldRoot(function, JSObject::kPropertiesOrHashOffset,
166                       RootIndex::kEmptyFixedArray);
167  StoreObjectFieldRoot(function, JSObject::kElementsOffset,
168                       RootIndex::kEmptyFixedArray);
169  StoreObjectFieldRoot(function, JSFunction::kFeedbackCellOffset,
170                       RootIndex::kManyClosuresCell);
171
172  StoreObjectFieldNoWriteBarrier(
173      function, JSFunction::kSharedFunctionInfoOffset, shared_info);
174  StoreObjectFieldNoWriteBarrier(function, JSFunction::kContextOffset, context);
175
176  // For the native closures that are initialized here (for `await`)
177  // we know that their SharedFunctionInfo::function_data(kAcquireLoad) slot
178  // contains a builtin index (as Smi), so there's no need to use
179  // CodeStubAssembler::GetSharedFunctionInfoCode() helper here,
180  // which almost doubles the size of `await` builtins (unnecessarily).
181  TNode<Smi> builtin_id = LoadObjectField<Smi>(
182      shared_info, SharedFunctionInfo::kFunctionDataOffset);
183  TNode<CodeT> code = LoadBuiltin(builtin_id);
184  StoreObjectFieldNoWriteBarrier(function, JSFunction::kCodeOffset, code);
185}
186
187TNode<JSFunction> AsyncBuiltinsAssembler::CreateUnwrapClosure(
188    TNode<NativeContext> native_context, TNode<Oddball> done) {
189  const TNode<Map> map = CAST(LoadContextElement(
190      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
191  const TNode<SharedFunctionInfo> on_fulfilled_shared =
192      AsyncIteratorValueUnwrapSharedFunConstant();
193  const TNode<Context> closure_context =
194      AllocateAsyncIteratorValueUnwrapContext(native_context, done);
195  return AllocateFunctionWithMapAndContext(map, on_fulfilled_shared,
196                                           closure_context);
197}
198
199TNode<Context> AsyncBuiltinsAssembler::AllocateAsyncIteratorValueUnwrapContext(
200    TNode<NativeContext> native_context, TNode<Oddball> done) {
201  CSA_DCHECK(this, IsBoolean(done));
202
203  TNode<Context> context = AllocateSyntheticFunctionContext(
204      native_context, ValueUnwrapContext::kLength);
205  StoreContextElementNoWriteBarrier(context, ValueUnwrapContext::kDoneSlot,
206                                    done);
207  return context;
208}
209
210TF_BUILTIN(AsyncIteratorValueUnwrap, AsyncBuiltinsAssembler) {
211  auto value = Parameter<Object>(Descriptor::kValue);
212  auto context = Parameter<Context>(Descriptor::kContext);
213
214  const TNode<Object> done =
215      LoadContextElement(context, ValueUnwrapContext::kDoneSlot);
216  CSA_DCHECK(this, IsBoolean(CAST(done)));
217
218  const TNode<Object> unwrapped_value =
219      CallBuiltin(Builtin::kCreateIterResultObject, context, value, done);
220
221  Return(unwrapped_value);
222}
223
224}  // namespace internal
225}  // namespace v8
226