Skip to content

Commit 8989f69

Browse files
committed
[lldb] Add initial formatter for Swift Actors (#10367)
Adds a synthetic formatter for `Builtin.DefaultActorStorage`. Each actor instance has a field named `$defaultActor` which has a type of `DefaultActorStorage`. Prior to this, that field was shown as opaque bytes. With this change, the value now has a single child, `unprioritised_jobs`, which represents the job queue for the actor. This is the first of a few changes, including a summary formatter which will show which state the actor is in (ie "running", "idle", etc). (cherry-picked from commit 23f6dc9)
1 parent 8274587 commit 8989f69

File tree

6 files changed

+270
-29
lines changed

6 files changed

+270
-29
lines changed

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

Lines changed: 208 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "Plugins/TypeSystem/Swift/TypeSystemSwiftTypeRef.h"
2020
#include "lldb/DataFormatters/FormattersHelpers.h"
2121
#include "lldb/DataFormatters/StringPrinter.h"
22+
#include "lldb/DataFormatters/TypeSynthetic.h"
2223
#include "lldb/Symbol/CompilerType.h"
2324
#include "lldb/Target/ExecutionContext.h"
2425
#include "lldb/Target/Process.h"
@@ -755,6 +756,34 @@ class EnumSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
755756
size_t m_child_index;
756757
};
757758

759+
static std::string mangledTypenameForTasksTuple(size_t count) {
760+
/*
761+
Global > TypeMangling > Type > Tuple
762+
TupleElement > Type > Structure
763+
Module, text="Swift"
764+
Identifier, text="UnsafeCurrentTask"
765+
*/
766+
using namespace ::swift::Demangle;
767+
using Kind = Node::Kind;
768+
NodeFactory factory;
769+
auto [root, tuple] = swift_demangle::MakeNodeChain(
770+
{Kind::TypeMangling, Kind::Type, Kind::Tuple}, factory);
771+
772+
// Make a TupleElement subtree N times, where N is the number of subtasks.
773+
for (size_t i = 0; i < count; ++i) {
774+
auto *structure = swift_demangle::MakeNodeChain(
775+
tuple, {Kind::TupleElement, Kind::Type, Kind::Structure}, factory);
776+
if (structure) {
777+
structure->addChild(
778+
factory.createNode(Kind::Module, ::swift::STDLIB_NAME), factory);
779+
structure->addChild(
780+
factory.createNode(Kind::Identifier, "UnsafeCurrentTask"), factory);
781+
}
782+
}
783+
784+
return mangleNode(root).result();
785+
}
786+
758787
/// Synthetic provider for `Swift.Task`.
759788
///
760789
/// As seen by lldb, a `Task` instance is an opaque pointer, with neither type
@@ -937,35 +966,6 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
937966
return std::distance(children.begin(), it);
938967
}
939968

