| // Copyright 2020 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-gen.h' |
| |
| namespace promise { |
| type PromiseAnyRejectElementContext extends FunctionContext; |
| extern enum PromiseAnyRejectElementContextSlots extends intptr |
| constexpr 'PromiseBuiltins::PromiseAnyRejectElementContextSlots' { |
| kPromiseAnyRejectElementRemainingSlot: |
| Slot<PromiseAnyRejectElementContext, Smi>, |
| kPromiseAnyRejectElementCapabilitySlot: |
| Slot<PromiseAnyRejectElementContext, PromiseCapability>, |
| kPromiseAnyRejectElementErrorsSlot: |
| Slot<PromiseAnyRejectElementContext, FixedArray>, |
| kPromiseAnyRejectElementLength |
| } |
| |
| extern operator '[]=' macro StoreContextElement( |
| Context, constexpr PromiseAnyRejectElementContextSlots, Object): void; |
| extern operator '[]' macro LoadContextElement( |
| Context, constexpr PromiseAnyRejectElementContextSlots): Object; |
| |
| // Creates the context used by all Promise.any reject element closures, |
| // together with the errors array. Since all closures for a single Promise.any |
| // call use the same context, we need to store the indices for the individual |
| // closures somewhere else (we put them into the identity hash field of the |
| // closures), and we also need to have a separate marker for when the closure |
| // was called already (we slap the native context onto the closure in that |
| // case to mark it's done). See Promise.all which uses the same approach. |
| transitioning macro CreatePromiseAnyRejectElementContext( |
| implicit context: Context)( |
| capability: PromiseCapability, |
| nativeContext: NativeContext): PromiseAnyRejectElementContext { |
| const rejectContext = %RawDownCast<PromiseAnyRejectElementContext>( |
| AllocateSyntheticFunctionContext( |
| nativeContext, |
| PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength)); |
| InitContextSlot( |
| rejectContext, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementRemainingSlot, |
| 1); |
| InitContextSlot( |
| rejectContext, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementCapabilitySlot, |
| capability); |
| InitContextSlot( |
| rejectContext, |
| PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementErrorsSlot, |
| kEmptyFixedArray); |
| return rejectContext; |
| } |
| |
| macro CreatePromiseAnyRejectElementFunction(implicit context: Context)( |
| rejectElementContext: PromiseAnyRejectElementContext, index: Smi, |
| nativeContext: NativeContext): JSFunction { |
| assert(index > 0); |
| assert(index < kPropertyArrayHashFieldMax); |
| const map = *ContextSlot( |
| nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); |
| const rejectInfo = PromiseAnyRejectElementSharedFunConstant(); |
| const reject = |
| AllocateFunctionWithMapAndContext(map, rejectInfo, rejectElementContext); |
| assert(kPropertyArrayNoHashSentinel == 0); |
| reject.properties_or_hash = index; |
| return reject; |
| } |
| |
| // https://tc39.es/proposal-promise-any/#sec-promise.any-reject-element-functions |
| transitioning javascript builtin |
| PromiseAnyRejectElementClosure( |
| js-implicit context: Context, receiver: JSAny, |
| target: JSFunction)(value: JSAny): JSAny { |
| // 1. Let F be the active function object. |
| |
| // 2. Let alreadyCalled be F.[[AlreadyCalled]]. |
| |
| // 3. If alreadyCalled.[[Value]] is true, return undefined. |
| |
| // We use the function's context as the marker to remember whether this |
| // reject element closure was already called. It points to the reject |
| // 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 reject element closure as done. |
| if (IsNativeContext(context)) deferred { |
| return Undefined; |
| } |
| |
| assert( |
| context.length == |
| SmiTag( |
| PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength)); |
| const context = %RawDownCast<PromiseAnyRejectElementContext>(context); |
| |
| // 4. Set alreadyCalled.[[Value]] to true. |
| const nativeContext = LoadNativeContext(context); |
| target.context = nativeContext; |
| |
| // 5. Let index be F.[[Index]]. |
| assert(kPropertyArrayNoHashSentinel == 0); |
| const identityHash = LoadJSReceiverIdentityHash(target) otherwise unreachable; |
| assert(identityHash > 0); |
| const index = identityHash - 1; |
| |
| // 6. Let errors be F.[[Errors]]. |
| let errors = *ContextSlot( |
| context, |
| PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementErrorsSlot); |
| |
| // 7. Let promiseCapability be F.[[Capability]]. |
| |
| // 8. Let remainingElementsCount be F.[[RemainingElements]]. |
| let remainingElementsCount = *ContextSlot( |
| context, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementRemainingSlot); |
| |
| // 9. Set errors[index] to x. |
| const newCapacity = IntPtrMax(SmiUntag(remainingElementsCount), index + 1); |
| if (newCapacity > errors.length_intptr) deferred { |
| errors = ExtractFixedArray(errors, 0, errors.length_intptr, newCapacity); |
| *ContextSlot( |
| context, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementErrorsSlot) = errors; |
| } |
| errors.objects[index] = value; |
| |
| // 10. Set remainingElementsCount.[[Value]] to |
| // remainingElementsCount.[[Value]] - 1. |
| remainingElementsCount = remainingElementsCount - 1; |
| *ContextSlot( |
| context, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementRemainingSlot) = remainingElementsCount; |
| |
| // 11. If remainingElementsCount.[[Value]] is 0, then |
| if (remainingElementsCount == 0) { |
| // a. Let error be a newly created AggregateError object. |
| |
| // b. Set error.[[AggregateErrors]] to errors. |
| const error = ConstructAggregateError(errors); |
| // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). |
| const capability = *ContextSlot( |
| context, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementCapabilitySlot); |
| Call(context, UnsafeCast<Callable>(capability.reject), Undefined, error); |
| } |
| |
| // 12. Return undefined. |
| return Undefined; |
| } |
| |
| transitioning macro PerformPromiseAny(implicit context: Context)( |
| nativeContext: NativeContext, iteratorRecord: iterator::IteratorRecord, |
| constructor: Constructor, resultCapability: PromiseCapability, |
| promiseResolveFunction: JSAny): JSAny labels |
| Reject(Object) { |
| // 1. Assert: ! IsConstructor(constructor) is true. |
| // 2. Assert: resultCapability is a PromiseCapability Record. |
| |
| // 3. Let errors be a new empty List. (Do nothing: errors is |
| // initialized lazily when the first Promise rejects.) |
| |
| // 4. Let remainingElementsCount be a new Record { [[Value]]: 1 }. |
| const rejectElementContext = |
| CreatePromiseAnyRejectElementContext(resultCapability, nativeContext); |
| |
| // 5. Let index be 0. |
| // (We subtract 1 in the PromiseAnyRejectElementClosure). |
| let index: Smi = 1; |
| |
| try { |
| const fastIteratorResultMap = *NativeContextSlot( |
| nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX); |
| // 8. Repeat, |
| while (true) { |
| let nextValue: JSAny; |
| try { |
| // a. Let next be IteratorStep(iteratorRecord). |
| |
| // b. If next is an abrupt completion, set |
| // iteratorRecord.[[Done]] to true. |
| |
| // c. ReturnIfAbrupt(next). |
| |
| // d. if next is false, then [continues below in "Done"] |
| const next: JSReceiver = iterator::IteratorStep( |
| iteratorRecord, fastIteratorResultMap) otherwise goto Done; |
| // e. Let nextValue be IteratorValue(next). |
| |
| // f. If nextValue is an abrupt completion, set |
| // iteratorRecord.[[Done]] to true. |
| |
| // g. ReturnIfAbrupt(nextValue). |
| nextValue = iterator::IteratorValue(next, fastIteratorResultMap); |
| } catch (e) { |
| goto Reject(e); |
| } |
| |
| // We store the indices as identity hash on the reject element |
| // closures. Thus, we need this limit. |
| if (index == kPropertyArrayHashFieldMax) { |
| // If there are too many elements (currently more than |
| // 2**21-1), raise a RangeError here (which is caught later and |
| // turned into a rejection of the resulting promise). We could |
| // gracefully handle this case as well and support more than |
| // this number of elements by going to a separate function and |
| // pass the larger indices via a separate context, but it |
| // doesn't seem likely that we need this, and it's unclear how |
| // the rest of the system deals with 2**21 live Promises |
| // anyway. |
| ThrowRangeError( |
| MessageTemplate::kTooManyElementsInPromiseCombinator, 'any'); |
| } |
| |
| // h. Append undefined to errors. (Do nothing: errors is initialized |
| // lazily when the first Promise rejects.) |
| |
| let nextPromise: JSAny; |
| // i. Let nextPromise be ? Call(constructor, promiseResolve, |
| // «nextValue »). |
| nextPromise = CallResolve(constructor, promiseResolveFunction, nextValue); |
| |
| // j. Let steps be the algorithm steps defined in Promise.any |
| // Reject Element Functions. |
| |
| // k. Let rejectElement be ! CreateBuiltinFunction(steps, « |
| // [[AlreadyCalled]], [[Index]], |
| // [[Errors]], [[Capability]], [[RemainingElements]] »). |
| |
| // l. Set rejectElement.[[AlreadyCalled]] to a new Record { |
| // [[Value]]: false }. |
| |
| // m. Set rejectElement.[[Index]] to index. |
| |
| // n. Set rejectElement.[[Errors]] to errors. |
| |
| // o. Set rejectElement.[[Capability]] to resultCapability. |
| |
| // p. Set rejectElement.[[RemainingElements]] to |
| // remainingElementsCount. |
| const rejectElement = CreatePromiseAnyRejectElementFunction( |
| rejectElementContext, index, nativeContext); |
| // q. Set remainingElementsCount.[[Value]] to |
| // remainingElementsCount.[[Value]] + 1. |
| const remainingElementsCount = *ContextSlot( |
| rejectElementContext, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementRemainingSlot); |
| *ContextSlot( |
| rejectElementContext, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementRemainingSlot) = |
| remainingElementsCount + 1; |
| |
| // r. Perform ? Invoke(nextPromise, "then", « |
| // resultCapability.[[Resolve]], rejectElement »). |
| let thenResult: JSAny; |
| |
| const then = GetProperty(nextPromise, kThenString); |
| thenResult = Call( |
| context, then, nextPromise, |
| UnsafeCast<JSAny>(resultCapability.resolve), rejectElement); |
| |
| // s. Increase index by 1. |
| index += 1; |
| |
| // For catch prediction, mark that rejections here are |
| // semantically handled by the combined Promise. |
| if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred { |
| SetPropertyStrict( |
| context, thenResult, kPromiseHandledBySymbol, |
| resultCapability.promise); |
| SetPropertyStrict( |
| context, rejectElement, kPromiseForwardingHandlerSymbol, True); |
| } |
| } |
| } catch (e) deferred { |
| iterator::IteratorCloseOnException(iteratorRecord); |
| goto Reject(e); |
| } label Done {} |
| |
| // (8.d) |
| // i. Set iteratorRecord.[[Done]] to true. |
| // ii. Set remainingElementsCount.[[Value]] to |
| // remainingElementsCount.[[Value]] - 1. |
| const remainingElementsCount = -- *ContextSlot( |
| rejectElementContext, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementRemainingSlot); |
| |
| // iii. If remainingElementsCount.[[Value]] is 0, then |
| if (remainingElementsCount == 0) deferred { |
| // 1. Let error be a newly created AggregateError object. |
| // 2. Set error.[[AggregateErrors]] to errors. |
| |
| // We may already have elements in "errors" - this happens when the |
| // Thenable calls the reject callback immediately. |
| const errors: FixedArray = *ContextSlot( |
| rejectElementContext, |
| PromiseAnyRejectElementContextSlots:: |
| kPromiseAnyRejectElementErrorsSlot); |
| |
| const error = ConstructAggregateError(errors); |
| // 3. Return ThrowCompletion(error). |
| goto Reject(error); |
| } |
| // iv. Return resultCapability.[[Promise]]. |
| return resultCapability.promise; |
| } |
| |
| // https://tc39.es/proposal-promise-any/#sec-promise.any |
| transitioning javascript builtin |
| PromiseAny( |
| js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny { |
| const nativeContext = LoadNativeContext(context); |
| |
| // 1. Let C be the this value. |
| const receiver = Cast<JSReceiver>(receiver) |
| otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.any'); |
| |
| // 2. Let promiseCapability be ? NewPromiseCapability(C). |
| const capability = NewPromiseCapability(receiver, False); |
| |
| // NewPromiseCapability guarantees that receiver is Constructor. |
| assert(Is<Constructor>(receiver)); |
| const constructor = UnsafeCast<Constructor>(receiver); |
| |
| try { |
| // 3. Let promiseResolve be GetPromiseResolve(C). |
| // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). |
| // (catch below) |
| const promiseResolveFunction = |
| GetPromiseResolve(nativeContext, constructor); |
| |
| // 5. Let iteratorRecord be GetIterator(iterable). |
| |
| // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). |
| // (catch below) |
| const iteratorRecord = iterator::GetIterator(iterable); |
| |
| // 7. Let result be PerformPromiseAny(iteratorRecord, C, |
| // promiseCapability). |
| |
| // 8. If result is an abrupt completion, then |
| |
| // a. If iteratorRecord.[[Done]] is false, set result to |
| // IteratorClose(iteratorRecord, result). |
| |
| // b. IfAbruptRejectPromise(result, promiseCapability). |
| |
| // [Iterator closing handled by PerformPromiseAny] |
| |
| // 9. Return Completion(result). |
| return PerformPromiseAny( |
| nativeContext, iteratorRecord, constructor, capability, |
| promiseResolveFunction) |
| otherwise Reject; |
| } catch (e) deferred { |
| goto Reject(e); |
| } label Reject(e: Object) deferred { |
| // Exception must be bound to a JS value. |
| assert(e != TheHole); |
| Call( |
| context, UnsafeCast<Callable>(capability.reject), Undefined, |
| UnsafeCast<JSAny>(e)); |
| return capability.promise; |
| } |
| } |
| |
| transitioning macro ConstructAggregateError(implicit context: Context)( |
| errors: FixedArray): JSObject { |
| const obj: JSObject = error::ConstructInternalAggregateErrorHelper( |
| context, SmiConstant(MessageTemplate::kAllPromisesRejected)); |
| const errorsJSArray = array::CreateJSArrayWithElements(errors); |
| SetOwnPropertyIgnoreAttributes( |
| obj, ErrorsStringConstant(), errorsJSArray, |
| SmiConstant(PropertyAttributes::DONT_ENUM)); |
| return obj; |
| } |
| |
| extern macro PromiseAnyRejectElementSharedFunConstant(): SharedFunctionInfo; |
| } |