builtins-async-gen.cc 14.2 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
using compiler::Node;

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

}  // namespace

27 28 29 30 31 32
TNode<Object> AsyncBuiltinsAssembler::AwaitOld(
    TNode<Context> context, TNode<JSGeneratorObject> generator,
    TNode<Object> value, TNode<JSPromise> outer_promise,
    TNode<IntPtrT> on_resolve_context_index,
    TNode<IntPtrT> on_reject_context_index,
    TNode<Oddball> is_predicted_as_caught) {
33
  const TNode<NativeContext> native_context = LoadNativeContext(context);
34

35
  static const int kWrappedPromiseOffset =
36
      FixedArray::SizeFor(Context::MIN_CONTEXT_EXTENDED_SLOTS);
37
  static const int kResolveClosureOffset =
38
      kWrappedPromiseOffset + JSPromise::kSizeWithEmbedderFields;
39 40 41 42 43
  static const int kRejectClosureOffset =
      kResolveClosureOffset + JSFunction::kSizeWithoutPrototype;
  static const int kTotalSize =
      kRejectClosureOffset + JSFunction::kSizeWithoutPrototype;

44
  TNode<HeapObject> base = AllocateInNewSpace(kTotalSize);
45
  TNode<Context> closure_context = UncheckedCast<Context>(base);
46
  {
47
    // Initialize the await context, storing the {generator} as extension.
48 49 50
    TNode<Map> map = CAST(
        LoadContextElement(native_context, Context::AWAIT_CONTEXT_MAP_INDEX));
    StoreMapNoWriteBarrier(closure_context, map);
51 52 53
    StoreObjectFieldNoWriteBarrier(
        closure_context, Context::kLengthOffset,
        SmiConstant(Context::MIN_CONTEXT_EXTENDED_SLOTS));
54
    const TNode<Object> empty_scope_info =
55 56 57 58 59 60 61
        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);
62 63 64
  }

  // Let promiseCapability be ! NewPromiseCapability(%Promise%).
65
  const TNode<JSFunction> promise_fun =
66
      CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX));
67
  CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun)));
68
  const TNode<Map> promise_map = CAST(
69
      LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset));
70 71
  // Assert that the JSPromise map has an instance size is
  // JSPromise::kSizeWithEmbedderFields.
72 73 74 75
  CSA_ASSERT(this,
             IntPtrEqual(LoadMapInstanceSizeInWords(promise_map),
                         IntPtrConstant(JSPromise::kSizeWithEmbedderFields /
                                        kTaggedSize)));
76
  TNode<JSPromise> promise;
77 78
  {
    // Initialize Promise
79 80
    TNode<HeapObject> wrapped_value =
        InnerAllocate(base, kWrappedPromiseOffset);
81
    StoreMapNoWriteBarrier(wrapped_value, promise_map);
82 83 84 85
    StoreObjectFieldRoot(wrapped_value, JSPromise::kPropertiesOrHashOffset,
                         RootIndex::kEmptyFixedArray);
    StoreObjectFieldRoot(wrapped_value, JSPromise::kElementsOffset,
                         RootIndex::kEmptyFixedArray);
86 87
    promise = CAST(wrapped_value);
    PromiseInit(promise);
88 89
  }

90
  // Initialize resolve handler
91
  TNode<HeapObject> on_resolve = InnerAllocate(base, kResolveClosureOffset);
92 93
  InitializeNativeClosure(closure_context, native_context, on_resolve,
                          on_resolve_context_index);
94

95
  // Initialize reject handler
96
  TNode<HeapObject> on_reject = InnerAllocate(base, kRejectClosureOffset);
97 98 99
  InitializeNativeClosure(closure_context, native_context, on_reject,
                          on_reject_context_index);

100
  TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
101 102 103 104 105

  // 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.
  Label if_debugging(this, Label::kDeferred), do_resolve_promise(this);
106 107
  Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
         &if_debugging, &do_resolve_promise);
108
  BIND(&if_debugging);
109 110 111
  var_throwaway =
      CAST(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value, promise,
                       outer_promise, on_reject, is_predicted_as_caught));
112 113
  Goto(&do_resolve_promise);
  BIND(&do_resolve_promise);
114 115

  // Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
116
  CallBuiltin(Builtins::kResolvePromise, context, promise, value);
117

118
  return CallBuiltin(Builtins::kPerformPromiseThen, context, promise,
119
                     on_resolve, on_reject, var_throwaway.value());
120 121
}

