• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 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-gen.h'
6
7namespace promise {
8type PromiseAnyRejectElementContext extends FunctionContext;
9extern enum PromiseAnyRejectElementContextSlots extends intptr
10constexpr 'PromiseBuiltins::PromiseAnyRejectElementContextSlots' {
11  kPromiseAnyRejectElementRemainingSlot:
12      Slot<PromiseAnyRejectElementContext, Smi>,
13  kPromiseAnyRejectElementCapabilitySlot:
14      Slot<PromiseAnyRejectElementContext, PromiseCapability>,
15  kPromiseAnyRejectElementErrorsSlot:
16      Slot<PromiseAnyRejectElementContext, FixedArray>,
17  kPromiseAnyRejectElementLength
18}
19
20extern operator '[]=' macro StoreContextElement(
21    Context, constexpr PromiseAnyRejectElementContextSlots, Object): void;
22extern operator '[]' macro LoadContextElement(
23    Context, constexpr PromiseAnyRejectElementContextSlots): Object;
24
25// Creates the context used by all Promise.any reject element closures,
26// together with the errors array. Since all closures for a single Promise.any
27// call use the same context, we need to store the indices for the individual
28// closures somewhere else (we put them into the identity hash field of the
29// closures), and we also need to have a separate marker for when the closure
30// was called already (we slap the native context onto the closure in that
31// case to mark it's done). See Promise.all which uses the same approach.
32transitioning macro CreatePromiseAnyRejectElementContext(
33    implicit context: Context)(
34    capability: PromiseCapability,
35    nativeContext: NativeContext): PromiseAnyRejectElementContext {
36  const rejectContext = %RawDownCast<PromiseAnyRejectElementContext>(
37      AllocateSyntheticFunctionContext(
38          nativeContext,
39          PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength));
40  InitContextSlot(
41      rejectContext,
42      PromiseAnyRejectElementContextSlots::
43          kPromiseAnyRejectElementRemainingSlot,
44      1);
45  InitContextSlot(
46      rejectContext,
47      PromiseAnyRejectElementContextSlots::
48          kPromiseAnyRejectElementCapabilitySlot,
49      capability);
50  InitContextSlot(
51      rejectContext,
52      PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementErrorsSlot,
53      kEmptyFixedArray);
54  return rejectContext;
55}
56
57macro CreatePromiseAnyRejectElementFunction(implicit context: Context)(
58    rejectElementContext: PromiseAnyRejectElementContext, index: Smi,
59    nativeContext: NativeContext): JSFunction {
60  dcheck(index > 0);
61  dcheck(index < kPropertyArrayHashFieldMax);
62  const map = *ContextSlot(
63      nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
64  const rejectInfo = PromiseAnyRejectElementSharedFunConstant();
65  const reject =
66      AllocateFunctionWithMapAndContext(map, rejectInfo, rejectElementContext);
67  dcheck(kPropertyArrayNoHashSentinel == 0);
68  reject.properties_or_hash = index;
69  return reject;
70}
71
72// https://tc39.es/ecma262/#sec-promise.any-reject-element-functions
73transitioning javascript builtin
74PromiseAnyRejectElementClosure(
75    js-implicit context: Context, receiver: JSAny,
76    target: JSFunction)(value: JSAny): JSAny {
77  // 1. Let F be the active function object.
78
79  // 2. Let alreadyCalled be F.[[AlreadyCalled]].
80
81  // 3. If alreadyCalled.[[Value]] is true, return undefined.
82
83  // We use the function's context as the marker to remember whether this
84  // reject element closure was already called. It points to the reject
85  // element context (which is a FunctionContext) until it was called the
86  // first time, in which case we make it point to the native context here
87  // to mark this reject element closure as done.
88  if (IsNativeContext(context)) deferred {
89      return Undefined;
90    }
91
92  dcheck(
93      context.length ==
94      SmiTag(
95          PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength));
96  const context = %RawDownCast<PromiseAnyRejectElementContext>(context);
97
98  // 4. Set alreadyCalled.[[Value]] to true.
99  const nativeContext = LoadNativeContext(context);
100  target.context = nativeContext;
101
102  // 5. Let index be F.[[Index]].
103  dcheck(kPropertyArrayNoHashSentinel == 0);
104  const identityHash = LoadJSReceiverIdentityHash(target) otherwise unreachable;
105  dcheck(identityHash > 0);
106  const index = identityHash - 1;
107
108  // 6. Let errors be F.[[Errors]].
109  let errors = *ContextSlot(
110      context,
111      PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementErrorsSlot);
112
113  // 7. Let promiseCapability be F.[[Capability]].
114
115  // 8. Let remainingElementsCount be F.[[RemainingElements]].
116  let remainingElementsCount = *ContextSlot(
117      context,
118      PromiseAnyRejectElementContextSlots::
119          kPromiseAnyRejectElementRemainingSlot);
120
121  // 9. Set errors[index] to x.
122
123  // The max computation below is an optimization to avoid excessive allocations
124  // in the case of input promises being asynchronously rejected in ascending
125  // index order.
126  //
127  // Note that subtracting 1 from remainingElementsCount is intentional. The
128  // value of remainingElementsCount is 1 larger than the actual value during
129  // iteration. So in the case of synchronous rejection, newCapacity is the
130  // correct size by subtracting 1. In the case of asynchronous rejection this
131  // is 1 smaller than the correct size, but is not incorrect as it is maxed
132  // with index + 1.
133  const newCapacity =
134      IntPtrMax(SmiUntag(remainingElementsCount) - 1, index + 1);
135  if (newCapacity > errors.length_intptr) deferred {
136      errors = ExtractFixedArray(errors, 0, errors.length_intptr, newCapacity);
137      *ContextSlot(
138          context,
139          PromiseAnyRejectElementContextSlots::
140              kPromiseAnyRejectElementErrorsSlot) = errors;
141    }
142  errors.objects[index] = value;
143
144  // 10. Set remainingElementsCount.[[Value]] to
145  // remainingElementsCount.[[Value]] - 1.
146  remainingElementsCount = remainingElementsCount - 1;
147  *ContextSlot(
148      context,
149      PromiseAnyRejectElementContextSlots::
150          kPromiseAnyRejectElementRemainingSlot) = remainingElementsCount;
151
152  // 11. If remainingElementsCount.[[Value]] is 0, then
153  if (remainingElementsCount == 0) {
154    //   a. Let error be a newly created AggregateError object.
155
156    //   b. Set error.[[AggregateErrors]] to errors.
157    const error = ConstructAggregateError(errors);
158    //   c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »).
159    const capability = *ContextSlot(
160        context,
161        PromiseAnyRejectElementContextSlots::
162            kPromiseAnyRejectElementCapabilitySlot);
163    Call(context, UnsafeCast<Callable>(capability.reject), Undefined, error);
164  }
165
166  // 12. Return undefined.
167  return Undefined;
168}
169
170transitioning macro PerformPromiseAny(implicit context: Context)(
171    nativeContext: NativeContext, iteratorRecord: iterator::IteratorRecord,
172    constructor: Constructor, resultCapability: PromiseCapability,
173    promiseResolveFunction: JSAny): JSAny labels
174Reject(JSAny) {
175  // 1. Assert: ! IsConstructor(constructor) is true.
176  // 2. Assert: resultCapability is a PromiseCapability Record.
177
178  // 3. Let errors be a new empty List. (Do nothing: errors is
179  // initialized lazily when the first Promise rejects.)
180
181  // 4. Let remainingElementsCount be a new Record { [[Value]]: 1 }.
182  const rejectElementContext =
183      CreatePromiseAnyRejectElementContext(resultCapability, nativeContext);
184
185  // 5. Let index be 0.
186  //    (We subtract 1 in the PromiseAnyRejectElementClosure).
187  let index: Smi = 1;
188
189  try {
190    const fastIteratorResultMap = *NativeContextSlot(
191        nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX);
192    // 8. Repeat,
193    while (true) {
194      let nextValue: JSAny;
195      try {
196        // a. Let next be IteratorStep(iteratorRecord).
197
198        // b. If next is an abrupt completion, set
199        // iteratorRecord.[[Done]] to true.
200
201        // c. ReturnIfAbrupt(next).
202
203        // d. if next is false, then [continues below in "Done"]
204        const next: JSReceiver = iterator::IteratorStep(
205            iteratorRecord, fastIteratorResultMap) otherwise goto Done;
206        // e. Let nextValue be IteratorValue(next).
207
208        // f. If nextValue is an abrupt completion, set
209        // iteratorRecord.[[Done]] to true.
210
211        // g. ReturnIfAbrupt(nextValue).
212        nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
213      } catch (e, _message) {
214        goto Reject(e);
215      }
216
217      // We store the indices as identity hash on the reject element
218      // closures. Thus, we need this limit.
219      if (index == kPropertyArrayHashFieldMax) {
220        // If there are too many elements (currently more than
221        // 2**21-1), raise a RangeError here (which is caught later and
222        // turned into a rejection of the resulting promise). We could
223        // gracefully handle this case as well and support more than
224        // this number of elements by going to a separate function and
225        // pass the larger indices via a separate context, but it
226        // doesn't seem likely that we need this, and it's unclear how
227        // the rest of the system deals with 2**21 live Promises
228        // anyway.
229        ThrowRangeError(
230            MessageTemplate::kTooManyElementsInPromiseCombinator, 'any');
231      }
232
233      // h. Append undefined to errors. (Do nothing: errors is initialized
234      // lazily when the first Promise rejects.)
235
236      let nextPromise: JSAny;
237      // i. Let nextPromise be ? Call(constructor, promiseResolve,
238      // «nextValue »).
239      nextPromise = CallResolve(constructor, promiseResolveFunction, nextValue);
240
241      // j. Let steps be the algorithm steps defined in Promise.any
242      // Reject Element Functions.
243
244      // k. Let rejectElement be ! CreateBuiltinFunction(steps, «
245      // [[AlreadyCalled]], [[Index]],
246      // [[Errors]], [[Capability]], [[RemainingElements]] »).
247
248      // l. Set rejectElement.[[AlreadyCalled]] to a new Record {
249      // [[Value]]: false }.
250
251      // m. Set rejectElement.[[Index]] to index.
252
253      // n. Set rejectElement.[[Errors]] to errors.
254
255      // o. Set rejectElement.[[Capability]] to resultCapability.
256
257      // p. Set rejectElement.[[RemainingElements]] to
258      // remainingElementsCount.
259      const rejectElement = CreatePromiseAnyRejectElementFunction(
260          rejectElementContext, index, nativeContext);
261      // q. Set remainingElementsCount.[[Value]] to
262      // remainingElementsCount.[[Value]] + 1.
263      const remainingElementsCount = *ContextSlot(
264          rejectElementContext,
265          PromiseAnyRejectElementContextSlots::
266              kPromiseAnyRejectElementRemainingSlot);
267      *ContextSlot(
268          rejectElementContext,
269          PromiseAnyRejectElementContextSlots::
270              kPromiseAnyRejectElementRemainingSlot) =
271          remainingElementsCount + 1;
272
273      // r. Perform ? Invoke(nextPromise, "then", «
274      // resultCapability.[[Resolve]], rejectElement »).
275      let thenResult: JSAny;
276
277      const then = GetProperty(nextPromise, kThenString);
278      thenResult = Call(
279          context, then, nextPromise,
280          UnsafeCast<JSAny>(resultCapability.resolve), rejectElement);
281
282      // s. Increase index by 1.
283      index += 1;
284
285      // For catch prediction, mark that rejections here are
286      // semantically handled by the combined Promise.
287      if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred {
288          SetPropertyStrict(
289              context, thenResult, kPromiseHandledBySymbol,
290              resultCapability.promise);
291          SetPropertyStrict(
292              context, rejectElement, kPromiseForwardingHandlerSymbol, True);
293        }
294    }
295  } catch (e, _message) deferred {
296    iterator::IteratorCloseOnException(iteratorRecord);
297    goto Reject(e);
298  } label Done {}
299
300  // (8.d)
301  //   i. Set iteratorRecord.[[Done]] to true.
302  //  ii. Set remainingElementsCount.[[Value]] to
303  //  remainingElementsCount.[[Value]] - 1.
304  const remainingElementsCount = -- *ContextSlot(
305      rejectElementContext,
306      PromiseAnyRejectElementContextSlots::
307          kPromiseAnyRejectElementRemainingSlot);
308
309  // iii. If remainingElementsCount.[[Value]] is 0, then
310  if (remainingElementsCount == 0) deferred {
311      // 1. Let error be a newly created AggregateError object.
312      // 2. Set error.[[AggregateErrors]] to errors.
313
314      // We may already have elements in "errors" - this happens when the
315      // Thenable calls the reject callback immediately.
316      const errors: FixedArray = *ContextSlot(
317          rejectElementContext,
318          PromiseAnyRejectElementContextSlots::
319              kPromiseAnyRejectElementErrorsSlot);
320
321      check(errors.length == index - 1);
322      const error = ConstructAggregateError(errors);
323      // 3. Return ThrowCompletion(error).
324      goto Reject(error);
325    }
326  // iv. Return resultCapability.[[Promise]].
327  return resultCapability.promise;
328}
329
330// https://tc39.es/ecma262/#sec-promise.any
331transitioning javascript builtin
332PromiseAny(
333    js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
334  const nativeContext = LoadNativeContext(context);
335
336  // 1. Let C be the this value.
337  const receiver = Cast<JSReceiver>(receiver)
338      otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.any');
339
340  // 2. Let promiseCapability be ? NewPromiseCapability(C).
341  const capability = NewPromiseCapability(receiver, False);
342
343  // NewPromiseCapability guarantees that receiver is Constructor.
344  dcheck(Is<Constructor>(receiver));
345  const constructor = UnsafeCast<Constructor>(receiver);
346
347  try {
348    // 3. Let promiseResolve be GetPromiseResolve(C).
349    // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
350    // (catch below)
351    const promiseResolveFunction =
352        GetPromiseResolve(nativeContext, constructor);
353
354    // 5. Let iteratorRecord be GetIterator(iterable).
355
356    // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
357    // (catch below)
358    const iteratorRecord = iterator::GetIterator(iterable);
359
360    // 7. Let result be PerformPromiseAny(iteratorRecord, C,
361    // promiseCapability).
362
363    // 8. If result is an abrupt completion, then
364
365    //   a. If iteratorRecord.[[Done]] is false, set result to
366    //   IteratorClose(iteratorRecord, result).
367
368    //   b. IfAbruptRejectPromise(result, promiseCapability).
369
370    // [Iterator closing handled by PerformPromiseAny]
371
372    // 9. Return Completion(result).
373    return PerformPromiseAny(
374        nativeContext, iteratorRecord, constructor, capability,
375        promiseResolveFunction)
376        otherwise Reject;
377  } catch (e, _message) deferred {
378    goto Reject(e);
379  } label Reject(e: JSAny) deferred {
380    // Exception must be bound to a JS value.
381    dcheck(e != TheHole);
382    Call(
383        context, UnsafeCast<Callable>(capability.reject), Undefined,
384        UnsafeCast<JSAny>(e));
385    return capability.promise;
386  }
387}
388
389transitioning macro ConstructAggregateError(implicit context: Context)(
390    errors: FixedArray): JSObject {
391  const obj: JSObject = error::ConstructInternalAggregateErrorHelper(
392      context, SmiConstant(MessageTemplate::kAllPromisesRejected));
393  const errorsJSArray = array::CreateJSArrayWithElements(errors);
394  SetOwnPropertyIgnoreAttributes(
395      obj, ErrorsStringConstant(), errorsJSArray,
396      SmiConstant(PropertyAttributes::DONT_ENUM));
397  return obj;
398}
399
400extern macro PromiseAnyRejectElementSharedFunConstant(): SharedFunctionInfo;
401}
402