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