Commit 7265fdde authored by littledan's avatar littledan Committed by Commit bot

Async/await Promise dependency graph

This patch knits together Promises returned by async/await such that when
one async function awaits the result of another one, catch prediction works
across the boundaries, whether the exception comes synchronously or
asynchronously. Edges are added in three places:
- When a locally uncaught await happens, if the value passed into await
  is a Promise, from the awaited value to the Promise under construction
  in the broader async function
- From a "throwaway" Promise, which may be found on the Promise debug
  stack, to the Promise under construction in the async function that
  surrounds it
- When a Promise is resolved with another Promise (e.g., when returning a
  Promise from an async function)

BUG=v8:5167

Review-Url: https://codereview.chromium.org/2317383002
Cr-Commit-Position: refs/heads/master@{#39522}
parent 7e07d3f6
...@@ -199,11 +199,11 @@ ...@@ -199,11 +199,11 @@
V(normal_ic_symbol) \ V(normal_ic_symbol) \
V(not_mapped_symbol) \ V(not_mapped_symbol) \
V(premonomorphic_symbol) \ V(premonomorphic_symbol) \
V(promise_await_handler_symbol) \
V(promise_combined_deferred_symbol) \
V(promise_debug_marker_symbol) \ V(promise_debug_marker_symbol) \
V(promise_deferred_reactions_symbol) \ V(promise_deferred_reactions_symbol) \
V(promise_forwarding_handler_symbol) \
V(promise_fulfill_reactions_symbol) \ V(promise_fulfill_reactions_symbol) \
V(promise_handled_by_symbol) \
V(promise_handled_hint_symbol) \ V(promise_handled_hint_symbol) \
V(promise_has_handler_symbol) \ V(promise_has_handler_symbol) \
V(promise_raw_symbol) \ V(promise_raw_symbol) \
......
...@@ -33,7 +33,10 @@ utils.Import(function(from) { ...@@ -33,7 +33,10 @@ utils.Import(function(from) {
ResolvePromise = from.ResolvePromise; ResolvePromise = from.ResolvePromise;
}); });
var promiseAwaitHandlerSymbol = utils.ImportNow("promise_await_handler_symbol"); var promiseHandledBySymbol =
utils.ImportNow("promise_handled_by_symbol");
var promiseForwardingHandlerSymbol =
utils.ImportNow("promise_forwarding_handler_symbol");
var promiseHandledHintSymbol = var promiseHandledHintSymbol =
utils.ImportNow("promise_handled_hint_symbol"); utils.ImportNow("promise_handled_hint_symbol");
var promiseHasHandlerSymbol = var promiseHasHandlerSymbol =
...@@ -56,10 +59,11 @@ function PromiseCastResolved(value) { ...@@ -56,10 +59,11 @@ function PromiseCastResolved(value) {
// Shared logic for the core of await. The parser desugars // Shared logic for the core of await. The parser desugars
// await awaited // await awaited
// into // into
// yield AsyncFunctionAwait{Caught,Uncaught}(.generator, awaited) // yield AsyncFunctionAwait{Caught,Uncaught}(.generator, awaited, .promise)
// The 'awaited' parameter is the value; the generator stands in // The 'awaited' parameter is the value; the generator stands in
// for the asyncContext, and mark is metadata for debugging // for the asyncContext, and .promise is the larger promise under
function AsyncFunctionAwait(generator, awaited, mark) { // construction by the enclosing async function.
function AsyncFunctionAwait(generator, awaited, outerPromise) {
// Promise.resolve(awaited).then( // Promise.resolve(awaited).then(
// value => AsyncFunctionNext(value), // value => AsyncFunctionNext(value),
// error => AsyncFunctionThrow(error) // error => AsyncFunctionThrow(error)
...@@ -83,12 +87,6 @@ function AsyncFunctionAwait(generator, awaited, mark) { ...@@ -83,12 +87,6 @@ function AsyncFunctionAwait(generator, awaited, mark) {
return; return;
} }
if (mark && DEBUG_IS_ACTIVE && IsPromise(awaited)) {
// Mark the reject handler callback such that it does not influence
// catch prediction.
SET_PRIVATE(onRejected, promiseAwaitHandlerSymbol, true);
}
// Just forwarding the exception, so no debugEvent for throwawayCapability // Just forwarding the exception, so no debugEvent for throwawayCapability
var throwawayCapability = NewPromiseCapability(GlobalPromise, false); var throwawayCapability = NewPromiseCapability(GlobalPromise, false);
...@@ -96,24 +94,38 @@ function AsyncFunctionAwait(generator, awaited, mark) { ...@@ -96,24 +94,38 @@ function AsyncFunctionAwait(generator, awaited, mark) {
// unhandled reject events as its work is done // unhandled reject events as its work is done
SET_PRIVATE(throwawayCapability.promise, promiseHasHandlerSymbol, true); SET_PRIVATE(throwawayCapability.promise, promiseHasHandlerSymbol, true);
return PerformPromiseThen(promise, onFulfilled, onRejected, PerformPromiseThen(promise, onFulfilled, onRejected, throwawayCapability);
throwawayCapability);
if (DEBUG_IS_ACTIVE && !IS_UNDEFINED(outerPromise)) {
if (IsPromise(awaited)) {
// Mark the reject handler callback to be a forwarding edge, rather
// than a meaningful catch handler
SET_PRIVATE(onRejected, promiseForwardingHandlerSymbol, true);
}
// Mark the dependency to outerPromise in case the throwaway Promise is
// found on the Promise stack
SET_PRIVATE(throwawayCapability.promise, promiseHandledBySymbol,
outerPromise);
}
} }
// Called by the parser from the desugaring of 'await' when catch // Called by the parser from the desugaring of 'await' when catch
// prediction indicates no locally surrounding catch block // prediction indicates no locally surrounding catch block
function AsyncFunctionAwaitUncaught(generator, awaited) { function AsyncFunctionAwaitUncaught(generator, awaited, outerPromise) {
// TODO(littledan): Install a dependency edge from awaited to outerPromise AsyncFunctionAwait(generator, awaited, outerPromise);
return AsyncFunctionAwait(generator, awaited, true);
} }
// Called by the parser from the desugaring of 'await' when catch // Called by the parser from the desugaring of 'await' when catch
// prediction indicates that there is a locally surrounding catch block // prediction indicates that there is a locally surrounding catch block
function AsyncFunctionAwaitCaught(generator, awaited) { function AsyncFunctionAwaitCaught(generator, awaited, outerPromise) {
if (DEBUG_IS_ACTIVE && IsPromise(awaited)) { if (DEBUG_IS_ACTIVE && IsPromise(awaited)) {
SET_PRIVATE(awaited, promiseHandledHintSymbol, true); SET_PRIVATE(awaited, promiseHandledHintSymbol, true);
} }
return AsyncFunctionAwait(generator, awaited, false); // Pass undefined for the outer Promise to not waste time setting up
// or following the dependency chain when this Promise is already marked
// as handled
AsyncFunctionAwait(generator, awaited, UNDEFINED);
} }
// How the parser rejects promises from async/await desugaring // How the parser rejects promises from async/await desugaring
......
...@@ -219,7 +219,8 @@ function PostNatives(utils) { ...@@ -219,7 +219,8 @@ function PostNatives(utils) {
"iterator_symbol", "iterator_symbol",
"promise_result_symbol", "promise_result_symbol",
"promise_state_symbol", "promise_state_symbol",
"promise_await_handler_symbol", "promise_forwarding_handler_symbol",
"promise_handled_by_symbol",
"promise_handled_hint_symbol", "promise_handled_hint_symbol",
"promise_has_handler_symbol", "promise_has_handler_symbol",
"object_freeze", "object_freeze",
......
...@@ -12,10 +12,10 @@ ...@@ -12,10 +12,10 @@
// Imports // Imports
var InternalArray = utils.InternalArray; var InternalArray = utils.InternalArray;
var promiseAwaitHandlerSymbol = var promiseHandledBySymbol =
utils.ImportNow("promise_await_handler_symbol"); utils.ImportNow("promise_handled_by_symbol");
var promiseCombinedDeferredSymbol = var promiseForwardingHandlerSymbol =
utils.ImportNow("promise_combined_deferred_symbol"); utils.ImportNow("promise_forwarding_handler_symbol");
var promiseHasHandlerSymbol = var promiseHasHandlerSymbol =
utils.ImportNow("promise_has_handler_symbol"); utils.ImportNow("promise_has_handler_symbol");
var promiseRejectReactionsSymbol = var promiseRejectReactionsSymbol =
...@@ -222,6 +222,7 @@ function PromiseAttachCallbacks(promise, deferred, onResolve, onReject) { ...@@ -222,6 +222,7 @@ function PromiseAttachCallbacks(promise, deferred, onResolve, onReject) {
function PromiseIdResolveHandler(x) { return x; } function PromiseIdResolveHandler(x) { return x; }
function PromiseIdRejectHandler(r) { %_ReThrow(r); } function PromiseIdRejectHandler(r) { %_ReThrow(r); }
SET_PRIVATE(PromiseIdRejectHandler, promiseForwardingHandlerSymbol, true);
function PromiseNopResolver() {} function PromiseNopResolver() {}
...@@ -236,7 +237,7 @@ function IsPromise(x) { ...@@ -236,7 +237,7 @@ function IsPromise(x) {
} }
function PromiseCreate() { function PromiseCreate() {
return new GlobalPromise(PromiseNopResolver) return new GlobalPromise(PromiseNopResolver);
} }
// ES#sec-promise-resolve-functions // ES#sec-promise-resolve-functions
...@@ -287,6 +288,10 @@ function ResolvePromise(promise, resolution) { ...@@ -287,6 +288,10 @@ function ResolvePromise(promise, resolution) {
var id; var id;
var name = "PromiseResolveThenableJob"; var name = "PromiseResolveThenableJob";
var instrumenting = DEBUG_IS_ACTIVE; var instrumenting = DEBUG_IS_ACTIVE;
if (instrumenting && IsPromise(resolution)) {
// Mark the dependency of the new promise on the resolution
SET_PRIVATE(resolution, promiseHandledBySymbol, promise);
}
%EnqueueMicrotask(function() { %EnqueueMicrotask(function() {
if (instrumenting) { if (instrumenting) {
%DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name }); %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
...@@ -310,7 +315,8 @@ function ResolvePromise(promise, resolution) { ...@@ -310,7 +315,8 @@ function ResolvePromise(promise, resolution) {
return; return;
} }
} }
FulfillPromise(promise, kFulfilled, resolution, promiseFulfillReactionsSymbol); FulfillPromise(promise, kFulfilled, resolution,
promiseFulfillReactionsSymbol);
} }
// ES#sec-rejectpromise // ES#sec-rejectpromise
...@@ -472,6 +478,10 @@ function PromiseAll(iterable) { ...@@ -472,6 +478,10 @@ function PromiseAll(iterable) {
var resolutions = new InternalArray(); var resolutions = new InternalArray();
var count; var count;
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
SET_PRIVATE(deferred.reject, promiseForwardingHandlerSymbol, true);
function CreateResolveElementFunction(index, values, promiseCapability) { function CreateResolveElementFunction(index, values, promiseCapability) {
var alreadyCalled = false; var alreadyCalled = false;
return (x) => { return (x) => {
...@@ -492,10 +502,14 @@ function PromiseAll(iterable) { ...@@ -492,10 +502,14 @@ function PromiseAll(iterable) {
for (var value of iterable) { for (var value of iterable) {
var nextPromise = this.resolve(value); var nextPromise = this.resolve(value);
++count; ++count;
nextPromise.then( var throwawayPromise = nextPromise.then(
CreateResolveElementFunction(i, resolutions, deferred), CreateResolveElementFunction(i, resolutions, deferred),
deferred.reject); deferred.reject);
SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred); // For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
if (IsPromise(throwawayPromise)) {
SET_PRIVATE(throwawayPromise, promiseHandledBySymbol, deferred.promise);
}
++i; ++i;
} }
...@@ -522,10 +536,20 @@ function PromiseRace(iterable) { ...@@ -522,10 +536,20 @@ function PromiseRace(iterable) {
// false debugEvent so that forwarding the rejection through race does not // false debugEvent so that forwarding the rejection through race does not
// trigger redundant ExceptionEvents // trigger redundant ExceptionEvents
var deferred = NewPromiseCapability(this, false); var deferred = NewPromiseCapability(this, false);
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
SET_PRIVATE(deferred.reject, promiseForwardingHandlerSymbol, true);
try { try {
for (var value of iterable) { for (var value of iterable) {
this.resolve(value).then(deferred.resolve, deferred.reject); var throwawayPromise = this.resolve(value).then(deferred.resolve,
SET_PRIVATE(deferred.reject, promiseCombinedDeferredSymbol, deferred); deferred.reject);
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
if (IsPromise(throwawayPromise)) {
SET_PRIVATE(throwawayPromise, promiseHandledBySymbol, deferred.promise);
}
} }
} catch (e) { } catch (e) {
deferred.reject(e) deferred.reject(e)
...@@ -537,19 +561,18 @@ function PromiseRace(iterable) { ...@@ -537,19 +561,18 @@ function PromiseRace(iterable) {
// Utility for debugger // Utility for debugger
function PromiseHasUserDefinedRejectHandlerCheck(handler, deferred) { function PromiseHasUserDefinedRejectHandlerCheck(handler, deferred) {
// If this handler was installed by async/await, it does not indicate // Recurse to the forwarding Promise, if any. This may be due to
// that there is a user-defined reject handler. // - await reaction forwarding to the throwaway Promise, which has
if (GET_PRIVATE(handler, promiseAwaitHandlerSymbol)) return false; // a dependency edge to the outer Promise.
if (handler !== PromiseIdRejectHandler) { // - PromiseIdResolveHandler forwarding to the output of .then
var combinedDeferred = GET_PRIVATE(handler, promiseCombinedDeferredSymbol); // - Promise.all/Promise.race forwarding to a throwaway Promise, which
if (IS_UNDEFINED(combinedDeferred)) return true; // has a dependency edge to the generated outer Promise.
if (PromiseHasUserDefinedRejectHandlerRecursive(combinedDeferred.promise)) { if (GET_PRIVATE(handler, promiseForwardingHandlerSymbol)) {
return true; return PromiseHasUserDefinedRejectHandlerRecursive(deferred.promise);
}
} else if (PromiseHasUserDefinedRejectHandlerRecursive(deferred.promise)) {
return true;
} }
return false;
// Otherwise, this is a real reject handler for the Promise
return true;
} }
function PromiseHasUserDefinedRejectHandlerRecursive(promise) { function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
...@@ -557,6 +580,17 @@ function PromiseHasUserDefinedRejectHandlerRecursive(promise) { ...@@ -557,6 +580,17 @@ function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
// in an async function, then it has a user-defined reject handler. // in an async function, then it has a user-defined reject handler.
if (GET_PRIVATE(promise, promiseHandledHintSymbol)) return true; if (GET_PRIVATE(promise, promiseHandledHintSymbol)) return true;
// If this Promise is subsumed by another Promise (a Promise resolved
// with another Promise, or an intermediate, hidden, throwaway Promise
// within async/await), then recurse on the outer Promise.
// In this case, the dependency is one possible way that the Promise
// could be resolved, so it does not subsume the other following cases.
var outerPromise = GET_PRIVATE(promise, promiseHandledBySymbol);
if (outerPromise &&
PromiseHasUserDefinedRejectHandlerRecursive(outerPromise)) {
return true;
}
var queue = GET_PRIVATE(promise, promiseRejectReactionsSymbol); var queue = GET_PRIVATE(promise, promiseRejectReactionsSymbol);
var deferreds = GET_PRIVATE(promise, promiseDeferredReactionsSymbol); var deferreds = GET_PRIVATE(promise, promiseDeferredReactionsSymbol);
...@@ -577,6 +611,8 @@ function PromiseHasUserDefinedRejectHandlerRecursive(promise) { ...@@ -577,6 +611,8 @@ function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
// Return whether the promise will be handled by a user-defined reject // Return whether the promise will be handled by a user-defined reject
// handler somewhere down the promise chain. For this, we do a depth-first // handler somewhere down the promise chain. For this, we do a depth-first
// search for a reject handler that's not the default PromiseIdRejectHandler. // search for a reject handler that's not the default PromiseIdRejectHandler.
// This function also traverses dependencies of one Promise on another,
// set up through async/await and Promises resolved with Promises.
function PromiseHasUserDefinedRejectHandler() { function PromiseHasUserDefinedRejectHandler() {
return PromiseHasUserDefinedRejectHandlerRecursive(this); return PromiseHasUserDefinedRejectHandlerRecursive(this);
}; };
......
...@@ -4563,7 +4563,7 @@ Expression* Parser::RewriteAwaitExpression(Expression* value, int await_pos) { ...@@ -4563,7 +4563,7 @@ Expression* Parser::RewriteAwaitExpression(Expression* value, int await_pos) {
// yield do { // yield do {
// promise_tmp = .promise; // promise_tmp = .promise;
// tmp = <operand>; // tmp = <operand>;
// %AsyncFunctionAwait(.generator_object, tmp); // %AsyncFunctionAwait(.generator_object, tmp, promise_tmp);
// promise_tmp // promise_tmp
// } // }
// The value of the expression is returned to the caller of the async // The value of the expression is returned to the caller of the async
...@@ -4608,11 +4608,13 @@ Expression* Parser::RewriteAwaitExpression(Expression* value, int await_pos) { ...@@ -4608,11 +4608,13 @@ Expression* Parser::RewriteAwaitExpression(Expression* value, int await_pos) {
zone()); zone());
ZoneList<Expression*>* async_function_await_args = ZoneList<Expression*>* async_function_await_args =
new (zone()) ZoneList<Expression*>(2, zone()); new (zone()) ZoneList<Expression*>(3, zone());
Expression* generator_object = Expression* generator_object =
factory()->NewVariableProxy(generator_object_variable); factory()->NewVariableProxy(generator_object_variable);
async_function_await_args->Add(generator_object, zone()); async_function_await_args->Add(generator_object, zone());
async_function_await_args->Add(factory()->NewVariableProxy(temp_var), zone()); async_function_await_args->Add(factory()->NewVariableProxy(temp_var), zone());
async_function_await_args->Add(factory()->NewVariableProxy(promise_temp_var),
zone());
// The parser emits calls to AsyncFunctionAwaitCaught, but the // The parser emits calls to AsyncFunctionAwaitCaught, but the
// AstNumberingVisitor will rewrite this to AsyncFunctionAwaitUncaught // AstNumberingVisitor will rewrite this to AsyncFunctionAwaitUncaught
......
...@@ -31,6 +31,19 @@ function rejectConstructor() { ...@@ -31,6 +31,19 @@ function rejectConstructor() {
async function argThrower(x = (() => { throw "d"; })()) { } // Exception d async function argThrower(x = (() => { throw "d"; })()) { } // Exception d
async function awaitThrow() {
await undefined;
throw "e"; // Exception e
}
function constructorThrow() {
return new Promise((resolve, reject) =>
Promise.resolve().then(() =>
reject("f") // Exception f
)
);
}
function suppressThrow() { function suppressThrow() {
return thrower(); return thrower();
} }
...@@ -80,7 +93,43 @@ async function indirectAwaitCatch(producer) { ...@@ -80,7 +93,43 @@ async function indirectAwaitCatch(producer) {
} }
} }
let catches = [caught, indirectCaught, indirectAwaitCatch]; function switchCatch(producer) {
let resolve;
let promise = new Promise(r => resolve = r);
async function localCaught() {
try {
await promise; // force switching to localUncaught and back
await producer();
} catch (e) { }
}
async function localUncaught() {
await undefined;
resolve();
}
localCaught();
localUncaught();
}
function switchDotCatch(producer) {
let resolve;
let promise = new Promise(r => resolve = r);
async function localCaught() {
await promise; // force switching to localUncaught and back
await producer();
}
async function localUncaught() {
await undefined;
resolve();
}
localCaught().catch(() => {});
localUncaught();
}
let catches = [caught,
indirectCaught,
indirectAwaitCatch,
switchCatch,
switchDotCatch];
let noncatches = [uncaught, indirectUncaught]; let noncatches = [uncaught, indirectUncaught];
let lateCatches = [dotCatch, let lateCatches = [dotCatch,
indirectReturnDotCatch, indirectReturnDotCatch,
...@@ -89,18 +138,19 @@ let lateCatches = [dotCatch, ...@@ -89,18 +138,19 @@ let lateCatches = [dotCatch,
let throws = [thrower, reject, argThrower, suppressThrow]; let throws = [thrower, reject, argThrower, suppressThrow];
let nonthrows = [awaitReturn, scalar, nothing]; let nonthrows = [awaitReturn, scalar, nothing];
let lateThrows = [awaitThrow, constructorThrow];
let uncatchable = [rejectConstructor]; let uncatchable = [rejectConstructor];
let cases = []; let cases = [];
for (let producer of throws) { for (let producer of throws.concat(lateThrows)) {
for (let consumer of catches) { for (let consumer of catches) {
cases.push({ producer, consumer, expectedEvents: 1, caught: true }); cases.push({ producer, consumer, expectedEvents: 1, caught: true });
cases.push({ producer, consumer, expectedEvents: 0, caught: false }); cases.push({ producer, consumer, expectedEvents: 0, caught: false });
} }
} }
for (let producer of throws) { for (let producer of throws.concat(lateThrows)) {
for (let consumer of noncatches) { for (let consumer of noncatches) {
cases.push({ producer, consumer, expectedEvents: 1, caught: true }); cases.push({ producer, consumer, expectedEvents: 1, caught: true });
cases.push({ producer, consumer, expectedEvents: 1, caught: false }); cases.push({ producer, consumer, expectedEvents: 1, caught: false });
...@@ -121,6 +171,13 @@ for (let producer of uncatchable) { ...@@ -121,6 +171,13 @@ for (let producer of uncatchable) {
} }
} }
for (let producer of lateThrows) {
for (let consumer of lateCatches) {
cases.push({ producer, consumer, expectedEvents: 1, caught: true });
cases.push({ producer, consumer, expectedEvents: 0, caught: false });
}
}
for (let producer of throws) { for (let producer of throws) {
for (let consumer of lateCatches) { for (let consumer of lateCatches) {
cases.push({ producer, consumer, expectedEvents: 1, caught: true }); cases.push({ producer, consumer, expectedEvents: 1, caught: true });
......
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