Commit 1f0cd952 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[async] Initial async generator support for --async-stack-traces.

This forces .generator_object variable to stack slot 0 for async
generator functions so that the stack trace construction logic
can extract the JSAsyncGeneratorObject appropriately.

Bug: v8:7522
Change-Id: I37b52836bb512bcf5cd7e10e1738c8e7895b06ea
Ref: nodejs/node#11865
Design-Document: http://bit.ly/v8-zero-cost-async-stack-traces
Reviewed-on: https://chromium-review.googlesource.com/c/1264556Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56415}
parent 2a2c9e5f
......@@ -309,6 +309,7 @@ void DeclarationScope::SetDefaults() {
scope_uses_super_property_ = false;
has_rest_ = false;
has_promise_ = false;
has_generator_object_ = false;
sloppy_block_function_map_ = nullptr;
receiver_ = nullptr;
new_target_ = nullptr;
......@@ -778,6 +779,7 @@ Variable* DeclarationScope::DeclareGeneratorObjectVar(
Variable* result = EnsureRareData()->generator_object =
NewTemporary(name, kNotAssigned);
result->set_is_used();
has_generator_object_ = true;
return result;
}
......@@ -1494,6 +1496,7 @@ void DeclarationScope::ResetAfterPreparsing(AstValueFactory* ast_value_factory,
rare_data_ = nullptr;
has_rest_ = false;
has_promise_ = false;
has_generator_object_ = false;
DCHECK_NE(zone_, ast_value_factory->zone());
zone_->ReleaseMemory();
......@@ -2184,6 +2187,15 @@ void DeclarationScope::AllocatePromise() {
DCHECK_EQ(kPromiseVarIndex, promise_var()->index());
}
void DeclarationScope::AllocateGeneratorObject() {
if (!has_generator_object_) return;
DCHECK_NOT_NULL(generator_object_var());
DCHECK_EQ(this, generator_object_var()->scope());
AllocateStackSlot(generator_object_var());
DCHECK_EQ(VariableLocation::LOCAL, generator_object_var()->location());
DCHECK_EQ(kGeneratorObjectVarIndex, generator_object_var()->index());
}
void Scope::AllocateNonParameterLocal(Variable* var) {
DCHECK(var->scope() == this);
if (var->IsUnallocated() && MustAllocate(var)) {
......@@ -2252,10 +2264,18 @@ void Scope::AllocateVariablesRecursively() {
return;
}
// Make sure to allocate the .promise first, so that it get's
// the required stack slot 0 in case it's needed. See
// Make sure to allocate the .promise (for async functions) or
// .generator_object (for async generators) first, so that it
// get's the required stack slot 0 in case it's needed. See
// http://bit.ly/v8-zero-cost-async-stack-traces for details.
if (is_function_scope()) AsDeclarationScope()->AllocatePromise();
if (is_function_scope()) {
FunctionKind kind = GetClosureScope()->function_kind();
if (IsAsyncGeneratorFunction(kind)) {
AsDeclarationScope()->AllocateGeneratorObject();
} else if (IsAsyncFunction(kind)) {
AsDeclarationScope()->AllocatePromise();
}
}
// Allocate variables for inner scopes.
for (Scope* scope = inner_scope_; scope != nullptr; scope = scope->sibling_) {
......
......@@ -766,11 +766,19 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
// literals, or nullptr. Only valid for function scopes.
Variable* function_var() const { return function_; }
// The variable holding the JSGeneratorObject for generator, async
// and async generator functions, and modules. Only valid for
// function and module scopes.
Variable* generator_object_var() const {
DCHECK(is_function_scope() || is_module_scope());
return GetRareVariable(RareVariable::kGeneratorObject);
}
// For async generators, the .generator_object variable is always
// allocated to a fixed stack slot, such that the stack trace
// construction logic can access it.
static constexpr int kGeneratorObjectVarIndex = 0;
// The variable holding the promise returned from async functions.
// Only valid for function scopes in async functions (i.e. not
// for async generators).
......@@ -914,6 +922,7 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
void AllocateParameterLocals();
void AllocateReceiver();
void AllocatePromise();
void AllocateGeneratorObject();
void ResetAfterPreparsing(AstValueFactory* ast_value_factory, bool aborted);
......@@ -972,6 +981,8 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
bool has_rest_ : 1;
// This function scope has a .promise variable.
bool has_promise_ : 1;
// This function scope has a .generator_object variable.
bool has_generator_object_ : 1;
// This scope has a parameter called "arguments".
bool has_arguments_parameter_ : 1;
// This scope uses "super" property ('super.foo').
......
......@@ -266,7 +266,8 @@ bool Builtins::IsLazy(int index) {
case kArraySomeLoopEagerDeoptContinuation:
case kArraySomeLoopLazyDeoptContinuation:
case kAsyncFunctionAwaitResolveClosure: // https://crbug.com/v8/7522
case kAsyncFunctionAwaitRejectClosure: // https://crbug.com/v8/7522
case kAsyncGeneratorAwaitResolveClosure: // https://crbug.com/v8/7522
case kAsyncGeneratorYieldResolveClosure: // https://crbug.com/v8/7522
case kAsyncGeneratorAwaitCaught: // https://crbug.com/v8/6786.
case kAsyncGeneratorAwaitUncaught: // https://crbug.com/v8/6786.
// CEntry variants must be immovable, whereas lazy deserialization allocates
......
......@@ -628,12 +628,14 @@ void CaptureAsyncStackTrace(Isolate* isolate, Handle<JSPromise> promise,
PromiseReaction::cast(promise->reactions()), isolate);
if (!reaction->next()->IsSmi()) return;
// Check if the {reaction} has the Await Fulfill and
// Await Rejected functions as its handlers.
// Check if the {reaction} has one of the known async function or
// async generator continuations as its fulfill handler.
if (IsBuiltinFunction(isolate, reaction->fulfill_handler(),
Builtins::kAsyncFunctionAwaitResolveClosure) &&
IsBuiltinFunction(isolate, reaction->reject_handler(),
Builtins::kAsyncFunctionAwaitRejectClosure)) {
Builtins::kAsyncFunctionAwaitResolveClosure) ||
IsBuiltinFunction(isolate, reaction->fulfill_handler(),
Builtins::kAsyncGeneratorAwaitResolveClosure) ||
IsBuiltinFunction(isolate, reaction->fulfill_handler(),
Builtins::kAsyncGeneratorYieldResolveClosure)) {
// Now peak into the handlers' AwaitContext to get to
// the JSGeneratorObject for the async function.
Handle<Context> context(
......@@ -648,6 +650,22 @@ void CaptureAsyncStackTrace(Isolate* isolate, Handle<JSPromise> promise,
// Try to continue from here.
Handle<JSFunction> function(generator_object->function(), isolate);
Handle<SharedFunctionInfo> shared(function->shared(), isolate);
if (IsAsyncGeneratorFunction(shared->kind())) {
Handle<Object> dot_generator_object(
generator_object->parameters_and_registers()->get(
DeclarationScope::kGeneratorObjectVarIndex +
shared->scope_info()->ParameterCount()),
isolate);
if (!dot_generator_object->IsJSAsyncGeneratorObject()) return;
Handle<JSAsyncGeneratorObject> async_generator_object =
Handle<JSAsyncGeneratorObject>::cast(dot_generator_object);
Handle<AsyncGeneratorRequest> async_generator_request(
AsyncGeneratorRequest::cast(async_generator_object->queue()),
isolate);
promise = handle(JSPromise::cast(async_generator_request->promise()),
isolate);
} else {
CHECK(IsAsyncFunction(shared->kind()));
Handle<Object> dot_promise(
generator_object->parameters_and_registers()->get(
DeclarationScope::kPromiseVarIndex +
......@@ -655,6 +673,7 @@ void CaptureAsyncStackTrace(Isolate* isolate, Handle<JSPromise> promise,
isolate);
if (!dot_promise->IsJSPromise()) return;
promise = Handle<JSPromise>::cast(dot_promise);
}
} else {
// We have some generic promise chain here, so try to
// continue with the chained promise on the reaction
......@@ -768,7 +787,27 @@ Handle<Object> Isolate::CaptureSimpleStackTrace(Handle<JSReceiver> error_object,
this);
FunctionKind const kind = inspector.GetFunction()->shared()->kind();
if (IsAsyncGeneratorFunction(kind)) {
// TODO(bmeurer): Handle async generators here.
Handle<Object> const dot_generator_object =
inspector.GetExpression(DeclarationScope::kGeneratorObjectVarIndex);
if (dot_generator_object->IsUndefined(this)) {
// The .generator_object was not yet initialized (i.e. we see a
// really early exception in the setup of the async generator).
} else {
// Check if there's a pending async request on the generator object.
Handle<JSAsyncGeneratorObject> async_generator_object =
Handle<JSAsyncGeneratorObject>::cast(dot_generator_object);
if (!async_generator_object->queue()->IsUndefined(this)) {
// Take the promise from the first async generatot request.
Handle<AsyncGeneratorRequest> request(
AsyncGeneratorRequest::cast(async_generator_object->queue()),
this);
// We can start collecting an async stack trace from the
// promise on the {request}.
Handle<JSPromise> promise(JSPromise::cast(request->promise()), this);
CaptureAsyncStackTrace(this, promise, &builder);
}
}
} else {
DCHECK(IsAsyncFunction(kind));
Handle<Object> const dot_promise =
......
......@@ -252,19 +252,19 @@ frame size: 22
parameter count: 1
bytecode array length: 490
bytecodes: [
B(SwitchOnGeneratorState), R(2), U8(0), U8(3),
B(SwitchOnGeneratorState), R(0), U8(0), U8(3),
B(Mov), R(closure), R(11),
B(Mov), R(this), R(12),
B(InvokeIntrinsic), U8(Runtime::k_CreateJSGeneratorObject), R(11), U8(2),
B(Star), R(2),
B(Star), R(0),
/* 17 E> */ B(StackCheck),
B(Mov), R(context), R(13),
B(Mov), R(context), R(14),
B(Ldar), R(2),
/* 17 E> */ B(SuspendGenerator), R(2), R(0), U8(15), U8(0),
B(ResumeGenerator), R(2), R(0), U8(15),
B(Ldar), R(0),
/* 17 E> */ B(SuspendGenerator), R(0), R(0), U8(15), U8(0),
B(ResumeGenerator), R(0), R(0), U8(15),
B(Star), R(15),
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(2), U8(1),
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(0), U8(1),
B(SwitchOnSmiNoFeedback), U8(3), U8(2), I8(0),
B(Ldar), R(15),
/* 17 E> */ B(Throw),
......@@ -300,16 +300,16 @@ bytecodes: [
B(Star), R(7),
B(Mov), R(8), R(3),
/* 22 E> */ B(StackCheck),
B(Mov), R(3), R(0),
B(Mov), R(3), R(1),
/* 42 S> */ B(LdaFalse),
B(Star), R(21),
B(Mov), R(2), R(19),
B(Mov), R(0), R(20),
B(Mov), R(0), R(19),
B(Mov), R(1), R(20),
B(InvokeIntrinsic), U8(Runtime::k_AsyncGeneratorYield), R(19), U8(3),
/* 42 E> */ B(SuspendGenerator), R(2), R(0), U8(19), U8(1),
B(ResumeGenerator), R(2), R(0), U8(19),
/* 42 E> */ B(SuspendGenerator), R(0), R(0), U8(19), U8(1),
B(ResumeGenerator), R(0), R(0), U8(19),
B(Star), R(19),
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(2), U8(1),
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(0), U8(1),
B(SwitchOnSmiNoFeedback), U8(10), U8(2), I8(0),
B(Ldar), R(19),
/* 42 E> */ B(Throw),
......@@ -398,12 +398,12 @@ bytecodes: [
B(ReThrow),
B(LdaUndefined),
B(Star), R(16),
B(Mov), R(2), R(15),
B(Mov), R(0), R(15),
B(CallJSRuntime), U8(%async_generator_await_uncaught), R(15), U8(2),
B(SuspendGenerator), R(2), R(0), U8(15), U8(2),
B(ResumeGenerator), R(2), R(0), U8(15),
B(SuspendGenerator), R(0), R(0), U8(15), U8(2),
B(ResumeGenerator), R(0), R(0), U8(15),
B(Star), R(15),
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(2), U8(1),
B(InvokeIntrinsic), U8(Runtime::k_GeneratorGetResumeMode), R(0), U8(1),
B(Star), R(16),
B(LdaZero),
B(TestReferenceEqual), R(16),
......@@ -424,7 +424,7 @@ bytecodes: [
B(PushContext), R(15),
B(LdaImmutableCurrentContextSlot), U8(4),
B(Star), R(17),
B(Mov), R(2), R(16),
B(Mov), R(0), R(16),
B(InvokeIntrinsic), U8(Runtime::k_AsyncGeneratorReject), R(16), U8(2),
B(PopContext), R(15),
B(Star), R(12),
......@@ -441,7 +441,7 @@ bytecodes: [
B(LdaTheHole),
B(SetPendingMessage),
B(Star), R(13),
B(InvokeIntrinsic), U8(Runtime::k_GeneratorClose), R(2), U8(1),
B(InvokeIntrinsic), U8(Runtime::k_GeneratorClose), R(0), U8(1),
B(Ldar), R(13),
B(SetPendingMessage),
B(Ldar), R(11),
......@@ -449,7 +449,7 @@ bytecodes: [
B(Jump), U8(22),
B(LdaTrue),
B(Star), R(16),
B(Mov), R(2), R(14),
B(Mov), R(0), R(14),
B(Mov), R(12), R(15),
B(InvokeIntrinsic), U8(Runtime::k_AsyncGeneratorResolve), R(14), U8(3),
/* 50 S> */ B(Return),
......
......@@ -172,3 +172,99 @@
await test(one);
})());
})();
// Basic test for async generators called from async
// functions with an explicit throw.
(function() {
async function one(x) {
for await (const y of two(x)) {}
}
async function* two(x) {
await x;
throw new Error();
}
async function test(f) {
try {
await f(1);
assertUnreachable();
} catch (e) {
assertInstanceof(e, Error);
assertMatches(/Error.+at two.+at async one.+at async test/ms, e.stack);
}
}
assertPromiseResult((async () => {
await test(one);
await test(one);
%OptimizeFunctionOnNextCall(two);
await test(one);
%OptimizeFunctionOnNextCall(one);
await test(one);
})());
})();
// Basic test for async functions called from async
// generators with an explicit throw.
(function() {
async function* one(x) {
await two(x);
}
async function two(x) {
await x;
throw new Error();
}
async function test(f) {
try {
for await (const x of f(1)) {}
assertUnreachable();
} catch (e) {
assertInstanceof(e, Error);
assertMatches(/Error.+at two.+at async one.+at async test/ms, e.stack);
}
}
assertPromiseResult((async () => {
await test(one);
await test(one);
%OptimizeFunctionOnNextCall(two);
await test(one);
%OptimizeFunctionOnNextCall(one);
await test(one);
})());
})();
// Basic test for async functions called from async
// generators with an explicit throw (with yield).
(function() {
async function* one(x) {
yield two(x);
}
async function two(x) {
await x;
throw new Error();
}
async function test(f) {
try {
for await (const x of f(1)) {}
assertUnreachable();
} catch (e) {
assertInstanceof(e, Error);
assertMatches(/Error.+at two.+at async one.+at async test/ms, e.stack);
}
}
assertPromiseResult((async () => {
await test(one);
await test(one);
%OptimizeFunctionOnNextCall(two);
await test(one);
%OptimizeFunctionOnNextCall(one);
await test(one);
})());
})();
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