940-
private:
941-
std::string mangledTypenameForTasksTuple(size_t count) {
942-
/*
943-
Global > TypeMangling > Type > Tuple
944-
TupleElement > Type > Structure
945-
Module, text="Swift"
946-
Identifier, text="UnsafeCurrentTask"
947-
*/
948-
using namespace ::swift::Demangle;
949-
using Kind = Node::Kind;
950-
NodeFactory factory;
951-
auto [root, tuple] = swift_demangle::MakeNodeChain(
952-
{Kind::TypeMangling, Kind::Type, Kind::Tuple}, factory);
953-
954-
// Make a TupleElement subtree N times, where N is the number of subtasks.
955-
for (size_t i = 0; i < count; ++i) {
956-
auto *structure = swift_demangle::MakeNodeChain(
957-
tuple, {Kind::TupleElement, Kind::Type, Kind::Structure}, factory);
958-
if (structure) {
959-
structure->addChild(
960-
factory.createNode(Kind::Module, ::swift::STDLIB_NAME), factory);
961-
structure->addChild(
962-
factory.createNode(Kind::Identifier, "UnsafeCurrentTask"), factory);
963-
}
964-
}
965-
966-
return mangleNode(root).result();
967-
}
968-
969969
private:
970970
TypeSystemSwiftTypeRef *m_ts = nullptr;
971971
addr_t m_task_ptr = LLDB_INVALID_ADDRESS;
@@ -1320,6 +1320,177 @@ class TaskGroupSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
13201320
// Cache and storage of constructed child values.
13211321
std::vector<ValueObjectSP> m_children;
13221322
};
1323+
1324+
class ActorSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
1325+
public:
1326+
ActorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
1327+
: SyntheticChildrenFrontEnd(*valobj_sp.get()) {
1328+
bool is_64bit = false;
1329+
if (auto target_sp = m_backend.GetTargetSP())
1330+
is_64bit = target_sp->GetArchitecture().GetTriple().isArch64Bit();
1331+
1332+
std::optional<uint32_t> concurrency_version;
1333+
if (auto process_sp = m_backend.GetProcessSP())
1334+
concurrency_version =
1335+
SwiftLanguageRuntime::FindConcurrencyDebugVersion(*process_sp);
1336+
1337+
m_is_supported_target = is_64bit && concurrency_version.value_or(0) == 1;
1338+
if (!m_is_supported_target)
1339+
return;
1340+
1341+
auto target_sp = m_backend.GetTargetSP();
1342+
auto ts_or_err =
1343+
target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeSwift);
1344+
if (auto err = ts_or_err.takeError()) {
1345+
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
1346+
std::move(err),
1347+
"could not get Swift type system for Task synthetic "
1348+
"provider: {0}");
1349+
return;
1350+
}
1351+
m_ts = llvm::dyn_cast_or_null<TypeSystemSwiftTypeRef>(ts_or_err->get());
1352+
}
1353+
1354+
llvm::Expected<uint32_t> CalculateNumChildren() override {
1355+
return m_is_supported_target ? 1 : 0;
1356+
}
1357+
1358+
lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override {
1359+
if (!m_is_supported_target || idx != 0)
1360+
return {};
1361+
1362+
if (!m_unprioritised_jobs_sp) {
1363+
std::string mangled_typename =
1364+
mangledTypenameForTasksTuple(m_job_addrs.size());
1365+
CompilerType tasks_tuple_type =
1366+
m_ts->GetTypeFromMangledTypename(ConstString(mangled_typename));
1367+
DataExtractor data{m_job_addrs.data(),
1368+
m_job_addrs.size() * sizeof(addr_t),
1369+
endian::InlHostByteOrder(), sizeof(void *)};
1370+
m_unprioritised_jobs_sp = ValueObject::CreateValueObjectFromData(
1371+
"unprioritised_jobs", data, m_backend.GetExecutionContextRef(),
1372+
tasks_tuple_type);
1373+
}
1374+
return m_unprioritised_jobs_sp;
1375+
}
1376+
1377+
size_t GetIndexOfChildWithName(ConstString name) override {
1378+
if (m_is_supported_target && name == "unprioritised_jobs")
1379+
return 0;
1380+
return UINT32_MAX;
1381+
}
1382+
1383+
lldb::ChildCacheState Update() override {
1384+
if (!m_is_supported_target)
1385+
return ::eReuse;
1386+
1387+
m_job_addrs.clear();
1388+
1389+
if (!m_task_type)
1390+
if (auto target_sp = m_backend.GetTargetSP()) {
1391+
if (auto ts_or_err = target_sp->GetScratchTypeSystemForLanguage(
1392+
eLanguageTypeSwift)) {
1393+
if (auto *ts = llvm::dyn_cast_or_null<TypeSystemSwiftTypeRef>(
1394+
ts_or_err->get()))
1395+
// TypeMangling for "Swift.UnsafeCurrentTask"
1396+
m_task_type = ts->GetTypeFromMangledTypename(ConstString("$sSctD"));
1397+
} else {
1398+
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
1399+
ts_or_err.takeError(),
1400+
"could not get Swift type system for Task synthetic "
1401+
"provider: {0}");
1402+
return ChildCacheState::eReuse;
1403+
}
1404+
}
1405+
1406+
if (!m_task_type)
1407+
return ChildCacheState::eReuse;
1408+
1409+
// Get the actor's queue of unprioritized jobs (tasks) by following the
1410+
// "linked list" embedded in storage provided by SchedulerPrivate.
1411+
DefaultActorImpl actor{m_backend.GetProcessSP(),
1412+
m_backend.GetLoadAddress()};
1413+
Status status;
1414+
Job first_job = actor.getFirstJob(status);
1415+
Job current_job = first_job;
1416+
while (current_job) {
1417+
m_job_addrs.push_back(current_job.addr);
1418+
current_job = current_job.getNextScheduledJob(status);
1419+
}
1420+
1421+
if (status.Fail()) {
1422+
LLDB_LOG(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
1423+
"could not read actor's job pointers: {0}", status.AsCString());
1424+
return ChildCacheState::eReuse;
1425+
}
1426+
1427+
return ChildCacheState::eRefetch;
1428+
}
1429+
1430+
bool MightHaveChildren() override { return m_is_supported_target; }
1431+
1432+
private:
1433+
/// Lightweight wrapper around Job pointers, for the purpose of traversing to
1434+
/// the next scheduled Job.
1435+
struct Job {
1436+
ProcessSP process_sp;
1437+
addr_t addr;
1438+
1439+
operator bool() const { return addr && addr != LLDB_INVALID_ADDRESS; }
1440+
1441+
// void *SchedulerPrivate[2] is the first Job specific field, its layout
1442+
// follows the HeapObject base class (size 16).
1443+
static constexpr offset_t SchedulerPrivateOffset = 16;
1444+
static constexpr offset_t NextJobOffset = SchedulerPrivateOffset;
1445+
1446+
Job getNextScheduledJob(Status &status) {
1447+
addr_t next_job = LLDB_INVALID_ADDRESS;
1448+
if (status.Success())
1449+
next_job =
1450+
process_sp->ReadPointerFromMemory(addr + NextJobOffset, status);
1451+
return {process_sp, next_job};
1452+
}
1453+
};
1454+
1455+
/// Lightweight wrapper around DefaultActorImpl/$defaultActor, for the purpose
1456+
/// of accessing contents of ActiveActorStatus.
1457+
struct DefaultActorImpl {
1458+
ProcessSP process_sp;
1459+
addr_t addr;
1460+
1461+
// `$defaultActor`'s offset within the actor object.
1462+
//
1463+
// The $defaultActor field does not point to the start of DefaultActorImpl,
1464+
// it has as an address that points past the HeapObject layout.
1465+
static constexpr offset_t DefaultActorFieldOffset = 16;
1466+
// ActiveActorStatus's offset within DefaultActorImpl.
1467+
//
1468+
// ActiveActorStatus is declared alignas(2*sizeof(void*)). The layout of
1469+
// DefaultActorImpl puts the status record after HeapObject (size 16), and
1470+
// its first field (bool size 1), an offset of +32 from the start of the
1471+
// actor. This offset is relative to DefaultActorImpl, but this code needs
1472+
// an offset relative to the $defaultActor field, and is adjusted as such.
1473+
static constexpr offset_t ActiveActorStatusOffset =
1474+
32 - DefaultActorFieldOffset;
1475+
// FirstJob's offset within ActiveActorStatus.
1476+
static constexpr offset_t FirstJobOffset = ActiveActorStatusOffset + 8;
1477+
1478+
Job getFirstJob(Status &status) {
1479+
addr_t first_job = LLDB_INVALID_ADDRESS;
1480+
if (status.Success())
1481+
first_job =
1482+
process_sp->ReadPointerFromMemory(addr + FirstJobOffset, status);
1483+
return {process_sp, first_job};
1484+
}
1485+
};
1486+
1487+
private:
1488+
bool m_is_supported_target = false;
1489+
TypeSystemSwiftTypeRef *m_ts = nullptr;
1490+
std::vector<addr_t> m_job_addrs;
1491+
CompilerType m_task_type;
1492+
ValueObjectSP m_unprioritised_jobs_sp;
1493+
};
13231494
}
13241495
}
13251496
}
@@ -1410,6 +1581,14 @@ lldb_private::formatters::swift::TaskGroupSyntheticFrontEndCreator(
14101581
return new TaskGroupSyntheticFrontEnd(valobj_sp);
14111582
}
14121583

