Skip to content

[lldb][formatter] Add children of Swift Tasks #10057

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 57 additions & 9 deletions lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h"
#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "Plugins/TypeSystem/Swift/SwiftDemangle.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/DataFormatters/StringPrinter.h"
#include "lldb/Symbol/CompilerType.h"
Expand All @@ -28,6 +29,7 @@
#include "lldb/ValueObject/ValueObject.h"
#include "lldb/lldb-enumerations.h"
#include "swift/AST/Types.h"
#include "swift/Demangling/Demangle.h"
#include "swift/Demangling/ManglingMacros.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
Expand Down Expand Up @@ -761,10 +763,10 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
auto ts_or_err =
target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeSwift);
if (auto err = ts_or_err.takeError()) {
LLDB_LOG(
GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
"could not get Swift type system for Task synthetic provider: {0}",
fmt_consume(std::move(err)));
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
std::move(err),
"could not get Swift type system for Task synthetic "
"provider: {0}");
return;
}
m_ts = llvm::dyn_cast_or_null<TypeSystemSwiftTypeRef>(ts_or_err->get());
Expand All @@ -782,6 +784,7 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
"isStatusRecordLocked",
"isEscalated",
"isEnqueued",
"children",
"isRunning",
};

Expand Down Expand Up @@ -837,7 +840,22 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
RETURN_CHILD(m_is_escalated_sp, isEscalated, bool_type);
case 10:
RETURN_CHILD(m_is_enqueued_sp, isEnqueued, bool_type);
case 11:
case 11: {
if (!m_child_tasks_sp) {
const auto &tasks = m_task_info.childTasks;
std::string mangled_typename =
mangledTypenameForTasksTuple(tasks.size());
CompilerType tasks_tuple_type =
m_ts->GetTypeFromMangledTypename(ConstString(mangled_typename));
DataExtractor data{tasks.data(), tasks.size() * sizeof(tasks[0]),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of sizeof raises an eyebrow. Wouldn't you want the size of a task on the target here? (For example because of 32-bit pointers on watchOS)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I did this is because data at hand (an lldb defined struct, AsyncTaskInfo), is internal lldb data. It comes from the target, but at this point it's internal data, not target data. Does that sound reasonable to you? It definitely felt like an uncommon scenario as I wrote this.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, if this is a host data structure then this is of course fine!

endian::InlHostByteOrder(), sizeof(void *)};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use multiples of the pointer size on the target, since we still need to bake in this knowledge.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment, may have been my misunderstanding!

m_child_tasks_sp = ValueObject::CreateValueObjectFromData(
"children", data, m_backend.GetExecutionContextRef(),
tasks_tuple_type);
}
return m_child_tasks_sp;
}
case 12:
RETURN_CHILD(m_is_running_sp, isRunning, bool_type);
default:
return {};
Expand All @@ -858,17 +876,17 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
llvm::Expected<ReflectionContextInterface::AsyncTaskInfo> task_info =
reflection_ctx->asyncTaskInfo(task_ptr);
if (auto err = task_info.takeError()) {
LLDB_LOG(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
"could not get info for async task {0:x}: {1}", task_ptr,
fmt_consume(std::move(err)));
LLDB_LOG_ERROR(
GetLog(LLDBLog::DataFormatters | LLDBLog::Types), std::move(err),
"could not get info for async task {0:x}: {1}", task_ptr);
} else {
m_task_info = *task_info;
for (auto child :
{m_id_sp, m_kind_sp, m_enqueue_priority_sp, m_is_child_task_sp,
m_is_future_sp, m_is_group_child_task_sp,
m_is_async_let_task_sp, m_is_cancelled_sp,
m_is_status_record_locked_sp, m_is_escalated_sp,
m_is_enqueued_sp, m_is_running_sp})
m_is_enqueued_sp, m_child_tasks_sp, m_is_running_sp})
child.reset();
}
}
Expand All @@ -886,6 +904,35 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
return std::distance(children.begin(), it);
}

private:
std::string mangledTypenameForTasksTuple(size_t count) {
/*
Global > TypeMangling > Type > Tuple
TupleElement > Type > Structure
Module, text="Swift"
Identifier, text="UnsafeCurrentTask"
*/
using namespace ::swift::Demangle;
using Kind = Node::Kind;
NodeFactory factory;
auto [root, tuple] = swift_demangle::MakeNodeChain(
{Kind::TypeMangling, Kind::Type, Kind::Tuple}, factory);

// Make a TupleElement subtree N times, where N is the number of subtasks.
for (size_t i = 0; i < count; ++i) {
auto *structure = swift_demangle::MakeNodeChain(
tuple, {Kind::TupleElement, Kind::Type, Kind::Structure}, factory);
if (structure) {
structure->addChild(
factory.createNode(Kind::Module, ::swift::STDLIB_NAME), factory);
structure->addChild(
factory.createNode(Kind::Identifier, "UnsafeCurrentTask"), factory);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case you didn't already know, we also have

  CompilerType
  CreateTupleType(const std::vector<TupleElement> &elements);

in TypeSystemSwift.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't, thanks.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, that interface isn't a match for this use case. I don't have a CompilerType instance for the element types. I could create one, but is it worth it to mangle the element type only for CreateTupleType to demangle it?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not.

}
}

return mangleNode(root).result();
}

