// 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(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(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(JSReceiver): intptr labels IfNoHash; type PromiseAllResolveElementContext extends FunctionContext; extern enum PromiseAllResolveElementContextSlots extends intptr constexpr 'PromiseBuiltins::PromiseAllResolveElementContextSlots' { kPromiseAllResolveElementRemainingSlot: Slot, kPromiseAllResolveElementCapabilitySlot: Slot, kPromiseAllResolveElementValuesSlot: Slot, 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( 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; } } dcheck( promiseContext.length == SmiTag(PromiseAllResolveElementContextSlots:: kPromiseAllResolveElementLength)); const nativeContext = LoadNativeContext(promiseContext); function.context = nativeContext; // Determine the index from the {function}. dcheck(kPropertyArrayNoHashSentinel == 0); const identityHash = LoadJSReceiverIdentityHash(function) otherwise unreachable; dcheck(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(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(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(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(context); return PromiseAllResolveElementClosure( value, target, PromiseAllSettledWrapResultAsRejectedFunctor{}, true); } }