1584+
SyntheticChildrenFrontEnd *
1585+
lldb_private::formatters::swift::ActorSyntheticFrontEndCreator(
1586+
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
1587+
if (!valobj_sp)
1588+
return nullptr;
1589+
return new ActorSyntheticFrontEnd(valobj_sp);
1590+
}
1591+
14131592
bool lldb_private::formatters::swift::ObjC_Selector_SummaryProvider(
14141593
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
14151594
LLDB_SCOPED_TIMER();

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ CheckedContinuationSyntheticFrontEndCreator(CXXSyntheticChildren *,
136136

137137
SyntheticChildrenFrontEnd *
138138
TaskGroupSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP);
139+
140+
SyntheticChildrenFrontEnd *ActorSyntheticFrontEndCreator(CXXSyntheticChildren *,
141+
lldb::ValueObjectSP);
139142
}
140143
}
141144
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,12 @@ static void LoadSwiftFormatters(lldb::TypeCategoryImplSP swift_category_sp) {
434434
"Swift.TaskGroup synthetic children",
435435
ConstString("^Swift\\.(Throwing)?TaskGroup<.+>"), synth_flags, true);
436436

437+
AddCXXSynthetic(
438+
swift_category_sp,
439+
lldb_private::formatters::swift::ActorSyntheticFrontEndCreator,
440+
"Actor synthetic children", ConstString("Builtin.DefaultActorStorage"),
441+
synth_flags);
442+
437443
AddCXXSynthetic(
438444
swift_category_sp,
439445
lldb_private::formatters::swift::SwiftBasicTypeSyntheticFrontEndCreator,
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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
@skipUnlessFoundation
11+
def test_actor_unprioritised_jobs(self):
12+
"""Verify that an actor exposes its unprioritised jobs (queue)."""
13+
self.build()
14+
_, _, thread, _ = lldbutil.run_to_source_breakpoint(
15+
self, "break here", lldb.SBFileSpec("main.swift")
16+
)
17+
frame = thread.GetSelectedFrame()
18+
unprioritised_jobs = frame.var("a.$defaultActor.unprioritised_jobs")
19+
# There are 4 child tasks (async let), the first one occupies the actor
20+
# with a sleep, the next 3 go on to the queue.
21+
self.assertEqual(unprioritised_jobs.num_children, 3)
22+
for job in unprioritised_jobs:
23+
self.assertRegex(job.name, r"^\d+")
24+
self.assertRegex(job.summary, r"^id:\d+ flags:\S+")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Foundation
2+
3+
actor Actor {
4+
var data: Int = 15
5+
6+
func occupy() async {
7+
Thread.sleep(forTimeInterval: 100)
8+
}
9+
10+
func work() async -> Int {
11+
let result = data
12+
data += 1
13+
return result
14+
}
15+
}
16+
17+
@main struct Entry {
18+
static func main() async {
19+
let a = Actor()
20+
async let _ = a.occupy()
21+
async let _ = a.work()
22+
async let _ = a.work()
23+
async let _ = a.work()
24+
print("break here")
25+
}
26+
}

0 commit comments

Comments
 (0)