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 8namespace promise { 9const kPromiseBuiltinsPromiseContextLength: constexpr int31 10 generates 'PromiseBuiltins::kPromiseContextLength'; 11 12// Creates the context used by all Promise.all resolve element closures, 13// together with the values array. Since all closures for a single Promise.all 14// call use the same context, we need to store the indices for the individual 15// closures somewhere else (we put them into the identity hash field of the 16// closures), and we also need to have a separate marker for when the closure 17// was called already (we slap the native context onto the closure in that 18// case to mark it's done). 19macro CreatePromiseAllResolveElementContext(implicit context: Context)( 20 capability: PromiseCapability, 21 nativeContext: NativeContext): PromiseAllResolveElementContext { 22 const resolveContext = %RawDownCast< 23 PromiseAllResolveElementContext>(AllocateSyntheticFunctionContext( 24 nativeContext, 25 PromiseAllResolveElementContextSlots::kPromiseAllResolveElementLength)); 26 InitContextSlot( 27 resolveContext, 28 PromiseAllResolveElementContextSlots:: 29 kPromiseAllResolveElementRemainingSlot, 30 1); 31 InitContextSlot( 32 resolveContext, 33 PromiseAllResolveElementContextSlots:: 34 kPromiseAllResolveElementCapabilitySlot, 35 capability); 36 InitContextSlot( 37 resolveContext, 38 PromiseAllResolveElementContextSlots::kPromiseAllResolveElementValuesSlot, 39 kEmptyFixedArray); 40 return resolveContext; 41} 42 43macro CreatePromiseAllResolveElementFunction(implicit context: Context)( 44 resolveElementContext: PromiseAllResolveElementContext, index: Smi, 45 nativeContext: NativeContext, 46 resolveFunction: SharedFunctionInfo): JSFunction { 47 dcheck(index > 0); 48 dcheck(index < kPropertyArrayHashFieldMax); 49 50 const map = *ContextSlot( 51 nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); 52 const resolve = AllocateFunctionWithMapAndContext( 53 map, resolveFunction, resolveElementContext); 54 55 dcheck(kPropertyArrayNoHashSentinel == 0); 56 resolve.properties_or_hash = index; 57 return resolve; 58} 59 60@export 61macro CreatePromiseResolvingFunctionsContext(implicit context: Context)( 62 promise: JSPromise, debugEvent: Boolean, nativeContext: NativeContext): 63 PromiseResolvingFunctionContext { 64 const resolveContext = %RawDownCast<PromiseResolvingFunctionContext>( 65 AllocateSyntheticFunctionContext( 66 nativeContext, 67 PromiseResolvingFunctionContextSlot::kPromiseContextLength)); 68 InitContextSlot( 69 resolveContext, PromiseResolvingFunctionContextSlot::kPromiseSlot, 70 promise); 71 InitContextSlot( 72 resolveContext, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot, 73 False); 74 InitContextSlot( 75 resolveContext, PromiseResolvingFunctionContextSlot::kDebugEventSlot, 76 debugEvent); 77 static_assert( 78 PromiseResolvingFunctionContextSlot::kPromiseContextLength == 79 ContextSlot::MIN_CONTEXT_SLOTS + 3); 80 return resolveContext; 81} 82 83macro IsPromiseThenLookupChainIntact(implicit context: Context)( 84 nativeContext: NativeContext, receiverMap: Map): bool { 85 if (IsForceSlowPath()) return false; 86 if (!IsJSPromiseMap(receiverMap)) return false; 87 if (receiverMap.prototype != *NativeContextSlot( 88 nativeContext, ContextSlot::PROMISE_PROTOTYPE_INDEX)) { 89 return false; 90 } 91 return !IsPromiseThenProtectorCellInvalid(); 92} 93 94struct PromiseAllResolveElementFunctor { 95 macro Call(implicit context: Context)( 96 resolveElementContext: PromiseAllResolveElementContext, 97 nativeContext: NativeContext, index: Smi, 98 _capability: PromiseCapability): Callable { 99 return CreatePromiseAllResolveElementFunction( 100 resolveElementContext, index, nativeContext, 101 PromiseAllResolveElementSharedFunConstant()); 102 } 103} 104 105struct PromiseAllRejectElementFunctor { 106 macro Call(implicit context: Context)( 107 _resolveElementContext: PromiseAllResolveElementContext, 108 _nativeContext: NativeContext, _index: Smi, 109 capability: PromiseCapability): Callable { 110 return UnsafeCast<Callable>(capability.reject); 111 } 112} 113 114struct PromiseAllSettledResolveElementFunctor { 115 macro Call(implicit context: Context)( 116 resolveElementContext: PromiseAllResolveElementContext, 117 nativeContext: NativeContext, index: Smi, 118 _capability: PromiseCapability): Callable { 119 return CreatePromiseAllResolveElementFunction( 120 resolveElementContext, index, nativeContext, 121 PromiseAllSettledResolveElementSharedFunConstant()); 122 } 123} 124 125struct PromiseAllSettledRejectElementFunctor { 126 macro Call(implicit context: Context)( 127 resolveElementContext: PromiseAllResolveElementContext, 128 nativeContext: NativeContext, index: Smi, 129 _capability: PromiseCapability): Callable { 130 return CreatePromiseAllResolveElementFunction( 131 resolveElementContext, index, nativeContext, 132 PromiseAllSettledRejectElementSharedFunConstant()); 133 } 134} 135 136transitioning macro PerformPromiseAll<F1: type, F2: type>( 137 implicit context: Context)( 138 nativeContext: NativeContext, iter: iterator::IteratorRecord, 139 constructor: Constructor, capability: PromiseCapability, 140 promiseResolveFunction: JSAny, createResolveElementFunctor: F1, 141 createRejectElementFunctor: F2): JSAny labels 142Reject(JSAny) { 143 const promise = capability.promise; 144 const resolve = capability.resolve; 145 const reject = capability.reject; 146 147 // For catch prediction, don't treat the .then calls as handling it; 148 // instead, recurse outwards. 149 if (IsDebugActive()) deferred { 150 SetPropertyStrict(context, reject, kPromiseForwardingHandlerSymbol, True); 151 } 152 153 const resolveElementContext = 154 CreatePromiseAllResolveElementContext(capability, nativeContext); 155 156 let index: Smi = 1; 157 158 try { 159 const fastIteratorResultMap = *NativeContextSlot( 160 nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX); 161 while (true) { 162 let nextValue: JSAny; 163 try { 164 // Let next be IteratorStep(iteratorRecord.[[Iterator]]). 165 // If next is an abrupt completion, set iteratorRecord.[[Done]] to 166 // true. ReturnIfAbrupt(next). 167 const next: JSReceiver = iterator::IteratorStep( 168 iter, fastIteratorResultMap) otherwise goto Done; 169 170 // Let nextValue be IteratorValue(next). 171 // If nextValue is an abrupt completion, set iteratorRecord.[[Done]] 172 // to true. 173 // ReturnIfAbrupt(nextValue). 174 nextValue = iterator::IteratorValue(next, fastIteratorResultMap); 175 } catch (e, _message) { 176 goto Reject(e); 177 } 178 179 // Check if we reached the limit. 180 if (index == kPropertyArrayHashFieldMax) { 181 // If there are too many elements (currently more than 2**21-1), 182 // raise a RangeError here (which is caught below and turned into 183 // a rejection of the resulting promise). We could gracefully handle 184 // this case as well and support more than this number of elements 185 // by going to a separate function and pass the larger indices via a 186 // separate context, but it doesn't seem likely that we need this, 187 // and it's unclear how the rest of the system deals with 2**21 live 188 // Promises anyway. 189 ThrowRangeError( 190 MessageTemplate::kTooManyElementsInPromiseCombinator, 'all'); 191 } 192 193 // Set remainingElementsCount.[[Value]] to 194 // remainingElementsCount.[[Value]] + 1. 195 *ContextSlot( 196 resolveElementContext, 197 PromiseAllResolveElementContextSlots:: 198 kPromiseAllResolveElementRemainingSlot) += 1; 199 200 // Let resolveElement be CreateBuiltinFunction(steps, 201 // « [[AlreadyCalled]], 202 // [[Index]], 203 // [[Values]], 204 // [[Capability]], 205 // [[RemainingElements]] 206 // »). 207 // Set resolveElement.[[AlreadyCalled]] to a Record { [[Value]]: false 208 // }. Set resolveElement.[[Index]] to index. Set 209 // resolveElement.[[Values]] to values. Set 210 // resolveElement.[[Capability]] to resultCapability. Set 211 // resolveElement.[[RemainingElements]] to remainingElementsCount. 212 const resolveElementFun = createResolveElementFunctor.Call( 213 resolveElementContext, nativeContext, index, capability); 214 const rejectElementFun = createRejectElementFunctor.Call( 215 resolveElementContext, nativeContext, index, capability); 216 217 // We can skip the "then" lookup on the result of the "resolve" call and 218 // immediately chain the continuation onto the {next_value} if: 219 // 220 // (a) The {constructor} is the intrinsic %Promise% function, and 221 // looking up "resolve" on {constructor} yields the initial 222 // Promise.resolve() builtin, and 223 // (b) the promise @@species protector cell is valid, meaning that 224 // no one messed with the Symbol.species property on any 225 // intrinsic promise or on the Promise.prototype, and 226 // (c) the {next_value} is a JSPromise whose [[Prototype]] field 227 // contains the intrinsic %PromisePrototype%, and 228 // (d) we're not running with async_hooks or DevTools enabled. 229 // 230 // In that case we also don't need to allocate a chained promise for 231 // the PromiseReaction (aka we can pass undefined to 232 // PerformPromiseThen), since this is only necessary for DevTools and 233 // PromiseHooks. 234 if (promiseResolveFunction != Undefined || NeedsAnyPromiseHooks() || 235 IsPromiseSpeciesProtectorCellInvalid() || Is<Smi>(nextValue) || 236 !IsPromiseThenLookupChainIntact( 237 nativeContext, UnsafeCast<HeapObject>(nextValue).map)) { 238 // Let nextPromise be ? Call(constructor, _promiseResolve_, « 239 // nextValue »). 240 const nextPromise = 241 CallResolve(constructor, promiseResolveFunction, nextValue); 242 243 // Perform ? Invoke(nextPromise, "then", « resolveElement, 244 // resultCapability.[[Reject]] »). 245 const then = GetProperty(nextPromise, kThenString); 246 const thenResult = Call( 247 nativeContext, then, nextPromise, resolveElementFun, 248 rejectElementFun); 249 250 // For catch prediction, mark that rejections here are 251 // semantically handled by the combined Promise. 252 if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred { 253 SetPropertyStrict( 254 context, thenResult, kPromiseHandledBySymbol, promise); 255 } 256 } else { 257 PerformPromiseThenImpl( 258 UnsafeCast<JSPromise>(nextValue), resolveElementFun, 259 rejectElementFun, Undefined); 260 } 261 262 // Set index to index + 1. 263 index += 1; 264 } 265 } catch (e, _message) deferred { 266 iterator::IteratorCloseOnException(iter); 267 goto Reject(e); 268 } label Done {} 269 270 // Set iteratorRecord.[[Done]] to true. 271 // Set remainingElementsCount.[[Value]] to 272 // remainingElementsCount.[[Value]] - 1. 273 const remainingElementsCount = -- *ContextSlot( 274 resolveElementContext, 275 PromiseAllResolveElementContextSlots:: 276 kPromiseAllResolveElementRemainingSlot); 277 278 check(remainingElementsCount >= 0); 279 280 if (remainingElementsCount > 0) { 281 // Pre-allocate the backing store for the {values} to the desired 282 // capacity. We may already have elements in "values" - this happens 283 // when the Thenable calls the resolve callback immediately. 284 const valuesRef:&FixedArray = ContextSlot( 285 resolveElementContext, 286 PromiseAllResolveElementContextSlots:: 287 kPromiseAllResolveElementValuesSlot); 288 const values = *valuesRef; 289 // 'index' is a 1-based index and incremented after every Promise. Later we 290 // use 'values' as a 0-based array, so capacity 'index - 1' is enough. 291 const newCapacity = SmiUntag(index) - 1; 292 293 const oldCapacity = values.length_intptr; 294 if (oldCapacity < newCapacity) { 295 *valuesRef = ExtractFixedArray(values, 0, oldCapacity, newCapacity); 296 } 297 } else 298 deferred { 299 // If remainingElementsCount.[[Value]] is 0, then 300 // Let valuesArray be CreateArrayFromList(values). 301 // Perform ? Call(resultCapability.[[Resolve]], undefined, 302 // « valuesArray »). 303 304 const values: FixedArray = *ContextSlot( 305 resolveElementContext, 306 PromiseAllResolveElementContextSlots:: 307 kPromiseAllResolveElementValuesSlot); 308 const arrayMap = 309 *NativeContextSlot( 310 nativeContext, ContextSlot::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX); 311 const valuesArray = NewJSArray(arrayMap, values); 312 Call(nativeContext, UnsafeCast<JSAny>(resolve), Undefined, valuesArray); 313 } 314 315 // Return resultCapability.[[Promise]]. 316 return promise; 317} 318 319transitioning macro GeneratePromiseAll<F1: type, F2: type>( 320 implicit context: Context)( 321 receiver: JSAny, iterable: JSAny, createResolveElementFunctor: F1, 322 createRejectElementFunctor: F2, message: constexpr string): JSAny { 323 const nativeContext = LoadNativeContext(context); 324 // Let C be the this value. 325 // If Type(C) is not Object, throw a TypeError exception. 326 const receiver = Cast<JSReceiver>(receiver) 327 otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, message); 328 329 // Let promiseCapability be ? NewPromiseCapability(C). 330 // Don't fire debugEvent so that forwarding the rejection through all does 331 // not trigger redundant ExceptionEvents 332 const capability = NewPromiseCapability(receiver, False); 333 334 // NewPromiseCapability guarantees that receiver is Constructor. 335 dcheck(Is<Constructor>(receiver)); 336 const constructor = UnsafeCast<Constructor>(receiver); 337 338 try { 339 // Let promiseResolve be GetPromiseResolve(C). 340 // IfAbruptRejectPromise(promiseResolve, promiseCapability). 341 const promiseResolveFunction = 342 GetPromiseResolve(nativeContext, constructor); 343 344 // Let iterator be GetIterator(iterable). 345 // IfAbruptRejectPromise(iterator, promiseCapability). 346 let i = iterator::GetIterator(iterable); 347 348 // Let result be PerformPromiseAll(iteratorRecord, C, 349 // promiseCapability). If result is an abrupt completion, then 350 // If iteratorRecord.[[Done]] is false, let result be 351 // IteratorClose(iterator, result). 352 // IfAbruptRejectPromise(result, promiseCapability). 353 return PerformPromiseAll( 354 nativeContext, i, constructor, capability, promiseResolveFunction, 355 createResolveElementFunctor, createRejectElementFunctor) 356 otherwise Reject; 357 } catch (e, _message) deferred { 358 goto Reject(e); 359 } label Reject(e: JSAny) deferred { 360 const reject = UnsafeCast<JSAny>(capability.reject); 361 Call(context, reject, Undefined, e); 362 return capability.promise; 363 } 364} 365 366// ES#sec-promise.all 367transitioning javascript builtin PromiseAll( 368 js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny { 369 return GeneratePromiseAll( 370 receiver, iterable, PromiseAllResolveElementFunctor{}, 371 PromiseAllRejectElementFunctor{}, 'Promise.all'); 372} 373 374// ES#sec-promise.allsettled 375// Promise.allSettled ( iterable ) 376transitioning javascript builtin PromiseAllSettled( 377 js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny { 378 return GeneratePromiseAll( 379 receiver, iterable, PromiseAllSettledResolveElementFunctor{}, 380 PromiseAllSettledRejectElementFunctor{}, 'Promise.allSettled'); 381} 382 383extern macro PromiseAllResolveElementSharedFunConstant(): SharedFunctionInfo; 384extern macro PromiseAllSettledRejectElementSharedFunConstant(): 385 SharedFunctionInfo; 386extern macro PromiseAllSettledResolveElementSharedFunConstant(): 387 SharedFunctionInfo; 388} 389