diff options
Diffstat (limited to 'deps/v8/src/builtins/promise-any.tq')
-rw-r--r-- | deps/v8/src/builtins/promise-any.tq | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/deps/v8/src/builtins/promise-any.tq b/deps/v8/src/builtins/promise-any.tq new file mode 100644 index 00000000000..1046ed0a89c --- /dev/null +++ b/deps/v8/src/builtins/promise-any.tq @@ -0,0 +1,372 @@ +// 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 { +extern enum PromiseAnyRejectElementContextSlots extends int31 +constexpr 'PromiseBuiltins::PromiseAnyRejectElementContextSlots' { + kPromiseAnyRejectElementRemainingSlot, + kPromiseAnyRejectElementCapabilitySlot, + kPromiseAnyRejectElementErrorsArraySlot, + 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): Context { + const rejectContext = AllocateSyntheticFunctionContext( + nativeContext, + PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength); + rejectContext[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementRemainingSlot] = SmiConstant(1); + rejectContext[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementCapabilitySlot] = capability; + // Will be set later. + rejectContext[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementErrorsArraySlot] = Undefined; + return rejectContext; +} + +macro CreatePromiseAnyRejectElementFunction(implicit context: Context)( + rejectElementContext: Context, index: Smi, + nativeContext: NativeContext): JSFunction { + assert(index > 0); + assert(index < kPropertyArrayHashFieldMax); + const map = UnsafeCast<Map>( + nativeContext + [NativeContextSlot::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 == + PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength); + + // 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]]. + if (context[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementErrorsArraySlot] == Undefined) { + // We're going to reject the Promise with a more fundamental error (e.g., + // something went wrong with iterating the Promises). We don't need to + // construct the "errors" array. + return Undefined; + } + + const errorsArray = UnsafeCast<FixedArray>( + context[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementErrorsArraySlot]); + + // 7. Let promiseCapability be F.[[Capability]]. + + // 8. Let remainingElementsCount be F.[[RemainingElements]]. + let remainingElementsCount = + UnsafeCast<Smi>(context[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementRemainingSlot]); + // 9. Set errors[index] to x. + errorsArray.objects[index] = value; + + // 10. Set remainingElementsCount.[[Value]] to + // remainingElementsCount.[[Value]] - 1. + remainingElementsCount = remainingElementsCount - 1; + 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(errorsArray); + // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). + const capability = UnsafeCast<PromiseCapability>( + context[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementCapabilitySlot]); + Call(context, UnsafeCast<Callable>(capability.reject), Undefined, error); + } + + // 12. Return undefined. + return Undefined; +} + +transitioning macro PerformPromiseAny(implicit context: Context)( + iteratorRecord: iterator::IteratorRecord, constructor: Constructor, + resultCapability: PromiseCapability): JSAny labels +Reject(Object) { + // 1. Assert: ! IsConstructor(constructor) is true. + // 2. Assert: resultCapability is a PromiseCapability Record. + + const nativeContext = LoadNativeContext(context); + + // 3. Let errors be a new empty List. + let growableErrorsArray = growable_fixed_array::NewGrowableFixedArray(); + + // 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 { + // We can skip the "resolve" lookup on {constructor} if it's the + // Promise constructor and the Promise.resolve protector is intact, + // as that guards the lookup path for the "resolve" property on the + // Promise constructor. + let promiseResolveFunction: JSAny = Undefined; + if (!IsPromiseResolveLookupChainIntact(nativeContext, constructor)) + deferred { + // 6. Let promiseResolve be ? Get(constructor, `"resolve"`). + const promiseResolve = GetProperty(constructor, kResolveString); + // 7. If IsCallable(promiseResolve) is false, throw a + // TypeError exception. + promiseResolveFunction = Cast<Callable>(promiseResolve) + otherwise ThrowTypeError( + MessageTemplate::kCalledNonCallable, 'resolve'); + } + const fastIteratorResultMap = UnsafeCast<Map>( + nativeContext[NativeContextSlot::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. + growableErrorsArray.Push(Undefined); + + 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 = UnsafeCast<Smi>( + rejectElementContext[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementRemainingSlot]); + 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. + let remainingElementsCount = UnsafeCast<Smi>( + rejectElementContext[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementRemainingSlot]); + remainingElementsCount -= 1; + rejectElementContext[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementRemainingSlot] = + remainingElementsCount; + + const errorsArray = growableErrorsArray.ToFixedArray(); + rejectElementContext[PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementErrorsArraySlot] = + errorsArray; + + // 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. + const error = ConstructAggregateError(errorsArray); + // 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 { + // 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 { + let iteratorRecord: iterator::IteratorRecord; + try { + // 3. Let iteratorRecord be GetIterator(iterable). + + // 4. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + // (catch below) + iteratorRecord = iterator::GetIterator(iterable); + + // 5. Let result be PerformPromiseAny(iteratorRecord, C, + // promiseCapability). + + // 6. 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] + + // 7. Return Completion(result). + return PerformPromiseAny(iteratorRecord, constructor, capability) + 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)( + errorsArray: FixedArray): JSObject { + const obj: JSAggregateError = error::ConstructInternalAggregateErrorHelper( + context, SmiConstant(MessageTemplate::kAllPromisesRejected)); + obj.errors = errorsArray; + return obj; +} + +extern macro PromiseAnyRejectElementSharedFunConstant(): SharedFunctionInfo; +} |