| // Copyright 2019 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.h' |
| #include 'src/builtins/builtins-promise-gen.h' |
| |
| namespace promise { |
| const kPromiseBuiltinsPromiseContextLength: constexpr int31 |
| generates 'PromiseBuiltins::kPromiseContextLength'; |
| |
| // Creates the context used by all Promise.all resolve element closures, |
| // together with the values array. Since all closures for a single Promise.all |
| // 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). |
| macro CreatePromiseAllResolveElementContext(implicit context: Context)( |
| capability: PromiseCapability, |
| nativeContext: NativeContext): PromiseAllResolveElementContext { |
| const resolveContext = %RawDownCast< |
| PromiseAllResolveElementContext>(AllocateSyntheticFunctionContext( |
| nativeContext, |
| PromiseAllResolveElementContextSlots::kPromiseAllResolveElementLength)); |
| InitContextSlot( |
| resolveContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementRemainingSlot, |
| 1); |
| InitContextSlot( |
| resolveContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementCapabilitySlot, |
| capability); |
| InitContextSlot( |
| resolveContext, |
| PromiseAllResolveElementContextSlots::kPromiseAllResolveElementValuesSlot, |
| kEmptyFixedArray); |
| return resolveContext; |
| } |
| |
| macro CreatePromiseAllResolveElementFunction(implicit context: Context)( |
| resolveElementContext: PromiseAllResolveElementContext, index: Smi, |
| nativeContext: NativeContext, |
| resolveFunction: SharedFunctionInfo): JSFunction { |
| assert(index > 0); |
| assert(index < kPropertyArrayHashFieldMax); |
| |
| const map = *ContextSlot( |
| nativeContext, ContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); |
| const resolve = AllocateFunctionWithMapAndContext( |
| map, resolveFunction, resolveElementContext); |
| |
| assert(kPropertyArrayNoHashSentinel == 0); |
| resolve.properties_or_hash = index; |
| return resolve; |
| } |
| |
| @export |
| macro CreatePromiseResolvingFunctionsContext(implicit context: Context)( |
| promise: JSPromise, debugEvent: Boolean, nativeContext: NativeContext): |
| PromiseResolvingFunctionContext { |
| const resolveContext = %RawDownCast<PromiseResolvingFunctionContext>( |
| AllocateSyntheticFunctionContext( |
| nativeContext, |
| PromiseResolvingFunctionContextSlot::kPromiseContextLength)); |
| InitContextSlot( |
| resolveContext, PromiseResolvingFunctionContextSlot::kPromiseSlot, |
| promise); |
| InitContextSlot( |
| resolveContext, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot, |
| False); |
| InitContextSlot( |
| resolveContext, PromiseResolvingFunctionContextSlot::kDebugEventSlot, |
| debugEvent); |
| static_assert( |
| PromiseResolvingFunctionContextSlot::kPromiseContextLength == |
| ContextSlot::MIN_CONTEXT_SLOTS + 3); |
| return resolveContext; |
| } |
| |
| macro IsPromiseThenLookupChainIntact(implicit context: Context)( |
| nativeContext: NativeContext, receiverMap: Map): bool { |
| if (IsForceSlowPath()) return false; |
| if (!IsJSPromiseMap(receiverMap)) return false; |
| if (receiverMap.prototype != *NativeContextSlot( |
| nativeContext, ContextSlot::PROMISE_PROTOTYPE_INDEX)) { |
| return false; |
| } |
| return !IsPromiseThenProtectorCellInvalid(); |
| } |
| |
| struct PromiseAllResolveElementFunctor { |
| macro Call(implicit context: Context)( |
| resolveElementContext: PromiseAllResolveElementContext, |
| nativeContext: NativeContext, index: Smi, |
| _capability: PromiseCapability): Callable { |
| return CreatePromiseAllResolveElementFunction( |
| resolveElementContext, index, nativeContext, |
| PromiseAllResolveElementSharedFunConstant()); |
| } |
| } |
| |
| struct PromiseAllRejectElementFunctor { |
| macro Call(implicit context: Context)( |
| _resolveElementContext: PromiseAllResolveElementContext, |
| _nativeContext: NativeContext, _index: Smi, |
| capability: PromiseCapability): Callable { |
| return UnsafeCast<Callable>(capability.reject); |
| } |
| } |
| |
| struct PromiseAllSettledResolveElementFunctor { |
| macro Call(implicit context: Context)( |
| resolveElementContext: PromiseAllResolveElementContext, |
| nativeContext: NativeContext, index: Smi, |
| _capability: PromiseCapability): Callable { |
| return CreatePromiseAllResolveElementFunction( |
| resolveElementContext, index, nativeContext, |
| PromiseAllSettledResolveElementSharedFunConstant()); |
| } |
| } |
| |
| struct PromiseAllSettledRejectElementFunctor { |
| macro Call(implicit context: Context)( |
| resolveElementContext: PromiseAllResolveElementContext, |
| nativeContext: NativeContext, index: Smi, |
| _capability: PromiseCapability): Callable { |
| return CreatePromiseAllResolveElementFunction( |
| resolveElementContext, index, nativeContext, |
| PromiseAllSettledRejectElementSharedFunConstant()); |
| } |
| } |
| |
| transitioning macro PerformPromiseAll<F1: type, F2: type>( |
| implicit context: Context)( |
| nativeContext: NativeContext, iter: iterator::IteratorRecord, |
| constructor: Constructor, capability: PromiseCapability, |
| promiseResolveFunction: JSAny, createResolveElementFunctor: F1, |
| createRejectElementFunctor: F2): JSAny labels |
| Reject(Object) { |
| const promise = capability.promise; |
| const resolve = capability.resolve; |
| const reject = capability.reject; |
| |
| // For catch prediction, don't treat the .then calls as handling it; |
| // instead, recurse outwards. |
| if (IsDebugActive()) deferred { |
| SetPropertyStrict(context, reject, kPromiseForwardingHandlerSymbol, True); |
| } |
| |
| const resolveElementContext = |
| CreatePromiseAllResolveElementContext(capability, nativeContext); |
| |
| let index: Smi = 1; |
| |
| try { |
| const fastIteratorResultMap = *NativeContextSlot( |
| nativeContext, ContextSlot::ITERATOR_RESULT_MAP_INDEX); |
| while (true) { |
| let nextValue: JSAny; |
| try { |
| // Let next be IteratorStep(iteratorRecord.[[Iterator]]). |
| // If next is an abrupt completion, set iteratorRecord.[[Done]] to |
| // true. ReturnIfAbrupt(next). |
| const next: JSReceiver = iterator::IteratorStep( |
| iter, fastIteratorResultMap) otherwise goto Done; |
| |
| // Let nextValue be IteratorValue(next). |
| // If nextValue is an abrupt completion, set iteratorRecord.[[Done]] |
| // to true. |
| // ReturnIfAbrupt(nextValue). |
| nextValue = iterator::IteratorValue(next, fastIteratorResultMap); |
| } catch (e) { |
| goto Reject(e); |
| } |
| |
| // Check if we reached the limit. |
| if (index == kPropertyArrayHashFieldMax) { |
| // If there are too many elements (currently more than 2**21-1), |
| // raise a RangeError here (which is caught below 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, 'all'); |
| } |
| |
| // Set remainingElementsCount.[[Value]] to |
| // remainingElementsCount.[[Value]] + 1. |
| *ContextSlot( |
| resolveElementContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementRemainingSlot) += 1; |
| |
| // Let resolveElement be CreateBuiltinFunction(steps, |
| // « [[AlreadyCalled]], |
| // [[Index]], |
| // [[Values]], |
| // [[Capability]], |
| // [[RemainingElements]] |
| // »). |
| // Set resolveElement.[[AlreadyCalled]] to a Record { [[Value]]: false |
| // }. Set resolveElement.[[Index]] to index. Set |
| // resolveElement.[[Values]] to values. Set |
| // resolveElement.[[Capability]] to resultCapability. Set |
| // resolveElement.[[RemainingElements]] to remainingElementsCount. |
| const resolveElementFun = createResolveElementFunctor.Call( |
| resolveElementContext, nativeContext, index, capability); |
| const rejectElementFun = createRejectElementFunctor.Call( |
| resolveElementContext, nativeContext, index, capability); |
| |
| // We can skip the "then" lookup on the result of the "resolve" call and |
| // immediately chain the continuation onto the {next_value} if: |
| // |
| // (a) The {constructor} is the intrinsic %Promise% function, and |
| // looking up "resolve" on {constructor} yields the initial |
| // Promise.resolve() builtin, and |
| // (b) the promise @@species protector cell is valid, meaning that |
| // no one messed with the Symbol.species property on any |
| // intrinsic promise or on the Promise.prototype, and |
| // (c) the {next_value} is a JSPromise whose [[Prototype]] field |
| // contains the intrinsic %PromisePrototype%, and |
| // (d) we're not running with async_hooks or DevTools enabled. |
| // |
| // In that case we also don't need to allocate a chained promise for |
| // the PromiseReaction (aka we can pass undefined to |
| // PerformPromiseThen), since this is only necessary for DevTools and |
| // PromiseHooks. |
| if (promiseResolveFunction != Undefined || |
| IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() || |
| IsPromiseSpeciesProtectorCellInvalid() || Is<Smi>(nextValue) || |
| !IsPromiseThenLookupChainIntact( |
| nativeContext, UnsafeCast<HeapObject>(nextValue).map)) { |
| // Let nextPromise be ? Call(constructor, _promiseResolve_, « |
| // nextValue »). |
| const nextPromise = |
| CallResolve(constructor, promiseResolveFunction, nextValue); |
| |
| // Perform ? Invoke(nextPromise, "then", « resolveElement, |
| // resultCapability.[[Reject]] »). |
| const then = GetProperty(nextPromise, kThenString); |
| const thenResult = Call( |
| nativeContext, then, nextPromise, resolveElementFun, |
| rejectElementFun); |
| |
| // For catch prediction, mark that rejections here are |
| // semantically handled by the combined Promise. |
| if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred { |
| SetPropertyStrict( |
| context, thenResult, kPromiseHandledBySymbol, promise); |
| } |
| } else { |
| PerformPromiseThenImpl( |
| UnsafeCast<JSPromise>(nextValue), resolveElementFun, |
| rejectElementFun, Undefined); |
| } |
| |
| // Set index to index + 1. |
| index += 1; |
| } |
| } catch (e) deferred { |
| iterator::IteratorCloseOnException(iter); |
| goto Reject(e); |
| } label Done {} |
| |
| // Set iteratorRecord.[[Done]] to true. |
| // Set remainingElementsCount.[[Value]] to |
| // remainingElementsCount.[[Value]] - 1. |
| const remainingElementsCount = -- *ContextSlot( |
| resolveElementContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementRemainingSlot); |
| |
| check(remainingElementsCount >= 0); |
| |
| if (remainingElementsCount > 0) { |
| // Pre-allocate the backing store for the {values} to the desired |
| // capacity. We may already have elements in "values" - this happens |
| // when the Thenable calls the resolve callback immediately. |
| const valuesRef:&FixedArray = ContextSlot( |
| resolveElementContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementValuesSlot); |
| const values = *valuesRef; |
| // 'index' is a 1-based index and incremented after every Promise. Later we |
| // use 'values' as a 0-based array, so capacity 'index - 1' is enough. |
| const newCapacity = SmiUntag(index) - 1; |
| |
| const oldCapacity = values.length_intptr; |
| if (oldCapacity < newCapacity) { |
| *valuesRef = ExtractFixedArray(values, 0, oldCapacity, newCapacity); |
| } |
| } else |
| deferred { |
| // If remainingElementsCount.[[Value]] is 0, then |
| // Let valuesArray be CreateArrayFromList(values). |
| // Perform ? Call(resultCapability.[[Resolve]], undefined, |
| // « valuesArray »). |
| |
| const values: FixedArray = *ContextSlot( |
| resolveElementContext, |
| PromiseAllResolveElementContextSlots:: |
| kPromiseAllResolveElementValuesSlot); |
| const arrayMap = |
| *NativeContextSlot( |
| nativeContext, ContextSlot::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX); |
| const valuesArray = NewJSArray(arrayMap, values); |
| Call(nativeContext, UnsafeCast<JSAny>(resolve), Undefined, valuesArray); |
| } |
| |
| // Return resultCapability.[[Promise]]. |
| return promise; |
| } |
| |
| transitioning macro GeneratePromiseAll<F1: type, F2: type>( |
| implicit context: Context)( |
| receiver: JSAny, iterable: JSAny, createResolveElementFunctor: F1, |
| createRejectElementFunctor: F2): JSAny { |
| const nativeContext = LoadNativeContext(context); |
| // Let C be the this value. |
| // If Type(C) is not Object, throw a TypeError exception. |
| const receiver = Cast<JSReceiver>(receiver) |
| otherwise ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'Promise.all'); |
| |
| // Let promiseCapability be ? NewPromiseCapability(C). |
| // Don't fire debugEvent so that forwarding the rejection through all does |
| // not trigger redundant ExceptionEvents |
| const capability = NewPromiseCapability(receiver, False); |
| |
| // NewPromiseCapability guarantees that receiver is Constructor. |
| assert(Is<Constructor>(receiver)); |
| const constructor = UnsafeCast<Constructor>(receiver); |
| |
| try { |
| // Let promiseResolve be GetPromiseResolve(C). |
| // IfAbruptRejectPromise(promiseResolve, promiseCapability). |
| const promiseResolveFunction = |
| GetPromiseResolve(nativeContext, constructor); |
| |
| // Let iterator be GetIterator(iterable). |
| // IfAbruptRejectPromise(iterator, promiseCapability). |
| let i = iterator::GetIterator(iterable); |
| |
| // Let result be PerformPromiseAll(iteratorRecord, C, |
| // promiseCapability). If result is an abrupt completion, then |
| // If iteratorRecord.[[Done]] is false, let result be |
| // IteratorClose(iterator, result). |
| // IfAbruptRejectPromise(result, promiseCapability). |
| return PerformPromiseAll( |
| nativeContext, i, constructor, capability, promiseResolveFunction, |
| createResolveElementFunctor, createRejectElementFunctor) |
| otherwise Reject; |
| } catch (e) deferred { |
| goto Reject(e); |
| } label Reject(e: Object) deferred { |
| // Exception must be bound to a JS value. |
| const e = UnsafeCast<JSAny>(e); |
| const reject = UnsafeCast<JSAny>(capability.reject); |
| Call(context, reject, Undefined, e); |
| return capability.promise; |
| } |
| } |
| |
| // ES#sec-promise.all |
| transitioning javascript builtin PromiseAll( |
| js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny { |
| return GeneratePromiseAll( |
| receiver, iterable, PromiseAllResolveElementFunctor{}, |
| PromiseAllRejectElementFunctor{}); |
| } |
| |
| // ES#sec-promise.allsettled |
| // Promise.allSettled ( iterable ) |
| transitioning javascript builtin PromiseAllSettled( |
| js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny { |
| return GeneratePromiseAll( |
| receiver, iterable, PromiseAllSettledResolveElementFunctor{}, |
| PromiseAllSettledRejectElementFunctor{}); |
| } |
| |
| extern macro PromiseAllResolveElementSharedFunConstant(): SharedFunctionInfo; |
| extern macro PromiseAllSettledRejectElementSharedFunConstant(): |
| SharedFunctionInfo; |
| extern macro PromiseAllSettledResolveElementSharedFunConstant(): |
| SharedFunctionInfo; |
| } |