[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
Wed Oct 14 18:50:28 PDT 2026


https://github.com/satyajanga updated https://github.com/llvm/llvm-project/pull/229180

>From d03f10c93242bbed5b92ae6288ff9870de7157cb 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                 |  18 +-
 lldb/source/Target/TargetGroup.cpp            | 120 +++++++
 lldb/source/Target/TargetGroupList.cpp        |  74 +++++
 lldb/unittests/Target/CMakeLists.txt          |   3 +
 lldb/unittests/Target/TargetGroupTest.cpp     | 293 ++++++++++++++++++
 .../gn/secondary/lldb/source/Target/BUILD.gn  |   2 +
 14 files changed, 826 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..c06b81c5c56b87
--- /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.
+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.
+  ///
+  /// Target::Destroy() automatically removes a target from all its groups;
+  /// clients do not need to remove memberships during target destruction.
+  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..fa44e444fbfd11
--- /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 synchronized 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..f2bf2039a7ba69 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -58,6 +58,8 @@
 #include "lldb/Target/StackFrame.h"
 #include "lldb/Target/StackFrameRecognizer.h"
 #include "lldb/Target/SystemRuntime.h"
+#include "lldb/Target/TargetGroup.h"
+#include "lldb/Target/TargetGroupList.h"
 #include "lldb/Target/Thread.h"
 #include "lldb/Target/ThreadSpec.h"
 #include "lldb/Target/UnixSignals.h"
@@ -196,7 +198,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 +390,7 @@ void Target::SetREPL(lldb::LanguageType language, lldb::REPLSP repl_sp) {
 }
 
 void Target::Destroy() {
+  m_debugger.GetTargetGroupList().RemoveTarget(shared_from_this());
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
   m_valid = false;
   DeleteCurrentProcess();
@@ -411,6 +415,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..c6971e4a00af04
--- /dev/null
+++ b/lldb/source/Target/TargetGroupList.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "lldb/Target/Target.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 = target_sp->m_target_groups.GetGroups();
+  for (const TargetGroupSP &group_sp : groups)
+    group_sp->RemoveTarget(target_sp);
+  target_sp->m_target_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