Commit 6f994a0b authored by Marja Hölttä's avatar Marja Hölttä Committed by Commit Bot

[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-anyCo-authored-by: 's avatarJoshua 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: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarIgor Sheludko <ishell@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran  <gsathya@chromium.org>
Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67502}
parent fd2548f3
......@@ -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
......
This diff is collapsed.
......@@ -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) \
......
// 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);
// 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'));
......
// 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);
});
});
// 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);
});
});
// 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);
});
});
// 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([])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment