Skip to content

Commit 2947ca6

Browse files
committed
[lldb] Add synthetic formatter for Swift.TaskGroup (#10143)
Add synthetic data formatter for `TaskGroup`. This formatter prints the group's tasks using the previously added formatter for `Task`. (cherry-picked from commit 065c58e)
1 parent 2041102 commit 2947ca6

File tree

6 files changed

+262
-0
lines changed

6 files changed

+262
-0
lines changed

lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h"
1717
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
1818
#include "Plugins/TypeSystem/Swift/SwiftDemangle.h"
19+
#include "Plugins/TypeSystem/Swift/TypeSystemSwiftTypeRef.h"
1920
#include "lldb/DataFormatters/FormattersHelpers.h"
2021
#include "lldb/DataFormatters/StringPrinter.h"
2122
#include "lldb/Symbol/CompilerType.h"
23+
#include "lldb/Target/ExecutionContext.h"
2224
#include "lldb/Target/Process.h"
2325
#include "lldb/Utility/ConstString.h"
2426
#include "lldb/Utility/DataBufferHeap.h"
@@ -28,11 +30,13 @@
2830
#include "lldb/Utility/Timer.h"
2931
#include "lldb/ValueObject/ValueObject.h"
3032
#include "lldb/lldb-enumerations.h"
33+
#include "swift/ABI/Task.h"
3134
#include "swift/AST/Types.h"
3235
#include "swift/Demangling/Demangle.h"
3336
#include "swift/Demangling/ManglingMacros.h"
3437
#include "llvm/ADT/STLExtras.h"
3538
#include "llvm/ADT/StringRef.h"
39+
#include "llvm/Support/Error.h"
3640
#include "llvm/Support/FormatAdapters.h"
3741
#include "llvm/Support/raw_ostream.h"
3842
#include <optional>
@@ -950,6 +954,188 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
950954
ValueObjectSP m_child_tasks_sp;
951955
ValueObjectSP m_is_running_sp;
952956
};
957+
958+
class TaskGroupSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
959+
public:
960+
TaskGroupSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
961+
: SyntheticChildrenFrontEnd(*valobj_sp.get()) {
962+
bool is_64bit = false;
963+
if (auto target_sp = m_backend.GetTargetSP())
964+
is_64bit = target_sp->GetArchitecture().GetTriple().isArch64Bit();
965+
966+
std::optional<uint32_t> concurrency_version;
967+
if (auto process_sp = m_backend.GetProcessSP())
968+
concurrency_version =
969+
SwiftLanguageRuntime::FindConcurrencyDebugVersion(*process_sp);
970+
971+
m_is_supported_target = is_64bit && concurrency_version.value_or(0) == 1;
972+
}
973+
974+
llvm::Expected<uint32_t> CalculateNumChildren() override {
975+
if (!m_is_supported_target)
976+
return m_backend.GetNumChildren();
977+
978+
return m_task_addrs.size();
979+
}
980+
981+
bool MightHaveChildren() override { return true; }
982+
983+
lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override {
984+
if (!m_is_supported_target)
985+
return m_backend.GetChildAtIndex(idx);
986+
987+
if (!m_task_type || idx >= m_task_addrs.size())
988+
return {};
989+
990+
if (auto valobj_sp = m_children[idx])
991+
return valobj_sp;
992+
993+
addr_t task_addr = m_task_addrs[idx];
994+
auto child_name = ("[" + Twine(idx) + "]").str();
995+
auto task_sp = ValueObject::CreateValueObjectFromAddress(
996+
child_name, task_addr, m_backend.GetExecutionContextRef(), m_task_type,
997+
false);
998+
if (auto synthetic_sp = task_sp->GetSyntheticValue())
999+
task_sp = synthetic_sp;
1000+
1001+
m_children[idx] = task_sp;
1002+
return task_sp;
1003+
}
1004+
1005+
size_t GetIndexOfChildWithName(ConstString name) override {
1006+
if (!m_is_supported_target)
1007+
return m_backend.GetIndexOfChildWithName(name);
1008+
1009+
StringRef buf = name.GetStringRef();
1010+
size_t idx = UINT32_MAX;
1011+
if (buf.consume_front("[") && !buf.consumeInteger(10, idx) && buf == "]")
1012+
return idx;
1013+
return UINT32_MAX;
1014+
}
1015+
1016+
lldb::ChildCacheState Update() override {
1017+
if (!m_is_supported_target)
1018+
return ChildCacheState::eReuse;
1019+
1020+
m_task_addrs.clear();
1021+
m_children.clear();
1022+
1023+
if (!m_task_type)
1024+
if (auto target_sp = m_backend.GetTargetSP()) {
1025+
if (auto ts_or_err = target_sp->GetScratchTypeSystemForLanguage(
1026+
eLanguageTypeSwift)) {
1027+
if (auto *ts = llvm::dyn_cast_or_null<TypeSystemSwiftTypeRef>(
1028+
ts_or_err->get()))
1029+
// TypeMangling for "Swift.UnsafeCurrentTask"
1030+
m_task_type = ts->GetTypeFromMangledTypename(ConstString("$sSctD"));
1031+
} else {
1032+
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
1033+
ts_or_err.takeError(),
1034+
"could not get Swift type system for Task synthetic "
1035+
"provider: {0}");
1036+
return ChildCacheState::eReuse;
1037+
}
1038+
}
1039+
1040+
if (!m_task_type)
1041+
return ChildCacheState::eReuse;
1042+
1043+
// Get the (opaque) pointer to the `TaskGroupBase`.
1044+
addr_t task_group_ptr = LLDB_INVALID_ADDRESS;
1045+
if (auto opaque_group_ptr_sp = m_backend.GetChildMemberWithName("_group"))
1046+
task_group_ptr =
1047+
opaque_group_ptr_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
1048+
1049+
TaskGroupBase task_group{m_backend.GetProcessSP(), task_group_ptr};
1050+
1051+
// Get the TaskGroup's child tasks by getting all tasks in the range
1052+
// [FirstChild, LastChild].
1053+
//
1054+
// Child tasks are connected together using ChildFragment::NextChild.
1055+
Status status;
1056+
auto current_task = task_group.getFirstChild(status);
1057+
auto last_task = task_group.getLastChild(status);
1058+
while (current_task) {
1059+
m_task_addrs.push_back(current_task.addr);
1060+
if (current_task == last_task)
1061+
break;
1062+
current_task = current_task.getNextChild(status);
1063+
}
1064+
1065+
// Populate the child cache with null values.
1066+
m_children.resize(m_task_addrs.size());
1067+
1068+
if (status.Fail()) {
1069+
LLDB_LOG(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
1070+
"could not read TaskGroup's child task pointers: {0}",
1071+
status.AsCString());
1072+
return ChildCacheState::eReuse;
1073+
}
1074+
1075+
return ChildCacheState::eRefetch;
1076+
}
1077+
1078+
private:
1079+
/// Lightweight Task pointer wrapper, for the purpose of traversing to the
1080+
/// Task's next sibling (via `ChildFragment::NextChild`).
1081+
struct Task {
1082+
ProcessSP process_sp;
1083+
addr_t addr;
1084+
1085+
operator bool() const { return addr && addr != LLDB_INVALID_ADDRESS; }
1086+
1087+
bool operator==(const Task &other) const { return addr == other.addr; }
1088+
bool operator!=(const Task &other) const { return !(*this == other); }
1089+
1090+
static constexpr offset_t AsyncTaskSize = sizeof(::swift::AsyncTask);
1091+
static constexpr offset_t ChildFragmentOffset = AsyncTaskSize;
1092+
static constexpr offset_t NextChildOffset = ChildFragmentOffset + 0x8;
1093+
1094+
Task getNextChild(Status &status) {
1095+
addr_t next_task = LLDB_INVALID_ADDRESS;
1096+
if (status.Success())
1097+
next_task =
1098+
process_sp->ReadPointerFromMemory(addr + NextChildOffset, status);
1099+
return {process_sp, next_task};
1100+
}
1101+
};
1102+
1103+
/// Lightweight wrapper around TaskGroup opaque pointers (`TaskGroupBase`),
1104+
/// for the purpose of traversing its child tasks.
1105+
struct TaskGroupBase {
1106+
ProcessSP process_sp;
1107+
addr_t addr;
1108+
1109+
// FirstChild offset for a TaskGroupBase instance.
1110+
static constexpr offset_t FirstChildOffset = 0x18;
1111+
static constexpr offset_t LastChildOffset = 0x20;
1112+
1113+
Task getFirstChild(Status &status) {
1114+
addr_t first_child = LLDB_INVALID_ADDRESS;
1115+
if (status.Success())
1116+
first_child =
1117+
process_sp->ReadPointerFromMemory(addr + FirstChildOffset, status);
1118+
return {process_sp, first_child};
1119+
}
1120+
1121+
Task getLastChild(Status &status) {
1122+
addr_t last_child = LLDB_INVALID_ADDRESS;
1123+
if (status.Success())
1124+
last_child =
1125+
process_sp->ReadPointerFromMemory(addr + LastChildOffset, status);
1126+
return {process_sp, last_child};
1127+
}
1128+
};
1129+
1130+
private:
1131+
bool m_is_supported_target = false;
1132+
// Type for Swift.UnsafeCurrentTask.
1133+
CompilerType m_task_type;
1134+
// The TaskGroup's list of child task addresses.
1135+
std::vector<addr_t> m_task_addrs;
1136+
// Cache and storage of constructed child values.
1137+
std::vector<ValueObjectSP> m_children;
1138+
};
9531139
}
9541140
}
9551141
}
@@ -1016,6 +1202,14 @@ lldb_private::formatters::swift::TaskSyntheticFrontEndCreator(
10161202
return new TaskSyntheticFrontEnd(valobj_sp);
10171203
}
10181204

