Commit edb4d315 authored by littledan's avatar littledan Committed by Commit bot

Mark await expressions as caught or uncaught

Handle some examples of the "asynchronous case" by marking await expressions
as either caught or uncaught; in the caught case, this marks the Promise passed
in as having a catch predicted. The marking is done in AST numbering, which
chooses between two different runtime function calls based on catch prediction.

BUG=v8:5167

Review-Url: https://codereview.chromium.org/2276243002
Cr-Commit-Position: refs/heads/master@{#39394}
parent ee8ae932
......@@ -249,6 +249,27 @@ void AstNumberingVisitor::VisitCallRuntime(CallRuntime* node) {
IncrementNodeCount();
node->set_base_id(ReserveIdRange(CallRuntime::num_ids()));
VisitArguments(node->arguments());
// To support catch prediction within async/await:
//
// The AstNumberingVisitor is when catch prediction currently occurs, and it
// is the only common point that has access to this information. The parser
// just doesn't know yet. Take the following two cases of catch prediction:
//
// try { await fn(); } catch (e) { }
// try { await fn(); } finally { }
//
// When parsing the await that we want to mark as caught or uncaught, it's
// not yet known whether it will be followed by a 'finally' or a 'catch.
// The AstNumberingVisitor is what learns whether it is caught. To make
// the information available later to the runtime, the AstNumberingVisitor
// has to stash it somewhere. Changing the runtime function into another
// one in ast-numbering seemed like a simple and straightforward solution to
// that problem.
if (node->is_jsruntime() &&
node->context_index() == Context::ASYNC_FUNCTION_AWAIT_CAUGHT_INDEX &&
catch_prediction_ == HandlerTable::ASYNC_AWAIT) {
node->set_context_index(Context::ASYNC_FUNCTION_AWAIT_UNCAUGHT_INDEX);
}
}
......
......@@ -1986,6 +1986,10 @@ class CallRuntime final : public Expression {
DCHECK(is_jsruntime());
return context_index_;
}
void set_context_index(int index) {
DCHECK(is_jsruntime());
context_index_ = index;
}
const Runtime::Function* function() const {
DCHECK(!is_jsruntime());
return function_;
......@@ -3144,6 +3148,16 @@ class AstNodeFactory final BASE_EMBEDDED {
try_block, scope, variable, catch_block, HandlerTable::DESUGARING, pos);
}
TryCatchStatement* NewTryCatchStatementForAsyncAwait(Block* try_block,
Scope* scope,
Variable* variable,
Block* catch_block,
int pos) {
return new (zone_)
TryCatchStatement(try_block, scope, variable, catch_block,
HandlerTable::ASYNC_AWAIT, pos);
}
TryFinallyStatement* NewTryFinallyStatement(Block* try_block,
Block* finally_block, int pos) {
return new (zone_) TryFinallyStatement(try_block, finally_block, pos);
......
......@@ -871,6 +871,9 @@ void AstPrinter::PrintTryStatement(TryStatement* node) {
case HandlerTable::DESUGARING:
prediction = "DESUGARING";
break;
case HandlerTable::ASYNC_AWAIT:
prediction = "ASYNC_AWAIT";
break;
}
Print(" %s\n", prediction);
}
......
......@@ -68,7 +68,10 @@ enum ContextLookupFlags {
V(ARRAY_SLICE_INDEX, JSFunction, array_slice) \
V(ARRAY_UNSHIFT_INDEX, JSFunction, array_unshift) \
V(ARRAY_VALUES_ITERATOR_INDEX, JSFunction, array_values_iterator) \
V(ASYNC_FUNCTION_AWAIT_INDEX, JSFunction, async_function_await) \
V(ASYNC_FUNCTION_AWAIT_CAUGHT_INDEX, JSFunction, \
async_function_await_caught) \
V(ASYNC_FUNCTION_AWAIT_UNCAUGHT_INDEX, JSFunction, \
async_function_await_uncaught) \
V(DERIVED_GET_TRAP_INDEX, JSFunction, derived_get_trap) \
V(ERROR_FUNCTION_INDEX, JSFunction, error_function) \
V(ERROR_TO_STRING, JSFunction, error_to_string) \
......
......@@ -199,10 +199,12 @@
V(normal_ic_symbol) \
V(not_mapped_symbol) \
V(premonomorphic_symbol) \
V(promise_await_handler_symbol) \
V(promise_combined_deferred_symbol) \
V(promise_debug_marker_symbol) \
V(promise_deferred_reactions_symbol) \
V(promise_fulfill_reactions_symbol) \
V(promise_handled_hint_symbol) \
V(promise_has_handler_symbol) \
V(promise_raw_symbol) \
V(promise_reject_reactions_symbol) \
......
......@@ -1311,6 +1311,8 @@ Isolate::CatchType Isolate::PredictExceptionCatcher() {
JavaScriptFrame* js_frame = static_cast<JavaScriptFrame*>(frame);
HandlerTable::CatchPrediction prediction = PredictException(js_frame);
if (prediction == HandlerTable::DESUGARING) return CAUGHT_BY_DESUGARING;
if (prediction == HandlerTable::ASYNC_AWAIT) return CAUGHT_BY_ASYNC_AWAIT;
if (prediction == HandlerTable::PROMISE) return CAUGHT_BY_PROMISE;
if (prediction != HandlerTable::UNCAUGHT) return CAUGHT_BY_JAVASCRIPT;
}
......@@ -1714,6 +1716,7 @@ Handle<Object> Isolate::GetPromiseOnStackOnThrow() {
for (JavaScriptFrameIterator it(this); !it.done(); it.Advance()) {
switch (PredictException(it.frame())) {
case HandlerTable::UNCAUGHT:
case HandlerTable::ASYNC_AWAIT:
break;
case HandlerTable::CAUGHT:
case HandlerTable::DESUGARING:
......
......@@ -751,7 +751,9 @@ class Isolate {
NOT_CAUGHT,
CAUGHT_BY_JAVASCRIPT,
CAUGHT_BY_EXTERNAL,
CAUGHT_BY_DESUGARING
CAUGHT_BY_DESUGARING,
CAUGHT_BY_PROMISE,
CAUGHT_BY_ASYNC_AWAIT
};
CatchType PredictExceptionCatcher();
......
......@@ -14,48 +14,97 @@
var AsyncFunctionNext;
var AsyncFunctionThrow;
var GlobalPromise;
var IsPromise;
var NewPromiseCapability;
var PerformPromiseThen;
var PromiseCastResolved;
var PromiseCreate;
var RejectPromise;
var ResolvePromise;
utils.Import(function(from) {
AsyncFunctionNext = from.AsyncFunctionNext;
AsyncFunctionThrow = from.AsyncFunctionThrow;
IsPromise = from.IsPromise;
GlobalPromise = from.GlobalPromise;
NewPromiseCapability = from.NewPromiseCapability;
PromiseCastResolved = from.PromiseCastResolved;
PromiseCreate = from.PromiseCreate;
PerformPromiseThen = from.PerformPromiseThen;
RejectPromise = from.RejectPromise;
ResolvePromise = from.ResolvePromise;
});
var promiseAwaitHandlerSymbol = utils.ImportNow("promise_await_handler_symbol");
var promiseHandledHintSymbol =
utils.ImportNow("promise_handled_hint_symbol");
// -------------------------------------------------------------------
function AsyncFunctionAwait(generator, value) {
// Promise.resolve(value).then(
function PromiseCastResolved(value) {
if (IsPromise(value)) {
return value;
} else {
var promise = PromiseCreate();
ResolvePromise(promise, value);
return promise;
}
}
// ES#abstract-ops-async-function-await
// AsyncFunctionAwait ( value )
// Shared logic for the core of await. The parser desugars
// await awaited
// into
// yield AsyncFunctionAwait{Caught,Uncaught}(.generator, awaited)
// The 'awaited' parameter is the value; the generator stands in
// for the asyncContext, and mark is metadata for debugging
function AsyncFunctionAwait(generator, awaited, mark) {
// Promise.resolve(awaited).then(
// value => AsyncFunctionNext(value),
// error => AsyncFunctionThrow(error)
// );
var promise = PromiseCastResolved(value);
var promise = PromiseCastResolved(awaited);
var onFulfilled =
(sentValue) => %_Call(AsyncFunctionNext, generator, sentValue);
var onRejected =
(sentError) => %_Call(AsyncFunctionThrow, generator, sentError);
// false debugEvent to avoid redundant ExceptionEvents
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
var throwawayCapability = NewPromiseCapability(GlobalPromise, false);
return PerformPromiseThen(promise, onFulfilled, onRejected,
throwawayCapability);
}
// Called by the parser from the desugaring of 'await' when catch
// prediction indicates no locally surrounding catch block
function AsyncFunctionAwaitUncaught(generator, awaited) {
// TODO(littledan): Install a dependency edge from awaited to outerPromise
return AsyncFunctionAwait(generator, awaited, true);
}
// Called by the parser from the desugaring of 'await' when catch
// prediction indicates that there is a locally surrounding catch block
function AsyncFunctionAwaitCaught(generator, awaited) {
if (DEBUG_IS_ACTIVE && IsPromise(awaited)) {
SET_PRIVATE(awaited, promiseHandledHintSymbol, true);
}
return AsyncFunctionAwait(generator, awaited, false);
}
// How the parser rejects promises from async/await desugaring
function RejectPromiseNoDebugEvent(promise, reason) {
return RejectPromise(promise, reason, false);
}
%InstallToContext([
"async_function_await", AsyncFunctionAwait,
"async_function_await_caught", AsyncFunctionAwaitCaught,
"async_function_await_uncaught", AsyncFunctionAwaitUncaught,
"reject_promise_no_debug_event", RejectPromiseNoDebugEvent,
]);
......
......@@ -191,6 +191,7 @@ function PostNatives(utils) {
"GlobalPromise",
"IntlParseDate",
"IntlParseNumber",
"IsPromise",
"MapEntries",
"MapIterator",
"MapIteratorNext",
......@@ -198,8 +199,8 @@ function PostNatives(utils) {
"MinSimple",
"NewPromiseCapability",
"PerformPromiseThen",
"PromiseCastResolved",
"PromiseThen",
"PromiseCreate",
"RegExpSubclassExecJS",
"RegExpSubclassMatch",
"RegExpSubclassReplace",
......@@ -207,6 +208,7 @@ function PostNatives(utils) {
"RegExpSubclassSplit",
"RegExpSubclassTest",
"RejectPromise",
"ResolvePromise",
"SetIterator",
"SetIteratorNext",
"SetValues",
......@@ -220,6 +222,8 @@ function PostNatives(utils) {
"iterator_symbol",
"promise_result_symbol",
"promise_state_symbol",
"promise_await_handler_symbol",
"promise_handled_hint_symbol",
"object_freeze",
"object_is_frozen",
"object_is_sealed",
......
......@@ -12,6 +12,8 @@
// Imports
var InternalArray = utils.InternalArray;
var promiseAwaitHandlerSymbol =
utils.ImportNow("promise_await_handler_symbol");
var promiseCombinedDeferredSymbol =
utils.ImportNow("promise_combined_deferred_symbol");
var promiseHasHandlerSymbol =
......@@ -22,6 +24,8 @@ var promiseFulfillReactionsSymbol =
utils.ImportNow("promise_fulfill_reactions_symbol");
var promiseDeferredReactionsSymbol =
utils.ImportNow("promise_deferred_reactions_symbol");
var promiseHandledHintSymbol =
utils.ImportNow("promise_handled_hint_symbol");
var promiseRawSymbol = utils.ImportNow("promise_raw_symbol");
var promiseStateSymbol = utils.ImportNow("promise_state_symbol");
var promiseResultSymbol = utils.ImportNow("promise_result_symbol");
......@@ -381,16 +385,6 @@ function PromiseReject(r) {
}
}
function PromiseCastResolved(value) {
if (IsPromise(value)) {
return value;
} else {
var promise = PromiseInit(new GlobalPromise(promiseRawSymbol));
var resolveResult = ResolvePromise(promise, value);
return promise;
}
}
function PerformPromiseThen(promise, onResolve, onReject, resultCapability) {
if (!IS_CALLABLE(onResolve)) onResolve = PromiseIdResolveHandler;
if (!IS_CALLABLE(onReject)) onReject = PromiseIdRejectHandler;
......@@ -543,6 +537,9 @@ function PromiseRace(iterable) {
// Utility for debugger
function PromiseHasUserDefinedRejectHandlerCheck(handler, deferred) {
// If this handler was installed by async/await, it does not indicate
// that there is a user-defined reject handler.
if (GET_PRIVATE(handler, promiseAwaitHandlerSymbol)) return false;
if (handler !== PromiseIdRejectHandler) {
var combinedDeferred = GET_PRIVATE(handler, promiseCombinedDeferredSymbol);
if (IS_UNDEFINED(combinedDeferred)) return true;
......@@ -556,16 +553,22 @@ function PromiseHasUserDefinedRejectHandlerCheck(handler, deferred) {
}
function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
// If this promise was marked as being handled by a catch block
// in an async function, then it has a user-defined reject handler.
if (GET_PRIVATE(promise, promiseHandledHintSymbol)) return true;
var queue = GET_PRIVATE(promise, promiseRejectReactionsSymbol);
var deferreds = GET_PRIVATE(promise, promiseDeferredReactionsSymbol);
if (IS_UNDEFINED(queue)) return false;
if (!IS_ARRAY(queue)) {
return PromiseHasUserDefinedRejectHandlerCheck(queue, deferreds);
} else {
for (var i = 0; i < queue.length; i += 2) {
if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], queue[i + 1])) {
return true;
}
}
for (var i = 0; i < queue.length; i += 2) {
if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], queue[i + 1])) {
return true;
}
}
return false;
......@@ -623,12 +626,14 @@ utils.InstallFunctions(extrasUtils, 0, [
]);
utils.Export(function(to) {
to.PromiseCastResolved = PromiseCastResolved;
to.IsPromise = IsPromise;
to.PromiseCreate = PromiseCreate;
to.PromiseThen = PromiseThen;
to.GlobalPromise = GlobalPromise;
to.NewPromiseCapability = NewPromiseCapability;
to.PerformPromiseThen = PerformPromiseThen;
to.ResolvePromise = ResolvePromise;
to.RejectPromise = RejectPromise;
});
......
......@@ -4625,6 +4625,9 @@ class HandlerTable : public FixedArray {
// catching are part of a desugaring and should therefore not
// be visible to the user (we won't notify the debugger of such
// exceptions).
ASYNC_AWAIT, // The exception will be caught and cause a promise rejection
// in the desugaring of an async function, so special
// async/await handling in the debugger can take place.
};
// Getters for handler table based on ranges.
......@@ -4677,8 +4680,8 @@ class HandlerTable : public FixedArray {
static const int kReturnEntrySize = 2;
// Encoding of the {handler} field.
class HandlerPredictionField : public BitField<CatchPrediction, 0, 2> {};
class HandlerOffsetField : public BitField<int, 2, 30> {};
class HandlerPredictionField : public BitField<CatchPrediction, 0, 3> {};
class HandlerOffsetField : public BitField<int, 3, 29> {};
};
// ByteArray represents fixed sized byte arrays. Used for the relocation info
......
......@@ -3473,8 +3473,10 @@ Block* Parser::BuildRejectPromiseOnException(Block* inner_block, bool* ok) {
factory()->NewReturnStatement(promise_reject, kNoSourcePosition);
catch_block->statements()->Add(return_promise_reject, zone());
TryStatement* try_catch_statement = factory()->NewTryCatchStatement(
inner_block, catch_scope, catch_variable, catch_block, kNoSourcePosition);
TryStatement* try_catch_statement =
factory()->NewTryCatchStatementForAsyncAwait(inner_block, catch_scope,
catch_variable, catch_block,
kNoSourcePosition);
// There is no TryCatchFinally node, so wrap it in an outer try/finally
Block* outer_try_block =
......@@ -4404,8 +4406,13 @@ Expression* Parser::RewriteAwaitExpression(Expression* value, int await_pos) {
factory()->NewVariableProxy(generator_object_variable);
async_function_await_args->Add(generator_object, zone());
async_function_await_args->Add(factory()->NewVariableProxy(temp_var), zone());
Expression* async_function_await = factory()->NewCallRuntime(
Context::ASYNC_FUNCTION_AWAIT_INDEX, async_function_await_args, nopos);
// The parser emits calls to AsyncFunctionAwaitCaught, but the
// AstNumberingVisitor will rewrite this to AsyncFunctionAwaitUncaught
// if there is no local enclosing try/catch block.
Expression* async_function_await =
factory()->NewCallRuntime(Context::ASYNC_FUNCTION_AWAIT_CAUGHT_INDEX,
async_function_await_args, nopos);
// Wrap await to provide a break location between value evaluation and yield.
Expression* await_assignment = factory()->NewAssignment(
......
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