private:
TypeSystemSwiftTypeRef *m_ts = nullptr;
ReflectionContextInterface::AsyncTaskInfo m_task_info;
Expand All @@ -900,6 +947,7 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
ValueObjectSP m_is_status_record_locked_sp;
ValueObjectSP m_is_escalated_sp;
ValueObjectSP m_is_enqueued_sp;
ValueObjectSP m_child_tasks_sp;
ValueObjectSP m_is_running_sp;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ class TargetReflectionContext : public ReflectionContextInterface {
result.kind = task_info.Kind;
result.enqueuePriority = task_info.EnqueuePriority;
result.resumeAsyncContext = task_info.ResumeAsyncContext;
for (auto child : task_info.ChildTasks)
result.childTasks.push_back(child);
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class ReflectionContextInterface {
uint32_t kind = 0;
uint32_t enqueuePriority = 0;
lldb::addr_t resumeAsyncContext = LLDB_INVALID_ADDRESS;
std::vector<lldb::addr_t> childTasks;
};
// The default limits are copied from swift-inspect.
virtual llvm::Expected<AsyncTaskInfo>
Expand Down
36 changes: 36 additions & 0 deletions lldb/source/Plugins/TypeSystem/Swift/SwiftDemangle.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "swift/Demangling/Demangler.h"
#include "swift/Demangling/ManglingFlavor.h"
#include "llvm/ADT/ArrayRef.h"
#include <initializer_list>

namespace lldb_private {
namespace swift_demangle {
Expand Down Expand Up @@ -97,6 +98,41 @@ GetDemangledTypeMangling(swift::Demangle::Demangler &dem,
return GetTypeMangling(dem.demangleSymbol(name));
}

/// Create a parent-child chain of demangle nodes for the specified node kinds.
///
/// \param initial[in] The starting node to construct the chain from.
/// \param kinds[in] The node kind for each created node.
/// \param factory[in] The factory to create nodes.
/// \return The tail leaf node created, whose kind is the last kind in \c kinds.
inline NodePointer
MakeNodeChain(NodePointer initial,
std::initializer_list<swift::Demangle::Node::Kind> kinds,
NodeFactory &factory) {
auto *current = initial;
for (auto kind : kinds) {
if (!current)
return nullptr;
auto *child = factory.createNode(kind);
current->addChild(child, factory);
current = child;
}
return current;
}

/// Create a parent-child chain of demangle nodes for the specified node kinds.
///
/// \param initial[in] The starting node to construct the chain from.
/// \param kinds[in] The node kind for each created node.
/// \param factory[in] The factory to create nodes.
/// \return A pair of nodes: the head and tail of the constructed nodes.
inline std::pair<NodePointer, NodePointer>
MakeNodeChain(std::initializer_list<swift::Demangle::Node::Kind> kinds,
NodeFactory &factory) {
auto *root = factory.createNode(Node::Kind::Global);
auto *leaf = MakeNodeChain(root, kinds, factory);
return {root, leaf};
}

/// Wrap node in Global/TypeMangling/Type.
inline swift::Demangle::NodePointer
MangleType(swift::Demangle::Demangler &dem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def test_top_level_task(self):
"isAsyncLetTask = false",
"isCancelled = false",
"isEnqueued = ",
"children = {}",
],
)

Expand All @@ -49,5 +50,6 @@ def test_current_task(self):
"isAsyncLetTask = true",
"isCancelled = false",
"isEnqueued = false",
"children = {}",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SWIFT_SOURCES := main.swift
SWIFTFLAGS_EXTRAS := -parse-as-library
include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil


class TestCase(TestBase):

@skipUnlessDarwin
@swiftTest
def test(self):
""""""
self.build()
lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.swift")
)
self.expect(
"language swift task info",
substrs=[
"(UnsafeCurrentTask) current_task = {",
"id = 1",
"isChildTask = false",
"isAsyncLetTask = false",
"children = {",
"0 = {",
"id = 2",
"isChildTask = true",
"isAsyncLetTask = true",
"children = {}",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
func f() async -> Int {
try? await Task.sleep(for: .seconds(300))
return 30
}

@main struct Main {
static func main() async {
async let number = f()
await print("break here \(number)")
}
}