1205+
SyntheticChildrenFrontEnd *
1206+
lldb_private::formatters::swift::TaskGroupSyntheticFrontEndCreator(
1207+
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
1208+
if (!valobj_sp)
1209+
return nullptr;
1210+
return new TaskGroupSyntheticFrontEnd(valobj_sp);
1211+
}
1212+
10191213
bool lldb_private::formatters::swift::ObjC_Selector_SummaryProvider(
10201214
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
10211215
LLDB_SCOPED_TIMER();

lldb/source/Plugins/Language/Swift/SwiftFormatters.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ SyntheticChildrenFrontEnd *EnumSyntheticFrontEndCreator(CXXSyntheticChildren *,
119119

120120
SyntheticChildrenFrontEnd *TaskSyntheticFrontEndCreator(CXXSyntheticChildren *,
121121
lldb::ValueObjectSP);
122+
123+
SyntheticChildrenFrontEnd *
124+
TaskGroupSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP);
122125
}
123126
}
124127
}

lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,11 @@ static void LoadSwiftFormatters(lldb::TypeCategoryImplSP swift_category_sp) {
416416
lldb_private::formatters::swift::TaskSyntheticFrontEndCreator,
417417
"Swift.UnsafeCurrentTask synthetic children",
418418
ConstString("Swift.UnsafeCurrentTask"), synth_flags);
419+
AddCXXSynthetic(
420+
swift_category_sp,
421+
lldb_private::formatters::swift::TaskGroupSyntheticFrontEndCreator,
422+
"Swift.TaskGroup synthetic children",
423+
ConstString("^Swift\\.TaskGroup<.+>"), synth_flags, true);
419424