122 123 124 125 126 127
TNode<Object> AsyncBuiltinsAssembler::AwaitOptimized(
    TNode<Context> context, TNode<JSGeneratorObject> generator,
    TNode<JSPromise> promise, TNode<JSPromise> outer_promise,
    TNode<IntPtrT> on_resolve_context_index,
    TNode<IntPtrT> on_reject_context_index,
    TNode<Oddball> is_predicted_as_caught) {
128
  const TNode<NativeContext> native_context = LoadNativeContext(context);
129 130

  static const int kResolveClosureOffset =
131
      FixedArray::SizeFor(Context::MIN_CONTEXT_EXTENDED_SLOTS);
132 133 134 135 136 137
  static const int kRejectClosureOffset =
      kResolveClosureOffset + JSFunction::kSizeWithoutPrototype;
  static const int kTotalSize =
      kRejectClosureOffset + JSFunction::kSizeWithoutPrototype;

  // 2. Let promise be ? PromiseResolve(« promise »).
138 139
  // We skip this step, because promise is already guaranteed to be a
  // JSPRomise at this point.
140

141
  TNode<HeapObject> base = AllocateInNewSpace(kTotalSize);
142
  TNode<Context> closure_context = UncheckedCast<Context>(base);
143
  {
144
    // Initialize the await context, storing the {generator} as extension.
145 146 147
    TNode<Map> map = CAST(
        LoadContextElement(native_context, Context::AWAIT_CONTEXT_MAP_INDEX));
    StoreMapNoWriteBarrier(closure_context, map);
148 149 150
    StoreObjectFieldNoWriteBarrier(
        closure_context, Context::kLengthOffset,
        SmiConstant(Context::MIN_CONTEXT_EXTENDED_SLOTS));
151
    const TNode<Object> empty_scope_info =
152 153 154 155 156 157 158
        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);
159 160
  }

161
  // Initialize resolve handler
162
  TNode<HeapObject> on_resolve = InnerAllocate(base, kResolveClosureOffset);
163 164
  InitializeNativeClosure(closure_context, native_context, on_resolve,
                          on_resolve_context_index);
165

166
  // Initialize reject handler
167
  TNode<HeapObject> on_reject = InnerAllocate(base, kRejectClosureOffset);
168 169 170
  InitializeNativeClosure(closure_context, native_context, on_reject,
                          on_reject_context_index);

171
  TVARIABLE(HeapObject, var_throwaway, UndefinedConstant());
172 173 174 175 176

  // 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.
  Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this);
177 178
  Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
         &if_debugging, &do_perform_promise_then);
179
  BIND(&if_debugging);
180 181 182
  var_throwaway =
      CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise,
                       outer_promise, on_reject, is_predicted_as_caught));
183 184
  Goto(&do_perform_promise_then);
  BIND(&do_perform_promise_then);
185

186
  return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
187
                     on_resolve, on_reject, var_throwaway.value());
188 189
}

190 191 192 193 194 195 196
TNode<Object> AsyncBuiltinsAssembler::Await(
    TNode<Context> context, TNode<JSGeneratorObject> generator,
    TNode<Object> value, TNode<JSPromise> outer_promise,
    TNode<IntPtrT> on_resolve_context_index,
    TNode<IntPtrT> on_reject_context_index,
    TNode<Oddball> is_predicted_as_caught) {
  TVARIABLE(Object, result);
197 198
  Label if_old(this), if_new(this), done(this),
      if_slow_constructor(this, Label::kDeferred);
199

200
  // We do the `PromiseResolve(%Promise%,value)` avoiding to unnecessarily
201 202 203 204 205
  // 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 and can just use the `AwaitOptimized`
  // logic.
  GotoIf(TaggedIsSmi(value), &if_old);
206
  TNode<HeapObject> value_object = CAST(value);
207
  const TNode<Map> value_map = LoadMap(value_object);
208 209 210 211 212
  GotoIfNot(IsJSPromiseMap(value_map), &if_old);
  // 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.
213 214
  const TNode<NativeContext> native_context = LoadNativeContext(context);
  const TNode<Object> promise_prototype =
215
      LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
216
  GotoIfNot(TaggedEqual(LoadMapPrototype(value_map), promise_prototype),
217 218 219 220 221 222 223 224
            &if_slow_constructor);
  Branch(IsPromiseSpeciesProtectorCellInvalid(), &if_slow_constructor, &if_new);

  // 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);
  {
225
    const TNode<Object> value_constructor =
226
        GetProperty(context, value, isolate()->factory()->constructor_string());
227
    const TNode<Object> promise_function =
228
        LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
229
    Branch(TaggedEqual(value_constructor, promise_function), &if_new, &if_old);
230
  }
231 232

  BIND(&if_old);
233 234 235
  result = AwaitOld(context, generator, value, outer_promise,
                    on_resolve_context_index, on_reject_context_index,
                    is_predicted_as_caught);
236 237 238
  Goto(&done);

  BIND(&if_new);
239 240 241
  result = AwaitOptimized(context, generator, CAST(value), outer_promise,
                          on_resolve_context_index, on_reject_context_index,
                          is_predicted_as_caught);
242 243 244 245 246 247
  Goto(&done);

  BIND(&done);
  return result.value();
}

