• 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#include 'src/objects/property-array.h'
8
9namespace promise {
10
11struct PromiseAllWrapResultAsFulfilledFunctor {
12  macro Call(_nativeContext: NativeContext, value: JSAny): JSAny {
13    return value;
14  }
15}
16
17struct PromiseAllSettledWrapResultAsFulfilledFunctor {
18  transitioning
19  macro Call(implicit context: Context)(
20      nativeContext: NativeContext, value: JSAny): JSAny {
21    // TODO(gsathya): Optimize the creation using a cached map to
22    // prevent transitions here.
23    // 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
24    const objectFunction =
25        *NativeContextSlot(nativeContext, ContextSlot::OBJECT_FUNCTION_INDEX);
26    const objectFunctionMap =
27        UnsafeCast<Map>(objectFunction.prototype_or_initial_map);
28    const obj = AllocateJSObjectFromMap(objectFunctionMap);
29
30    // 10. Perform ! CreateDataProperty(obj, "status", "fulfilled").
31    FastCreateDataProperty(
32        obj, StringConstant('status'), StringConstant('fulfilled'));
33
34    // 11. Perform ! CreateDataProperty(obj, "value", x).
35    FastCreateDataProperty(obj, StringConstant('value'), value);
36    return obj;
37  }
38}
39
40struct PromiseAllSettledWrapResultAsRejectedFunctor {
41  transitioning
42  macro Call(implicit context: Context)(
43      nativeContext: NativeContext, value: JSAny): JSAny {
44    // TODO(gsathya): Optimize the creation using a cached map to
45    // prevent transitions here.
46    // 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
47    const objectFunction =
48        *NativeContextSlot(nativeContext, ContextSlot::OBJECT_FUNCTION_INDEX);
49    const objectFunctionMap =
50        UnsafeCast<Map>(objectFunction.prototype_or_initial_map);
51    const obj = AllocateJSObjectFromMap(objectFunctionMap);
52
53    // 10. Perform ! CreateDataProperty(obj, "status", "rejected").
54    FastCreateDataProperty(
55        obj, StringConstant('status'), StringConstant('rejected'));
56
57    // 11. Perform ! CreateDataProperty(obj, "reason", x).
58    FastCreateDataProperty(obj, StringConstant('reason'), value);
59    return obj;
60  }
61}
62
63extern macro LoadJSReceiverIdentityHash(JSReceiver): intptr labels IfNoHash;
64
65type PromiseAllResolveElementContext extends FunctionContext;
66extern enum PromiseAllResolveElementContextSlots extends intptr
67constexpr 'PromiseBuiltins::PromiseAllResolveElementContextSlots' {
68  kPromiseAllResolveElementRemainingSlot:
69      Slot<PromiseAllResolveElementContext, Smi>,
70  kPromiseAllResolveElementCapabilitySlot:
71      Slot<PromiseAllResolveElementContext, PromiseCapability>,
72  kPromiseAllResolveElementValuesSlot:
73      Slot<PromiseAllResolveElementContext, FixedArray>,
74  kPromiseAllResolveElementLength
75}
76extern operator '[]=' macro StoreContextElement(
77    Context, constexpr PromiseAllResolveElementContextSlots, Object): void;
78extern operator '[]' macro LoadContextElement(
79    Context, constexpr PromiseAllResolveElementContextSlots): Object;
80
81const kPropertyArrayNoHashSentinel: constexpr int31
82    generates 'PropertyArray::kNoHashSentinel';
83
84const kPropertyArrayHashFieldMax: constexpr int31
85    generates 'PropertyArray::HashField::kMax';
86
87transitioning macro PromiseAllResolveElementClosure<F: type>(
88    implicit context: PromiseAllResolveElementContext|NativeContext)(
89    value: JSAny, function: JSFunction, wrapResultFunctor: F,
90    hasResolveAndRejectClosures: constexpr bool): JSAny {
91  // We use the {function}s context as the marker to remember whether this
92  // resolve element closure was already called. It points to the resolve
93  // element context (which is a FunctionContext) until it was called the
94  // first time, in which case we make it point to the native context here
95  // to mark this resolve element closure as done.
96  let promiseContext: PromiseAllResolveElementContext;
97  typeswitch (context) {
98    case (NativeContext): deferred {
99      return Undefined;
100    }
101    case (context: PromiseAllResolveElementContext): {
102      promiseContext = context;
103    }
104  }
105
106  dcheck(
107      promiseContext.length ==
108      SmiTag(PromiseAllResolveElementContextSlots::
109                 kPromiseAllResolveElementLength));
110  const nativeContext = LoadNativeContext(promiseContext);
111  function.context = nativeContext;
112
113  // Determine the index from the {function}.
114  dcheck(kPropertyArrayNoHashSentinel == 0);
115  const identityHash =
116      LoadJSReceiverIdentityHash(function) otherwise unreachable;
117  dcheck(identityHash > 0);
118  const index = identityHash - 1;
119
120  let remainingElementsCount = *ContextSlot(
121      promiseContext,
122      PromiseAllResolveElementContextSlots::
123          kPromiseAllResolveElementRemainingSlot);
124
125  let values = *ContextSlot(
126      promiseContext,
127      PromiseAllResolveElementContextSlots::
128          kPromiseAllResolveElementValuesSlot);
129  const newCapacity = index + 1;
130  if (newCapacity > values.length_intptr) deferred {
131      // This happens only when the promises are resolved during iteration.
132      values = ExtractFixedArray(values, 0, values.length_intptr, newCapacity);
133      *ContextSlot(
134          promiseContext,
135          PromiseAllResolveElementContextSlots::
136              kPromiseAllResolveElementValuesSlot) = values;
137    }
138
139  // Promise.allSettled, for each input element, has both a resolve and a reject
140  // closure that share an [[AlreadyCalled]] boolean. That is, the input element
141  // can only be settled once: after resolve is called, reject returns early,
142  // and vice versa. Using {function}'s context as the marker only tracks
143  // per-closure instead of per-element. When the second resolve/reject closure
144  // is called on the same index, values.object[index] will already exist and
145  // will not be the hole value. In that case, return early. Everything up to
146  // this point is not yet observable to user code. This is not a problem for
147  // Promise.all since Promise.all has a single resolve closure (no reject) per
148  // element.
149  if (hasResolveAndRejectClosures) {
150    if (values.objects[index] != TheHole) deferred {
151        return Undefined;
152      }
153  }
154
155  // Update the value depending on whether Promise.all or
156  // Promise.allSettled is called.
157  const updatedValue = wrapResultFunctor.Call(nativeContext, value);
158
159  values.objects[index] = updatedValue;
160
161  remainingElementsCount = remainingElementsCount - 1;
162  check(remainingElementsCount >= 0);
163
164  *ContextSlot(
165      promiseContext,
166      PromiseAllResolveElementContextSlots::
167          kPromiseAllResolveElementRemainingSlot) = remainingElementsCount;
168  if (remainingElementsCount == 0) {
169    const capability = *ContextSlot(
170        promiseContext,
171        PromiseAllResolveElementContextSlots::
172            kPromiseAllResolveElementCapabilitySlot);
173    const resolve = UnsafeCast<JSAny>(capability.resolve);
174    const arrayMap =
175        *NativeContextSlot(
176        nativeContext, ContextSlot::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX);
177    const valuesArray = NewJSArray(arrayMap, values);
178    Call(promiseContext, resolve, Undefined, valuesArray);
179  }
180  return Undefined;
181}
182
183transitioning javascript builtin
184PromiseAllResolveElementClosure(
185    js-implicit context: Context, receiver: JSAny,
186    target: JSFunction)(value: JSAny): JSAny {
187  const context =
188      %RawDownCast<PromiseAllResolveElementContext|NativeContext>(context);
189  return PromiseAllResolveElementClosure(
190      value, target, PromiseAllWrapResultAsFulfilledFunctor{}, false);
191}
192
193transitioning javascript builtin
194PromiseAllSettledResolveElementClosure(
195    js-implicit context: Context, receiver: JSAny,
196    target: JSFunction)(value: JSAny): JSAny {
197  const context =
198      %RawDownCast<PromiseAllResolveElementContext|NativeContext>(context);
199  return PromiseAllResolveElementClosure(
200      value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}, true);
201}
202
203transitioning javascript builtin
204PromiseAllSettledRejectElementClosure(
205    js-implicit context: Context, receiver: JSAny,
206    target: JSFunction)(value: JSAny): JSAny {
207  const context =
208      %RawDownCast<PromiseAllResolveElementContext|NativeContext>(context);
209  return PromiseAllResolveElementClosure(
210      value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true);
211}
212}
213