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 assert(index > 0); 61 assert(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 assert(kPropertyArrayNoHashSentinel == 0); 68 reject.properties_or_hash = index; 69 return reject; 70} 71 72// https://tc39.es/proposal-promise-any/#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 assert( 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 assert(kPropertyArrayNoHashSentinel == 0); 104 const identityHash = LoadJSReceiverIdentityHash(target) otherwise unreachable; 105 assert(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 const newCapacity = IntPtrMax(SmiUntag(remainingElementsCount), index + 1); 123 if (newCapacity > errors.length_intptr) deferred { 124 errors = ExtractFixedArray(errors, 0, errors.length_intptr, newCapacity); 125 *ContextSlot( 126 context, 127 PromiseAnyRejectElementContextSlots:: 128 kPromiseAnyRejectElementErrorsSlot) = errors; 129 } 130 errors.objects[index] = value; 131 132 // 10. Set remainingElementsCount.[[Value]] to 133 // remainingElementsCount.[[Value]] - 1. 134 remainingElementsCount = remainingElementsCount - 1; 135 *ContextSlot( 136 context, 137 PromiseAnyRejectElementContextSlots:: 138 kPromiseAnyRejectElementRemainingSlot) = remainingElementsCount; 139 140 // 11. If remainingElementsCount.[[Value]] is 0, then 141 if (remainingElementsCount == 0) { 142 // a. Let error be a newly created AggregateError object. 143 144 // b. Set error.[[AggregateErrors]] to errors. 145 const error = ConstructAggregateError(errors); 146 // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). 147 const capability = *ContextSlot( 148 context, 149 PromiseAnyRejectElementContextSlots:: 150 kPromiseAnyRejectElementCapabilitySlot); 151 Call(context, UnsafeCast<Callable>(capability.reject), Undefined, error); 152 } 153 154 // 12. Return undefined. 155 return Undefined; 156} 157 158transitioning macro PerformPromiseAny(implicit context: Context)( 159 nativeContext: NativeContext, iteratorRecord: iterator::IteratorRecord, 160 constructor: Constructor, resultCapability: PromiseCapability, 161 promiseResolveFunction: JSAny): JSAny labels 162Reject(Object) { 163 // 1. Assert: ! IsConstructor(constructor) is true. 164 // 2. Assert: resultCapability is a PromiseCapability Record. 165 166 // 3. Let errors be a new empty List. (Do nothing: errors is 167 // initialized lazily when the first Promise rejects.) 168 169 // 4. Let remainingElementsCount be a new Record { [[Value]]: 1 }. 170 const rejectElementContext = 171 CreatePromiseAnyRejectElementContext(resultCapability, nativeContext); 172 173 // 5. Let index be 0. 174 // (We subtract 1 in the PromiseAnyRejectElementClosure). 175 let index: Smi = 1; 176 177 try { 178 const fastIteratorResultMap = *NativeContextSlot( 179 nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX); 180 // 8. Repeat, 181 while (true) { 182 let nextValue: JSAny; 183 try { 184 // a. Let next be IteratorStep(iteratorRecord). 185 186 // b. If next is an abrupt completion, set 187 // iteratorRecord.[[Done]] to true. 188 189 // c. ReturnIfAbrupt(next). 190 191 // d. if next is false, then [continues below in "Done"] 192 const next: JSReceiver = iterator::IteratorStep( 193 iteratorRecord, fastIteratorResultMap) otherwise goto Done; 194 // e. Let nextValue be IteratorValue(next). 195 196 // f. If nextValue is an abrupt completion, set 197 // iteratorRecord.[[Done]] to true. 198 199 // g. ReturnIfAbrupt(nextValue). 200 nextValue = iterator::IteratorValue(next, fastIteratorResultMap); 201 } catch (e) { 202 goto Reject(e); 203 } 204 205 // We store the indices as identity hash on the reject element 206 // closures. Thus, we need this limit. 207 if (index == kPropertyArrayHashFieldMax) { 208 // If there are too many elements (currently more than 209 // 2**21-1), raise a RangeError here (which is caught later and 210 // turned into a rejection of the resulting promise). We could 211 // gracefully handle this case as well and support more than 212 // this number of elements by going to a separate function and 213 // pass the larger indices via a separate context, but it 214 // doesn't seem likely that we need this, and it's unclear how 215 // the rest of the system deals with 2**21 live Promises 216 // anyway. 217 ThrowRangeError( 218 MessageTemplate::kTooManyElementsInPromiseCombinator, 'any'); 219 } 220 221 // h. Append undefined to errors. (Do nothing: errors is initialized 222 // lazily when the first Promise rejects.) 223 224 let nextPromise: JSAny; 225 // i. Let nextPromise be ? Call(constructor, promiseResolve, 226 // «nextValue »). 227 nextPromise = CallResolve(constructor, promiseResolveFunction, nextValue); 228 229 // j. Let steps be the algorithm steps defined in Promise.any 230 // Reject Element Functions. 231 232 // k. Let rejectElement be ! CreateBuiltinFunction(steps, « 233 // [[AlreadyCalled]], [[Index]], 234 // [[Errors]], [[Capability]], [[RemainingElements]] »). 235 236 // l. Set rejectElement.[[AlreadyCalled]] to a new Record { 237 // [[Value]]: false }. 238 239 // m. Set rejectElement.[[Index]] to index. 240 241 // n. Set rejectElement.[[Errors]] to errors. 242 243 // o. Set rejectElement.[[Capability]] to resultCapability. 244 245 // p. Set rejectElement.[[RemainingElements]] to 246 // remainingElementsCount. 247 const rejectElement = CreatePromiseAnyRejectElementFunction( 248 rejectElementContext, index, nativeContext); 249 // q. Set remainingElementsCount.[[Value]] to 250 // remainingElementsCount.[[Value]] + 1. 251 const remainingElementsCount = *ContextSlot( 252 rejectElementContext, 253 PromiseAnyRejectElementContextSlots:: 254 kPromiseAnyRejectElementRemainingSlot); 255 *ContextSlot( 256 rejectElementContext, 257 PromiseAnyRejectElementContextSlots:: 258 kPromiseAnyRejectElementRemainingSlot) = 259 remainingElementsCount + 1; 260 261 // r. Perform ? Invoke(nextPromise, "then", « 262 // resultCapability.[[Resolve]], rejectElement »). 263 let thenResult: JSAny; 264 265 const then = GetProperty(nextPromise, kThenString); 266 thenResult = Call( 267 context, then, nextPromise, 268 UnsafeCast<JSAny>(resultCapability.resolve), rejectElement); 269 270 // s. Increase index by 1. 271 index += 1; 272 273 // For catch prediction, mark that rejections here are 274 // semantically handled by the combined Promise. 275 if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred { 276 SetPropertyStrict( 277 context, thenResult, kPromiseHandledBySymbol, 278 resultCapability.promise); 279 SetPropertyStrict( 280 context, rejectElement, kPromiseForwardingHandlerSymbol, True); 281 } 282 } 283 } catch (e) deferred { 284 iterator::IteratorCloseOnException(iteratorRecord); 285 goto Reject(e); 286 } label Done {} 287 288 // (8.d) 289 // i. Set iteratorRecord.[[Done]] to true. 290 // ii. Set remainingElementsCount.[[Value]] to 291 // remainingElementsCount.[[Value]] - 1. 292 const remainingElementsCount = -- *ContextSlot( 293 rejectElementContext, 294 PromiseAnyRejectElementContextSlots:: 295 kPromiseAnyRejectElementRemainingSlot); 296 297 // iii. If remainingElementsCount.[[Value]] is 0, then 298 if (remainingElementsCount == 0) deferred { 299 // 1. Let error be a newly created AggregateError object. 300 // 2. Set error.[[AggregateErrors]] to errors. 301 302 // We may already have elements in "errors" - this happens when the 303 // Thenable calls the reject callback immediately. 304 const errors: FixedArray = *ContextSlot( 305 rejectElementContext, 306 PromiseAnyRejectElementContextSlots:: 307 kPromiseAnyRejectElementErrorsSlot); 308 309 const error = ConstructAggregateError(errors); 310 // 3. Return ThrowCompletion(error). 311 goto Reject(error); 312 } 313 // iv. Return resultCapability.[[Promise]]. 314 return resultCapability.promise; 315} 316 317// https://tc39.es/proposal-promise-any/#sec-promise.any 318transitioning javascript builtin 319PromiseAny( 320 js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny { 321 const nativeContext = LoadNativeContext(context); 322 323 // 1. Let C be the this value. 324 const receiver = Cast<JSReceiver>(receiver) 325 otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.any'); 326 327 // 2. Let promiseCapability be ? NewPromiseCapability(C). 328 const capability = NewPromiseCapability(receiver, False); 329 330 // NewPromiseCapability guarantees that receiver is Constructor. 331 assert(Is<Constructor>(receiver)); 332 const constructor = UnsafeCast<Constructor>(receiver); 333 334 try { 335 // 3. Let promiseResolve be GetPromiseResolve(C). 336 // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). 337 // (catch below) 338 const promiseResolveFunction = 339 GetPromiseResolve(nativeContext, constructor); 340 341 // 5. Let iteratorRecord be GetIterator(iterable). 342 343 // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). 344 // (catch below) 345 const iteratorRecord = iterator::GetIterator(iterable); 346 347 // 7. Let result be PerformPromiseAny(iteratorRecord, C, 348 // promiseCapability). 349 350 // 8. If result is an abrupt completion, then 351 352 // a. If iteratorRecord.[[Done]] is false, set result to 353 // IteratorClose(iteratorRecord, result). 354 355 // b. IfAbruptRejectPromise(result, promiseCapability). 356 357 // [Iterator closing handled by PerformPromiseAny] 358 359 // 9. Return Completion(result). 360 return PerformPromiseAny( 361 nativeContext, iteratorRecord, constructor, capability, 362 promiseResolveFunction) 363 otherwise Reject; 364 } catch (e) deferred { 365 goto Reject(e); 366 } label Reject(e: Object) deferred { 367 // Exception must be bound to a JS value. 368 assert(e != TheHole); 369 Call( 370 context, UnsafeCast<Callable>(capability.reject), Undefined, 371 UnsafeCast<JSAny>(e)); 372 return capability.promise; 373 } 374} 375 376transitioning macro ConstructAggregateError(implicit context: Context)( 377 errors: FixedArray): JSObject { 378 const obj: JSObject = error::ConstructInternalAggregateErrorHelper( 379 context, SmiConstant(MessageTemplate::kAllPromisesRejected)); 380 const errorsJSArray = array::CreateJSArrayWithElements(errors); 381 SetOwnPropertyIgnoreAttributes( 382 obj, ErrorsStringConstant(), errorsJSArray, 383 SmiConstant(PropertyAttributes::DONT_ENUM)); 384 return obj; 385} 386 387extern macro PromiseAnyRejectElementSharedFunConstant(): SharedFunctionInfo; 388} 389