[runtime] Add async-stack-trace support for Promise.allSettled

... with zero cost.

Bug: v8:9357
Change-Id: I66985c3fd3e7b4efa354eb564c641562cf55ab49
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3518909
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79632}
This commit is contained in:
jameslahm 2022-03-13 12:52:15 +08:00 committed by V8 LUCI CQ
parent cfa4581e69
commit 89ed081c17
7 changed files with 81 additions and 4 deletions

View File

@ -104,7 +104,8 @@ BUILTIN(CallSitePrototypeGetPosition) {
BUILTIN(CallSitePrototypeGetPromiseIndex) { BUILTIN(CallSitePrototypeGetPromiseIndex) {
HandleScope scope(isolate); HandleScope scope(isolate);
CHECK_CALLSITE(frame, "getPromiseIndex"); CHECK_CALLSITE(frame, "getPromiseIndex");
if (!frame->IsPromiseAll() && !frame->IsPromiseAny()) { if (!frame->IsPromiseAll() && !frame->IsPromiseAny() &&
!frame->IsPromiseAllSettled()) {
return ReadOnlyRoots(isolate).null_value(); return ReadOnlyRoots(isolate).null_value();
} }
return Smi::FromInt(CallSiteInfo::GetSourcePosition(frame)); return Smi::FromInt(CallSiteInfo::GetSourcePosition(frame));

View File

@ -989,6 +989,25 @@ void CaptureAsyncStackTrace(Isolate* isolate, Handle<JSPromise> promise,
PromiseCapability::cast(context->get(index)), isolate); PromiseCapability::cast(context->get(index)), isolate);
if (!capability->promise().IsJSPromise()) return; if (!capability->promise().IsJSPromise()) return;
promise = handle(JSPromise::cast(capability->promise()), isolate); promise = handle(JSPromise::cast(capability->promise()), isolate);
} else if (IsBuiltinFunction(
isolate, reaction->fulfill_handler(),
Builtin::kPromiseAllSettledResolveElementClosure)) {
Handle<JSFunction> function(JSFunction::cast(reaction->fulfill_handler()),
isolate);
Handle<Context> context(function->context(), isolate);
Handle<JSFunction> combinator(
context->native_context().promise_all_settled(), isolate);
builder->AppendPromiseCombinatorFrame(function, combinator);
// Now peak into the Promise.allSettled() resolve element context to
// find the promise capability that's being resolved when all
// the concurrent promises resolve.
int const index =
PromiseBuiltins::kPromiseAllResolveElementCapabilitySlot;
Handle<PromiseCapability> capability(
PromiseCapability::cast(context->get(index)), isolate);
if (!capability->promise().IsJSPromise()) return;
promise = handle(JSPromise::cast(capability->promise()), isolate);
} else if (IsBuiltinFunction(isolate, reaction->reject_handler(), } else if (IsBuiltinFunction(isolate, reaction->reject_handler(),
Builtin::kPromiseAnyRejectElementClosure)) { Builtin::kPromiseAnyRejectElementClosure)) {
Handle<JSFunction> function(JSFunction::cast(reaction->reject_handler()), Handle<JSFunction> function(JSFunction::cast(reaction->reject_handler()),

View File

@ -2387,8 +2387,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
isolate_, promise_fun, "all", Builtin::kPromiseAll, 1, true); isolate_, promise_fun, "all", Builtin::kPromiseAll, 1, true);
native_context()->set_promise_all(*promise_all); native_context()->set_promise_all(*promise_all);
InstallFunctionWithBuiltinId(isolate_, promise_fun, "allSettled", Handle<JSFunction> promise_all_settled =
Builtin::kPromiseAllSettled, 1, true); InstallFunctionWithBuiltinId(isolate_, promise_fun, "allSettled",
Builtin::kPromiseAllSettled, 1, true);
native_context()->set_promise_all_settled(*promise_all_settled);
Handle<JSFunction> promise_any = InstallFunctionWithBuiltinId( Handle<JSFunction> promise_any = InstallFunctionWithBuiltinId(
isolate_, promise_fun, "any", Builtin::kPromiseAny, 1, true); isolate_, promise_fun, "any", Builtin::kPromiseAny, 1, true);

View File

@ -22,6 +22,12 @@ bool CallSiteInfo::IsPromiseAll() const {
return fun == fun.native_context().promise_all(); return fun == fun.native_context().promise_all();
} }
bool CallSiteInfo::IsPromiseAllSettled() const {
if (!IsAsync()) return false;
JSFunction fun = JSFunction::cast(function());
return fun == fun.native_context().promise_all_settled();
}
bool CallSiteInfo::IsPromiseAny() const { bool CallSiteInfo::IsPromiseAny() const {
if (!IsAsync()) return false; if (!IsAsync()) return false;
JSFunction fun = JSFunction::cast(function()); JSFunction fun = JSFunction::cast(function());
@ -507,6 +513,7 @@ int CallSiteInfo::GetSourcePosition(Handle<CallSiteInfo> info) {
return info->code_offset_or_source_position(); return info->code_offset_or_source_position();
} }
DCHECK(!info->IsPromiseAll()); DCHECK(!info->IsPromiseAll());
DCHECK(!info->IsPromiseAllSettled());
DCHECK(!info->IsPromiseAny()); DCHECK(!info->IsPromiseAny());
int source_position = int source_position =
ComputeSourcePosition(info, info->code_offset_or_source_position()); ComputeSourcePosition(info, info->code_offset_or_source_position());
@ -711,7 +718,8 @@ void SerializeJSStackFrame(Isolate* isolate, Handle<CallSiteInfo> frame,
Handle<Object> function_name = CallSiteInfo::GetFunctionName(frame); Handle<Object> function_name = CallSiteInfo::GetFunctionName(frame);
if (frame->IsAsync()) { if (frame->IsAsync()) {
builder->AppendCStringLiteral("async "); builder->AppendCStringLiteral("async ");
if (frame->IsPromiseAll() || frame->IsPromiseAny()) { if (frame->IsPromiseAll() || frame->IsPromiseAny() ||
frame->IsPromiseAllSettled()) {
builder->AppendCStringLiteral("Promise."); builder->AppendCStringLiteral("Promise.");
builder->AppendString(Handle<String>::cast(function_name)); builder->AppendString(Handle<String>::cast(function_name));
builder->AppendCStringLiteral(" (index "); builder->AppendCStringLiteral(" (index ");

View File

@ -40,6 +40,7 @@ class CallSiteInfo : public TorqueGeneratedCallSiteInfo<CallSiteInfo, Struct> {
bool IsMethodCall() const; bool IsMethodCall() const;
bool IsToplevel() const; bool IsToplevel() const;
bool IsPromiseAll() const; bool IsPromiseAll() const;
bool IsPromiseAllSettled() const;
bool IsPromiseAny() const; bool IsPromiseAny() const;
bool IsNative() const; bool IsNative() const;

View File

@ -345,6 +345,7 @@ enum ContextLookupFlags {
V(OBJECT_TO_STRING, JSFunction, object_to_string) \ V(OBJECT_TO_STRING, JSFunction, object_to_string) \
V(OBJECT_VALUE_OF_FUNCTION_INDEX, JSFunction, object_value_of_function) \ V(OBJECT_VALUE_OF_FUNCTION_INDEX, JSFunction, object_value_of_function) \
V(PROMISE_ALL_INDEX, JSFunction, promise_all) \ V(PROMISE_ALL_INDEX, JSFunction, promise_all) \
V(PROMISE_ALL_SETTLED_INDEX, JSFunction, promise_all_settled) \
V(PROMISE_ANY_INDEX, JSFunction, promise_any) \ V(PROMISE_ANY_INDEX, JSFunction, promise_any) \
V(PROMISE_FUNCTION_INDEX, JSFunction, promise_function) \ V(PROMISE_FUNCTION_INDEX, JSFunction, promise_function) \
V(RANGE_ERROR_FUNCTION_INDEX, JSFunction, range_error_function) \ V(RANGE_ERROR_FUNCTION_INDEX, JSFunction, range_error_function) \

View File

@ -0,0 +1,45 @@
// Copyright 2022 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 --async-stack-traces
// Basic test with Promise.allSettled().
(function() {
async function fine() { }
async function thrower() {
await fine();
throw new Error();
}
async function driver() {
return await Promise.allSettled([fine(), fine(), thrower(), thrower()]);
}
async function test(f) {
const results = await f();
results.forEach((result, i) => {
if (result.status === 'rejected') {
const error = result.reason;
assertInstanceof(error, Error);
const stackRegexp = new RegExp("Error.+at thrower.+at " +
`async Promise.allSettled \\(index ${ i }\\)` +
".+ at async driver.+at async test",
"ms")
assertMatches(stackRegexp, error.stack);
}
});
}
assertPromiseResult((async () => {
%PrepareFunctionForOptimization(thrower);
%PrepareFunctionForOptimization(driver);
await test(driver);
await test(driver);
%OptimizeFunctionOnNextCall(thrower);
await test(driver);
%OptimizeFunctionOnNextCall(driver);
await test(driver);
})());
})();