• 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  assert(index > 0);
61  assert(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  assert(kPropertyArrayNoHashSentinel == 0);
68  reject.properties_or_hash = index;
69  return reject;
70}
71
72// https://tc39.es/proposal-promise-any/#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  assert(
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  assert(kPropertyArrayNoHashSentinel == 0);
104  const identityHash = LoadJSReceiverIdentityHash(target) otherwise unreachable;
105  assert(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  const newCapacity = IntPtrMax(SmiUntag(remainingElementsCount), index + 1);
123  if (newCapacity > errors.length_intptr) deferred {
124      errors = ExtractFixedArray(errors, 0, errors.length_intptr, newCapacity);
125      *ContextSlot(
126          context,
127          PromiseAnyRejectElementContextSlots::
128              kPromiseAnyRejectElementErrorsSlot) = errors;
129    }
130  errors.objects[index] = value;
131
132  // 10. Set remainingElementsCount.[[Value]] to
133  // remainingElementsCount.[[Value]] - 1.
134  remainingElementsCount = remainingElementsCount - 1;
135  *ContextSlot(
136      context,
137      PromiseAnyRejectElementContextSlots::
138          kPromiseAnyRejectElementRemainingSlot) = remainingElementsCount;
139
140  // 11. If remainingElementsCount.[[Value]] is 0, then
141  if (remainingElementsCount == 0) {
142    //   a. Let error be a newly created AggregateError object.
143
144    //   b. Set error.[[AggregateErrors]] to errors.
145    const error = ConstructAggregateError(errors);
146    //   c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »).
147    const capability = *ContextSlot(
148        context,
149        PromiseAnyRejectElementContextSlots::
150            kPromiseAnyRejectElementCapabilitySlot);
151    Call(context, UnsafeCast<Callable>(capability.reject), Undefined, error);
152  }
153
154  // 12. Return undefined.
155  return Undefined;
156}
157
158transitioning macro PerformPromiseAny(implicit context: Context)(
159    nativeContext: NativeContext, iteratorRecord: iterator::IteratorRecord,
160    constructor: Constructor, resultCapability: PromiseCapability,
161    promiseResolveFunction: JSAny): JSAny labels
162Reject(Object) {
163  // 1. Assert: ! IsConstructor(constructor) is true.
164  // 2. Assert: resultCapability is a PromiseCapability Record.
165
166  // 3. Let errors be a new empty List. (Do nothing: errors is
167  // initialized lazily when the first Promise rejects.)
168
169  // 4. Let remainingElementsCount be a new Record { [[Value]]: 1 }.
170  const rejectElementContext =
171      CreatePromiseAnyRejectElementContext(resultCapability, nativeContext);
172
173  // 5. Let index be 0.
174  //    (We subtract 1 in the PromiseAnyRejectElementClosure).
175  let index: Smi = 1;
176
177  try {
178    const fastIteratorResultMap = *NativeContextSlot(
179        nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX);
180    // 8. Repeat,
181    while (true) {
182      let nextValue: JSAny;
183      try {
184        // a. Let next be IteratorStep(iteratorRecord).
185
186        // b. If next is an abrupt completion, set
187        // iteratorRecord.[[Done]] to true.
188
189        // c. ReturnIfAbrupt(next).
190
191        // d. if next is false, then [continues below in "Done"]
192        const next: JSReceiver = iterator::IteratorStep(
193            iteratorRecord, fastIteratorResultMap) otherwise goto Done;
194        // e. Let nextValue be IteratorValue(next).
195
196        // f. If nextValue is an abrupt completion, set
197        // iteratorRecord.[[Done]] to true.
198
199        // g. ReturnIfAbrupt(nextValue).
200        nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
201      } catch (e) {
202        goto Reject(e);
203      }
204
205      // We store the indices as identity hash on the reject element
206      // closures. Thus, we need this limit.
207      if (index == kPropertyArrayHashFieldMax) {
208        // If there are too many elements (currently more than
209        // 2**21-1), raise a RangeError here (which is caught later and
210        // turned into a rejection of the resulting promise). We could
211        // gracefully handle this case as well and support more than
212        // this number of elements by going to a separate function and
213        // pass the larger indices via a separate context, but it
214        // doesn't seem likely that we need this, and it's unclear how
215        // the rest of the system deals with 2**21 live Promises
216        // anyway.
217        ThrowRangeError(
218            MessageTemplate::kTooManyElementsInPromiseCombinator, 'any');
219      }
220
221      // h. Append undefined to errors. (Do nothing: errors is initialized
222      // lazily when the first Promise rejects.)
223
224      let nextPromise: JSAny;
225      // i. Let nextPromise be ? Call(constructor, promiseResolve,
226      // «nextValue »).
227      nextPromise = CallResolve(constructor, promiseResolveFunction, nextValue);
228
229      // j. Let steps be the algorithm steps defined in Promise.any
230      // Reject Element Functions.
231
232      // k. Let rejectElement be ! CreateBuiltinFunction(steps, «
233      // [[AlreadyCalled]], [[Index]],
234      // [[Errors]], [[Capability]], [[RemainingElements]] »).
235
236      // l. Set rejectElement.[[AlreadyCalled]] to a new Record {
237      // [[Value]]: false }.
238
239      // m. Set rejectElement.[[Index]] to index.
240
241      // n. Set rejectElement.[[Errors]] to errors.
242
243      // o. Set rejectElement.[[Capability]] to resultCapability.
244
245      // p. Set rejectElement.[[RemainingElements]] to
246      // remainingElementsCount.
247      const rejectElement = CreatePromiseAnyRejectElementFunction(
248          rejectElementContext, index, nativeContext);
249      // q. Set remainingElementsCount.[[Value]] to
250      // remainingElementsCount.[[Value]] + 1.
251      const remainingElementsCount = *ContextSlot(
252          rejectElementContext,
253          PromiseAnyRejectElementContextSlots::
254              kPromiseAnyRejectElementRemainingSlot);
255      *ContextSlot(
256          rejectElementContext,
257          PromiseAnyRejectElementContextSlots::
258              kPromiseAnyRejectElementRemainingSlot) =
259          remainingElementsCount + 1;
260
261      // r. Perform ? Invoke(nextPromise, "then", «
262      // resultCapability.[[Resolve]], rejectElement »).
263      let thenResult: JSAny;
264
265      const then = GetProperty(nextPromise, kThenString);
266      thenResult = Call(
267          context, then, nextPromise,
268          UnsafeCast<JSAny>(resultCapability.resolve), rejectElement);
269
270      // s. Increase index by 1.
271      index += 1;
272
273      // For catch prediction, mark that rejections here are
274      // semantically handled by the combined Promise.
275      if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred {
276          SetPropertyStrict(
277              context, thenResult, kPromiseHandledBySymbol,
278              resultCapability.promise);
279          SetPropertyStrict(
280              context, rejectElement, kPromiseForwardingHandlerSymbol, True);
281        }
282    }
283  } catch (e) deferred {
284    iterator::IteratorCloseOnException(iteratorRecord);
285    goto Reject(e);
286  } label Done {}
287
288  // (8.d)
289  //   i. Set iteratorRecord.[[Done]] to true.
290  //  ii. Set remainingElementsCount.[[Value]] to
291  //  remainingElementsCount.[[Value]] - 1.
292  const remainingElementsCount = -- *ContextSlot(
293      rejectElementContext,
294      PromiseAnyRejectElementContextSlots::
295          kPromiseAnyRejectElementRemainingSlot);
296
297  // iii. If remainingElementsCount.[[Value]] is 0, then
298  if (remainingElementsCount == 0) deferred {
299      // 1. Let error be a newly created AggregateError object.
300      // 2. Set error.[[AggregateErrors]] to errors.
301
302      // We may already have elements in "errors" - this happens when the
303      // Thenable calls the reject callback immediately.
304      const errors: FixedArray = *ContextSlot(
305          rejectElementContext,
306          PromiseAnyRejectElementContextSlots::
307              kPromiseAnyRejectElementErrorsSlot);
308
309      const error = ConstructAggregateError(errors);
310      // 3. Return ThrowCompletion(error).
311      goto Reject(error);
312    }
313  // iv. Return resultCapability.[[Promise]].
314  return resultCapability.promise;
315}
316
317// https://tc39.es/proposal-promise-any/#sec-promise.any
318transitioning javascript builtin
319PromiseAny(
320    js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
321  const nativeContext = LoadNativeContext(context);
322
323  // 1. Let C be the this value.
324  const receiver = Cast<JSReceiver>(receiver)
325      otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.any');
326
327  // 2. Let promiseCapability be ? NewPromiseCapability(C).
328  const capability = NewPromiseCapability(receiver, False);
329
330  // NewPromiseCapability guarantees that receiver is Constructor.
331  assert(Is<Constructor>(receiver));
332  const constructor = UnsafeCast<Constructor>(receiver);
333
334  try {
335    // 3. Let promiseResolve be GetPromiseResolve(C).
336    // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
337    // (catch below)
338    const promiseResolveFunction =
339        GetPromiseResolve(nativeContext, constructor);
340
341    // 5. Let iteratorRecord be GetIterator(iterable).
342
343    // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
344    // (catch below)
345    const iteratorRecord = iterator::GetIterator(iterable);
346
347    // 7. Let result be PerformPromiseAny(iteratorRecord, C,
348    // promiseCapability).
349
350    // 8. If result is an abrupt completion, then
351
352    //   a. If iteratorRecord.[[Done]] is false, set result to
353    //   IteratorClose(iteratorRecord, result).
354
355    //   b. IfAbruptRejectPromise(result, promiseCapability).
356
357    // [Iterator closing handled by PerformPromiseAny]
358
359    // 9. Return Completion(result).
360    return PerformPromiseAny(
361        nativeContext, iteratorRecord, constructor, capability,
362        promiseResolveFunction)
363        otherwise Reject;
364  } catch (e) deferred {
365    goto Reject(e);
366  } label Reject(e: Object) deferred {
367    // Exception must be bound to a JS value.
368    assert(e != TheHole);
369    Call(
370        context, UnsafeCast<Callable>(capability.reject), Undefined,
371        UnsafeCast<JSAny>(e));
372    return capability.promise;
373  }
374}
375
376transitioning macro ConstructAggregateError(implicit context: Context)(
377    errors: FixedArray): JSObject {
378  const obj: JSObject = error::ConstructInternalAggregateErrorHelper(
379      context, SmiConstant(MessageTemplate::kAllPromisesRejected));
380  const errorsJSArray = array::CreateJSArrayWithElements(errors);
381  SetOwnPropertyIgnoreAttributes(
382      obj, ErrorsStringConstant(), errorsJSArray,
383      SmiConstant(PropertyAttributes::DONT_ENUM));
384  return obj;
385}
386
387extern macro PromiseAnyRejectElementSharedFunConstant(): SharedFunctionInfo;
388}
389