420425
AddCXXSummary(
421426
swift_category_sp, lldb_private::formatters::swift::Bool_SummaryProvider,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SWIFT_SOURCES := main.swift
2+
SWIFTFLAGS_EXTRAS := -parse-as-library
3+
include Makefile.rules
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
from lldbsuite.test.lldbtest import *
4+
from lldbsuite.test import lldbutil
5+
6+
7+
class TestCase(TestBase):
8+
9+
@swiftTest
10+
def test_value_printing(self):
11+
"""Print a TaskGroup and verify its children."""
12+
self.build()
13+
lldbutil.run_to_source_breakpoint(
14+
self, "break here", lldb.SBFileSpec("main.swift")
15+
)
16+
self.expect(
17+
"v group",
18+
substrs=[
19+
"[0] = {",
20+
"isGroupChildTask = true",
21+
"[1] = {",
22+
"isGroupChildTask = true",
23+
"[2] = {",
24+
"isGroupChildTask = true",
25+
],
26+
)
27+
28+
@swiftTest
29+
def test_value_api(self):
30+
"""Verify a TaskGroup contains its expected children."""
31+
self.build()
32+
_, process, _, _ = lldbutil.run_to_source_breakpoint(
33+
self, "break here", lldb.SBFileSpec("main.swift")
34+
)
35+
thread = process.GetSelectedThread()
36+
frame = thread.GetSelectedFrame()
37+
group = frame.FindVariable("group")
38+
self.assertEqual(group.num_children, 3)
39+
for task in group:
40+
self.assertEqual(str(task), str(group.GetChildMemberWithName(task.name)))
41+
self.assertEqual(
42+
task.GetChildMemberWithName("isGroupChildTask").summary, "true"
43+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@main struct Main {
2+
static func main() async {
3+
await withTaskGroup { group in
4+
for _ in 0..<3 {
5+
group.addTask {
6+
try? await Task.sleep(for: .seconds(300))
7+
}
8+
}
9+
10+
print("break here")
11+
for await _ in group {}
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)