blob: d86e265d6c2bcfc7a97c1016dbbdac10bbed68a5 [file] [log] [blame]
// 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;
}