[Promise.any] Add Promise.any
CL adopted from joshualitt@: https://chromium-review.googlesource.com/c/v8/v8/+/2002932 Link to explainer is here: https://github.com/tc39/proposal-promise-any Co-authored-by: Joshua Litt <joshualitt@chromium.org> Bug: v8:9808 Change-Id: I6872020e857d4b131d5663f95fd58e6271ccb067 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2124834 Commit-Queue: Marja Hölttä <marja@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Reviewed-by: Shu-yu Guo <syg@chromium.org> Cr-Commit-Position: refs/heads/master@{#67502}
This commit is contained in:
parent
fd2548f332
commit
6f994a0bdf
1
BUILD.gn
1
BUILD.gn
@ -1049,6 +1049,7 @@ torque_files = [
|
||||
"src/builtins/promise-abstract-operations.tq",
|
||||
"src/builtins/promise-all.tq",
|
||||
"src/builtins/promise-all-element-closure.tq",
|
||||
"src/builtins/promise-any.tq",
|
||||
"src/builtins/promise-constructor.tq",
|
||||
"src/builtins/promise-finally.tq",
|
||||
"src/builtins/promise-misc.tq",
|
||||
|
@ -255,6 +255,7 @@ constexpr 'CodeStubAssembler::ExtractFixedArrayFlag' {
|
||||
const kBigIntMaxLength: constexpr intptr generates 'BigInt::kMaxLength';
|
||||
|
||||
extern enum MessageTemplate {
|
||||
kAllPromisesRejected,
|
||||
kInvalidArrayBufferLength,
|
||||
kInvalidArrayLength,
|
||||
kInvalidIndex,
|
||||
@ -290,7 +291,7 @@ extern enum MessageTemplate {
|
||||
kPromiseNonCallable,
|
||||
kNotAPromise,
|
||||
kResolverNotAFunction,
|
||||
kTooManyElementsInPromiseAll,
|
||||
kTooManyElementsInPromiseCombinator,
|
||||
kToRadixFormatRange,
|
||||
kCalledOnNonObject,
|
||||
kRegExpGlobalInvokedOnNonGlobal,
|
||||
|
@ -1157,6 +1157,7 @@ namespace internal {
|
||||
V(AsyncGeneratorAwaitCaught) \
|
||||
V(AsyncGeneratorAwaitUncaught) \
|
||||
V(PromiseAll) \
|
||||
V(PromiseAny) \
|
||||
V(PromiseConstructor) \
|
||||
V(PromiseConstructorLazyDeoptContinuation) \
|
||||
V(PromiseFulfillReactionJob) \
|
||||
|
@ -40,6 +40,18 @@ class PromiseBuiltins {
|
||||
kPromiseAllResolveElementLength
|
||||
};
|
||||
|
||||
enum PromiseAnyRejectElementContextSlots {
|
||||
// Remaining elements count
|
||||
kPromiseAnyRejectElementRemainingSlot = Context::MIN_CONTEXT_SLOTS,
|
||||
|
||||
// Promise capability from Promise.any
|
||||
kPromiseAnyRejectElementCapabilitySlot,
|
||||
|
||||
// errors array from Promise.any
|
||||
kPromiseAnyRejectElementErrorsArraySlot,
|
||||
kPromiseAnyRejectElementLength
|
||||
};
|
||||
|
||||
enum FunctionContextSlot {
|
||||
kCapabilitySlot = Context::MIN_CONTEXT_SLOTS,
|
||||
|
||||
|
@ -25,6 +25,10 @@ namespace growable_fixed_array {
|
||||
this.array = this.ResizeFixedArray(this.capacity);
|
||||
}
|
||||
}
|
||||
macro ToFixedArray(): FixedArray {
|
||||
return this.ResizeFixedArray(this.length);
|
||||
}
|
||||
|
||||
macro ToJSArray(implicit context: Context)(): JSArray {
|
||||
const nativeContext: NativeContext = LoadNativeContext(context);
|
||||
const map: Map =
|
||||
|
@ -187,7 +187,8 @@ namespace promise {
|
||||
// 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::kTooManyElementsInPromiseAll);
|
||||
ThrowRangeError(
|
||||
MessageTemplate::kTooManyElementsInPromiseCombinator, 'all');
|
||||
}
|
||||
|
||||
// Set remainingElementsCount.[[Value]] to
|
||||
|
370
src/builtins/promise-any.tq
Normal file
370
src/builtins/promise-any.tq
Normal file
@ -0,0 +1,370 @@
|
||||
// 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 = UnsafeCast<SharedFunctionInfo>(
|
||||
nativeContext
|
||||
[NativeContextSlot::PROMISE_ANY_REJECT_ELEMENT_SHARED_FUN]);
|
||||
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]].
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
label Done {}
|
||||
catch (e) deferred {
|
||||
iterator::IteratorCloseOnException(iteratorRecord, e)
|
||||
otherwise Reject;
|
||||
}
|
||||
|
||||
// (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;
|
||||
}
|
||||
}
|
@ -498,7 +498,8 @@ namespace internal {
|
||||
T(TooManySpreads, \
|
||||
"Literal containing too many nested spreads (up to 65534 allowed)") \
|
||||
T(TooManyVariables, "Too many variables declared (only 4194303 allowed)") \
|
||||
T(TooManyElementsInPromiseAll, "Too many elements passed to Promise.all") \
|
||||
T(TooManyElementsInPromiseCombinator, \
|
||||
"Too many elements passed to Promise.%") \
|
||||
T(TypedArrayTooShort, \
|
||||
"Derived TypedArray constructor created an array which was too small") \
|
||||
T(UnexpectedEOS, "Unexpected end of input") \
|
||||
@ -591,7 +592,9 @@ namespace internal {
|
||||
"WeakRef: target must be an object") \
|
||||
T(OptionalChainingNoNew, "Invalid optional chain from new expression") \
|
||||
T(OptionalChainingNoSuper, "Invalid optional chain from super property") \
|
||||
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain")
|
||||
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain") \
|
||||
/* AggregateError */ \
|
||||
T(AllPromisesRejected, "All promises were rejected")
|
||||
|
||||
enum class MessageTemplate {
|
||||
#define TEMPLATE(NAME, STRING) k##NAME,
|
||||
|
@ -925,12 +925,25 @@ MaybeHandle<Object> ErrorUtils::FormatStackTrace(Isolate* isolate,
|
||||
}
|
||||
|
||||
Handle<String> MessageFormatter::Format(Isolate* isolate, MessageTemplate index,
|
||||
Handle<Object> arg) {
|
||||
Handle<Object> arg0,
|
||||
Handle<Object> arg1,
|
||||
Handle<Object> arg2) {
|
||||
Factory* factory = isolate->factory();
|
||||
Handle<String> result_string = Object::NoSideEffectsToString(isolate, arg);
|
||||
Handle<String> arg0_string = factory->empty_string();
|
||||
if (!arg0.is_null()) {
|
||||
arg0_string = Object::NoSideEffectsToString(isolate, arg0);
|
||||
}
|
||||
Handle<String> arg1_string = factory->empty_string();
|
||||
if (!arg1.is_null()) {
|
||||
arg1_string = Object::NoSideEffectsToString(isolate, arg1);
|
||||
}
|
||||
Handle<String> arg2_string = factory->empty_string();
|
||||
if (!arg2.is_null()) {
|
||||
arg2_string = Object::NoSideEffectsToString(isolate, arg2);
|
||||
}
|
||||
MaybeHandle<String> maybe_result_string = MessageFormatter::Format(
|
||||
isolate, index, result_string, factory->empty_string(),
|
||||
factory->empty_string());
|
||||
isolate, index, arg0_string, arg1_string, arg2_string);
|
||||
Handle<String> result_string;
|
||||
if (!maybe_result_string.ToHandle(&result_string)) {
|
||||
DCHECK(isolate->has_pending_exception());
|
||||
isolate->clear_pending_exception();
|
||||
|
@ -317,7 +317,9 @@ class MessageFormatter {
|
||||
Handle<String> arg2);
|
||||
|
||||
static Handle<String> Format(Isolate* isolate, MessageTemplate index,
|
||||
Handle<Object> arg);
|
||||
Handle<Object> arg0,
|
||||
Handle<Object> arg1 = Handle<Object>(),
|
||||
Handle<Object> arg2 = Handle<Object>());
|
||||
};
|
||||
|
||||
// A message handler is a convenience interface for accessing the list
|
||||
|
@ -4414,6 +4414,22 @@ void Genesis::InitializeGlobal_harmony_promise_any() {
|
||||
|
||||
JSObject::DefineAccessor(prototype, factory->errors_string(), getter,
|
||||
factory->undefined_value(), DONT_ENUM);
|
||||
|
||||
{
|
||||
Handle<SharedFunctionInfo> info = SimpleCreateSharedFunctionInfo(
|
||||
isolate_, Builtins::kPromiseAnyRejectElementClosure,
|
||||
factory->empty_string(), 1);
|
||||
native_context()->set_promise_any_reject_element_shared_fun(*info);
|
||||
}
|
||||
|
||||
Handle<JSFunction> promise_fun(
|
||||
JSFunction::cast(
|
||||
isolate()->native_context()->get(Context::PROMISE_FUNCTION_INDEX)),
|
||||
isolate());
|
||||
InstallFunctionWithBuiltinId(isolate_, promise_fun, "any",
|
||||
Builtins::kPromiseAny, 1, true);
|
||||
|
||||
DCHECK(promise_fun->HasFastProperties());
|
||||
}
|
||||
|
||||
void Genesis::InitializeGlobal_harmony_promise_all_settled() {
|
||||
|
@ -239,6 +239,8 @@ enum ContextLookupFlags {
|
||||
promise_all_settled_resolve_element_shared_fun) \
|
||||
V(PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
|
||||
promise_all_settled_reject_element_shared_fun) \
|
||||
V(PROMISE_ANY_REJECT_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
|
||||
promise_any_reject_element_shared_fun) \
|
||||
V(PROMISE_PROTOTYPE_INDEX, JSObject, promise_prototype) \
|
||||
V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
|
||||
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
|
||||
|
@ -24,6 +24,7 @@ const MIN_CONTEXT_SLOTS: constexpr int31
|
||||
generates 'Context::MIN_CONTEXT_SLOTS';
|
||||
|
||||
extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
|
||||
AGGREGATE_ERROR_FUNCTION_INDEX,
|
||||
ARRAY_BUFFER_FUN_INDEX,
|
||||
ARRAY_BUFFER_NOINIT_FUN_INDEX,
|
||||
ARRAY_BUFFER_MAP_INDEX,
|
||||
@ -50,6 +51,7 @@ extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
|
||||
PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN,
|
||||
PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN,
|
||||
PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN,
|
||||
PROMISE_ANY_REJECT_ELEMENT_SHARED_FUN,
|
||||
PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX,
|
||||
PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX,
|
||||
PROMISE_CATCH_FINALLY_SHARED_FUN,
|
||||
|
@ -38,6 +38,8 @@ namespace error {
|
||||
const errors: JSAny = arguments[0];
|
||||
const errorsArray =
|
||||
iterator::IterableToFixedArrayWithSymbolLookupSlow(errors);
|
||||
// errorsArray must be marked copy-on-write, since the "errors" getter
|
||||
// creates a thin JSArray wrapper around it.
|
||||
MakeFixedArrayCOW(errorsArray);
|
||||
|
||||
// 5. Set O.[[AggregateErrors]] to errorsList.
|
||||
@ -70,7 +72,10 @@ namespace error {
|
||||
}
|
||||
|
||||
extern runtime ConstructAggregateErrorHelper(
|
||||
Context, JSFunction, JSAny, JSAny): JSAggregateError;
|
||||
Context, JSFunction, JSAny, Object): JSAggregateError;
|
||||
|
||||
extern runtime ConstructInternalAggregateErrorHelper(Context, Object):
|
||||
JSAggregateError;
|
||||
|
||||
extern macro MakeFixedArrayCOW(FixedArray);
|
||||
|
||||
|
@ -279,5 +279,43 @@ RUNTIME_FUNCTION(Runtime_ConstructAggregateErrorHelper) {
|
||||
return *result;
|
||||
}
|
||||
|
||||
// A helper function to be called when constructing AggregateError objects. This
|
||||
// takes care of the Error-related construction, e.g., stack traces.
|
||||
RUNTIME_FUNCTION(Runtime_ConstructInternalAggregateErrorHelper) {
|
||||
DCHECK(FLAG_harmony_promise_any);
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_GE(args.length(), 1);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Smi, message, 0);
|
||||
|
||||
Handle<Object> arg0;
|
||||
if (args.length() >= 2) {
|
||||
DCHECK(args[1].IsObject());
|
||||
arg0 = args.at<Object>(1);
|
||||
}
|
||||
|
||||
Handle<Object> arg1;
|
||||
if (args.length() >= 3) {
|
||||
DCHECK(args[2].IsObject());
|
||||
arg1 = args.at<Object>(2);
|
||||
}
|
||||
|
||||
Handle<Object> arg2;
|
||||
if (args.length() >= 4) {
|
||||
CHECK(args[3].IsObject());
|
||||
arg2 = args.at<Object>(3);
|
||||
}
|
||||
|
||||
Handle<Object> message_string = MessageFormatter::Format(
|
||||
isolate, MessageTemplate(message->value()), arg0, arg1, arg2);
|
||||
|
||||
Handle<Object> result;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, result,
|
||||
ErrorUtils::Construct(isolate, isolate->aggregate_error_function(),
|
||||
isolate->aggregate_error_function(),
|
||||
message_string));
|
||||
return *result;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -367,7 +367,8 @@ namespace internal {
|
||||
F(ResolvePromise, 2, 1) \
|
||||
F(PromiseRejectAfterResolved, 2, 1) \
|
||||
F(PromiseResolveAfterResolved, 2, 1) \
|
||||
F(ConstructAggregateErrorHelper, 3, 1)
|
||||
F(ConstructAggregateErrorHelper, 3, 1) \
|
||||
F(ConstructInternalAggregateErrorHelper, -1 /* <= 4*/, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_PROXY(F, I) \
|
||||
F(CheckProxyGetSetTrapResult, 2, 1) \
|
||||
|
39
test/debugger/debug/es6/debug-promises/promise-any-caught.js
Normal file
39
test/debugger/debug/es6/debug-promises/promise-any-caught.js
Normal file
@ -0,0 +1,39 @@
|
||||
// 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.
|
||||
//
|
||||
// Flags: --harmony-promise-any
|
||||
|
||||
// Test debug events when we only listen to uncaught exceptions and a
|
||||
// Promise p3 created by Promise.any has a catch handler, and is rejected
|
||||
// because the Promise p2 passed to Promise.any is rejected. We
|
||||
// expect no Exception debug event to be triggered, since p3 and by
|
||||
// extension p2 have a catch handler.
|
||||
|
||||
let Debug = debug.Debug;
|
||||
|
||||
let expected_events = 2;
|
||||
|
||||
let p1 = Promise.resolve();
|
||||
p1.name = "p1";
|
||||
|
||||
let p2 = p1.then(function() {
|
||||
throw new Error("caught");
|
||||
});
|
||||
p2.name = "p2";
|
||||
|
||||
let p3 = Promise.any([p2]);
|
||||
p3.name = "p3";
|
||||
|
||||
p3.catch(function(e) {});
|
||||
|
||||
function listener(event, exec_state, event_data, data) {
|
||||
try {
|
||||
assertTrue(event != Debug.DebugEvent.Exception)
|
||||
} catch (e) {
|
||||
%AbortJS(e + "\n" + e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.setBreakOnUncaughtException();
|
||||
Debug.setListener(listener);
|
@ -0,0 +1,67 @@
|
||||
// Copyright 2015 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.
|
||||
//
|
||||
// Flags: --harmony-promise-any
|
||||
|
||||
// Test debug events when we only listen to uncaught exceptions and a
|
||||
// Promise p3 created by Promise.any has no catch handler, and is rejected
|
||||
// because the Promise p2 passed to Promise.any is rejected.
|
||||
// We expect one event for p2; the system recognizes the rejection of p3
|
||||
// to be redundant and based on the rejection of p2 and does not trigger
|
||||
// an additional rejection.
|
||||
|
||||
let Debug = debug.Debug;
|
||||
|
||||
let expected_events = 1;
|
||||
let log = [];
|
||||
|
||||
function listener(event, exec_state, event_data, data) {
|
||||
if (event != Debug.DebugEvent.Exception) return;
|
||||
try {
|
||||
expected_events--;
|
||||
assertTrue(expected_events >= 0);
|
||||
assertEquals("uncaught", event_data.exception().message);
|
||||
// Assert that the debug event is triggered at the throw site.
|
||||
assertTrue(exec_state.frame(0).sourceLineText().indexOf("// event") > 0);
|
||||
assertTrue(event_data.uncaught());
|
||||
} catch (e) {
|
||||
%AbortJS(e + "\n" + e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.setBreakOnUncaughtException();
|
||||
Debug.setListener(listener);
|
||||
|
||||
let p1 = Promise.resolve();
|
||||
p1.name = "p1";
|
||||
|
||||
let p2 = p1.then(function() {
|
||||
log.push("throw");
|
||||
throw new Error("uncaught"); // event
|
||||
});
|
||||
p2.name = "p2";
|
||||
|
||||
let p3 = Promise.any([p2]);
|
||||
p3.name = "p3";
|
||||
|
||||
log.push("end main");
|
||||
|
||||
function testDone(iteration) {
|
||||
function checkResult() {
|
||||
try {
|
||||
assertTrue(iteration < 10);
|
||||
if (expected_events === 0) {
|
||||
assertEquals(["end main", "throw"], log);
|
||||
} else {
|
||||
testDone(iteration + 1);
|
||||
}
|
||||
} catch (e) {
|
||||
%AbortJS(e + "\n" + e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
%EnqueueMicrotask(checkResult);
|
||||
}
|
||||
|
||||
testDone(0);
|
@ -69,6 +69,11 @@
|
||||
assertEquals('hello', error.message);
|
||||
})();
|
||||
|
||||
(function TestTwoParametersMessageIsSMI() {
|
||||
let error = new AggregateError([], 44);
|
||||
assertEquals('44', error.message);
|
||||
})();
|
||||
|
||||
(function TestTwoParametersMessageUndefined() {
|
||||
let error = new AggregateError([], undefined);
|
||||
assertFalse(Object.prototype.hasOwnProperty.call(error, 'message'));
|
||||
|
21
test/mjsunit/harmony/promise-any-overflow-1.js
Normal file
21
test/mjsunit/harmony/promise-any-overflow-1.js
Normal file
@ -0,0 +1,21 @@
|
||||
// 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.
|
||||
|
||||
// Flags: --allow-natives-syntax --harmony-promise-any
|
||||
|
||||
load('test/mjsunit/test-async.js');
|
||||
|
||||
// Make sure we properly throw a RangeError when overflowing the maximum
|
||||
// number of elements for Promise.any, which is capped at 2^21 bits right
|
||||
// now, since we store the indices as identity hash on the resolve element
|
||||
// closures.
|
||||
const a = new Array(2 ** 21 - 1);
|
||||
const p = Promise.resolve(1);
|
||||
for (let i = 0; i < a.length; ++i) a[i] = p;
|
||||
testAsync(assert => {
|
||||
assert.plan(1);
|
||||
Promise.any(a).then(assert.unreachable, reason => {
|
||||
assert.equals(true, reason instanceof RangeError);
|
||||
});
|
||||
});
|
18
test/mjsunit/harmony/promise-any-overflow-2.js
Normal file
18
test/mjsunit/harmony/promise-any-overflow-2.js
Normal file
@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
// Flags: --allow-natives-syntax --harmony-promise-any
|
||||
|
||||
load('test/mjsunit/test-async.js');
|
||||
|
||||
// Test that pre-allocation of the errors array works even if it needs to be
|
||||
// allocated in large object space.
|
||||
const a = new Array(64 * 1024);
|
||||
a.fill(Promise.reject(1));
|
||||
testAsync(assert => {
|
||||
assert.plan(1);
|
||||
Promise.any(a).then(assert.unreachable, b => {
|
||||
assert.equals(a.length, b.errors.length);
|
||||
});
|
||||
});
|
30
test/mjsunit/harmony/promise-any-resolve-not-callable.js
Normal file
30
test/mjsunit/harmony/promise-any-resolve-not-callable.js
Normal file
@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
// Flags: --allow-natives-syntax --harmony-promise-any
|
||||
|
||||
load('test/mjsunit/test-async.js');
|
||||
|
||||
// Promise.any should call IteratorClose if Promise.resolve is not callable.
|
||||
|
||||
let returnCount = 0;
|
||||
let iter = {
|
||||
[Symbol.iterator]() {
|
||||
return {
|
||||
return() {
|
||||
returnCount++;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Promise.resolve = "certainly not callable";
|
||||
|
||||
testAsync(assert => {
|
||||
assert.plan(2);
|
||||
Promise.any(iter).then(assert.unreachable, reason => {
|
||||
assert.equals(true, reason instanceof TypeError);
|
||||
assert.equals(1, returnCount);
|
||||
});
|
||||
});
|
94
test/mjsunit/harmony/promise-any.js
Normal file
94
test/mjsunit/harmony/promise-any.js
Normal file
@ -0,0 +1,94 @@
|
||||
// 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.
|
||||
//
|
||||
// Flags: --allow-natives-syntax --harmony-promise-any
|
||||
|
||||
load('test/mjsunit/test-async.js');
|
||||
|
||||
(function() {
|
||||
testAsync(assert => {
|
||||
assert.plan(1);
|
||||
Promise.any([]).then(
|
||||
assert.unreachable,
|
||||
(x) => { assert.equals(0, x.errors.length); }
|
||||
);
|
||||
});
|
||||
})();
|
||||
|
||||
(function() {
|
||||
const p1 = Promise.resolve(1);
|
||||
const p2 = Promise.resolve(2);
|
||||
const p3 = Promise.resolve(3);
|
||||
testAsync(assert => {
|
||||
assert.plan(1);
|
||||
Promise.any([p1, p2, p3]).then(
|
||||
(x) => { assert.equals(1, x); },
|
||||
assert.unreachable);
|
||||
});
|
||||
})();
|
||||
|
||||
(function() {
|
||||
let outsideResolve;
|
||||
let outsideReject;
|
||||
let p1 = new Promise(() => {});
|
||||
let p2 = new Promise(function(resolve, reject) {
|
||||
outsideResolve = resolve;
|
||||
outsideReject = reject;
|
||||
});
|
||||
let p3 = new Promise(() => {});
|
||||
testAsync(assert => {
|
||||
assert.plan(1);
|
||||
Promise.any([p1, p2, p3]).then(
|
||||
(x) => { assert.equals(2, x); },
|
||||
assert.unreachable
|
||||
);
|
||||
outsideResolve(2);
|
||||
});
|
||||
})();
|
||||
|
||||
(function() {
|
||||
const p1 = Promise.reject(1);
|
||||
const p2 = Promise.resolve(2);
|
||||
const p3 = Promise.resolve(3);
|
||||
testAsync(assert => {
|
||||
assert.plan(1);
|
||||
Promise.any([p1, p2, p3]).then(
|
||||
(x) => { assert.equals(2, x); },
|
||||
assert.unreachable);
|
||||
});
|
||||
})();
|
||||
|
||||
(function() {
|
||||
const p1 = Promise.reject(1);
|
||||
const p2 = Promise.reject(2);
|
||||
const p3 = Promise.reject(3);
|
||||
testAsync(assert => {
|
||||
assert.plan(4);
|
||||
Promise.any([p1, p2, p3]).then(
|
||||
assert.unreachable,
|
||||
(x) => {
|
||||
assert.equals(3, x.errors.length);
|
||||
assert.equals(1, x.errors[0]);
|
||||
assert.equals(2, x.errors[1]);
|
||||
assert.equals(3, x.errors[2]);
|
||||
}
|
||||
);
|
||||
});
|
||||
})();
|
||||
|
||||
(function() {
|
||||
testAsync(assert => {
|
||||
assert.plan(1);
|
||||
(async function() {
|
||||
const p1 = Promise.reject(1);
|
||||
const p2 = Promise.reject(2);
|
||||
const p3 = Promise.reject(3);
|
||||
try {
|
||||
await Promise.any([p1, p2, p3]);
|
||||
} catch (error) {
|
||||
assert.equals(1, 1);
|
||||
}
|
||||
})();
|
||||
});
|
||||
})();
|
@ -90,6 +90,7 @@
|
||||
# Long-running tests.
|
||||
# We really should find better solutions for these.
|
||||
'es6/promise-all-overflow-1': [SKIP],
|
||||
'harmony/promise-any-overflow-1': [SKIP],
|
||||
'migrations': [SKIP],
|
||||
'regress/regress-2073': [SKIP],
|
||||
|
||||
@ -117,6 +118,7 @@
|
||||
'asm/sqlite3/*': [PASS, SLOW, NO_VARIANTS],
|
||||
'compiler/regress-9017': [PASS, SLOW],
|
||||
'es6/promise-all-overflow-2': [PASS, SLOW, ['arch != x64', SKIP]],
|
||||
'harmony/promise-any-overflow-2': [PASS, SLOW, ['arch != x64', SKIP]],
|
||||
'copy-on-write-assert': [PASS, SLOW],
|
||||
'es6/typedarray-construct-offset-not-smi': [PASS, SLOW],
|
||||
'harmony/futex': [PASS, SLOW],
|
||||
|
@ -536,9 +536,6 @@
|
||||
# https://bugs.chromium.org/p/v8/issues/detail?id=9818
|
||||
'built-ins/AsyncFunction/proto-from-ctor-realm': [FAIL],
|
||||
|
||||
# https://bugs.chromium.org/p/v8/issues/detail?id=9808
|
||||
'built-ins/Promise/any/*': [FAIL],
|
||||
|
||||
# https://bugs.chromium.org/p/v8/issues/detail?id=10111
|
||||
# super() should evaluate arguments before checking IsConstructable
|
||||
'language/expressions/super/call-proto-not-ctor': [FAIL],
|
||||
|
@ -63,6 +63,7 @@ FEATURE_FLAGS = {
|
||||
'class-static-methods-private': '--harmony-private-methods',
|
||||
'AggregateError': '--harmony-promise-any',
|
||||
'logical-assignment-operators': '--harmony-logical-assignment',
|
||||
'Promise.any': '--harmony-promise-any',
|
||||
}
|
||||
|
||||
SKIPPED_FEATURES = set([])
|
||||
|
Loading…
Reference in New Issue
Block a user