248 249 250
void AsyncBuiltinsAssembler::InitializeNativeClosure(
    TNode<Context> context, TNode<NativeContext> native_context,
    TNode<HeapObject> function, TNode<IntPtrT> context_index) {
251 252
  TNode<Map> function_map = CAST(LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
253 254
  // Ensure that we don't have to initialize prototype_or_initial_map field of
  // JSFunction.
255 256 257 258
  CSA_ASSERT(this,
             IntPtrEqual(LoadMapInstanceSizeInWords(function_map),
                         IntPtrConstant(JSFunction::kSizeWithoutPrototype /
                                        kTaggedSize)));
259
  STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kTaggedSize);
260 261
  StoreMapNoWriteBarrier(function, function_map);
  StoreObjectFieldRoot(function, JSObject::kPropertiesOrHashOffset,
262
                       RootIndex::kEmptyFixedArray);
263
  StoreObjectFieldRoot(function, JSObject::kElementsOffset,
264
                       RootIndex::kEmptyFixedArray);
265
  StoreObjectFieldRoot(function, JSFunction::kFeedbackCellOffset,
266
                       RootIndex::kManyClosuresCell);
267

268 269
  TNode<SharedFunctionInfo> shared_info =
      CAST(LoadContextElement(native_context, context_index));
270 271 272 273
  StoreObjectFieldNoWriteBarrier(
      function, JSFunction::kSharedFunctionInfoOffset, shared_info);
  StoreObjectFieldNoWriteBarrier(function, JSFunction::kContextOffset, context);

274 275 276 277 278 279 280 281
  // For the native closures that are initialized here (for `await`)
  // we know that their SharedFunctionInfo::function_data() slot
  // 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);
  TNode<Code> code = LoadBuiltin(builtin_id);
282 283 284
  StoreObjectFieldNoWriteBarrier(function, JSFunction::kCodeOffset, code);
}

285
TNode<JSFunction> AsyncBuiltinsAssembler::CreateUnwrapClosure(
286
    TNode<NativeContext> native_context, TNode<Oddball> done) {
287 288 289
  const TNode<Map> map = CAST(LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
  const TNode<SharedFunctionInfo> on_fulfilled_shared = CAST(LoadContextElement(
290
      native_context, Context::ASYNC_ITERATOR_VALUE_UNWRAP_SHARED_FUN));
291
  const TNode<Context> closure_context =
292
      AllocateAsyncIteratorValueUnwrapContext(native_context, done);
293 294
  return AllocateFunctionWithMapAndContext(map, on_fulfilled_shared,
                                           closure_context);
295 296
}

297
TNode<Context> AsyncBuiltinsAssembler::AllocateAsyncIteratorValueUnwrapContext(
298
    TNode<NativeContext> native_context, TNode<Oddball> done) {
299 300
  CSA_ASSERT(this, IsBoolean(done));

301 302
  TNode<Context> context = AllocateSyntheticFunctionContext(
      native_context, ValueUnwrapContext::kLength);
303 304 305 306 307 308
  StoreContextElementNoWriteBarrier(context, ValueUnwrapContext::kDoneSlot,
                                    done);
  return context;
}

TF_BUILTIN(AsyncIteratorValueUnwrap, AsyncBuiltinsAssembler) {
309 310
  TNode<Object> value = CAST(Parameter(Descriptor::kValue));
  TNode<Context> context = CAST(Parameter(Descriptor::kContext));
311

312
  const TNode<Object> done =
313 314
      LoadContextElement(context, ValueUnwrapContext::kDoneSlot);
  CSA_ASSERT(this, IsBoolean(CAST(done)));
315

316
  const TNode<Object> unwrapped_value =
317
      CallBuiltin(Builtins::kCreateIterResultObject, context, value, done);
318 319 320 321

  Return(unwrapped_value);
}

322 323
}  // namespace internal
}  // namespace v8