[Lldb-commits] [lldb] [llvm] [lldb] Add target groups for related targets (PR #229180)
satyanarayana reddy janga via lldb-commits
lldb-commits at lists.llvm.org
Thu Oct 15 12:53:18 PDT 2026
https://github.com/satyajanga updated https://github.com/llvm/llvm-project/pull/229180
>From 5210a4d3cf47a6c9fd66ec4704ee9f8bf14e0047 Mon Sep 17 00:00:00 2001
From: satya janga <satyajanga at fb.com>
Date: Mon, 5 Oct 2026 11:10:34 -0700
Subject: [PATCH] [lldb] Add target groups for related targets
Introduce first-class TargetGroup objects with one extensible role per target, synchronized TargetGroupList collections for the debugger registry and target memberships, and a target groups list command.
Targets retain the groups they belong to, while groups reference member targets weakly. Accelerator integration and public SB APIs can follow separately.
---
lldb/include/lldb/Core/Debugger.h | 4 +
lldb/include/lldb/Target/Target.h | 15 +
lldb/include/lldb/Target/TargetGroup.h | 86 +++++
lldb/include/lldb/Target/TargetGroupList.h | 70 +++++
lldb/include/lldb/lldb-forward.h | 3 +
lldb/source/Commands/CommandObjectTarget.cpp | 134 ++++++++
lldb/source/Core/Debugger.cpp | 5 +-
lldb/source/Target/CMakeLists.txt | 2 +
lldb/source/Target/Target.cpp | 16 +-
lldb/source/Target/TargetGroup.cpp | 120 +++++++
lldb/source/Target/TargetGroupList.cpp | 73 +++++
lldb/unittests/Target/CMakeLists.txt | 3 +
lldb/unittests/Target/TargetGroupTest.cpp | 293 ++++++++++++++++++
.../gn/secondary/lldb/source/Target/BUILD.gn | 2 +
14 files changed, 823 insertions(+), 3 deletions(-)
create mode 100644 lldb/include/lldb/Target/TargetGroup.h
create mode 100644 lldb/include/lldb/Target/TargetGroupList.h
create mode 100644 lldb/source/Target/TargetGroup.cpp
create mode 100644 lldb/source/Target/TargetGroupList.cpp
create mode 100644 lldb/unittests/Target/TargetGroupTest.cpp
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index bcc14479c3360d..ce363d67f53cdd 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -29,6 +29,7 @@
#include "lldb/Host/Terminal.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Platform.h"
+#include "lldb/Target/TargetGroupList.h"
#include "lldb/Target/TargetList.h"
#include "lldb/Utility/Broadcaster.h"
#include "lldb/Utility/ConstString.h"
@@ -220,6 +221,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
/// A global shared target list.
TargetList &GetTargetList() { return m_target_list; }
+ TargetGroupList &GetTargetGroupList() { return m_target_group_list; }
+
PlatformList &GetPlatformList() { return m_platform_list; }
void DispatchInputInterrupt();
@@ -791,6 +794,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
TerminalState m_terminal_state;
TargetList m_target_list;
+ TargetGroupList m_target_group_list;
PlatformList m_platform_list;
lldb::ListenerSP m_listener_sp;
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 6757013e43a57d..437de5b35177da 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -35,6 +35,7 @@
#include "lldb/Target/Statistics.h"
#include "lldb/Target/SyntheticFrameProvider.h"
#include "lldb/Target/TargetAPIMutex.h"
+#include "lldb/Target/TargetGroupList.h"
#include "lldb/Target/ThreadSpec.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/Broadcaster.h"
@@ -2073,7 +2074,18 @@ class Target : public std::enable_shared_from_this<Target>,
/// Print all the signals set in this target.
void PrintDummySignals(Stream &strm, Args &signals);
+ /// Return the target groups this target belongs to.
+ std::vector<lldb::TargetGroupSP> GetTargetGroups() const;
+
protected:
+ friend class TargetGroup;
+ friend class TargetGroupList;
+
+ /// Called only by TargetGroup::AddTarget to record group membership.
+ void AddTargetGroup(const lldb::TargetGroupSP &group_sp);
+ /// Called only by TargetGroup::RemoveTarget to remove group membership.
+ void RemoveTargetGroup(const TargetGroup *group);
+
/// The mutex the calling thread must serialize on for its current policy, or
/// nullptr when that policy bypasses the API mutex entirely.
std::recursive_mutex *GetAPIMutexForCurrentPolicy();
@@ -2192,6 +2204,9 @@ class Target : public std::enable_shared_from_this<Target>,
/// signals you will have.
llvm::StringMap<DummySignalValues> m_dummy_signals;
+ /// Every target group this target belongs to.
+ TargetGroupList m_target_groups;
+
lldb::RegisterTypeBuilderSP m_register_type_builder_sp;
static void ImageSearchPathsChanged(const PathMappingList &path_list,
diff --git a/lldb/include/lldb/Target/TargetGroup.h b/lldb/include/lldb/Target/TargetGroup.h
new file mode 100644
index 00000000000000..5b1ead4aee044e
--- /dev/null
+++ b/lldb/include/lldb/Target/TargetGroup.h
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_TARGETGROUP_H
+#define LLDB_TARGET_TARGETGROUP_H
+
+#include "lldb/lldb-forward.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace lldb_private {
+
+/// A group of related targets and the roles they play in that relationship.
+///
+/// Target::Destroy() automatically removes a target from all its groups;
+/// clients do not need to remove memberships during target destruction.
+class TargetGroup : public std::enable_shared_from_this<TargetGroup> {
+public:
+ /// Describes the part a target plays in this group.
+ enum class Role {
+ CPU,
+ Accelerator,
+ };
+
+ /// Target groups have identity and maintain backlinks in their member
+ /// targets, so they cannot be copied or moved. Create one with
+ /// Debugger::GetTargetGroupList().CreateTargetGroup().
+ TargetGroup(const TargetGroup &) = delete;
+ TargetGroup &operator=(const TargetGroup &) = delete;
+ TargetGroup(TargetGroup &&) = delete;
+ TargetGroup &operator=(TargetGroup &&) = delete;
+ ~TargetGroup();
+
+ llvm::StringRef GetDescription() const { return m_description; }
+
+ /// Add \p target_sp to the group with \p role. Adding an existing member
+ /// succeeds only when its role matches \p role.
+ llvm::Error AddTarget(const lldb::TargetSP &target_sp, Role role);
+
+ /// Remove \p target_sp from the group.
+ bool RemoveTarget(const lldb::TargetSP &target_sp);
+
+ /// Return the role assigned to \p target_sp, if it is a member.
+ std::optional<Role> GetRole(const lldb::TargetSP &target_sp) const;
+
+ /// Return every target in the group.
+ std::vector<lldb::TargetSP> GetTargets() const;
+
+ /// Return targets whose role is \p role.
+ std::vector<lldb::TargetSP> GetTargets(Role role) const;
+
+private:
+ friend class TargetGroupList;
+
+ struct Entry {
+ lldb::TargetWP target_wp;
+ Role role;
+ };
+
+ TargetGroup(const lldb::DebuggerSP &debugger_sp, llvm::StringRef description,
+ std::shared_ptr<std::recursive_mutex> mutex)
+ : m_debugger_wp(debugger_sp), m_description(description),
+ m_mutex(std::move(mutex)) {}
+
+ lldb::DebuggerWP m_debugger_wp;
+ std::string m_description;
+ std::shared_ptr<std::recursive_mutex> m_mutex;
+ /// Targets are weakly held so membership does not extend their lifetime.
+ std::vector<Entry> m_entries;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_TARGETGROUP_H
diff --git a/lldb/include/lldb/Target/TargetGroupList.h b/lldb/include/lldb/Target/TargetGroupList.h
new file mode 100644
index 00000000000000..6550d97d9c773d
--- /dev/null
+++ b/lldb/include/lldb/Target/TargetGroupList.h
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_TARGETGROUPLIST_H
+#define LLDB_TARGET_TARGETGROUPLIST_H
+
+#include "lldb/Target/TargetGroup.h"
+
+#include <memory>
+#include <mutex>
+#include <vector>
+
+namespace lldb_private {
+
+/// A thread-safe collection of target groups. A Debugger uses one as its
+/// registry, and each Target uses one for the groups it belongs to.
+class TargetGroupList {
+private:
+ friend class Debugger;
+ friend class Target;
+
+ explicit TargetGroupList(Debugger &debugger)
+ : m_debugger(debugger),
+ m_mutex(std::make_shared<std::recursive_mutex>()) {}
+
+ TargetGroupList(Debugger &debugger,
+ const std::shared_ptr<std::recursive_mutex> &mutex)
+ : m_debugger(debugger), m_mutex(mutex) {}
+
+public:
+ ~TargetGroupList();
+
+ TargetGroupList(const TargetGroupList &) = delete;
+ TargetGroupList &operator=(const TargetGroupList &) = delete;
+ TargetGroupList(TargetGroupList &&) = delete;
+ TargetGroupList &operator=(TargetGroupList &&) = delete;
+
+ lldb::TargetGroupSP CreateTargetGroup(llvm::StringRef description);
+
+ /// Remove \p group_sp from this list. Other shared references, including
+ /// target membership lists, remain valid.
+ bool DeleteTargetGroup(const lldb::TargetGroupSP &group_sp);
+
+ /// Return a snapshot of the target groups. Modifying the returned vector
+ /// does not modify this list.
+ std::vector<lldb::TargetGroupSP> GetGroups() const;
+
+private:
+ friend class TargetGroup;
+
+ void Add(const lldb::TargetGroupSP &group_sp);
+ void Remove(const TargetGroup *group);
+ void Clear();
+ void RemoveTarget(const lldb::TargetSP &target_sp);
+
+ Debugger &m_debugger;
+ /// Shared by the debugger's list, each target's list, and every group.
+ /// Group membership operations never acquire Target::m_mutex.
+ std::shared_ptr<std::recursive_mutex> m_mutex;
+ std::vector<lldb::TargetGroupSP> m_groups;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_TARGETGROUPLIST_H
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 649f7b704d68ea..a8ece8512a6d4d 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -249,6 +249,7 @@ class SyntheticFrameProvider;
class SystemRuntime;
class Progress;
class Target;
+class TargetGroup;
class TargetList;
class TargetProperties;
class Thread;
@@ -490,6 +491,8 @@ typedef std::shared_ptr<lldb_private::SyntheticChildrenFrontEnd>
SyntheticChildrenFrontEndSP;
typedef std::shared_ptr<lldb_private::Target> TargetSP;
typedef std::weak_ptr<lldb_private::Target> TargetWP;
+typedef std::shared_ptr<lldb_private::TargetGroup> TargetGroupSP;
+typedef std::weak_ptr<lldb_private::TargetGroup> TargetGroupWP;
typedef std::shared_ptr<lldb_private::Thread> ThreadSP;
typedef std::weak_ptr<lldb_private::Thread> ThreadWP;
typedef std::shared_ptr<lldb_private::ThreadCollection> ThreadCollectionSP;
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index afb6ac043fea5d..4fe96fcf2a6d1f 100644
--- a/lldb/source/Commands/CommandObjectTarget.cpp
+++ b/lldb/source/Commands/CommandObjectTarget.cpp
@@ -45,6 +45,8 @@
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/StackFrame.h"
+#include "lldb/Target/TargetGroup.h"
+#include "lldb/Target/TargetGroupList.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadSpec.h"
#include "lldb/Utility/Args.h"
@@ -63,9 +65,12 @@
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatAdapters.h"
+#include <numeric>
+
using namespace lldb;
using namespace lldb_private;
@@ -489,6 +494,133 @@ class CommandObjectTargetList : public CommandObjectParsed {
}
};
+#pragma mark CommandObjectTargetGroups
+
+static llvm::StringRef GetTargetGroupRoleName(TargetGroup::Role role) {
+ switch (role) {
+ case TargetGroup::Role::CPU:
+ return "CPU";
+ case TargetGroup::Role::Accelerator:
+ return "accelerator";
+ }
+ llvm_unreachable("unhandled target group role");
+}
+
+class CommandObjectTargetGroupsList : public CommandObjectParsed {
+public:
+ CommandObjectTargetGroupsList(CommandInterpreter &interpreter)
+ : CommandObjectParsed(interpreter, "target groups list",
+ "List all target groups and their members.",
+ nullptr),
+ m_target_option(LLDB_OPT_SET_1, false, "target", 't', 0,
+ eArgTypeTargetID,
+ "List only groups containing this target.", 0) {
+ m_option_group.Append(&m_target_option, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
+ m_option_group.Finalize();
+ AddSimpleArgumentList(eArgTypeIndex, eArgRepeatStar);
+ }
+
+ ~CommandObjectTargetGroupsList() override = default;
+
+ Options *GetOptions() override { return &m_option_group; }
+
+protected:
+ void DoExecute(Args &args, CommandReturnObject &result) override {
+ Debugger &debugger = GetDebugger();
+ TargetGroupList &group_list = debugger.GetTargetGroupList();
+ std::vector<TargetGroupSP> groups = group_list.GetGroups();
+
+ std::vector<size_t> group_indexes;
+ if (args.empty()) {
+ group_indexes.resize(groups.size());
+ std::iota(group_indexes.begin(), group_indexes.end(), 0);
+ } else {
+ group_indexes.reserve(args.size());
+ for (size_t i = 0; i < args.size(); ++i) {
+ uint64_t group_index;
+ llvm::StringRef argument = args.GetArgumentAtIndex(i);
+ if (!llvm::to_integer(argument, group_index)) {
+ result.AppendErrorWithFormatv("invalid group index '{0}'", argument);
+ return;
+ }
+ if (group_index >= groups.size()) {
+ result.AppendErrorWithFormatv("group index {0} is out of range",
+ group_index);
+ return;
+ }
+ group_indexes.push_back(static_cast<size_t>(group_index));
+ }
+ }
+
+ TargetSP target_filter_sp;
+ if (m_target_option.GetOptionValue().OptionWasSet()) {
+ uint64_t target_index =
+ m_target_option.GetOptionValue().GetCurrentValue();
+ if (target_index >= LLDB_INVALID_INDEX32 ||
+ !(target_filter_sp = debugger.GetTargetList().GetTargetAtIndex(
+ static_cast<uint32_t>(target_index)))) {
+ result.AppendErrorWithFormatv("target index {0} is out of range",
+ target_index);
+ return;
+ }
+ }
+
+ Stream &strm = result.GetOutputStream();
+ if (groups.empty()) {
+ strm.PutCString("No target groups.\n");
+ result.SetStatus(eReturnStatusSuccessFinishResult);
+ return;
+ }
+
+ TargetList &target_list = debugger.GetTargetList();
+ bool listed_group = false;
+ for (size_t group_index : group_indexes) {
+ const TargetGroupSP &group_sp = groups[group_index];
+ if (target_filter_sp && !group_sp->GetRole(target_filter_sp))
+ continue;
+
+ listed_group = true;
+ strm.Format("group #{0}", group_index);
+ if (!group_sp->GetDescription().empty())
+ strm.Format(": {0}", group_sp->GetDescription());
+ strm.EOL();
+
+ for (const TargetSP &target_sp : group_sp->GetTargets()) {
+ std::optional<TargetGroup::Role> role = group_sp->GetRole(target_sp);
+ if (!role)
+ continue;
+ uint32_t target_index = target_list.GetIndexOfTarget(target_sp);
+ if (target_index == LLDB_INVALID_INDEX32)
+ strm.Format(" unlisted target: {0}\n",
+ GetTargetGroupRoleName(*role));
+ else
+ strm.Format(" target #{0}: {1}\n", target_index,
+ GetTargetGroupRoleName(*role));
+ }
+ }
+ if (!listed_group)
+ strm.PutCString("No matching target groups.\n");
+ result.SetStatus(eReturnStatusSuccessFinishResult);
+ }
+
+private:
+ OptionGroupOptions m_option_group;
+ OptionGroupUInt64 m_target_option;
+};
+
+class CommandObjectTargetGroups : public CommandObjectMultiword {
+public:
+ CommandObjectTargetGroups(CommandInterpreter &interpreter)
+ : CommandObjectMultiword(interpreter, "target groups",
+ "Commands for viewing target groups.",
+ "target groups <subcommand>") {
+ LoadSubCommand("list", CommandObjectSP(
+ new CommandObjectTargetGroupsList(interpreter)));
+ }
+
+ ~CommandObjectTargetGroups() override = default;
+};
+
#pragma mark CommandObjectTargetSelect
class CommandObjectTargetSelect : public CommandObjectParsed {
@@ -6269,6 +6401,8 @@ CommandObjectMultiwordTarget::CommandObjectMultiwordTarget(
LoadSubCommand(
"frame-provider",
CommandObjectSP(new CommandObjectTargetFrameProvider(interpreter)));
+ LoadSubCommand("groups",
+ CommandObjectSP(new CommandObjectTargetGroups(interpreter)));
LoadSubCommand("list",
CommandObjectSP(new CommandObjectTargetList(interpreter)));
LoadSubCommand("select",
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index db55a0d416b279..064f70c1a6f60c 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -1078,8 +1078,8 @@ Debugger::Debugger(lldb::LogOutputCallback log_callback, void *baton)
m_error_stream_sp(std::make_shared<LockableStreamFile>(
stderr, NativeFile::Unowned, m_output_mutex)),
m_broadcaster_manager_sp(BroadcasterManager::MakeBroadcasterManager()),
- m_terminal_state(), m_target_list(*this), m_platform_list(),
- m_listener_sp(Listener::MakeListener("lldb.Debugger")),
+ m_terminal_state(), m_target_list(*this), m_target_group_list(*this),
+ m_platform_list(), m_listener_sp(Listener::MakeListener("lldb.Debugger")),
m_source_manager_up(), m_source_file_cache(),
m_command_interpreter_up(
std::make_unique<CommandInterpreter>(*this, false)),
@@ -1201,6 +1201,7 @@ void Debugger::Clear() {
target_sp->Destroy();
}
}
+ m_target_group_list.Clear();
m_broadcaster_manager_sp->Clear();
// Close the input file _before_ we close the input read communications
diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt
index 0485175d65b9ca..23d3bb095c802c 100644
--- a/lldb/source/Target/CMakeLists.txt
+++ b/lldb/source/Target/CMakeLists.txt
@@ -57,6 +57,8 @@ add_lldb_library(lldbTarget
SystemRuntime.cpp
Target.cpp
TargetAPIMutex.cpp
+ TargetGroup.cpp
+ TargetGroupList.cpp
TargetList.cpp
Thread.cpp
ThreadCollection.cpp
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 1b4e2a207a4767..db53649a145c00 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -196,7 +196,8 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch,
m_target_session_name(
llvm::formatv("Session {0}", m_target_unique_id).str()),
m_frame_recognizer_manager_up(
- std::make_unique<StackFrameRecognizerManager>()) {
+ std::make_unique<StackFrameRecognizerManager>()),
+ m_target_groups(debugger, debugger.GetTargetGroupList().m_mutex) {
SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed");
SetEventName(eBroadcastBitModulesLoaded, "modules-loaded");
SetEventName(eBroadcastBitModulesUnloaded, "modules-unloaded");
@@ -387,6 +388,7 @@ void Target::SetREPL(lldb::LanguageType language, lldb::REPLSP repl_sp) {
}
void Target::Destroy() {
+ m_target_groups.RemoveTarget(shared_from_this());
std::lock_guard<std::recursive_mutex> guard(m_mutex);
m_valid = false;
DeleteCurrentProcess();
@@ -411,6 +413,18 @@ void Target::Destroy() {
ClearDummySignals(signal_args);
}
+std::vector<TargetGroupSP> Target::GetTargetGroups() const {
+ return m_target_groups.GetGroups();
+}
+
+void Target::AddTargetGroup(const TargetGroupSP &group_sp) {
+ m_target_groups.Add(group_sp);
+}
+
+void Target::RemoveTargetGroup(const TargetGroup *group) {
+ m_target_groups.Remove(group);
+}
+
llvm::StringRef Target::GetABIName() const {
lldb::ABISP abi_sp;
if (m_process_sp)
diff --git a/lldb/source/Target/TargetGroup.cpp b/lldb/source/Target/TargetGroup.cpp
new file mode 100644
index 00000000000000..e9f064121daff6
--- /dev/null
+++ b/lldb/source/Target/TargetGroup.cpp
@@ -0,0 +1,120 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/TargetGroup.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Target/Target.h"
+
+#include <algorithm>
+#include <cassert>
+
+using namespace lldb;
+using namespace lldb_private;
+
+TargetGroup::~TargetGroup() {
+ assert(m_entries.empty() &&
+ "target group destroyed while it still contains targets");
+}
+
+llvm::Error TargetGroup::AddTarget(const TargetSP &target_sp, Role role) {
+ if (!target_sp)
+ return llvm::createStringError(
+ "cannot add a null target to a target group");
+ if (!target_sp->IsValid())
+ return llvm::createStringError(
+ "cannot add an invalid target to a target group");
+
+ DebuggerSP debugger_sp = m_debugger_wp.lock();
+ if (!debugger_sp)
+ return llvm::createStringError("target group's debugger no longer exists");
+ if (&target_sp->GetDebugger() != debugger_sp.get())
+ return llvm::createStringError("target belongs to a different debugger");
+
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ for (const Entry &entry : m_entries) {
+ TargetSP member_sp = entry.target_wp.lock();
+ assert(member_sp && "target did not remove itself from its target group");
+ if (!member_sp)
+ continue;
+ if (member_sp != target_sp)
+ continue;
+ if (entry.role == role)
+ return llvm::Error::success();
+ return llvm::createStringError(
+ "target is already in the group with a different role");
+ }
+
+ m_entries.push_back({target_sp, role});
+ target_sp->AddTargetGroup(shared_from_this());
+ return llvm::Error::success();
+}
+
+bool TargetGroup::RemoveTarget(const TargetSP &target_sp) {
+ if (!target_sp)
+ return false;
+
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ bool removed = false;
+ auto it = std::remove_if(m_entries.begin(), m_entries.end(),
+ [&](const Entry &entry) {
+ TargetSP member_sp = entry.target_wp.lock();
+ assert(member_sp &&
+ "target did not remove itself from its "
+ "target group");
+ if (member_sp == target_sp) {
+ removed = true;
+ return true;
+ }
+ return false;
+ });
+ m_entries.erase(it, m_entries.end());
+ if (removed)
+ target_sp->RemoveTargetGroup(this);
+ return removed;
+}
+
+std::optional<TargetGroup::Role>
+TargetGroup::GetRole(const TargetSP &target_sp) const {
+ if (!target_sp)
+ return std::nullopt;
+
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ for (const Entry &entry : m_entries) {
+ TargetSP member_sp = entry.target_wp.lock();
+ assert(member_sp && "target did not remove itself from its target group");
+ if (member_sp == target_sp)
+ return entry.role;
+ }
+ return std::nullopt;
+}
+
+std::vector<TargetSP> TargetGroup::GetTargets() const {
+ std::vector<TargetSP> targets;
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ for (const Entry &entry : m_entries) {
+ TargetSP target_sp = entry.target_wp.lock();
+ assert(target_sp && "target did not remove itself from its target group");
+ if (target_sp)
+ targets.push_back(std::move(target_sp));
+ }
+ return targets;
+}
+
+std::vector<TargetSP> TargetGroup::GetTargets(Role role) const {
+ std::vector<TargetSP> targets;
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ for (const Entry &entry : m_entries) {
+ if (entry.role != role)
+ continue;
+ TargetSP target_sp = entry.target_wp.lock();
+ assert(target_sp && "target did not remove itself from its target group");
+ if (target_sp)
+ targets.push_back(std::move(target_sp));
+ }
+ return targets;
+}
diff --git a/lldb/source/Target/TargetGroupList.cpp b/lldb/source/Target/TargetGroupList.cpp
new file mode 100644
index 00000000000000..474d3285e55a57
--- /dev/null
+++ b/lldb/source/Target/TargetGroupList.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/TargetGroupList.h"
+#include "lldb/Core/Debugger.h"
+#include "llvm/ADT/STLExtras.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+TargetGroupList::~TargetGroupList() { Clear(); }
+
+TargetGroupSP TargetGroupList::CreateTargetGroup(llvm::StringRef description) {
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ TargetGroupSP group_sp(
+ new TargetGroup(m_debugger.shared_from_this(), description, m_mutex));
+ m_groups.push_back(group_sp);
+ return group_sp;
+}
+
+bool TargetGroupList::DeleteTargetGroup(const TargetGroupSP &group_sp) {
+ if (!group_sp)
+ return false;
+
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ auto it = llvm::find(m_groups, group_sp);
+ if (it == m_groups.end())
+ return false;
+ m_groups.erase(it);
+ return true;
+}
+
+std::vector<TargetGroupSP> TargetGroupList::GetGroups() const {
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ return m_groups;
+}
+
+void TargetGroupList::Add(const TargetGroupSP &group_sp) {
+ if (!group_sp)
+ return;
+
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ if (!llvm::is_contained(m_groups, group_sp))
+ m_groups.push_back(group_sp);
+}
+
+void TargetGroupList::Remove(const TargetGroup *group) {
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ llvm::erase_if(m_groups, [group](const TargetGroupSP &group_sp) {
+ return group_sp.get() == group;
+ });
+}
+
+void TargetGroupList::RemoveTarget(const TargetSP &target_sp) {
+ if (!target_sp)
+ return;
+
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ std::vector<TargetGroupSP> groups = m_groups;
+ for (const TargetGroupSP &group_sp : groups)
+ group_sp->RemoveTarget(target_sp);
+ m_groups.clear();
+}
+
+void TargetGroupList::Clear() {
+ std::lock_guard<std::recursive_mutex> guard(*m_mutex);
+ m_groups.clear();
+}
diff --git a/lldb/unittests/Target/CMakeLists.txt b/lldb/unittests/Target/CMakeLists.txt
index 7cf36f12cdede4..36fc582e41e1a2 100644
--- a/lldb/unittests/Target/CMakeLists.txt
+++ b/lldb/unittests/Target/CMakeLists.txt
@@ -19,14 +19,17 @@ add_lldb_unittest(TargetTests
StackFrameRecognizerTest.cpp
SummaryStatisticsTest.cpp
TargetAPIMutexTest.cpp
+ TargetGroupTest.cpp
FindFileTest.cpp
LINK_COMPONENTS
Option
Support
LINK_LIBS
+ lldbCommands
lldbCore
lldbHost
+ lldbInterpreter
lldbPluginObjectFileBreakpad
lldbPluginObjectFileELF
lldbPluginObjectFileMachO
diff --git a/lldb/unittests/Target/TargetGroupTest.cpp b/lldb/unittests/Target/TargetGroupTest.cpp
new file mode 100644
index 00000000000000..ca307b3812f4d9
--- /dev/null
+++ b/lldb/unittests/Target/TargetGroupTest.cpp
@@ -0,0 +1,293 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/TargetGroup.h"
+#include "Plugins/Platform/Linux/PlatformLinux.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/HostInfo.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Target/Platform.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Target/TargetGroupList.h"
+#include "lldb/Target/TargetList.h"
+#include "lldb/Utility/ArchSpec.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+namespace {
+class TargetGroupTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ FileSystem::Initialize();
+ HostInfo::Initialize();
+ platform_linux::PlatformLinux::Initialize();
+ ArchSpec arch("x86_64-pc-linux");
+ PlatformSP platform_sp =
+ platform_linux::PlatformLinux::CreateInstance(true, &arch);
+ ASSERT_TRUE(platform_sp);
+ Platform::SetHostPlatform(platform_sp);
+ }
+
+ void TearDown() override {
+ platform_linux::PlatformLinux::Terminate();
+ HostInfo::Terminate();
+ FileSystem::Terminate();
+ }
+
+ TargetSP CreateTarget(Debugger &debugger) {
+ ArchSpec arch("x86_64-pc-linux");
+ TargetSP target_sp;
+ PlatformSP platform_sp;
+ Status error = debugger.GetTargetList().CreateTarget(
+ debugger, "", arch, eLoadDependentsNo, platform_sp, target_sp);
+ EXPECT_TRUE(error.Success());
+ return target_sp;
+ }
+};
+} // namespace
+
+TEST_F(TargetGroupTest, StoresTargetRoles) {
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP cpu_target_sp = CreateTarget(*debugger_sp);
+ TargetSP accelerator_target_sp = CreateTarget(*debugger_sp);
+ ASSERT_TRUE(cpu_target_sp);
+ ASSERT_TRUE(accelerator_target_sp);
+
+ TargetGroupList &groups = debugger_sp->GetTargetGroupList();
+ TargetGroupSP group_sp =
+ groups.CreateTargetGroup("CPU and accelerator targets");
+ ASSERT_TRUE(group_sp);
+ EXPECT_EQ("CPU and accelerator targets", group_sp->GetDescription());
+
+ EXPECT_THAT_ERROR(group_sp->AddTarget(cpu_target_sp, TargetGroup::Role::CPU),
+ llvm::Succeeded());
+ EXPECT_THAT_ERROR(group_sp->AddTarget(accelerator_target_sp,
+ TargetGroup::Role::Accelerator),
+ llvm::Succeeded());
+
+ EXPECT_EQ(std::optional(TargetGroup::Role::CPU),
+ group_sp->GetRole(cpu_target_sp));
+ EXPECT_EQ(std::optional(TargetGroup::Role::Accelerator),
+ group_sp->GetRole(accelerator_target_sp));
+ EXPECT_EQ(std::vector<TargetSP>{cpu_target_sp},
+ group_sp->GetTargets(TargetGroup::Role::CPU));
+ EXPECT_EQ(std::vector<TargetSP>{accelerator_target_sp},
+ group_sp->GetTargets(TargetGroup::Role::Accelerator));
+ EXPECT_EQ(2U, group_sp->GetTargets().size());
+
+ EXPECT_THAT_ERROR(group_sp->AddTarget(cpu_target_sp, TargetGroup::Role::CPU),
+ llvm::Succeeded());
+ EXPECT_THAT_ERROR(
+ group_sp->AddTarget(cpu_target_sp, TargetGroup::Role::Accelerator),
+ llvm::FailedWithMessage(
+ "target is already in the group with a different role"));
+ EXPECT_EQ(std::optional(TargetGroup::Role::CPU),
+ group_sp->GetRole(cpu_target_sp));
+ EXPECT_EQ(1U, group_sp->GetTargets(TargetGroup::Role::CPU).size());
+ EXPECT_EQ(1U, group_sp->GetTargets(TargetGroup::Role::Accelerator).size());
+
+ ASSERT_EQ(1U, cpu_target_sp->GetTargetGroups().size());
+ EXPECT_EQ(group_sp, cpu_target_sp->GetTargetGroups().front());
+ EXPECT_EQ(std::vector<TargetGroupSP>{group_sp}, groups.GetGroups());
+}
+
+TEST_F(TargetGroupTest, DeleteGroupReleasesDebuggerOwnership) {
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP target_sp = CreateTarget(*debugger_sp);
+ ASSERT_TRUE(target_sp);
+ TargetGroupList &groups = debugger_sp->GetTargetGroupList();
+ TargetGroupSP group_sp = groups.CreateTargetGroup("related targets");
+ EXPECT_THAT_ERROR(group_sp->AddTarget(target_sp, TargetGroup::Role::CPU),
+ llvm::Succeeded());
+
+ EXPECT_TRUE(groups.DeleteTargetGroup(group_sp));
+ EXPECT_TRUE(groups.GetGroups().empty());
+ ASSERT_EQ(1U, target_sp->GetTargetGroups().size());
+ EXPECT_EQ(group_sp, target_sp->GetTargetGroups().front());
+
+ TargetGroupWP group_wp = group_sp;
+ group_sp.reset();
+ EXPECT_FALSE(group_wp.expired());
+ ASSERT_EQ(1U, target_sp->GetTargetGroups().size());
+ group_sp = target_sp->GetTargetGroups().front();
+ EXPECT_TRUE(group_sp->RemoveTarget(target_sp));
+ group_sp.reset();
+ EXPECT_TRUE(group_wp.expired());
+ EXPECT_TRUE(target_sp->GetTargetGroups().empty());
+}
+
+TEST_F(TargetGroupTest, TargetDestructionRemovesEveryMembership) {
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP target_sp = CreateTarget(*debugger_sp);
+ TargetSP survivor_sp = CreateTarget(*debugger_sp);
+ ASSERT_TRUE(target_sp);
+ ASSERT_TRUE(survivor_sp);
+ TargetGroupList &groups = debugger_sp->GetTargetGroupList();
+ TargetGroupSP first_group_sp = groups.CreateTargetGroup("first group");
+ TargetGroupSP second_group_sp = groups.CreateTargetGroup("second group");
+ EXPECT_THAT_ERROR(
+ first_group_sp->AddTarget(target_sp, TargetGroup::Role::CPU),
+ llvm::Succeeded());
+ EXPECT_THAT_ERROR(
+ second_group_sp->AddTarget(target_sp, TargetGroup::Role::Accelerator),
+ llvm::Succeeded());
+ EXPECT_THAT_ERROR(
+ second_group_sp->AddTarget(survivor_sp, TargetGroup::Role::CPU),
+ llvm::Succeeded());
+
+ target_sp->Destroy();
+
+ EXPECT_TRUE(target_sp->GetTargetGroups().empty());
+ EXPECT_EQ(std::nullopt, first_group_sp->GetRole(target_sp));
+ EXPECT_EQ(std::nullopt, second_group_sp->GetRole(target_sp));
+ EXPECT_EQ(std::optional(TargetGroup::Role::CPU),
+ second_group_sp->GetRole(survivor_sp));
+}
+
+TEST_F(TargetGroupTest, RejectsTargetFromAnotherDebugger) {
+ DebuggerSP group_debugger_sp = Debugger::CreateInstance();
+ DebuggerSP target_debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(group_debugger_sp);
+ ASSERT_TRUE(target_debugger_sp);
+
+ TargetSP target_sp = CreateTarget(*target_debugger_sp);
+ ASSERT_TRUE(target_sp);
+ TargetGroupSP group_sp =
+ group_debugger_sp->GetTargetGroupList().CreateTargetGroup(
+ "related targets");
+ ASSERT_TRUE(group_sp);
+
+ EXPECT_THAT_ERROR(
+ group_sp->AddTarget(target_sp, TargetGroup::Role::CPU),
+ llvm::FailedWithMessage("target belongs to a different debugger"));
+ EXPECT_TRUE(group_sp->GetTargets().empty());
+ EXPECT_TRUE(target_sp->GetTargetGroups().empty());
+}
+
+TEST_F(TargetGroupTest, RejectsInvalidTarget) {
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP target_sp = CreateTarget(*debugger_sp);
+ ASSERT_TRUE(target_sp);
+ TargetGroupSP group_sp =
+ debugger_sp->GetTargetGroupList().CreateTargetGroup("related targets");
+ ASSERT_TRUE(group_sp);
+
+ target_sp->Destroy();
+ EXPECT_THAT_ERROR(group_sp->AddTarget(target_sp, TargetGroup::Role::CPU),
+ llvm::FailedWithMessage(
+ "cannot add an invalid target to a target group"));
+ EXPECT_TRUE(group_sp->GetTargets().empty());
+ EXPECT_TRUE(target_sp->GetTargetGroups().empty());
+}
+
+TEST_F(TargetGroupTest, ListsNoTargetGroups) {
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ CommandReturnObject result(/*colors=*/false);
+ EXPECT_TRUE(debugger_sp->GetCommandInterpreter().HandleCommand(
+ "target groups list", eLazyBoolNo, result));
+ EXPECT_TRUE(result.Succeeded());
+ EXPECT_TRUE(result.GetErrorString().empty());
+ EXPECT_EQ("No target groups.\n", result.GetOutputString());
+}
+
+TEST_F(TargetGroupTest, ListsTargetGroupsAndMembers) {
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP cpu_target_sp = CreateTarget(*debugger_sp);
+ TargetSP accelerator_target_sp = CreateTarget(*debugger_sp);
+ ASSERT_TRUE(cpu_target_sp);
+ ASSERT_TRUE(accelerator_target_sp);
+
+ TargetGroupSP group_sp = debugger_sp->GetTargetGroupList().CreateTargetGroup(
+ "CPU and accelerator targets");
+ ASSERT_TRUE(group_sp);
+ EXPECT_THAT_ERROR(group_sp->AddTarget(cpu_target_sp, TargetGroup::Role::CPU),
+ llvm::Succeeded());
+ EXPECT_THAT_ERROR(group_sp->AddTarget(accelerator_target_sp,
+ TargetGroup::Role::Accelerator),
+ llvm::Succeeded());
+
+ CommandReturnObject result(/*colors=*/false);
+ EXPECT_TRUE(debugger_sp->GetCommandInterpreter().HandleCommand(
+ "target groups list", eLazyBoolNo, result));
+ EXPECT_TRUE(result.Succeeded());
+ EXPECT_TRUE(result.GetErrorString().empty());
+ EXPECT_EQ("group #0: CPU and accelerator targets\n"
+ " target #0: CPU\n"
+ " target #1: accelerator\n",
+ result.GetOutputString());
+}
+
+TEST_F(TargetGroupTest, FiltersTargetGroups) {
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP first_target_sp = CreateTarget(*debugger_sp);
+ TargetSP second_target_sp = CreateTarget(*debugger_sp);
+ ASSERT_TRUE(first_target_sp);
+ ASSERT_TRUE(second_target_sp);
+
+ TargetGroupList &groups = debugger_sp->GetTargetGroupList();
+ TargetGroupSP first_group_sp = groups.CreateTargetGroup("first group");
+ TargetGroupSP second_group_sp = groups.CreateTargetGroup("second group");
+ ASSERT_TRUE(first_group_sp);
+ ASSERT_TRUE(second_group_sp);
+ EXPECT_THAT_ERROR(
+ first_group_sp->AddTarget(first_target_sp, TargetGroup::Role::CPU),
+ llvm::Succeeded());
+ EXPECT_THAT_ERROR(second_group_sp->AddTarget(second_target_sp,
+ TargetGroup::Role::Accelerator),
+ llvm::Succeeded());
+
+ {
+ CommandReturnObject result(/*colors=*/false);
+ EXPECT_TRUE(debugger_sp->GetCommandInterpreter().HandleCommand(
+ "target groups list 1", eLazyBoolNo, result));
+ EXPECT_TRUE(result.Succeeded());
+ EXPECT_TRUE(result.GetErrorString().empty());
+ EXPECT_EQ("group #1: second group\n"
+ " target #1: accelerator\n",
+ result.GetOutputString());
+ }
+
+ {
+ CommandReturnObject result(/*colors=*/false);
+ EXPECT_TRUE(debugger_sp->GetCommandInterpreter().HandleCommand(
+ "target groups list --target 0", eLazyBoolNo, result));
+ EXPECT_TRUE(result.Succeeded());
+ EXPECT_TRUE(result.GetErrorString().empty());
+ EXPECT_EQ("group #0: first group\n"
+ " target #0: CPU\n",
+ result.GetOutputString());
+ }
+
+ {
+ CommandReturnObject result(/*colors=*/false);
+ EXPECT_TRUE(debugger_sp->GetCommandInterpreter().HandleCommand(
+ "target groups list 1 --target 0", eLazyBoolNo, result));
+ EXPECT_TRUE(result.Succeeded());
+ EXPECT_TRUE(result.GetErrorString().empty());
+ EXPECT_EQ("No matching target groups.\n", result.GetOutputString());
+ }
+}
diff --git a/llvm/utils/gn/secondary/lldb/source/Target/BUILD.gn b/llvm/utils/gn/secondary/lldb/source/Target/BUILD.gn
index 8704ab7d0fbb13..c61fccaf44dcad 100644
--- a/llvm/utils/gn/secondary/lldb/source/Target/BUILD.gn
+++ b/llvm/utils/gn/secondary/lldb/source/Target/BUILD.gn
@@ -78,6 +78,8 @@ static_library("Target") {
"SystemRuntime.cpp",
"Target.cpp",
"TargetAPIMutex.cpp",
+ "TargetGroup.cpp",
+ "TargetGroupList.cpp",
"TargetList.cpp",
"Thread.cpp",
"ThreadCollection.cpp",
More information about the lldb-commits
mailing list