From 72f4a831aabe2680e2cec1e7df4a01d111443ff7 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Fri, 20 Dec 2019 19:06:54 +0000 Subject: [PATCH] Revert "[SDK] Switch to is_sync to identify sync/async running." This reverts commit 886615d0f95bf5a752d6409b1109a3a4f9974912. 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 > Reviewed-by: Martin Kustermann > Commit-Queue: Clement Skau 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 Commit-Queue: Martin Kustermann --- runtime/vm/debugger.cc | 249 +++++------ runtime/vm/stack_frame.h | 12 +- runtime/vm/stack_trace.cc | 487 ++++++++++++---------- runtime/vm/stack_trace.h | 102 ++--- sdk/lib/async/stream_controller.dart | 1 - sdk_nnbd/lib/async/stream_controller.dart | 1 - 6 files changed, 438 insertions(+), 414 deletions(-) diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc index 972d61eba40..d91cb99f782 100644 --- a/runtime/vm/debugger.cc +++ b/runtime/vm/debugger.cc @@ -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(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(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(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) diff --git a/runtime/vm/stack_frame.h b/runtime/vm/stack_frame.h index ddc709382a9..72699aeced6 100644 --- a/runtime/vm/stack_frame.h +++ b/runtime/vm/stack_frame.h @@ -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( fp() + ((is_interpreted() ? kKBCSavedCallerFpSlotFromFp diff --git a/runtime/vm/stack_trace.cc b/runtime/vm/stack_trace.cc index eee8f8d37c2..2495e7a5d6d 100644 --- a/runtime/vm/stack_trace.cc +++ b/runtime/vm/stack_trace.cc @@ -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(frame->GetCallerSp()); + frame = frames.NextFrame(); + RawObject** last_caller_obj = reinterpret_cast(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(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 diff --git a/runtime/vm/stack_trace.h b/runtime/vm/stack_trace.h index 23648b1a9e9..2de8619c6f4 100644 --- a/runtime/vm/stack_trace.h +++ b/runtime/vm/stack_trace.h @@ -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: + // __ (0) + // _Closure.call (1) + // _AsyncAwaitCompleter.start (2) + // (3) + // + // Alternatively, for bytecode or optimized frames, we may see: + // __ (0) + // _AsyncAwaitCompleter.start (1) + // (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_ diff --git a/sdk/lib/async/stream_controller.dart b/sdk/lib/async/stream_controller.dart index be4eebb38e1..50e9aa34002 100644 --- a/sdk/lib/async/stream_controller.dart +++ b/sdk/lib/async/stream_controller.dart @@ -800,7 +800,6 @@ abstract class _AsyncStreamControllerDispatch // TODO(lrn): Use common superclass for callback-controllers when VM supports // constructors in mixin superclasses. -@pragma("vm:entry-point") class _AsyncStreamController = _StreamController with _AsyncStreamControllerDispatch; diff --git a/sdk_nnbd/lib/async/stream_controller.dart b/sdk_nnbd/lib/async/stream_controller.dart index 2273ead2da4..6f09d5d46a9 100644 --- a/sdk_nnbd/lib/async/stream_controller.dart +++ b/sdk_nnbd/lib/async/stream_controller.dart @@ -807,7 +807,6 @@ abstract class _AsyncStreamControllerDispatch // TODO(lrn): Use common superclass for callback-controllers when VM supports // constructors in mixin superclasses. -@pragma("vm:entry-point") class _AsyncStreamController = _StreamController with _AsyncStreamControllerDispatch;