Revert "[SDK] Switch to is_sync to identify sync/async running."

This reverts commit 886615d0f9.

Reason for revert:

There was an unexpected slowdown in some async benchmarks, e.g. Calls.AwaitAsyncCall.

Will revert for now and investigate next year.


Original change's description:
> [SDK] Switch to is_sync to identify sync/async running.
> 
> This should address the regression introduced by https://dart-review.googlesource.com/c/sdk/+/124988
> 
> Bug: https://github.com/dart-lang/sdk/issues/39525
> Change-Id: Id163b649bdd0363297c186559fa84ff87f908e4b
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/129062
> Reviewed-by: Clement Skau <cskau@google.com>
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Clement Skau <cskau@google.com>

TBR=kustermann@google.com,cskau@google.com

Change-Id: I5cda795cbccc01f22e0f8192473c171a4e9fca4b
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: https://github.com/dart-lang/sdk/issues/39525
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/129285
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2019-12-20 19:06:54 +00:00
committed by commit-bot@chromium.org
parent ffc17eba93
commit 72f4a831aa
6 changed files with 438 additions and 414 deletions
+132 -117
View File
@@ -2313,10 +2313,10 @@ DebuggerStackTrace* Debugger::CollectAwaiterReturnStackTrace() {
#if defined(DART_PRECOMPILED_RUNTIME)
// Causal async stacks are not supported in the AOT runtime.
ASSERT(!FLAG_async_debugger);
return nullptr;
return NULL;
#else
if (!FLAG_async_debugger) {
return nullptr;
return NULL;
}
Thread* thread = Thread::Current();
@@ -2337,148 +2337,163 @@ DebuggerStackTrace* Debugger::CollectAwaiterReturnStackTrace() {
Closure& async_activation = Closure::Handle(zone);
Object& next_async_activation = Object::Handle(zone);
Array& deopt_frame = Array::Handle(zone);
// Note: 'class' since Debugger declares a method by the same name.
class StackTrace& async_stack_trace = StackTrace::Handle(zone);
bool stack_has_async_function = false;
Closure& closure = Closure::Handle(zone);
CallerClosureFinder caller_closure_finder(zone);
for (StackFrame* frame = iterator.NextFrame(); frame != nullptr;
// Number of frames we are trying to skip that form "sync async" entry.
int skip_sync_async_frames_count = -1;
String& function_name = String::Handle(zone);
for (StackFrame* frame = iterator.NextFrame(); frame != NULL;
frame = iterator.NextFrame()) {
ASSERT(frame->IsValid());
if (FLAG_trace_debugger_stacktrace) {
OS::PrintErr("CollectAwaiterReturnStackTrace: visiting frame:\n\t%s\n",
frame->ToCString());
}
if (!frame->IsDartFrame()) {
continue;
}
if (frame->is_interpreted()) {
bytecode = frame->LookupDartBytecode();
function = bytecode.function();
if (function.IsNull()) {
continue; // Skip bytecode stub frame.
}
if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) {
ActivationFrame* activation =
CollectDartFrame(isolate, frame->pc(), frame, bytecode,
ActivationFrame::kAsyncActivation);
ASSERT(activation != nullptr);
stack_trace->AddActivation(activation);
stack_has_async_function = true;
// Grab the awaiter.
async_activation ^= activation->GetAsyncAwaiter();
async_stack_trace ^= activation->GetCausalStack();
// Bail if we've reach the end of the sync execution stack.
RawObject** last_caller_obj =
reinterpret_cast<RawObject**>(frame->GetCallerSp());
closure = StackTraceUtils::FindClosureInFrame(last_caller_obj, function,
frame->is_interpreted());
if (caller_closure_finder.IsRunningAsync(closure)) {
// We've reached the end of the stack of the sync execution.
break;
if (frame->IsDartFrame()) {
if (frame->is_interpreted()) {
bytecode = frame->LookupDartBytecode();
function = bytecode.function();
if (function.IsNull()) {
continue; // Skip bytecode stub frame.
}
} else {
stack_trace->AddActivation(
CollectDartFrame(isolate, frame->pc(), frame, bytecode));
}
} else {
code = frame->LookupDartCode();
if (code.is_optimized()) {
if (code.is_force_optimized()) {
if (FLAG_trace_debugger_stacktrace) {
function = code.function();
ASSERT(!function.IsNull());
OS::PrintErr(
"CollectAwaiterReturnStackTrace: "
"skipping force-optimized function: %s\n",
function.ToFullyQualifiedCString());
}
// Skip frame of force-optimized (and non-debuggable) function.
continue;
}
deopt_frame = DeoptimizeToArray(thread, frame, code);
bool found_async_awaiter = false;
bool abort_attempt_to_navigate_through_sync_async = false;
for (InlinedFunctionsIterator it(code, frame->pc()); !it.Done();
it.Advance()) {
inlined_code = it.code();
function = it.function();
if (FLAG_trace_debugger_stacktrace) {
ASSERT(!function.IsNull());
OS::PrintErr(
"CollectAwaiterReturnStackTrace: "
"visiting inlined function: %s\n ",
function.ToFullyQualifiedCString());
if (skip_sync_async_frames_count > 0) {
function_name = function.QualifiedScrubbedName();
if (!StackTraceUtils::CheckAndSkipAsync(&skip_sync_async_frames_count,
function_name)) {
// Unexpected function in synchronous call of async function.
break;
}
intptr_t deopt_frame_offset = it.GetDeoptFpOffset();
if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) {
ActivationFrame* activation = CollectDartFrame(
isolate, it.pc(), frame, inlined_code, deopt_frame,
deopt_frame_offset, ActivationFrame::kAsyncActivation);
stack_trace->AddActivation(activation);
stack_has_async_function = true;
// Grab the awaiter.
async_activation ^= activation->GetAsyncAwaiter();
found_async_awaiter = true;
// Bail if we've reach the end of the sync execution stack.
RawObject** last_caller_obj =
reinterpret_cast<RawObject**>(frame->GetCallerSp());
closure = StackTraceUtils::FindClosureInFrame(
last_caller_obj, function, frame->is_interpreted());
if (caller_closure_finder.IsRunningAsync(closure)) {
abort_attempt_to_navigate_through_sync_async = true;
break;
}
} else {
stack_trace->AddActivation(
CollectDartFrame(isolate, it.pc(), frame, inlined_code,
deopt_frame, deopt_frame_offset));
}
} // for (InlinedFunctionsIterator)
// Break out of outer loop.
if (found_async_awaiter ||
abort_attempt_to_navigate_through_sync_async) {
break;
}
} else { // code.is_optimized()
function = code.function();
if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) {
ActivationFrame* activation = CollectDartFrame(
isolate, frame->pc(), frame, code, Object::null_array(), 0,
ActivationFrame::kAsyncActivation);
ASSERT(activation != nullptr);
ActivationFrame* activation =
CollectDartFrame(isolate, frame->pc(), frame, bytecode,
ActivationFrame::kAsyncActivation);
ASSERT(activation != NULL);
stack_trace->AddActivation(activation);
stack_has_async_function = true;
// Grab the awaiter.
async_activation ^= activation->GetAsyncAwaiter();
async_stack_trace ^= activation->GetCausalStack();
// Interpreted bytecode does not invoke _ClosureCall().
// Skip _AsyncAwaitCompleterStart() only.
skip_sync_async_frames_count = 1;
} else {
stack_trace->AddActivation(
CollectDartFrame(isolate, frame->pc(), frame, bytecode));
}
} else {
code = frame->LookupDartCode();
if (code.is_optimized()) {
if (code.is_force_optimized()) {
if (FLAG_trace_debugger_stacktrace) {
function = code.function();
ASSERT(!function.IsNull());
OS::PrintErr(
"CollectAwaiterReturnStackTrace: "
"skipping force-optimized function: %s\n",
function.ToFullyQualifiedCString());
}
// Skip frame of force-optimized (and non-debuggable) function.
continue;
}
deopt_frame = DeoptimizeToArray(thread, frame, code);
bool found_async_awaiter = false;
bool abort_attempt_to_navigate_through_sync_async = false;
for (InlinedFunctionsIterator it(code, frame->pc()); !it.Done();
it.Advance()) {
inlined_code = it.code();
function = it.function();
// Bail if we've reach the end of sync execution stack.
RawObject** last_caller_obj =
reinterpret_cast<RawObject**>(frame->GetCallerSp());
closure = StackTraceUtils::FindClosureInFrame(
last_caller_obj, function, frame->is_interpreted());
if (caller_closure_finder.IsRunningAsync(closure)) {
if (skip_sync_async_frames_count > 0) {
function_name ^= function.QualifiedScrubbedName();
if (!StackTraceUtils::CheckAndSkipAsync(
&skip_sync_async_frames_count, function_name)) {
// Unexpected function in sync async call
skip_sync_async_frames_count = -1;
abort_attempt_to_navigate_through_sync_async = true;
break;
}
}
if (FLAG_trace_debugger_stacktrace) {
ASSERT(!function.IsNull());
OS::PrintErr(
"CollectAwaiterReturnStackTrace: "
"visiting inlined function: %s\n ",
function.ToFullyQualifiedCString());
}
intptr_t deopt_frame_offset = it.GetDeoptFpOffset();
if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) {
ActivationFrame* activation = CollectDartFrame(
isolate, it.pc(), frame, inlined_code, deopt_frame,
deopt_frame_offset, ActivationFrame::kAsyncActivation);
ASSERT(activation != NULL);
stack_trace->AddActivation(activation);
stack_has_async_function = true;
// Grab the awaiter.
async_activation ^= activation->GetAsyncAwaiter();
found_async_awaiter = true;
// async function might have been called synchronously, in which
// case we need to keep going down the stack.
// To determine how we are called we peek few more frames further
// expecting to see Closure_call followed by
// AsyncAwaitCompleter_start.
// If we are able to see those functions we continue going down
// thestack, if we are not, we break out of the loop as we are
// not interested in exploring rest of the stack - there is only
// dart-internal code left.
skip_sync_async_frames_count = 2;
} else {
stack_trace->AddActivation(
CollectDartFrame(isolate, it.pc(), frame, inlined_code,
deopt_frame, deopt_frame_offset));
}
}
// Break out of outer loop.
if (found_async_awaiter ||
abort_attempt_to_navigate_through_sync_async) {
break;
}
} else {
stack_trace->AddActivation(CollectDartFrame(
isolate, frame->pc(), frame, code, Object::null_array(), 0));
function = code.function();
if (skip_sync_async_frames_count > 0) {
function_name ^= function.QualifiedScrubbedName();
if (!StackTraceUtils::CheckAndSkipAsync(
&skip_sync_async_frames_count, function_name)) {
// Unexpected function in synchronous call of async function.
break;
}
}
if (function.IsAsyncClosure() || function.IsAsyncGenClosure()) {
ActivationFrame* activation = CollectDartFrame(
isolate, frame->pc(), frame, code, Object::null_array(), 0,
ActivationFrame::kAsyncActivation);
ASSERT(activation != NULL);
stack_trace->AddActivation(activation);
stack_has_async_function = true;
// Grab the awaiter.
async_activation ^= activation->GetAsyncAwaiter();
async_stack_trace ^= activation->GetCausalStack();
// see comment regarding skipping frames of async functions called
// synchronously above.
skip_sync_async_frames_count = 2;
} else {
stack_trace->AddActivation(CollectDartFrame(
isolate, frame->pc(), frame, code, Object::null_array(), 0));
}
}
}
}
} // for (frame in iterator)
}
// If the stack doesn't have any async functions on it, return nullptr.
// If the stack doesn't have any async functions on it, return NULL.
if (!stack_has_async_function) {
return nullptr;
return NULL;
}
// Append the awaiter return call stack.
@@ -2499,7 +2514,7 @@ DebuggerStackTrace* Debugger::CollectAwaiterReturnStackTrace() {
break;
}
async_activation = Closure::RawCast(next_async_activation.raw());
} // while (!async_activation.IsNull())
}
// Now we append the asynchronous causal stack trace. These are not active
// frames but a historical record of how this asynchronous function was
@@ -2554,7 +2569,7 @@ DebuggerStackTrace* Debugger::CollectAwaiterReturnStackTrace() {
}
// Follow the link.
async_stack_trace = async_stack_trace.async_link();
} // while (!async_stack_trace.IsNull())
}
return stack_trace;
#endif // defined(DART_PRECOMPILED_RUNTIME)
+6 -6
View File
@@ -132,12 +132,6 @@ class StackFrame : public ValueObject {
static void DumpCurrentTrace();
uword GetCallerSp() const {
return fp() +
((is_interpreted() ? kKBCCallerSpSlotFromFp : kCallerSpSlotFromFp) *
kWordSize);
}
protected:
explicit StackFrame(Thread* thread)
: fp_(0), sp_(0), pc_(0), thread_(thread), is_interpreted_(false) {}
@@ -157,6 +151,12 @@ class StackFrame : public ValueObject {
RawCode* GetCodeObject() const;
RawBytecode* GetBytecodeObject() const;
uword GetCallerSp() const {
return fp() +
((is_interpreted() ? kKBCCallerSpSlotFromFp : kCallerSpSlotFromFp) *
kWordSize);
}
uword GetCallerFp() const {
return *(reinterpret_cast<uword*>(
fp() + ((is_interpreted() ? kKBCSavedCallerFpSlotFromFp
+274 -213
View File
@@ -11,6 +11,35 @@
namespace dart {
// Keep in sync with
// sdk/lib/async/stream_controller.dart:_StreamController._STATE_SUBSCRIBED.
const intptr_t kStreamController_StateSubscribed = 1;
RawClosure* FindClosureInFrame(RawObject** last_object_in_caller,
const Function& function,
bool is_interpreted) {
NoSafepointScope nsp;
// The callee has function signature
// :async_op([result, exception, stack])
// So we are guaranteed to
// a) have only tagged arguments on the stack until we find the :async_op
// closure, and
// b) find the async closure.
auto& closure = Closure::Handle();
for (intptr_t i = 0; i < 4; i++) {
// KBC builds the stack upwards instead of the usual downwards stack.
RawObject* arg = last_object_in_caller[(is_interpreted ? -i : i)];
if (arg->IsHeapObject() && arg->GetClassId() == kClosureCid) {
closure = Closure::RawCast(arg);
if (closure.function() == function.raw()) {
return closure.raw();
}
}
}
UNREACHABLE();
}
// Find current yield index from async closure.
// Async closures contains a variable, :await_jump_var that holds the index into
// async wrapper.
@@ -59,6 +88,222 @@ intptr_t FindPcOffset(const Bytecode& bytecode, intptr_t yield_index) {
}
#endif
// Helper class for finding the closure of the caller.
// This is done via the _AsyncAwaitCompleter which holds a
// FutureResultOrListeners which in turn holds a callback.
class CallerClosureFinder {
public:
// Instance caches library and field references.
// This way we don't have to do the look-ups for every frame in the stack.
explicit CallerClosureFinder(Zone* zone)
: receiver_context_(Context::Handle(zone)),
receiver_function_(Function::Handle(zone)),
context_entry_(Object::Handle(zone)),
is_sync(Object::Handle(zone)),
future_(Object::Handle(zone)),
listener_(Object::Handle(zone)),
callback_(Object::Handle(zone)),
controller_(Object::Handle(zone)),
state_(Object::Handle(zone)),
var_data_(Object::Handle(zone)),
future_impl_class(Class::Handle(zone)),
async_await_completer_class(Class::Handle(zone)),
future_listener_class(Class::Handle(zone)),
async_start_stream_controller_class(Class::Handle(zone)),
stream_controller_class(Class::Handle(zone)),
controller_subscription_class(Class::Handle(zone)),
buffering_stream_subscription_class(Class::Handle(zone)),
async_stream_controller_class(Class::Handle(zone)),
completer_is_sync_field(Field::Handle(zone)),
completer_future_field(Field::Handle(zone)),
future_result_or_listeners_field(Field::Handle(zone)),
callback_field(Field::Handle(zone)),
controller_controller_field(Field::Handle(zone)),
var_data_field(Field::Handle(zone)),
state_field(Field::Handle(zone)),
on_data_field(Field::Handle(zone)) {
const auto& async_lib = Library::Handle(zone, Library::AsyncLibrary());
// Look up classes:
// - async:
future_impl_class =
async_lib.LookupClassAllowPrivate(Symbols::FutureImpl());
ASSERT(!future_impl_class.IsNull());
async_await_completer_class =
async_lib.LookupClassAllowPrivate(Symbols::_AsyncAwaitCompleter());
ASSERT(!async_await_completer_class.IsNull());
future_listener_class =
async_lib.LookupClassAllowPrivate(Symbols::_FutureListener());
ASSERT(!future_listener_class.IsNull());
// - async*:
async_start_stream_controller_class = async_lib.LookupClassAllowPrivate(
Symbols::_AsyncStarStreamController());
ASSERT(!async_start_stream_controller_class.IsNull());
stream_controller_class =
async_lib.LookupClassAllowPrivate(Symbols::_StreamController());
ASSERT(!stream_controller_class.IsNull());
async_stream_controller_class =
async_lib.LookupClassAllowPrivate(Symbols::_AsyncStreamController());
ASSERT(!async_stream_controller_class.IsNull());
controller_subscription_class =
async_lib.LookupClassAllowPrivate(Symbols::_ControllerSubscription());
ASSERT(!controller_subscription_class.IsNull());
buffering_stream_subscription_class = async_lib.LookupClassAllowPrivate(
Symbols::_BufferingStreamSubscription());
ASSERT(!buffering_stream_subscription_class.IsNull());
// Look up fields:
// - async:
completer_is_sync_field =
async_await_completer_class.LookupFieldAllowPrivate(Symbols::isSync());
ASSERT(!completer_is_sync_field.IsNull());
completer_future_field =
async_await_completer_class.LookupFieldAllowPrivate(Symbols::_future());
ASSERT(!completer_future_field.IsNull());
future_result_or_listeners_field =
future_impl_class.LookupFieldAllowPrivate(
Symbols::_resultOrListeners());
ASSERT(!future_result_or_listeners_field.IsNull());
callback_field =
future_listener_class.LookupFieldAllowPrivate(Symbols::callback());
ASSERT(!callback_field.IsNull());
// - async*:
controller_controller_field =
async_start_stream_controller_class.LookupFieldAllowPrivate(
Symbols::controller());
ASSERT(!controller_controller_field.IsNull());
state_field =
stream_controller_class.LookupFieldAllowPrivate(Symbols::_state());
ASSERT(!state_field.IsNull());
var_data_field =
stream_controller_class.LookupFieldAllowPrivate(Symbols::_varData());
ASSERT(!var_data_field.IsNull());
on_data_field = buffering_stream_subscription_class.LookupFieldAllowPrivate(
Symbols::_onData());
ASSERT(!on_data_field.IsNull());
}
RawClosure* FindCallerInAsyncClosure(const Context& receiver_context) {
context_entry_ = receiver_context.At(Context::kAsyncCompleterIndex);
ASSERT(context_entry_.IsInstance());
ASSERT(context_entry_.GetClassId() == async_await_completer_class.id());
const Instance& completer = Instance::Cast(context_entry_);
future_ = completer.GetField(completer_future_field);
ASSERT(!future_.IsNull());
ASSERT(future_.GetClassId() == future_impl_class.id());
listener_ =
Instance::Cast(future_).GetField(future_result_or_listeners_field);
if (listener_.GetClassId() != future_listener_class.id()) {
return Closure::null();
}
callback_ = Instance::Cast(listener_).GetField(callback_field);
// This happens for e.g.: await f().catchError(..);
if (callback_.IsNull()) {
return Closure::null();
}
ASSERT(callback_.IsClosure());
return Closure::Cast(callback_).raw();
}
RawClosure* FindCallerInAsyncGenClosure(const Context& receiver_context) {
context_entry_ = receiver_context.At(Context::kControllerIndex);
ASSERT(context_entry_.IsInstance());
ASSERT(context_entry_.GetClassId() ==
async_start_stream_controller_class.id());
const Instance& controller = Instance::Cast(context_entry_);
controller_ = controller.GetField(controller_controller_field);
ASSERT(!controller_.IsNull());
ASSERT(controller_.GetClassId() == async_stream_controller_class.id());
state_ = Instance::Cast(controller_).GetField(state_field);
ASSERT(state_.IsSmi());
if (Smi::Cast(state_).Value() != kStreamController_StateSubscribed) {
return Closure::null();
}
var_data_ = Instance::Cast(controller_).GetField(var_data_field);
ASSERT(var_data_.GetClassId() == controller_subscription_class.id());
callback_ = Instance::Cast(var_data_).GetField(on_data_field);
ASSERT(callback_.IsClosure());
return Closure::Cast(callback_).raw();
}
RawClosure* FindCaller(const Closure& receiver_closure) {
receiver_function_ = receiver_closure.function();
receiver_context_ = receiver_closure.context();
if (receiver_function_.IsAsyncClosure()) {
return FindCallerInAsyncClosure(receiver_context_);
} else if (receiver_function_.IsAsyncGenClosure()) {
return FindCallerInAsyncGenClosure(receiver_context_);
}
return Closure::null();
}
bool IsRunningAsync(const Closure& receiver_closure) {
receiver_function_ = receiver_closure.function();
receiver_context_ = receiver_closure.context();
// The async* functions are never started synchronously, they start running
// after the first `listen()` call to its returned `Stream`.
if (receiver_function_.IsAsyncGenClosure()) {
return true;
}
ASSERT(receiver_function_.IsAsyncClosure());
context_entry_ = receiver_context_.At(Context::kAsyncCompleterIndex);
ASSERT(context_entry_.IsInstance());
ASSERT(context_entry_.GetClassId() == async_await_completer_class.id());
const Instance& completer = Instance::Cast(context_entry_);
is_sync = completer.GetField(completer_is_sync_field);
ASSERT(!is_sync.IsNull());
ASSERT(is_sync.IsBool());
// _AsyncAwaitCompleter.isSync indicates whether the future should be
// completed async. or sync., based on whether it has yielded yet.
// isSync is true when the :async_op is running async.
return Bool::Cast(is_sync).value();
}
private:
Context& receiver_context_;
Function& receiver_function_;
Object& context_entry_;
Object& is_sync;
Object& future_;
Object& listener_;
Object& callback_;
Object& controller_;
Object& state_;
Object& var_data_;
Class& future_impl_class;
Class& async_await_completer_class;
Class& future_listener_class;
Class& async_start_stream_controller_class;
Class& stream_controller_class;
Class& controller_subscription_class;
Class& buffering_stream_subscription_class;
Class& async_stream_controller_class;
Field& completer_is_sync_field;
Field& completer_future_field;
Field& future_result_or_listeners_field;
Field& callback_field;
Field& controller_controller_field;
Field& var_data_field;
Field& state_field;
Field& on_data_field;
};
void StackTraceUtils::CollectFramesLazy(
Thread* thread,
const GrowableObjectArray& code_array,
@@ -106,15 +351,15 @@ void StackTraceUtils::CollectFramesLazy(
// Next, look up caller's closure on the stack and walk backwards through
// the yields.
RawObject** last_caller_obj =
reinterpret_cast<RawObject**>(frame->GetCallerSp());
frame = frames.NextFrame();
RawObject** last_caller_obj = reinterpret_cast<RawObject**>(frame->sp());
closure = FindClosureInFrame(last_caller_obj, function,
frame->is_interpreted());
// If this async function hasn't yielded yet, we're still dealing with a
// normal stack. Continue to next frame as usual.
if (!caller_closure_finder.IsRunningAsync(closure)) {
frame = frames.NextFrame();
// Don't advance frame since we already did so just above.
continue;
}
@@ -190,22 +435,26 @@ intptr_t StackTraceUtils::CountFrames(Thread* thread,
bool* sync_async_end) {
Zone* zone = thread->zone();
intptr_t frame_count = 0;
DartFrameIterator frames(thread, StackFrameIterator::kNoCrossThreadIteration);
StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread,
StackFrameIterator::kNoCrossThreadIteration);
StackFrame* frame = frames.NextFrame();
ASSERT(frame != nullptr); // We expect to find a dart invocation frame.
ASSERT(frame != NULL); // We expect to find a dart invocation frame.
Function& function = Function::Handle(zone);
Code& code = Code::Handle(zone);
Bytecode& bytecode = Bytecode::Handle(zone);
Closure& closure = Closure::Handle(zone);
CallerClosureFinder caller_closure_finder(zone);
String& function_name = String::Handle(zone);
const bool async_function_is_null = async_function.IsNull();
ASSERT(async_function_is_null || sync_async_end != nullptr);
for (; frame != nullptr; frame = frames.NextFrame()) {
int sync_async_gap_frames = -1;
ASSERT(async_function_is_null || sync_async_end != NULL);
for (; frame != NULL && sync_async_gap_frames != 0;
frame = frames.NextFrame()) {
if (!frame->IsDartFrame()) {
continue;
}
if (skip_frames > 0) {
skip_frames--;
continue;
}
if (frame->is_interpreted()) {
bytecode = frame->LookupDartBytecode();
function = bytecode.function();
@@ -216,35 +465,22 @@ intptr_t StackTraceUtils::CountFrames(Thread* thread,
code = frame->LookupDartCode();
function = code.function();
}
frame_count++;
// If we're only walking the stack for a particular async func. (for
// --causal-async-stacks) return whether it's running sync-async.
if (!async_function_is_null &&
(async_function.raw() == function.parent_function())) {
if (function.IsAsyncGenClosure()) {
// async* is always running async.
if (sync_async_gap_frames > 0) {
function_name = function.QualifiedScrubbedName();
if (!CheckAndSkipAsync(&sync_async_gap_frames, function_name)) {
*sync_async_end = false;
return frame_count;
} else if (function.IsAsyncClosure()) {
// Extract closure from next frame to determine if we're running async.
RawObject** last_caller_obj =
reinterpret_cast<RawObject**>(frame->GetCallerSp());
closure = FindClosureInFrame(last_caller_obj, function,
frame->is_interpreted());
if (caller_closure_finder.IsRunningAsync(closure)) {
*sync_async_end = false;
return frame_count;
}
*sync_async_end = true;
return frame_count;
}
} else {
frame_count++;
}
if (!async_function_is_null &&
(async_function.raw() == function.parent_function())) {
sync_async_gap_frames = kSyncAsyncFrameGap;
}
}
if (!async_function_is_null) {
*sync_async_end = true;
*sync_async_end = sync_async_gap_frames == 0;
}
return frame_count;
}
@@ -256,7 +492,8 @@ intptr_t StackTraceUtils::CollectFrames(Thread* thread,
intptr_t count,
int skip_frames) {
Zone* zone = thread->zone();
DartFrameIterator frames(thread, StackFrameIterator::kNoCrossThreadIteration);
StackFrameIterator frames(ValidationPolicy::kDontValidateFrames, thread,
StackFrameIterator::kNoCrossThreadIteration);
StackFrame* frame = frames.NextFrame();
ASSERT(frame != NULL); // We expect to find a dart invocation frame.
Function& function = Function::Handle(zone);
@@ -266,6 +503,9 @@ intptr_t StackTraceUtils::CollectFrames(Thread* thread,
intptr_t collected_frames_count = 0;
for (; (frame != NULL) && (collected_frames_count < count);
frame = frames.NextFrame()) {
if (!frame->IsDartFrame()) {
continue;
}
if (skip_frames > 0) {
skip_frames--;
continue;
@@ -327,183 +567,4 @@ intptr_t StackTraceUtils::ExtractAsyncStackTraceInfo(
return async_stack_trace_length;
}
CallerClosureFinder::CallerClosureFinder(Zone* zone)
: receiver_context_(Context::Handle(zone)),
receiver_function_(Function::Handle(zone)),
context_entry_(Object::Handle(zone)),
is_sync(Object::Handle(zone)),
future_(Object::Handle(zone)),
listener_(Object::Handle(zone)),
callback_(Object::Handle(zone)),
controller_(Object::Handle(zone)),
state_(Object::Handle(zone)),
var_data_(Object::Handle(zone)),
future_impl_class(Class::Handle(zone)),
async_await_completer_class(Class::Handle(zone)),
future_listener_class(Class::Handle(zone)),
async_start_stream_controller_class(Class::Handle(zone)),
stream_controller_class(Class::Handle(zone)),
controller_subscription_class(Class::Handle(zone)),
buffering_stream_subscription_class(Class::Handle(zone)),
async_stream_controller_class(Class::Handle(zone)),
completer_is_sync_field(Field::Handle(zone)),
completer_future_field(Field::Handle(zone)),
future_result_or_listeners_field(Field::Handle(zone)),
callback_field(Field::Handle(zone)),
controller_controller_field(Field::Handle(zone)),
var_data_field(Field::Handle(zone)),
state_field(Field::Handle(zone)),
on_data_field(Field::Handle(zone)) {
// Instance caches library and field references.
// This way we don't have to do the look-ups for every frame in the stack.
const auto& async_lib = Library::Handle(zone, Library::AsyncLibrary());
// Look up classes:
// - async:
future_impl_class = async_lib.LookupClassAllowPrivate(Symbols::FutureImpl());
ASSERT(!future_impl_class.IsNull());
async_await_completer_class =
async_lib.LookupClassAllowPrivate(Symbols::_AsyncAwaitCompleter());
ASSERT(!async_await_completer_class.IsNull());
future_listener_class =
async_lib.LookupClassAllowPrivate(Symbols::_FutureListener());
ASSERT(!future_listener_class.IsNull());
// - async*:
async_start_stream_controller_class =
async_lib.LookupClassAllowPrivate(Symbols::_AsyncStarStreamController());
ASSERT(!async_start_stream_controller_class.IsNull());
stream_controller_class =
async_lib.LookupClassAllowPrivate(Symbols::_StreamController());
ASSERT(!stream_controller_class.IsNull());
async_stream_controller_class =
async_lib.LookupClassAllowPrivate(Symbols::_AsyncStreamController());
ASSERT(!async_stream_controller_class.IsNull());
controller_subscription_class =
async_lib.LookupClassAllowPrivate(Symbols::_ControllerSubscription());
ASSERT(!controller_subscription_class.IsNull());
buffering_stream_subscription_class = async_lib.LookupClassAllowPrivate(
Symbols::_BufferingStreamSubscription());
ASSERT(!buffering_stream_subscription_class.IsNull());
// Look up fields:
// - async:
completer_is_sync_field =
async_await_completer_class.LookupFieldAllowPrivate(Symbols::isSync());
ASSERT(!completer_is_sync_field.IsNull());
completer_future_field =
async_await_completer_class.LookupFieldAllowPrivate(Symbols::_future());
ASSERT(!completer_future_field.IsNull());
future_result_or_listeners_field =
future_impl_class.LookupFieldAllowPrivate(Symbols::_resultOrListeners());
ASSERT(!future_result_or_listeners_field.IsNull());
callback_field =
future_listener_class.LookupFieldAllowPrivate(Symbols::callback());
ASSERT(!callback_field.IsNull());
// - async*:
controller_controller_field =
async_start_stream_controller_class.LookupFieldAllowPrivate(
Symbols::controller());
ASSERT(!controller_controller_field.IsNull());
state_field =
stream_controller_class.LookupFieldAllowPrivate(Symbols::_state());
ASSERT(!state_field.IsNull());
var_data_field =
stream_controller_class.LookupFieldAllowPrivate(Symbols::_varData());
ASSERT(!var_data_field.IsNull());
on_data_field = buffering_stream_subscription_class.LookupFieldAllowPrivate(
Symbols::_onData());
ASSERT(!on_data_field.IsNull());
}
RawClosure* CallerClosureFinder::FindCallerInAsyncClosure(
const Context& receiver_context) {
context_entry_ = receiver_context.At(Context::kAsyncCompleterIndex);
ASSERT(context_entry_.IsInstance());
ASSERT(context_entry_.GetClassId() == async_await_completer_class.id());
const Instance& completer = Instance::Cast(context_entry_);
future_ = completer.GetField(completer_future_field);
ASSERT(!future_.IsNull());
ASSERT(future_.GetClassId() == future_impl_class.id());
listener_ =
Instance::Cast(future_).GetField(future_result_or_listeners_field);
if (listener_.GetClassId() != future_listener_class.id()) {
return Closure::null();
}
callback_ = Instance::Cast(listener_).GetField(callback_field);
// This happens for e.g.: await f().catchError(..);
if (callback_.IsNull()) {
return Closure::null();
}
ASSERT(callback_.IsClosure());
return Closure::Cast(callback_).raw();
}
RawClosure* CallerClosureFinder::FindCallerInAsyncGenClosure(
const Context& receiver_context) {
context_entry_ = receiver_context.At(Context::kControllerIndex);
ASSERT(context_entry_.IsInstance());
ASSERT(context_entry_.GetClassId() ==
async_start_stream_controller_class.id());
const Instance& controller = Instance::Cast(context_entry_);
controller_ = controller.GetField(controller_controller_field);
ASSERT(!controller_.IsNull());
ASSERT(controller_.GetClassId() == async_stream_controller_class.id());
state_ = Instance::Cast(controller_).GetField(state_field);
ASSERT(state_.IsSmi());
if (Smi::Cast(state_).Value() != kStreamController_StateSubscribed) {
return Closure::null();
}
var_data_ = Instance::Cast(controller_).GetField(var_data_field);
ASSERT(var_data_.GetClassId() == controller_subscription_class.id());
callback_ = Instance::Cast(var_data_).GetField(on_data_field);
ASSERT(callback_.IsClosure());
return Closure::Cast(callback_).raw();
}
RawClosure* CallerClosureFinder::FindCaller(const Closure& receiver_closure) {
receiver_function_ = receiver_closure.function();
receiver_context_ = receiver_closure.context();
if (receiver_function_.IsAsyncClosure()) {
return FindCallerInAsyncClosure(receiver_context_);
} else if (receiver_function_.IsAsyncGenClosure()) {
return FindCallerInAsyncGenClosure(receiver_context_);
}
return Closure::null();
}
bool CallerClosureFinder::IsRunningAsync(const Closure& receiver_closure) {
receiver_function_ = receiver_closure.function();
receiver_context_ = receiver_closure.context();
// The async* functions are never started synchronously, they start running
// after the first `listen()` call to its returned `Stream`.
if (receiver_function_.IsAsyncGenClosure()) {
return true;
}
ASSERT(receiver_function_.IsAsyncClosure());
context_entry_ = receiver_context_.At(Context::kAsyncCompleterIndex);
ASSERT(context_entry_.IsInstance());
ASSERT(context_entry_.GetClassId() == async_await_completer_class.id());
const Instance& completer = Instance::Cast(context_entry_);
is_sync = completer.GetField(completer_is_sync_field);
ASSERT(!is_sync.IsNull());
ASSERT(is_sync.IsBool());
// _AsyncAwaitCompleter.isSync indicates whether the future should be
// completed async. or sync., based on whether it has yielded yet.
// isSync is true when the :async_op is running async.
return Bool::Cast(is_sync).value();
}
} // namespace dart
+26 -76
View File
@@ -64,87 +64,37 @@ class StackTraceUtils : public AllStatic {
Array* async_code_array,
Array* async_pc_offset_array);
// Find the closure corresponding to `function` in (presumably) its parent
// stack frame (based on the frame's SP).
static RawClosure* FindClosureInFrame(RawObject** last_object_in_caller,
const Function& function,
bool is_interpreted) {
NoSafepointScope nsp;
// The number of frames involved in a "sync-async" gap: a synchronous initial
// invocation of an asynchronous function. See CheckAndSkipAsync.
static constexpr intptr_t kSyncAsyncFrameGap = 2;
// The callee has function signature
// :async_op([result, exception, stack])
// So we are guaranteed to
// a) have only (up to three) tagged arguments on the stack until we find
// the :async_op closure, and
// b) find the async closure.
auto& closure = Closure::Handle();
for (intptr_t i = 0; i < 4; i++) {
// KBC builds the stack upwards instead of the usual downwards stack.
RawObject* arg = last_object_in_caller[(is_interpreted ? -i : i)];
if (arg->IsHeapObject() && arg->GetClassId() == kClosureCid) {
closure = Closure::RawCast(arg);
if (closure.function() == function.raw()) {
return closure.raw();
}
}
// A synchronous invocation of an async function involves the following
// frames:
// <async function>__<anonymous_closure> (0)
// _Closure.call (1)
// _AsyncAwaitCompleter.start (2)
// <async_function> (3)
//
// Alternatively, for bytecode or optimized frames, we may see:
// <async function>__<anonymous_closure> (0)
// _AsyncAwaitCompleter.start (1)
// <async_function> (2)
static bool CheckAndSkipAsync(int* skip_sync_async_frames_count,
const String& function_name) {
ASSERT(*skip_sync_async_frames_count > 0);
if (function_name.Equals(Symbols::_AsyncAwaitCompleterStart())) {
*skip_sync_async_frames_count = 0;
return true;
}
UNREACHABLE();
if (function_name.Equals(Symbols::_ClosureCall()) &&
*skip_sync_async_frames_count == 2) {
(*skip_sync_async_frames_count)--;
return true;
}
return false;
}
};
// Helper class for finding the closure of the caller.
// This is done via the _AsyncAwaitCompleter which holds a
// FutureResultOrListeners which in turn holds a callback.
class CallerClosureFinder {
public:
// Instance caches library and field references.
// This way we don't have to do the look-ups for every frame in the stack.
explicit CallerClosureFinder(Zone* zone);
RawClosure* FindCallerInAsyncClosure(const Context& receiver_context);
RawClosure* FindCallerInAsyncGenClosure(const Context& receiver_context);
RawClosure* FindCaller(const Closure& receiver_closure);
bool IsRunningAsync(const Closure& receiver_closure);
private:
// Keep in sync with
// sdk/lib/async/stream_controller.dart:_StreamController._STATE_SUBSCRIBED.
const intptr_t kStreamController_StateSubscribed = 1;
Context& receiver_context_;
Function& receiver_function_;
Object& context_entry_;
Object& is_sync;
Object& future_;
Object& listener_;
Object& callback_;
Object& controller_;
Object& state_;
Object& var_data_;
Class& future_impl_class;
Class& async_await_completer_class;
Class& future_listener_class;
Class& async_start_stream_controller_class;
Class& stream_controller_class;
Class& controller_subscription_class;
Class& buffering_stream_subscription_class;
Class& async_stream_controller_class;
Field& completer_is_sync_field;
Field& completer_future_field;
Field& future_result_or_listeners_field;
Field& callback_field;
Field& controller_controller_field;
Field& var_data_field;
Field& state_field;
Field& on_data_field;
};
} // namespace dart
#endif // RUNTIME_VM_STACK_TRACE_H_
-1
View File
@@ -800,7 +800,6 @@ abstract class _AsyncStreamControllerDispatch<T>
// TODO(lrn): Use common superclass for callback-controllers when VM supports
// constructors in mixin superclasses.
@pragma("vm:entry-point")
class _AsyncStreamController<T> = _StreamController<T>
with _AsyncStreamControllerDispatch<T>;
@@ -807,7 +807,6 @@ abstract class _AsyncStreamControllerDispatch<T>
// TODO(lrn): Use common superclass for callback-controllers when VM supports
// constructors in mixin superclasses.
@pragma("vm:entry-point")
class _AsyncStreamController<T> = _StreamController<T>
with _AsyncStreamControllerDispatch<T>;