This patch introduces a publish/subscribe mechanism, allowing for
loosely-coupled senders and receivers, with strongly-typed messages
passing between them.  For example, a GCC subsystem could publish
messages about events, and a plugin could subscribe to them.

An example can be seen in the selftests.

gcc/ChangeLog:
        * Makefile.in (OBJS-libcommon): Add pub-sub.o.
        * pub-sub.cc: New file.
        * pub-sub.h: New file.
        * selftest-run-tests.cc (selftest::run_tests): Call
        selftest::pub_sub_cc_tests.
        * selftest.h (selftest::pub_sub_cc_tests): New decl.
---
 gcc/Makefile.in           |   1 +
 gcc/pub-sub.cc            | 138 ++++++++++++++++++++++++++++++++++++++
 gcc/pub-sub.h             |  68 +++++++++++++++++++
 gcc/selftest-run-tests.cc |   1 +
 gcc/selftest.h            |   1 +
 5 files changed, 209 insertions(+)
 create mode 100644 gcc/pub-sub.cc
 create mode 100644 gcc/pub-sub.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 5c24a9aab00a5..f09915780192c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1892,6 +1892,7 @@ OBJS-libcommon = \
        graphviz.o pex.o \
        pretty-print.o intl.o \
        json.o json-parsing.o \
+       pub-sub.o \
        xml.o \
        sbitmap.o \
        vec.o input.o hash-table.o ggc-none.o memory-block.o \
diff --git a/gcc/pub-sub.cc b/gcc/pub-sub.cc
new file mode 100644
index 0000000000000..192cfa59fd3d6
--- /dev/null
+++ b/gcc/pub-sub.cc
@@ -0,0 +1,138 @@
+/* Loosely-coupled notifications via the Publish-Subscribe pattern.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   Contributed by David Malcolm <[email protected]>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#define INCLUDE_LIST
+#define INCLUDE_STRING
+#include "config.h"
+
+#include "system.h"
+#include "coretypes.h"
+
+#include "pub-sub.h"
+
+
+#if CHECKING_P
+
+#include "selftest.h"
+
+namespace selftest {
+
+/* Selftests.  */
+
+// A topic for use in selftests
+
+namespace snafu {
+
+struct paper_jam {};
+
+struct out_of_paper
+{
+  int tray;
+};
+
+struct ink_low
+{
+  std::string color;
+};
+
+struct subscriber
+{
+  virtual void on_message (const paper_jam &m) = 0;
+  virtual void on_message (const out_of_paper &m) = 0;
+  virtual void on_message (const ink_low &m) = 0;
+};
+
+} // namespace snafu
+
+static void
+test_example ()
+{
+  struct logger : public snafu::subscriber
+  {
+    void on_message (const snafu::paper_jam &) final override
+    {
+      m_log += "paper jam\n";
+    }
+    void on_message (const snafu::out_of_paper &m) final override
+    {
+      m_log += "out of paper (tray " + std::to_string (m.tray) + ")\n";
+    }
+    void on_message (const snafu::ink_low &m) final override
+    {
+      m_log += "ink low: " + m.color + "\n";
+    }
+
+    std::string m_log;
+  };
+
+  pub_sub::channel<snafu::subscriber> printer_a;
+  pub_sub::channel<snafu::subscriber> printer_b;
+  pub_sub::channel<snafu::subscriber> printer_c;
+
+  // No subscribers yet
+  ASSERT_EQ (printer_a.get_if_active (), nullptr);
+  ASSERT_EQ (printer_b.get_if_active (), nullptr);
+  ASSERT_EQ (printer_c.get_if_active (), nullptr);
+
+  // Subscribers to individual channels
+  logger log_a;
+  logger log_b;
+  logger log_c;
+  printer_a.add_subscriber (log_a);
+  printer_b.add_subscriber (log_b);
+  printer_c.add_subscriber (log_c);
+
+  // A subscriber to all channels
+  logger log_all;
+  printer_a.add_subscriber (log_all);
+  printer_b.add_subscriber (log_all);
+  printer_c.add_subscriber (log_all);
+
+  // The channels now have subscribers
+  ASSERT_EQ (printer_a.get_if_active (), &printer_a);
+  ASSERT_EQ (printer_b.get_if_active (), &printer_b);
+  ASSERT_EQ (printer_c.get_if_active (), &printer_c);
+
+  // Publish a message to each channel
+  printer_a.publish (snafu::paper_jam {});
+  printer_b.publish (snafu::out_of_paper {1});
+  printer_c.publish (snafu::ink_low {"cyan"});
+
+  // Verify that the subscribers got the messages they were meant to
+  ASSERT_EQ (log_a.m_log, "paper jam\n");
+  ASSERT_EQ (log_b.m_log, "out of paper (tray 1)\n");
+  ASSERT_EQ (log_c.m_log, "ink low: cyan\n");
+  ASSERT_EQ (log_all.m_log,
+            "paper jam\n"
+            "out of paper (tray 1)\n"
+            "ink low: cyan\n");
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+pub_sub_cc_tests ()
+{
+  test_example ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/pub-sub.h b/gcc/pub-sub.h
new file mode 100644
index 0000000000000..30837c4c85216
--- /dev/null
+++ b/gcc/pub-sub.h
@@ -0,0 +1,68 @@
+/* Loosely-coupled notifications via the Publish-Subscribe pattern.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   Contributed by David Malcolm <[email protected]>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_PUB_SUB_H
+#define GCC_PUB_SUB_H
+
+namespace pub_sub {
+
+template <typename Subscriber>
+class channel
+{
+public:
+  using subscriber = Subscriber;
+
+  // A node within the std::list
+  using subscription = typename std::list<subscriber *>::iterator;
+
+  /* Return this if this channel has subscribers, or nullptr if
+     there are none.  */
+  const channel *
+  get_if_active () const
+  {
+    if (m_subscribers.empty ())
+      return nullptr;
+    return this;
+  }
+
+  template <typename Message>
+  void publish (const Message &m) const
+  {
+    for (auto sub : m_subscribers)
+      sub->on_message (m);
+  }
+
+  subscription
+  add_subscriber (subscriber &s)
+  {
+    return m_subscribers.insert (m_subscribers.end (), &s);
+  }
+  void unsubscribe (subscription s)
+  {
+    m_subscribers.remove (s);
+  }
+
+private:
+  std::list<subscriber *> m_subscribers;
+};
+
+} // namespace pub_sub
+
+#endif /* GCC_PUB_SUB_H */
diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc
index c68d3e79a3109..d0e210492f414 100644
--- a/gcc/selftest-run-tests.cc
+++ b/gcc/selftest-run-tests.cc
@@ -83,6 +83,7 @@ selftest::run_tests ()
   splay_tree_cc_tests ();
   xml_cc_tests ();
   graphviz_cc_tests ();
+  pub_sub_cc_tests ();
 
   /* Mid-level data structures.  */
   input_cc_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 4501d34181c7c..dc0b080de210e 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -246,6 +246,7 @@ extern void ordered_hash_map_tests_cc_tests ();
 extern void path_coverage_cc_tests ();
 extern void predict_cc_tests ();
 extern void pretty_print_cc_tests ();
+extern void pub_sub_cc_tests ();
 extern void range_op_tests ();
 extern void range_tests ();
 extern void read_rtl_function_cc_tests ();
-- 
2.26.3

Reply via email to