builtins-async-gen.cc 9.74 KB
Newer Older
1 2 3 4
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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 13 14 15

namespace v8 {
namespace internal {

16 17 18 19 20 21 22 23 24
namespace {
// Describe fields of Context associated with the AsyncIterator unwrap closure.
class ValueUnwrapContext {
 public:
  enum Fields { kDoneSlot = Context::MIN_CONTEXT_SLOTS, kLength };
};

}  // namespace

25
TNode<Object> AsyncBuiltinsAssembler::Await(
26 27
    TNode<Context> context, TNode<JSGeneratorObject> generator,
    TNode<Object> value, TNode<JSPromise> outer_promise,
28 29
    TNode<SharedFunctionInfo> on_resolve_sfi,
    TNode<SharedFunctionInfo> on_reject_sfi,
30
    TNode<Oddball> is_predicted_as_caught) {
31
  const TNode<NativeContext> native_context = LoadNativeContext(context);
32

33 34 35 36
  // We do the `PromiseResolve(%Promise%,value)` avoiding to unnecessarily
  // create wrapper promises. Now if {value} is already a promise with the
  // intrinsics %Promise% constructor as its "constructor", we don't need
  // to allocate the wrapper promise.
37
  {
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    TVARIABLE(Object, var_value, value);
    Label if_slow_path(this, Label::kDeferred), if_done(this),
        if_slow_constructor(this, Label::kDeferred);
    GotoIf(TaggedIsSmi(value), &if_slow_path);
    TNode<HeapObject> value_object = CAST(value);
    const TNode<Map> value_map = LoadMap(value_object);
    GotoIfNot(IsJSPromiseMap(value_map), &if_slow_path);
    // We can skip the "constructor" lookup on {value} if it's [[Prototype]]
    // is the (initial) Promise.prototype and the @@species protector is
    // intact, as that guards the lookup path for "constructor" on
    // JSPromise instances which have the (initial) Promise.prototype.
    const TNode<Object> promise_prototype =
        LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
    GotoIfNot(TaggedEqual(LoadMapPrototype(value_map), promise_prototype),
              &if_slow_constructor);
    Branch(IsPromiseSpeciesProtectorCellInvalid(), &if_slow_constructor,
           &if_done);

    // At this point, {value} doesn't have the initial promise prototype or
    // the promise @@species protector was invalidated, but {value} could still
    // have the %Promise% as its "constructor", so we need to check that as
    // well.
    BIND(&if_slow_constructor);
    {
      const TNode<Object> value_constructor = GetProperty(
          context, value, isolate()->factory()->constructor_string());
      const TNode<Object> promise_function =
          LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
      Branch(TaggedEqual(value_constructor, promise_function), &if_done,
             &if_slow_path);
    }

    BIND(&if_slow_path);
    {
      // We need to mark the {value} wrapper as having {outer_promise}
      // as its parent, which is why we need to inline a good chunk of
      // logic from the `PromiseResolve` builtin here.
      var_value = NewJSPromise(native_context, outer_promise);
      CallBuiltin(Builtin::kResolvePromise, native_context, var_value.value(),
                  value);
      Goto(&if_done);
    }

    BIND(&if_done);
    value = var_value.value();
83 84
  }

85 86 87 88
  static const int kClosureContextSize =
      FixedArray::SizeFor(Context::MIN_CONTEXT_EXTENDED_SLOTS);
  TNode<Context> closure_context =
      UncheckedCast<Context>(AllocateInNewSpace(kClosureContextSize));
89
  {
90
    // Initialize the await context, storing the {generator} as extension.
91 92 93
    TNode<Map> map = CAST(
        LoadContextElement(native_context, Context::AWAIT_CONTEXT_MAP_INDEX));
    StoreMapNoWriteBarrier(closure_context, map);
94 95 96
    StoreObjectFieldNoWriteBarrier(
        closure_context, Context::kLengthOffset,
        SmiConstant(Context::MIN_CONTEXT_EXTENDED_SLOTS));
97
    const TNode<Object> empty_scope_info =
98 99 100 101 102 103 104
        LoadContextElement(native_context, Context::SCOPE_INFO_INDEX);
    StoreContextElementNoWriteBarrier(
        closure_context, Context::SCOPE_INFO_INDEX, empty_scope_info);
    StoreContextElementNoWriteBarrier(closure_context, Context::PREVIOUS_INDEX,
                                      native_context);
    StoreContextElementNoWriteBarrier(closure_context, Context::EXTENSION_INDEX,
                                      generator);
105 106
  }

107 108 109
  // Allocate and initialize resolve handler
  TNode<HeapObject> on_resolve =
      AllocateInNewSpace(JSFunction::kSizeWithoutPrototype);
110
  InitializeNativeClosure(closure_context, native_context, on_resolve,
111
                          on_resolve_sfi);
112

113 114 115
  // Allocate and initialize reject handler
  TNode<HeapObject> on_reject =
      AllocateInNewSpace(JSFunction::kSizeWithoutPrototype);
116
  InitializeNativeClosure(closure_context, native_context, on_reject,
117
                          on_reject_sfi);
118 119 120 121

  // Deal with PromiseHooks and debug support in the runtime. This
  // also allocates the throwaway promise, which is only needed in
  // case of PromiseHooks or debugging.
122 123 124
  TVARIABLE(Object, var_throwaway, UndefinedConstant());
  Label if_instrumentation(this, Label::kDeferred),
      if_instrumentation_done(this);
125
  TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
126 127 128
  GotoIf(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
             promiseHookFlags),
         &if_instrumentation);
129
#ifdef V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
130
  // This call to NewJSPromise is to keep behaviour parity with what happens
131 132
  // in Runtime::kDebugAsyncFunctionSuspended below if native hooks are set.
  // It creates a throwaway promise that will trigger an init event and get
133
  // passed into Builtin::kPerformPromiseThen below.
134 135 136
  GotoIfNot(IsContextPromiseHookEnabled(promiseHookFlags),
            &if_instrumentation_done);
  var_throwaway = NewJSPromise(context, value);
137
#endif  // V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
138 139
  Goto(&if_instrumentation_done);
  BIND(&if_instrumentation);
140
  {
141 142 143
    var_throwaway = CallRuntime(Runtime::kDebugAsyncFunctionSuspended,
                                native_context, value, outer_promise, on_reject,
                                generator, is_predicted_as_caught);
144
    Goto(&if_instrumentation_done);
145
  }
146
  BIND(&if_instrumentation_done);
147

148 149
  return CallBuiltin(Builtin::kPerformPromiseThen, native_context, value,
                     on_resolve, on_reject, var_throwaway.value());
150 151
}

152 153
void AsyncBuiltinsAssembler::InitializeNativeClosure(
    TNode<Context> context, TNode<NativeContext> native_context,
154
    TNode<HeapObject> function, TNode<SharedFunctionInfo> shared_info) {
155 156
  TNode<Map> function_map = CAST(LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
157 158
  // Ensure that we don't have to initialize prototype_or_initial_map field of
  // JSFunction.
159
  CSA_DCHECK(this,
160 161 162
             IntPtrEqual(LoadMapInstanceSizeInWords(function_map),
                         IntPtrConstant(JSFunction::kSizeWithoutPrototype /
                                        kTaggedSize)));
163
  static_assert(JSFunction::kSizeWithoutPrototype == 7 * kTaggedSize);
164 165
  StoreMapNoWriteBarrier(function, function_map);
  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 173 174 175

  StoreObjectFieldNoWriteBarrier(
      function, JSFunction::kSharedFunctionInfoOffset, shared_info);
  StoreObjectFieldNoWriteBarrier(function, JSFunction::kContextOffset, context);

176
  // For the native closures that are initialized here (for `await`)
177
  // we know that their SharedFunctionInfo::function_data(kAcquireLoad) slot
178 179 180 181 182
  // contains a builtin index (as Smi), so there's no need to use
  // CodeStubAssembler::GetSharedFunctionInfoCode() helper here,
  // which almost doubles the size of `await` builtins (unnecessarily).
  TNode<Smi> builtin_id = LoadObjectField<Smi>(
      shared_info, SharedFunctionInfo::kFunctionDataOffset);
183 184
  TNode<CodeT> code = LoadBuiltin(builtin_id);
  StoreObjectFieldNoWriteBarrier(function, JSFunction::kCodeOffset, code);
185 186
}

187
TNode<JSFunction> AsyncBuiltinsAssembler::CreateUnwrapClosure(
188
    TNode<NativeContext> native_context, TNode<Oddball> done) {
189 190
  const TNode<Map> map = CAST(LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
191 192
  const TNode<SharedFunctionInfo> on_fulfilled_shared =
      AsyncIteratorValueUnwrapSharedFunConstant();
193
  const TNode<Context> closure_context =
194
      AllocateAsyncIteratorValueUnwrapContext(native_context, done);
195 196
  return AllocateFunctionWithMapAndContext(map, on_fulfilled_shared,
                                           closure_context);
197 198
}

199
TNode<Context> AsyncBuiltinsAssembler::AllocateAsyncIteratorValueUnwrapContext(
200
    TNode<NativeContext> native_context, TNode<Oddball> done) {
201
  CSA_DCHECK(this, IsBoolean(done));
202

203 204
  TNode<Context> context = AllocateSyntheticFunctionContext(
      native_context, ValueUnwrapContext::kLength);
205 206 207 208 209 210
  StoreContextElementNoWriteBarrier(context, ValueUnwrapContext::kDoneSlot,
                                    done);
  return context;
}

TF_BUILTIN(AsyncIteratorValueUnwrap, AsyncBuiltinsAssembler) {
211 212
  auto value = Parameter<Object>(Descriptor::kValue);
  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 222 223

  Return(unwrapped_value);
}

224 225
}  // namespace internal
}  // namespace v8