| // Copyright 2019 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. |
| |
| #include 'src/builtins/builtins-promise.h' |
| #include 'src/builtins/builtins-promise-gen.h' |
| #include 'src/objects/property-array.h' |
| |
| namespace promise { |
| |
| struct PromiseAllWrapResultAsFulfilledFunctor { |
| macro Call(_nativeContext: NativeContext, value: JSAny): JSAny { |
| return value; |
| } |
| } |
| |
| struct PromiseAllSettledWrapResultAsFulfilledFunctor { |
| transitioning |
| macro Call(implicit context: Context)( |
| nativeContext: NativeContext, value: JSAny): JSAny { |
| // TODO(gsathya): Optimize the creation using a cached map to |
| // prevent transitions here. |
| // 9. Let obj be ! ObjectCreate(%ObjectPrototype%). |
| const objectFunction = |
| *NativeContextSlot(nativeContext, ContextSlot::OBJECT_FUNCTION_INDEX); |
| const objectFunctionMap = |
| UnsafeCast<Map>(objectFunction.prototype_or_initial_map); |
| const obj = AllocateJSObjectFromMap(objectFunctionMap); |
| |
| // 10. Perform ! CreateDataProperty(obj, "status", "fulfilled"). |
| FastCreateDataProperty( |
| obj, StringConstant('status'), StringConstant('fulfilled')); |
| |
| // 11. Perform ! CreateDataProperty(obj, "value", x). |
| FastCreateDataProperty(obj, StringConstant('value'), value); |
| return obj; |
| } |
| } |
| |
| struct PromiseAllSettledWrapResultAsRejectedFunctor { |
| transitioning |
| macro Call(implicit context: Context)( |
| nativeContext: NativeContext, value: JSAny): JSAny { |
| // TODO(gsathya): Optimize the creation using a cached map to |
| // prevent transitions here. |
| // 9. Let obj be ! ObjectCreate(%ObjectPrototype%). |
| const objectFunction = |
| *NativeContextSlot(nativeContext, ContextSlot::OBJECT_FUNCTION_INDEX); |
| const objectFunctionMap = |
| UnsafeCast<Map>(objectFunction.prototype_or_initial_map); |
| const obj = AllocateJSObjectFromMap(objectFunctionMap); |
| |
| // 10. Perform ! CreateDataProperty(obj, "status", "rejected"). |
| FastCreateDataProperty( |
| obj, StringConstant('status'), StringConstant('rejected')); |
| |
| // 11. Perform ! CreateDataProperty(obj, "reason", x). |
| FastCreateDataProperty(obj, StringConstant('reason'), value); |
| return obj; |
| } |
| } |
| |
| extern macro LoadJSReceiverIdentityHash(Object): intptr labels IfNoHash; |
| |
| type PromiseAllResolveElementContext extends FunctionContext; |
| extern enum PromiseAllResolveElementContextSlots extends intptr |
| constexpr 'PromiseBuiltins::PromiseAllResolveElementContextSlots' { |
| kPromiseAllResolveElementRemainingSlot: |
| Slot<PromiseAllResolveElementContext, Smi>, |
| kPromiseAllResolveElementCapabilitySlot: |
| Slot<PromiseAllResolveElementContext, PromiseCapability>, |
| kPromiseAllResolveElementValuesSlot: |
| Slot<PromiseAllResolveElementContext, FixedArray>, |
| kPromiseAllResolveElementLength |
| } |
| extern operator '[]=' macro StoreContextElement( |
| Context, constexpr PromiseAllResolveElementContextSlots, Object): void; |
| extern operator '[]' macro LoadContextElement( |
| Context, constexpr PromiseAllResolveElementContextSlots): Object; |
| |
| const kPropertyArrayNoHashSentinel: constexpr int31 |
| generates 'PropertyArray::kNoHashSentinel'; |
| |
| const kPropertyArrayHashFieldMax: constexpr int31 |
| generates 'PropertyArray::HashField::kMax'; |
| |
| transitioning macro PromiseAllResolveElementClosure<F: type>( |
| implicit context: PromiseAllResolveElementContext|NativeContext)( |
| value: JSAny, function: JSFunction, wrapResultFunctor: F, |
| hasResolveAndRejectClosures: constexpr bool): JSAny { |
| // We use the {function}s context as the marker to remember whether this |
| // resolve element closure was already called. It points to the resolve |
| // element context (which is a FunctionContext) until it was called the |
| // first time, in which case we make it point to the native context here |
| // to mark this resolve element closure as done. |
| let promiseContext: PromiseAllResolveElementContext; |
| typeswitch (context) { |
| case (NativeContext): deferred { |
| return Undefined; |
| } |
| case (context: PromiseAllResolveElementContext): { |
| promiseContext = context; |
| } |
| } |
| |
| assert( |
| promiseContext.length == |
| SmiTag(PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementLength)); |
| const nativeContext = LoadNativeContext(promiseContext); |
| function.context = nativeContext; |
| |
| // Determine the index from the {function}. |
| assert(kPropertyArrayNoHashSentinel == 0); |
| const identityHash = |
| LoadJSReceiverIdentityHash(function) otherwise unreachable; |
| assert(identityHash > 0); |
| const index = identityHash - 1; |
| |
| let remainingElementsCount = *ContextSlot( |
| promiseContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementRemainingSlot); |
| |
| let values = *ContextSlot( |
| promiseContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementValuesSlot); |
| const newCapacity = index + 1; |
| if (newCapacity > values.length_intptr) deferred { |
| // This happens only when the promises are resolved during iteration. |
| values = ExtractFixedArray(values, 0, values.length_intptr, newCapacity); |
| *ContextSlot( |
| promiseContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementValuesSlot) = values; |
| } |
| |
| // Promise.allSettled, for each input element, has both a resolve and a reject |
| // closure that share an [[AlreadyCalled]] boolean. That is, the input element |
| // can only be settled once: after resolve is called, reject returns early, |
| // and vice versa. Using {function}'s context as the marker only tracks |
| // per-closure instead of per-element. When the second resolve/reject closure |
| // is called on the same index, values.object[index] will already exist and |
| // will not be the hole value. In that case, return early. Everything up to |
| // this point is not yet observable to user code. This is not a problem for |
| // Promise.all since Promise.all has a single resolve closure (no reject) per |
| // element. |
| if (hasResolveAndRejectClosures) { |
| if (values.objects[index] != TheHole) deferred { |
| return Undefined; |
| } |
| } |
| |
| // Update the value depending on whether Promise.all or |
| // Promise.allSettled is called. |
| const updatedValue = wrapResultFunctor.Call(nativeContext, value); |
| |
| values.objects[index] = updatedValue; |
| |
| remainingElementsCount = remainingElementsCount - 1; |
| check(remainingElementsCount >= 0); |
| |
| *ContextSlot( |
| promiseContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementRemainingSlot) = remainingElementsCount; |
| if (remainingElementsCount == 0) { |
| const capability = *ContextSlot( |
| promiseContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementCapabilitySlot); |
| const resolve = UnsafeCast<JSAny>(capability.resolve); |
| const arrayMap = |
| *NativeContextSlot( |
| nativeContext, ContextSlot::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX); |
| const valuesArray = NewJSArray(arrayMap, values); |
| Call(promiseContext, resolve, Undefined, valuesArray); |
| } |
| return Undefined; |
| } |
| |
| transitioning javascript builtin |
| PromiseAllResolveElementClosure( |
| js-implicit context: Context, receiver: JSAny, |
| target: JSFunction)(value: JSAny): JSAny { |
| const context = |
| %RawDownCast<PromiseAllResolveElementContext|NativeContext>(context); |
| return PromiseAllResolveElementClosure( |
| value, target, PromiseAllWrapResultAsFulfilledFunctor{}, false); |
| } |
| |
| transitioning javascript builtin |
| PromiseAllSettledResolveElementClosure( |
| js-implicit context: Context, receiver: JSAny, |
| target: JSFunction)(value: JSAny): JSAny { |
| const context = |
| %RawDownCast<PromiseAllResolveElementContext|NativeContext>(context); |
| return PromiseAllResolveElementClosure( |
| value, target, PromiseAllSettledWrapResultAsFulfilledFunctor{}, true); |
| } |
| |
| transitioning javascript builtin |
| PromiseAllSettledRejectElementClosure( |
| js-implicit context: Context, receiver: JSAny, |
| target: JSFunction)(value: JSAny): JSAny { |
| const context = |
| %RawDownCast<PromiseAllResolveElementContext|NativeContext>(context); |
| return PromiseAllResolveElementClosure( |
| value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true); |
| } |
| } |