1// Copyright 2020 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-gen.h' 6 7namespace promise { 8type PromiseAnyRejectElementContext extends FunctionContext; 9extern enum PromiseAnyRejectElementContextSlots extends intptr 10constexpr 'PromiseBuiltins::PromiseAnyRejectElementContextSlots' { 11 kPromiseAnyRejectElementRemainingSlot: 12 Slot<PromiseAnyRejectElementContext, Smi>, 13 kPromiseAnyRejectElementCapabilitySlot: 14 Slot<PromiseAnyRejectElementContext, PromiseCapability>, 15 kPromiseAnyRejectElementErrorsSlot: 16 Slot<PromiseAnyRejectElementContext, FixedArray>, 17 kPromiseAnyRejectElementLength 18} 19 20extern operator '[]=' macro StoreContextElement( 21 Context, constexpr PromiseAnyRejectElementContextSlots, Object): void; 22extern operator '[]' macro LoadContextElement( 23 Context, constexpr PromiseAnyRejectElementContextSlots): Object; 24 25// Creates the context used by all Promise.any reject element closures, 26// together with the errors array. Since all closures for a single Promise.any 27// call use the same context, we need to store the indices for the individual 28// closures somewhere else (we put them into the identity hash field of the 29// closures), and we also need to have a separate marker for when the closure 30// was called already (we slap the native context onto the closure in that 31// case to mark it's done). See Promise.all which uses the same approach. 32transitioning macro CreatePromiseAnyRejectElementContext( 33 implicit context: Context)( 34 capability: PromiseCapability, 35 nativeContext: NativeContext): PromiseAnyRejectElementContext { 36 const rejectContext = %RawDownCast<PromiseAnyRejectElementContext>( 37 AllocateSyntheticFunctionContext( 38 nativeContext, 39 PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength)); 40 InitContextSlot( 41 rejectContext, 42 PromiseAnyRejectElementContextSlots:: 43 kPromiseAnyRejectElementRemainingSlot, 44 1); 45 InitContextSlot( 46 rejectContext, 47 PromiseAnyRejectElementContextSlots:: 48 kPromiseAnyRejectElementCapabilitySlot, 49 capability); 50 InitContextSlot( 51 rejectContext, 52 PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementErrorsSlot, 53 kEmptyFixedArray); 54 return rejectContext; 55} 56 57macro CreatePromiseAnyRejectElementFunction(implicit context: Context)( 58 rejectElementContext: PromiseAnyRejectElementContext, index: Smi, 59 nativeContext: NativeContext): JSFunction { 60 dcheck(index > 0); 61 dcheck(index < kPropertyArrayHashFieldMax); 62 const map = *ContextSlot( 63 nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); 64 const rejectInfo = PromiseAnyRejectElementSharedFunConstant(); 65 const reject = 66 AllocateFunctionWithMapAndContext(map, rejectInfo, rejectElementContext); 67 dcheck(kPropertyArrayNoHashSentinel == 0); 68 reject.properties_or_hash = index; 69 return reject; 70} 71 72// https://tc39.es/ecma262/#sec-promise.any-reject-element-functions 73transitioning javascript builtin 74PromiseAnyRejectElementClosure( 75 js-implicit context: Context, receiver: JSAny, 76 target: JSFunction)(value: JSAny): JSAny { 77 // 1. Let F be the active function object. 78 79 // 2. Let alreadyCalled be F.[[AlreadyCalled]]. 80 81 // 3. If alreadyCalled.[[Value]] is true, return undefined. 82 83 // We use the function's context as the marker to remember whether this 84 // reject element closure was already called. It points to the reject 85 // element context (which is a FunctionContext) until it was called the 86 // first time, in which case we make it point to the native context here 87 // to mark this reject element closure as done. 88 if (IsNativeContext(context)) deferred { 89 return Undefined; 90 } 91 92 dcheck( 93 context.length == 94 SmiTag( 95 PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength)); 96 const context = %RawDownCast<PromiseAnyRejectElementContext>(context); 97 98 // 4. Set alreadyCalled.[[Value]] to true. 99 const nativeContext = LoadNativeContext(context); 100 target.context = nativeContext; 101 102 // 5. Let index be F.[[Index]]. 103 dcheck(kPropertyArrayNoHashSentinel == 0); 104 const identityHash = LoadJSReceiverIdentityHash(target) otherwise unreachable; 105 dcheck(identityHash > 0); 106 const index = identityHash - 1; 107 108 // 6. Let errors be F.[[Errors]]. 109 let errors = *ContextSlot( 110 context, 111 PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementErrorsSlot); 112 113 // 7. Let promiseCapability be F.[[Capability]]. 114 115 // 8. Let remainingElementsCount be F.[[RemainingElements]]. 116 let remainingElementsCount = *ContextSlot( 117 context, 118 PromiseAnyRejectElementContextSlots:: 119 kPromiseAnyRejectElementRemainingSlot); 120 121 // 9. Set errors[index] to x. 122 123 // The max computation below is an optimization to avoid excessive allocations 124 // in the case of input promises being asynchronously rejected in ascending 125 // index order. 126 // 127 // Note that subtracting 1 from remainingElementsCount is intentional. The 128 // value of remainingElementsCount is 1 larger than the actual value during 129 // iteration. So in the case of synchronous rejection, newCapacity is the 130 // correct size by subtracting 1. In the case of asynchronous rejection this 131 // is 1 smaller than the correct size, but is not incorrect as it is maxed 132 // with index + 1. 133 const newCapacity = 134 IntPtrMax(SmiUntag(remainingElementsCount) - 1, index + 1); 135 if (newCapacity > errors.length_intptr) deferred { 136 errors = ExtractFixedArray(errors, 0, errors.length_intptr, newCapacity); 137 *ContextSlot( 138 context, 139 PromiseAnyRejectElementContextSlots:: 140 kPromiseAnyRejectElementErrorsSlot) = errors; 141 } 142 errors.objects[index] = value; 143 144 // 10. Set remainingElementsCount.[[Value]] to 145 // remainingElementsCount.[[Value]] - 1. 146 remainingElementsCount = remainingElementsCount - 1; 147 *ContextSlot( 148 context, 149 PromiseAnyRejectElementContextSlots:: 150 kPromiseAnyRejectElementRemainingSlot) = remainingElementsCount; 151 152 // 11. If remainingElementsCount.[[Value]] is 0, then 153 if (remainingElementsCount == 0) { 154 // a. Let error be a newly created AggregateError object. 155 156 // b. Set error.[[AggregateErrors]] to errors. 157 const error = ConstructAggregateError(errors); 158 // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). 159 const capability = *ContextSlot( 160 context, 161 PromiseAnyRejectElementContextSlots:: 162 kPromiseAnyRejectElementCapabilitySlot); 163 Call(context, UnsafeCast<Callable>(capability.reject), Undefined, error); 164 } 165 166 // 12. Return undefined. 167 return Undefined; 168} 169 170transitioning macro PerformPromiseAny(implicit context: Context)( 171 nativeContext: NativeContext, iteratorRecord: iterator::IteratorRecord, 172 constructor: Constructor, resultCapability: PromiseCapability, 173 promiseResolveFunction: JSAny): JSAny labels 174Reject(JSAny) { 175 // 1. Assert: ! IsConstructor(constructor) is true. 176 // 2. Assert: resultCapability is a PromiseCapability Record. 177 178 // 3. Let errors be a new empty List. (Do nothing: errors is 179 // initialized lazily when the first Promise rejects.) 180 181 // 4. Let remainingElementsCount be a new Record { [[Value]]: 1 }. 182 const rejectElementContext = 183 CreatePromiseAnyRejectElementContext(resultCapability, nativeContext); 184 185 // 5. Let index be 0. 186 // (We subtract 1 in the PromiseAnyRejectElementClosure). 187 let index: Smi = 1; 188 189 try { 190 const fastIteratorResultMap = *NativeContextSlot( 191 nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX); 192 // 8. Repeat, 193 while (true) { 194 let nextValue: JSAny; 195 try { 196 // a. Let next be IteratorStep(iteratorRecord). 197 198 // b. If next is an abrupt completion, set 199 // iteratorRecord.[[Done]] to true. 200 201 // c. ReturnIfAbrupt(next). 202 203 // d. if next is false, then [continues below in "Done"] 204 const next: JSReceiver = iterator::IteratorStep( 205 iteratorRecord, fastIteratorResultMap) otherwise goto Done; 206 // e. Let nextValue be IteratorValue(next). 207 208 // f. If nextValue is an abrupt completion, set 209 // iteratorRecord.[[Done]] to true. 210 211 // g. ReturnIfAbrupt(nextValue). 212 nextValue = iterator::IteratorValue(next, fastIteratorResultMap); 213 } catch (e, _message) { 214 goto Reject(e); 215 } 216 217 // We store the indices as identity hash on the reject element 218 // closures. Thus, we need this limit. 219 if (index == kPropertyArrayHashFieldMax) { 220 // If there are too many elements (currently more than 221 // 2**21-1), raise a RangeError here (which is caught later and 222 // turned into a rejection of the resulting promise). We could 223 // gracefully handle this case as well and support more than 224 // this number of elements by going to a separate function and 225 // pass the larger indices via a separate context, but it 226 // doesn't seem likely that we need this, and it's unclear how 227 // the rest of the system deals with 2**21 live Promises 228 // anyway. 229 ThrowRangeError( 230 MessageTemplate::kTooManyElementsInPromiseCombinator, 'any'); 231 } 232 233 // h. Append undefined to errors. (Do nothing: errors is initialized 234 // lazily when the first Promise rejects.) 235 236 let nextPromise: JSAny; 237 // i. Let nextPromise be ? Call(constructor, promiseResolve, 238 // «nextValue »). 239 nextPromise = CallResolve(constructor, promiseResolveFunction, nextValue); 240 241 // j. Let steps be the algorithm steps defined in Promise.any 242 // Reject Element Functions. 243 244 // k. Let rejectElement be ! CreateBuiltinFunction(steps, « 245 // [[AlreadyCalled]], [[Index]], 246 // [[Errors]], [[Capability]], [[RemainingElements]] »). 247 248 // l. Set rejectElement.[[AlreadyCalled]] to a new Record { 249 // [[Value]]: false }. 250 251 // m. Set rejectElement.[[Index]] to index. 252 253 // n. Set rejectElement.[[Errors]] to errors. 254 255 // o. Set rejectElement.[[Capability]] to resultCapability. 256 257 // p. Set rejectElement.[[RemainingElements]] to 258 // remainingElementsCount. 259 const rejectElement = CreatePromiseAnyRejectElementFunction( 260 rejectElementContext, index, nativeContext); 261 // q. Set remainingElementsCount.[[Value]] to 262 // remainingElementsCount.[[Value]] + 1. 263 const remainingElementsCount = *ContextSlot( 264 rejectElementContext, 265 PromiseAnyRejectElementContextSlots:: 266 kPromiseAnyRejectElementRemainingSlot); 267 *ContextSlot( 268 rejectElementContext, 269 PromiseAnyRejectElementContextSlots:: 270 kPromiseAnyRejectElementRemainingSlot) = 271 remainingElementsCount + 1; 272 273 // r. Perform ? Invoke(nextPromise, "then", « 274 // resultCapability.[[Resolve]], rejectElement »). 275 let thenResult: JSAny; 276 277 const then = GetProperty(nextPromise, kThenString); 278 thenResult = Call( 279 context, then, nextPromise, 280 UnsafeCast<JSAny>(resultCapability.resolve), rejectElement); 281 282 // s. Increase index by 1. 283 index += 1; 284 285 // For catch prediction, mark that rejections here are 286 // semantically handled by the combined Promise. 287 if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred { 288 SetPropertyStrict( 289 context, thenResult, kPromiseHandledBySymbol, 290 resultCapability.promise); 291 SetPropertyStrict( 292 context, rejectElement, kPromiseForwardingHandlerSymbol, True); 293 } 294 } 295 } catch (e, _message) deferred { 296 iterator::IteratorCloseOnException(iteratorRecord); 297 goto Reject(e); 298 } label Done {} 299 300 // (8.d) 301 // i. Set iteratorRecord.[[Done]] to true. 302 // ii. Set remainingElementsCount.[[Value]] to 303 // remainingElementsCount.[[Value]] - 1. 304 const remainingElementsCount = -- *ContextSlot( 305 rejectElementContext, 306 PromiseAnyRejectElementContextSlots:: 307 kPromiseAnyRejectElementRemainingSlot); 308 309 // iii. If remainingElementsCount.[[Value]] is 0, then 310 if (remainingElementsCount == 0) deferred { 311 // 1. Let error be a newly created AggregateError object. 312 // 2. Set error.[[AggregateErrors]] to errors. 313 314 // We may already have elements in "errors" - this happens when the 315 // Thenable calls the reject callback immediately. 316 const errors: FixedArray = *ContextSlot( 317 rejectElementContext, 318 PromiseAnyRejectElementContextSlots:: 319 kPromiseAnyRejectElementErrorsSlot); 320 321 check(errors.length == index - 1); 322 const error = ConstructAggregateError(errors); 323 // 3. Return ThrowCompletion(error). 324 goto Reject(error); 325 } 326 // iv. Return resultCapability.[[Promise]]. 327 return resultCapability.promise; 328} 329 330// https://tc39.es/ecma262/#sec-promise.any 331transitioning javascript builtin 332PromiseAny( 333 js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny { 334 const nativeContext = LoadNativeContext(context); 335 336 // 1. Let C be the this value. 337 const receiver = Cast<JSReceiver>(receiver) 338 otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.any'); 339 340 // 2. Let promiseCapability be ? NewPromiseCapability(C). 341 const capability = NewPromiseCapability(receiver, False); 342 343 // NewPromiseCapability guarantees that receiver is Constructor. 344 dcheck(Is<Constructor>(receiver)); 345 const constructor = UnsafeCast<Constructor>(receiver); 346 347 try { 348 // 3. Let promiseResolve be GetPromiseResolve(C). 349 // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). 350 // (catch below) 351 const promiseResolveFunction = 352 GetPromiseResolve(nativeContext, constructor); 353 354 // 5. Let iteratorRecord be GetIterator(iterable). 355 356 // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). 357 // (catch below) 358 const iteratorRecord = iterator::GetIterator(iterable); 359 360 // 7. Let result be PerformPromiseAny(iteratorRecord, C, 361 // promiseCapability). 362 363 // 8. If result is an abrupt completion, then 364 365 // a. If iteratorRecord.[[Done]] is false, set result to 366 // IteratorClose(iteratorRecord, result). 367 368 // b. IfAbruptRejectPromise(result, promiseCapability). 369 370 // [Iterator closing handled by PerformPromiseAny] 371 372 // 9. Return Completion(result). 373 return PerformPromiseAny( 374 nativeContext, iteratorRecord, constructor, capability, 375 promiseResolveFunction) 376 otherwise Reject; 377 } catch (e, _message) deferred { 378 goto Reject(e); 379 } label Reject(e: JSAny) deferred { 380 // Exception must be bound to a JS value. 381 dcheck(e != TheHole); 382 Call( 383 context, UnsafeCast<Callable>(capability.reject), Undefined, 384 UnsafeCast<JSAny>(e)); 385 return capability.promise; 386 } 387} 388 389transitioning macro ConstructAggregateError(implicit context: Context)( 390 errors: FixedArray): JSObject { 391 const obj: JSObject = error::ConstructInternalAggregateErrorHelper( 392 context, SmiConstant(MessageTemplate::kAllPromisesRejected)); 393 const errorsJSArray = array::CreateJSArrayWithElements(errors); 394 SetOwnPropertyIgnoreAttributes( 395 obj, ErrorsStringConstant(), errorsJSArray, 396 SmiConstant(PropertyAttributes::DONT_ENUM)); 397 return obj; 398} 399 400extern macro PromiseAnyRejectElementSharedFunConstant(): SharedFunctionInfo; 401} 402