• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 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-promise.h'
6#include 'src/builtins/builtins-promise-gen.h'
7
8namespace promise {
9const kPromiseBuiltinsPromiseContextLength: constexpr int31
10    generates 'PromiseBuiltins::kPromiseContextLength';
11
12// Creates the context used by all Promise.all resolve element closures,
13// together with the values array. Since all closures for a single Promise.all
14// call use the same context, we need to store the indices for the individual
15// closures somewhere else (we put them into the identity hash field of the
16// closures), and we also need to have a separate marker for when the closure
17// was called already (we slap the native context onto the closure in that
18// case to mark it's done).
19macro CreatePromiseAllResolveElementContext(implicit context: Context)(
20    capability: PromiseCapability,
21    nativeContext: NativeContext): PromiseAllResolveElementContext {
22  const resolveContext = %RawDownCast<
23      PromiseAllResolveElementContext>(AllocateSyntheticFunctionContext(
24      nativeContext,
25      PromiseAllResolveElementContextSlots::kPromiseAllResolveElementLength));
26  InitContextSlot(
27      resolveContext,
28      PromiseAllResolveElementContextSlots::
29          kPromiseAllResolveElementRemainingSlot,
30      1);
31  InitContextSlot(
32      resolveContext,
33      PromiseAllResolveElementContextSlots::
34          kPromiseAllResolveElementCapabilitySlot,
35      capability);
36  InitContextSlot(
37      resolveContext,
38      PromiseAllResolveElementContextSlots::kPromiseAllResolveElementValuesSlot,
39      kEmptyFixedArray);
40  return resolveContext;
41}
42
43macro CreatePromiseAllResolveElementFunction(implicit context: Context)(
44    resolveElementContext: PromiseAllResolveElementContext, index: Smi,
45    nativeContext: NativeContext,
46    resolveFunction: SharedFunctionInfo): JSFunction {
47  dcheck(index > 0);
48  dcheck(index < kPropertyArrayHashFieldMax);
49
50  const map = *ContextSlot(
51      nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
52  const resolve = AllocateFunctionWithMapAndContext(
53      map, resolveFunction, resolveElementContext);
54
55  dcheck(kPropertyArrayNoHashSentinel == 0);
56  resolve.properties_or_hash = index;
57  return resolve;
58}
59
60@export
61macro CreatePromiseResolvingFunctionsContext(implicit context: Context)(
62    promise: JSPromise, debugEvent: Boolean, nativeContext: NativeContext):
63    PromiseResolvingFunctionContext {
64  const resolveContext = %RawDownCast<PromiseResolvingFunctionContext>(
65      AllocateSyntheticFunctionContext(
66          nativeContext,
67          PromiseResolvingFunctionContextSlot::kPromiseContextLength));
68  InitContextSlot(
69      resolveContext, PromiseResolvingFunctionContextSlot::kPromiseSlot,
70      promise);
71  InitContextSlot(
72      resolveContext, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot,
73      False);
74  InitContextSlot(
75      resolveContext, PromiseResolvingFunctionContextSlot::kDebugEventSlot,
76      debugEvent);
77  static_assert(
78      PromiseResolvingFunctionContextSlot::kPromiseContextLength ==
79      ContextSlot::MIN_CONTEXT_SLOTS + 3);
80  return resolveContext;
81}
82
83macro IsPromiseThenLookupChainIntact(implicit context: Context)(
84    nativeContext: NativeContext, receiverMap: Map): bool {
85  if (IsForceSlowPath()) return false;
86  if (!IsJSPromiseMap(receiverMap)) return false;
87  if (receiverMap.prototype != *NativeContextSlot(
88          nativeContext, ContextSlot::PROMISE_PROTOTYPE_INDEX)) {
89    return false;
90  }
91  return !IsPromiseThenProtectorCellInvalid();
92}
93
94struct PromiseAllResolveElementFunctor {
95  macro Call(implicit context: Context)(
96      resolveElementContext: PromiseAllResolveElementContext,
97      nativeContext: NativeContext, index: Smi,
98      _capability: PromiseCapability): Callable {
99    return CreatePromiseAllResolveElementFunction(
100        resolveElementContext, index, nativeContext,
101        PromiseAllResolveElementSharedFunConstant());
102  }
103}
104
105struct PromiseAllRejectElementFunctor {
106  macro Call(implicit context: Context)(
107      _resolveElementContext: PromiseAllResolveElementContext,
108      _nativeContext: NativeContext, _index: Smi,
109      capability: PromiseCapability): Callable {
110    return UnsafeCast<Callable>(capability.reject);
111  }
112}
113
114struct PromiseAllSettledResolveElementFunctor {
115  macro Call(implicit context: Context)(
116      resolveElementContext: PromiseAllResolveElementContext,
117      nativeContext: NativeContext, index: Smi,
118      _capability: PromiseCapability): Callable {
119    return CreatePromiseAllResolveElementFunction(
120        resolveElementContext, index, nativeContext,
121        PromiseAllSettledResolveElementSharedFunConstant());
122  }
123}
124
125struct PromiseAllSettledRejectElementFunctor {
126  macro Call(implicit context: Context)(
127      resolveElementContext: PromiseAllResolveElementContext,
128      nativeContext: NativeContext, index: Smi,
129      _capability: PromiseCapability): Callable {
130    return CreatePromiseAllResolveElementFunction(
131        resolveElementContext, index, nativeContext,
132        PromiseAllSettledRejectElementSharedFunConstant());
133  }
134}
135
136transitioning macro PerformPromiseAll<F1: type, F2: type>(
137    implicit context: Context)(
138    nativeContext: NativeContext, iter: iterator::IteratorRecord,
139    constructor: Constructor, capability: PromiseCapability,
140    promiseResolveFunction: JSAny, createResolveElementFunctor: F1,
141    createRejectElementFunctor: F2): JSAny labels
142Reject(JSAny) {
143  const promise = capability.promise;
144  const resolve = capability.resolve;
145  const reject = capability.reject;
146
147  // For catch prediction, don't treat the .then calls as handling it;
148  // instead, recurse outwards.
149  if (IsDebugActive()) deferred {
150      SetPropertyStrict(context, reject, kPromiseForwardingHandlerSymbol, True);
151    }
152
153  const resolveElementContext =
154      CreatePromiseAllResolveElementContext(capability, nativeContext);
155
156  let index: Smi = 1;
157
158  try {
159    const fastIteratorResultMap = *NativeContextSlot(
160        nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX);
161    while (true) {
162      let nextValue: JSAny;
163      try {
164        // Let next be IteratorStep(iteratorRecord.[[Iterator]]).
165        // If next is an abrupt completion, set iteratorRecord.[[Done]] to
166        // true. ReturnIfAbrupt(next).
167        const next: JSReceiver = iterator::IteratorStep(
168            iter, fastIteratorResultMap) otherwise goto Done;
169
170        // Let nextValue be IteratorValue(next).
171        // If nextValue is an abrupt completion, set iteratorRecord.[[Done]]
172        // to true.
173        // ReturnIfAbrupt(nextValue).
174        nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
175      } catch (e, _message) {
176        goto Reject(e);
177      }
178
179      // Check if we reached the limit.
180      if (index == kPropertyArrayHashFieldMax) {
181        // If there are too many elements (currently more than 2**21-1),
182        // raise a RangeError here (which is caught below and turned into
183        // a rejection of the resulting promise). We could gracefully handle
184        // this case as well and support more than this number of elements
185        // by going to a separate function and pass the larger indices via a
186        // separate context, but it doesn't seem likely that we need this,
187        // and it's unclear how the rest of the system deals with 2**21 live
188        // Promises anyway.
189        ThrowRangeError(
190            MessageTemplate::kTooManyElementsInPromiseCombinator, 'all');
191      }
192
193      // Set remainingElementsCount.[[Value]] to
194      //     remainingElementsCount.[[Value]] + 1.
195      *ContextSlot(
196          resolveElementContext,
197          PromiseAllResolveElementContextSlots::
198              kPromiseAllResolveElementRemainingSlot) += 1;
199
200      // Let resolveElement be CreateBuiltinFunction(steps,
201      //                                             « [[AlreadyCalled]],
202      //                                               [[Index]],
203      //                                               [[Values]],
204      //                                               [[Capability]],
205      //                                               [[RemainingElements]]
206      //                                               »).
207      // Set resolveElement.[[AlreadyCalled]] to a Record { [[Value]]: false
208      // }. Set resolveElement.[[Index]] to index. Set
209      // resolveElement.[[Values]] to values. Set
210      // resolveElement.[[Capability]] to resultCapability. Set
211      // resolveElement.[[RemainingElements]] to remainingElementsCount.
212      const resolveElementFun = createResolveElementFunctor.Call(
213          resolveElementContext, nativeContext, index, capability);
214      const rejectElementFun = createRejectElementFunctor.Call(
215          resolveElementContext, nativeContext, index, capability);
216
217      // We can skip the "then" lookup on the result of the "resolve" call and
218      // immediately chain the continuation onto the {next_value} if:
219      //
220      //   (a) The {constructor} is the intrinsic %Promise% function, and
221      //       looking up "resolve" on {constructor} yields the initial
222      //       Promise.resolve() builtin, and
223      //   (b) the promise @@species protector cell is valid, meaning that
224      //       no one messed with the Symbol.species property on any
225      //       intrinsic promise or on the Promise.prototype, and
226      //   (c) the {next_value} is a JSPromise whose [[Prototype]] field
227      //       contains the intrinsic %PromisePrototype%, and
228      //   (d) we're not running with async_hooks or DevTools enabled.
229      //
230      // In that case we also don't need to allocate a chained promise for
231      // the PromiseReaction (aka we can pass undefined to
232      // PerformPromiseThen), since this is only necessary for DevTools and
233      // PromiseHooks.
234      if (promiseResolveFunction != Undefined || NeedsAnyPromiseHooks() ||
235          IsPromiseSpeciesProtectorCellInvalid() || Is<Smi>(nextValue) ||
236          !IsPromiseThenLookupChainIntact(
237              nativeContext, UnsafeCast<HeapObject>(nextValue).map)) {
238        // Let nextPromise be ? Call(constructor, _promiseResolve_, «
239        // nextValue »).
240        const nextPromise =
241            CallResolve(constructor, promiseResolveFunction, nextValue);
242
243        // Perform ? Invoke(nextPromise, "then", « resolveElement,
244        //                  resultCapability.[[Reject]] »).
245        const then = GetProperty(nextPromise, kThenString);
246        const thenResult = Call(
247            nativeContext, then, nextPromise, resolveElementFun,
248            rejectElementFun);
249
250        // For catch prediction, mark that rejections here are
251        // semantically handled by the combined Promise.
252        if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred {
253            SetPropertyStrict(
254                context, thenResult, kPromiseHandledBySymbol, promise);
255          }
256      } else {
257        PerformPromiseThenImpl(
258            UnsafeCast<JSPromise>(nextValue), resolveElementFun,
259            rejectElementFun, Undefined);
260      }
261
262      // Set index to index + 1.
263      index += 1;
264    }
265  } catch (e, _message) deferred {
266    iterator::IteratorCloseOnException(iter);
267    goto Reject(e);
268  } label Done {}
269
270  // Set iteratorRecord.[[Done]] to true.
271  // Set remainingElementsCount.[[Value]] to
272  //    remainingElementsCount.[[Value]] - 1.
273  const remainingElementsCount = -- *ContextSlot(
274      resolveElementContext,
275      PromiseAllResolveElementContextSlots::
276          kPromiseAllResolveElementRemainingSlot);
277
278  check(remainingElementsCount >= 0);
279
280  if (remainingElementsCount > 0) {
281    // Pre-allocate the backing store for the {values} to the desired
282    // capacity. We may already have elements in "values" - this happens
283    // when the Thenable calls the resolve callback immediately.
284    const valuesRef:&FixedArray = ContextSlot(
285        resolveElementContext,
286        PromiseAllResolveElementContextSlots::
287            kPromiseAllResolveElementValuesSlot);
288    const values = *valuesRef;
289    // 'index' is a 1-based index and incremented after every Promise. Later we
290    // use 'values' as a 0-based array, so capacity 'index - 1' is enough.
291    const newCapacity = SmiUntag(index) - 1;
292
293    const oldCapacity = values.length_intptr;
294    if (oldCapacity < newCapacity) {
295      *valuesRef = ExtractFixedArray(values, 0, oldCapacity, newCapacity);
296    }
297  } else
298    deferred {
299      // If remainingElementsCount.[[Value]] is 0, then
300      //     Let valuesArray be CreateArrayFromList(values).
301      //     Perform ? Call(resultCapability.[[Resolve]], undefined,
302      //                    « valuesArray »).
303
304      const values: FixedArray = *ContextSlot(
305          resolveElementContext,
306          PromiseAllResolveElementContextSlots::
307              kPromiseAllResolveElementValuesSlot);
308      const arrayMap =
309          *NativeContextSlot(
310          nativeContext, ContextSlot::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX);
311      const valuesArray = NewJSArray(arrayMap, values);
312      Call(nativeContext, UnsafeCast<JSAny>(resolve), Undefined, valuesArray);
313    }
314
315  // Return resultCapability.[[Promise]].
316  return promise;
317}
318
319transitioning macro GeneratePromiseAll<F1: type, F2: type>(
320    implicit context: Context)(
321    receiver: JSAny, iterable: JSAny, createResolveElementFunctor: F1,
322    createRejectElementFunctor: F2, message: constexpr string): JSAny {
323  const nativeContext = LoadNativeContext(context);
324  // Let C be the this value.
325  // If Type(C) is not Object, throw a TypeError exception.
326  const receiver = Cast<JSReceiver>(receiver)
327      otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, message);
328
329  // Let promiseCapability be ? NewPromiseCapability(C).
330  // Don't fire debugEvent so that forwarding the rejection through all does
331  // not trigger redundant ExceptionEvents
332  const capability = NewPromiseCapability(receiver, False);
333
334  // NewPromiseCapability guarantees that receiver is Constructor.
335  dcheck(Is<Constructor>(receiver));
336  const constructor = UnsafeCast<Constructor>(receiver);
337
338  try {
339    // Let promiseResolve be GetPromiseResolve(C).
340    // IfAbruptRejectPromise(promiseResolve, promiseCapability).
341    const promiseResolveFunction =
342        GetPromiseResolve(nativeContext, constructor);
343
344    // Let iterator be GetIterator(iterable).
345    // IfAbruptRejectPromise(iterator, promiseCapability).
346    let i = iterator::GetIterator(iterable);
347
348    // Let result be PerformPromiseAll(iteratorRecord, C,
349    // promiseCapability). If result is an abrupt completion, then
350    //   If iteratorRecord.[[Done]] is false, let result be
351    //       IteratorClose(iterator, result).
352    //    IfAbruptRejectPromise(result, promiseCapability).
353    return PerformPromiseAll(
354        nativeContext, i, constructor, capability, promiseResolveFunction,
355        createResolveElementFunctor, createRejectElementFunctor)
356        otherwise Reject;
357  } catch (e, _message) deferred {
358    goto Reject(e);
359  } label Reject(e: JSAny) deferred {
360    const reject = UnsafeCast<JSAny>(capability.reject);
361    Call(context, reject, Undefined, e);
362    return capability.promise;
363  }
364}
365
366// ES#sec-promise.all
367transitioning javascript builtin PromiseAll(
368    js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
369  return GeneratePromiseAll(
370      receiver, iterable, PromiseAllResolveElementFunctor{},
371      PromiseAllRejectElementFunctor{}, 'Promise.all');
372}
373
374// ES#sec-promise.allsettled
375// Promise.allSettled ( iterable )
376transitioning javascript builtin PromiseAllSettled(
377    js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
378  return GeneratePromiseAll(
379      receiver, iterable, PromiseAllSettledResolveElementFunctor{},
380      PromiseAllSettledRejectElementFunctor{}, 'Promise.allSettled');
381}
382
383extern macro PromiseAllResolveElementSharedFunConstant(): SharedFunctionInfo;
384extern macro PromiseAllSettledRejectElementSharedFunConstant():
385    SharedFunctionInfo;
386extern macro PromiseAllSettledResolveElementSharedFunConstant():
387    SharedFunctionInfo;
388}
389