Skip to content

Commit 1d3fd35

Browse files
authored
[lldb] Introduce Swift Task synthetic provider (#9404)
Add a synthetic provider for Swift's `Task` and `UnsafeCurrentTask` types. This uses `ReflectionContext::asyncTaskInfo`. In this initial version, only the bool flags are plugged through. Follow up changes will add more metadata (ex task ID, priority), child tasks, and backtraces.
1 parent 8f5a221 commit 1d3fd35

File tree

8 files changed

+245
-1
lines changed

8 files changed

+245
-1
lines changed

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

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "SwiftFormatters.h"
1414
#include "Plugins/Language/Swift/SwiftStringIndex.h"
15+
#include "Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h"
1516
#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h"
1617
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
1718
#include "lldb/Core/ValueObject.h"
@@ -20,12 +21,17 @@
2021
#include "lldb/Target/Process.h"
2122
#include "lldb/Utility/ConstString.h"
2223
#include "lldb/Utility/DataBufferHeap.h"
24+
#include "lldb/Utility/LLDBLog.h"
25+
#include "lldb/Utility/Log.h"
2326
#include "lldb/Utility/Status.h"
2427
#include "lldb/Utility/Timer.h"
2528
#include "lldb/lldb-enumerations.h"
2629
#include "swift/AST/Types.h"
2730
#include "swift/Demangling/ManglingMacros.h"
31+
#include "llvm/ADT/STLExtras.h"
2832
#include "llvm/ADT/StringRef.h"
33+
#include "llvm/Support/FormatAdapters.h"
34+
#include "llvm/Support/raw_ostream.h"
2935
#include <optional>
3036

3137
// FIXME: we should not need this
@@ -721,6 +727,7 @@ bool lldb_private::formatters::swift::BuiltinObjC_SummaryProvider(
721727
namespace lldb_private {
722728
namespace formatters {
723729
namespace swift {
730+
724731
class EnumSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
725732
public:
726733
EnumSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
@@ -736,6 +743,112 @@ class EnumSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
736743
ConstString m_element_name;
737744
size_t m_child_index;
738745
};
746+
747+
/// Synthetic provider for `Swift.Task`.
748+
///
749+
/// As seen by lldb, a `Task` instance is an opaque pointer, with neither type
750+
/// metadata nor an AST to describe it. To implement this synthetic provider, a
751+
/// `Task`'s state is retrieved from a `ReflectionContext`, and that data is
752+
/// used to manually construct `ValueObject` children.
753+
class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
754+
public:
755+
TaskSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
756+
: SyntheticChildrenFrontEnd(*valobj_sp.get()) {}
757+
758+
constexpr static StringLiteral TaskChildren[] = {
759+
"isChildTask", "isFuture", "isGroupChildTask",
760+
"isAsyncLetTask", "isCancelled", "isStatusRecordLocked",
761+
"isEscalated", "isEnqueued", "isRunning",
762+
};
763+
764+
llvm::Expected<uint32_t> CalculateNumChildren() override {
765+
auto count = ArrayRef(TaskChildren).size();
766+
return m_task_info.hasIsRunning ? count : count - 1;
767+
}
768+
769+
lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override {
770+
auto target_sp = m_backend.GetTargetSP();
771+
#define RETURN_CHILD(FIELD, NAME) \
772+
if (!FIELD) \
773+
FIELD = ValueObject::CreateValueObjectFromBool(target_sp, \
774+
m_task_info.NAME, #NAME); \
775+
return FIELD
776+
777+
switch (idx) {
778+
case 0:
779+
RETURN_CHILD(m_is_child_task_sp, isChildTask);
780+
case 1:
781+
RETURN_CHILD(m_is_future_sp, isFuture);
782+
case 2:
783+
RETURN_CHILD(m_is_group_child_task_sp, isGroupChildTask);
784+
case 3:
785+
RETURN_CHILD(m_is_async_let_task_sp, isAsyncLetTask);
786+
case 4:
787+
RETURN_CHILD(m_is_cancelled_sp, isCancelled);
788+
case 5:
789+
RETURN_CHILD(m_is_status_record_locked_sp, isStatusRecordLocked);
790+
case 6:
791+
RETURN_CHILD(m_is_escalated_sp, isEscalated);
792+
case 7:
793+
RETURN_CHILD(m_is_enqueued_sp, isEnqueued);
794+
case 8:
795+
RETURN_CHILD(m_is_running_sp, isRunning);
796+
default:
797+
return {};
798+
}
799+
800+
#undef RETURN_CHILD
801+
}
802+
803+
lldb::ChildCacheState Update() override {
804+
if (auto *runtime = SwiftLanguageRuntime::Get(m_backend.GetProcessSP())) {
805+
ThreadSafeReflectionContext reflection_ctx =
806+
runtime->GetReflectionContext();
807+
ValueObjectSP task_obj_sp = m_backend.GetChildMemberWithName("_task");
808+
uint64_t task_ptr = task_obj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
809+
if (task_ptr != LLDB_INVALID_ADDRESS) {
810+
llvm::Expected<ReflectionContextInterface::AsyncTaskInfo> task_info =
811+
reflection_ctx->asyncTaskInfo(task_ptr);
812+
if (auto err = task_info.takeError()) {
813+
LLDB_LOG(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
814+
"could not get info for async task {0:x}: {1}", task_ptr,
815+
fmt_consume(std::move(err)));
816+
} else {
817+
m_task_info = *task_info;
818+
for (auto child :
819+
{m_is_child_task_sp, m_is_future_sp, m_is_group_child_task_sp,
820+
m_is_async_let_task_sp, m_is_cancelled_sp,
821+
m_is_status_record_locked_sp, m_is_escalated_sp,
822+
m_is_enqueued_sp, m_is_running_sp})
823+
child.reset();
824+
}
825+
}
826+
}
827+
return ChildCacheState::eRefetch;
828+
}
829+
830+
bool MightHaveChildren() override { return true; }
831+
832+
size_t GetIndexOfChildWithName(ConstString name) override {
833+
ArrayRef children = TaskChildren;
834+
auto it = llvm::find(children, name);
835+
if (it == children.end())
836+
return UINT32_MAX;
837+
return std::distance(children.begin(), it);
838+
}
839+
840+
private:
841+
ReflectionContextInterface::AsyncTaskInfo m_task_info;
842+
ValueObjectSP m_is_child_task_sp;
843+
ValueObjectSP m_is_future_sp;
844+
ValueObjectSP m_is_group_child_task_sp;
845+
ValueObjectSP m_is_async_let_task_sp;
846+
ValueObjectSP m_is_cancelled_sp;
847+
ValueObjectSP m_is_status_record_locked_sp;
848+
ValueObjectSP m_is_escalated_sp;
849+
ValueObjectSP m_is_enqueued_sp;
850+
ValueObjectSP m_is_running_sp;
851+
};
739852
}
740853
}
741854
}
@@ -794,6 +907,14 @@ lldb_private::formatters::swift::EnumSyntheticFrontEndCreator(
794907
return (new EnumSyntheticFrontEnd(valobj_sp));
795908
}
796909

910+
SyntheticChildrenFrontEnd *
911+
lldb_private::formatters::swift::TaskSyntheticFrontEndCreator(
912+
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
913+
if (!valobj_sp)
914+
return NULL;
915+
return new TaskSyntheticFrontEnd(valobj_sp);
916+
}
917+
797918
bool lldb_private::formatters::swift::ObjC_Selector_SummaryProvider(
798919
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
799920
LLDB_SCOPED_TIMER();

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ bool TypePreservingNSNumber_SummaryProvider(ValueObject &valobj, Stream &stream,
116116

117117
SyntheticChildrenFrontEnd *EnumSyntheticFrontEndCreator(CXXSyntheticChildren *,
118118
lldb::ValueObjectSP);
119+
120+
SyntheticChildrenFrontEnd *TaskSyntheticFrontEndCreator(CXXSyntheticChildren *,
121+
lldb::ValueObjectSP);
119122
}
120123
}
121124
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,14 @@ static void LoadSwiftFormatters(lldb::TypeCategoryImplSP swift_category_sp) {
408408
SetConfig::Get().RegisterSyntheticChildrenCreators(swift_category_sp,
409409
synth_flags);
410410

411-
synth_flags.SetSkipPointers(true);
411+
AddCXXSynthetic(swift_category_sp,
412+
lldb_private::formatters::swift::TaskSyntheticFrontEndCreator,
413+
"Swift.Task synthetic children",
414+
ConstString("^Swift\\.Task<.+,.+>"), synth_flags, true);
415+
AddCXXSynthetic(swift_category_sp,
416+
lldb_private::formatters::swift::TaskSyntheticFrontEndCreator,
417+
"Swift.UnsafeCurrentTask synthetic children",
418+
ConstString("Swift.UnsafeCurrentTask"), synth_flags);
412419

413420
AddCXXSummary(
414421
swift_category_sp, lldb_private::formatters::swift::Bool_SummaryProvider,

lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include "swift/RemoteInspection/ReflectionContext.h"
1314
#include "ReflectionContextInterface.h"
1415
#include "SwiftLanguageRuntimeImpl.h"
1516
#include "lldb/Utility/LLDBLog.h"
@@ -368,6 +369,28 @@ class TargetReflectionContext : public ReflectionContextInterface {
368369
return m_reflection_ctx.stripSignedPointer(pointer);
369370
}
370371

372+
llvm::Expected<AsyncTaskInfo>
373+
asyncTaskInfo(lldb::addr_t AsyncTaskPtr, unsigned ChildTaskLimit,
374+
unsigned AsyncBacktraceLimit) override {
375+
auto [error, task_info] = m_reflection_ctx.asyncTaskInfo(
376+
AsyncTaskPtr, ChildTaskLimit, AsyncBacktraceLimit);
377+
if (error)
378+
return llvm::createStringError(*error);
379+
380+
AsyncTaskInfo result;
381+
result.isChildTask = task_info.IsChildTask;
382+
result.isFuture = task_info.IsFuture;
383+
result.isGroupChildTask = task_info.IsGroupChildTask;
384+
result.isAsyncLetTask = task_info.IsAsyncLetTask;
385+
result.isCancelled = task_info.IsCancelled;
386+
result.isStatusRecordLocked = task_info.IsStatusRecordLocked;
387+
result.isEscalated = task_info.IsEscalated;
388+
result.hasIsRunning = task_info.HasIsRunning;
389+
result.isRunning = task_info.IsRunning;
390+
result.isEnqueued = task_info.IsEnqueued;
391+
return result;
392+
}
393+
371394
private:
372395
/// Return a description of the layout of the record (classes, structs and
373396
/// tuples) type given its typeref.

lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "lldb/lldb-types.h"
1919
#include "swift/ABI/ObjectFile.h"
2020
#include "swift/Remote/RemoteAddress.h"
21+
#include "swift/RemoteInspection/ReflectionContext.h"
2122
#include "swift/RemoteInspection/TypeRef.h"
2223
#include <optional>
2324
#include "llvm/ADT/STLFunctionalExtras.h"
@@ -145,6 +146,23 @@ class ReflectionContextInterface {
145146
swift::reflection::DescriptorFinder *descriptor_finder) = 0;
146147
virtual swift::remote::RemoteAbsolutePointer
147148
StripSignedPointer(swift::remote::RemoteAbsolutePointer pointer) = 0;
149+
struct AsyncTaskInfo {
150+
bool isChildTask = false;
151+
bool isFuture = false;
152+
bool isGroupChildTask = false;
153+
bool isAsyncLetTask = false;
154+
bool isCancelled = false;
155+
bool isStatusRecordLocked = false;
156+
bool isEscalated = false;
157+
/// If false, the IsRunning flag is not valid.
158+
bool hasIsRunning = false;
159+
bool isRunning = false;
160+
bool isEnqueued = false;
161+
};
162+
// The default limits are copied from swift-inspect.
163+
virtual llvm::Expected<AsyncTaskInfo>
164+
asyncTaskInfo(lldb::addr_t AsyncTaskPtr, unsigned ChildTaskLimit = 1000000,
165+
unsigned AsyncBacktraceLimit = 1000) = 0;
148166
};
149167

150168
using ThreadSafeReflectionContext = LockGuarded<ReflectionContextInterface>;
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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
def test_top_level_task(self):
10+
"""Test Task synthetic child provider for top-level Task."""
11+
self.build()
12+
lldbutil.run_to_source_breakpoint(
13+
self, "break for top-level task", lldb.SBFileSpec("main.swift")
14+
)
15+
# Note: The value of isEnqueued is timing dependent. For that reason,
16+
# the test checks only that it has a value, not what the value is.
17+
self.expect(
18+
"frame var task",
19+
substrs=[
20+
"(Task<(), Error>) task = {",
21+
"isChildTask = false",
22+
"isFuture = true",
23+
"isGroupChildTask = false",
24+
"isAsyncLetTask = false",
25+
"isCancelled = false",
26+
"isEnqueued = ",
27+
],
28+
)
29+
30+
def test_current_task(self):
31+
"""Test Task synthetic child for UnsafeCurrentTask (from an async let)."""
32+
self.build()
33+
lldbutil.run_to_source_breakpoint(
34+
self, "break for current task", lldb.SBFileSpec("main.swift")
35+
)
36+
self.expect(
37+
"frame var currentTask",
38+
substrs=[
39+
"(UnsafeCurrentTask) currentTask = {",
40+
"isChildTask = true",
41+
"isFuture = true",
42+
"isGroupChildTask = false",
43+
"isAsyncLetTask = true",
44+
"isCancelled = false",
45+
"isEnqueued = false",
46+
],
47+
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
func f() async -> Int {
2+
withUnsafeCurrentTask { currentTask in
3+
if let currentTask {
4+
print("break for current task")
5+
}
6+
}
7+
return 30
8+
}
9+
10+
@main struct Main {
11+
static func main() async {
12+
let task = Task {
13+
// Extend the task's lifetime, hopefully long enough for the breakpoint to hit.
14+
try await Task.sleep(for: .seconds(0.5))
15+
print("inside")
16+
}
17+
print("break for top-level task")
18+
19+
async let number = f()
20+
print(await number)
21+
}
22+
}

0 commit comments

Comments
 (0)