From a1c422b4d20816dcc0b9dce28a2712cf84c641f7 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 2 Oct 2025 13:47:58 -0400
Subject: [PATCH 29/36] FIXME: add AST topic/channel

---
 gcc/Makefile.in                               |   1 +
 gcc/channels.h                                |   2 +
 gcc/doc/invoke.texi                           |   8 ++
 gcc/opts-diagnostic.cc                        |   9 ++
 gcc/opts-diagnostic.h                         |   3 +
 gcc/testsuite/gcc.dg/plugin/ast-1.c           |  12 ++
 .../gcc.dg/plugin/ast_dump_plugin.cc          |  77 +++++++++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp        |   2 +
 gcc/topics/ast-events-recorder.h              |  79 +++++++++++
 gcc/topics/ast-events.h                       |  55 ++++++++
 gcc/tree-diagnostic-ast.cc                    | 126 ++++++++++++++++++
 gcc/tree-diagnostic-sink-extensions.h         |   3 +
 12 files changed, 377 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/plugin/ast-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/ast_dump_plugin.cc
 create mode 100644 gcc/topics/ast-events-recorder.h
 create mode 100644 gcc/topics/ast-events.h
 create mode 100644 gcc/tree-diagnostic-ast.cc

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 6ed09f5dfc5..0ab7abafde8 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1752,6 +1752,7 @@ OBJS = \
 	tree-data-ref.o \
 	tree-dfa.o \
 	tree-diagnostic.o \
+	tree-diagnostic-ast.o \
 	tree-diagnostic-cfg.o \
 	tree-diagnostic-client-data-hooks.o \
 	tree-dump.o \
diff --git a/gcc/channels.h b/gcc/channels.h
index 5e3cd891bc5..c46066cd3fc 100644
--- a/gcc/channels.h
+++ b/gcc/channels.h
@@ -29,6 +29,7 @@ namespace gcc {
    publish/subscribe channels for.  */
 namespace topics {
   namespace analyzer_events { struct subscriber; }
+  namespace ast_events { struct subscriber; }
   namespace pass_events { struct subscriber; }
 } // namespace gcc::topics
 
@@ -37,6 +38,7 @@ namespace topics {
 struct compiler_channels
 {
   pub_sub::channel<topics::analyzer_events::subscriber> analyzer_events_channel;
+  pub_sub::channel<topics::ast_events::subscriber> ast_events_channel;
   pub_sub::channel<topics::pass_events::subscriber> pass_events_channel;
 };
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 42040d46236..c5b67607ec3 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -6504,6 +6504,14 @@ Text sinks ignore the information.  SARIF sinks will add the graphs within
 @code{theRun.graphs}.  HTML sinks will generate SVG displaying the graphs.
 The precise form of the information is subject to change without notice.
 
+@item ast=@r{[}yes@r{|}no@r{]}
+If @code{ast=yes} for a sink, then GCC will attempt to send information
+to that sink about the abstract syntax tree for the translation unit.
+Text sinks ignore the information.  SARIF sinks will add it as a graph within
+@code{theRun.graphs}.  HTML sinks will generate an SVG displaying the tree
+in graph form.  The precise form of the information is subject to change
+without notice.
+
 @item cfgs=@r{[}yes@r{|}no@r{]}
 If @code{cfgs=yes} for a sink, then GCC will attempt to send information
 to that sink about the control flow graphs for the functions it is compiling.
diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc
index 8d4bc1438b2..7e364b97ebc 100644
--- a/gcc/opts-diagnostic.cc
+++ b/gcc/opts-diagnostic.cc
@@ -50,6 +50,7 @@ class gcc_extra_keys : public diagnostics::output_spec::key_handler
 public:
   gcc_extra_keys ()
   : m_analyzer_graphs (false),
+  : m_ast (false),
     m_cfgs (false)
   {
   }
@@ -60,6 +61,8 @@ public:
   {
     if (key == "analyzer-graphs")
       return parse_bool_value (ctxt, key, value, m_analyzer_graphs);
+    if (key == "ast")
+      return parse_bool_value (ctxt, key, value, m_ast);
     if (key == "cfgs")
       return parse_bool_value (ctxt, key, value, m_cfgs);
     return result::unrecognized;
@@ -69,10 +72,12 @@ public:
   get_keys (auto_vec<const char *> &out_known_keys) const final override
   {
     out_known_keys.safe_push ("analyzer-graphs");
+    out_known_keys.safe_push ("ast");
     out_known_keys.safe_push ("cfgs");
   }
 
   bool m_analyzer_graphs;
+  bool m_ast;
   bool m_cfgs;
 };
 
@@ -112,6 +117,10 @@ handle_gcc_specific_keys (const gcc_extra_keys &gcc_keys,
 {
   if (gcc_keys.m_analyzer_graphs)
     sink.add_extension (ext_factory.make_analyzer_graphs_extension (sink));
+
+  if (gcc_keys.m_ast)
+    sink.add_extension (ext_factory.make_ast_extension (sink));
+
   if (gcc_keys.m_cfgs)
     sink.add_extension (ext_factory.make_cfg_extension (sink));
 }
diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h
index 6b24ee3f946..6ce937a23de 100644
--- a/gcc/opts-diagnostic.h
+++ b/gcc/opts-diagnostic.h
@@ -73,6 +73,9 @@ public:
   virtual std::unique_ptr<diagnostics::sink::extension>
   make_analyzer_graphs_extension (diagnostics::sink &sink) const = 0;
 
+  virtual std::unique_ptr<diagnostics::sink::extension>
+  make_ast_extension (diagnostics::sink &sink) const = 0;
+
   virtual std::unique_ptr<diagnostics::sink::extension>
   make_cfg_extension (diagnostics::sink &sink) const = 0;
 
diff --git a/gcc/testsuite/gcc.dg/plugin/ast-1.c b/gcc/testsuite/gcc.dg/plugin/ast-1.c
new file mode 100644
index 00000000000..314a43a57ae
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/ast-1.c
@@ -0,0 +1,12 @@
+/* { dg-options "-fdiagnostics-show-nesting" } */
+
+struct foo
+{
+  int x;
+  int y;
+};
+
+int foo_get_x (struct foo *f)
+{
+  return f->x;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/ast_dump_plugin.cc b/gcc/testsuite/gcc.dg/plugin/ast_dump_plugin.cc
new file mode 100644
index 00000000000..ea5f915b1c8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/ast_dump_plugin.cc
@@ -0,0 +1,77 @@
+/* A plugin to dump any AST information from the frontend
+   as a nested diagnostic.  */
+
+#define INCLUDE_LIST
+#define INCLUDE_VECTOR
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "diagnostic-core.h"
+#include "context.h"
+#include "channels.h"
+#include "topics/ast-events-recorder.h"
+
+int plugin_is_GPL_compatible;
+
+/* Record AST nodes, and dump them as a nested diagnostic.  */
+
+namespace ast_events = gcc::topics::ast_events;
+
+class ast_dumper
+{
+public:
+  ast_dumper (pub_sub::channel<ast_events::subscriber> &ast_events_channel)
+  {
+    ast_events_channel.add_subscriber (m_recorder);
+  }
+
+  void
+  dump_as_diagnostic ()
+  {
+    auto_diagnostic_group d;
+    inform (UNKNOWN_LOCATION, "AST nodes");
+    for (auto &iter : m_recorder.m_root_node.m_children)
+      visit_node (*iter);
+  }
+
+private:
+  void
+  visit_node (const ast_events::recorder::saved_node &node)
+  {
+    auto_diagnostic_nesting_level sentinel;
+    inform (node.m_start_loc, "%qs", node.m_kind);
+    for (auto &iter : node.m_children)
+      visit_node (*iter);
+  }
+
+  ast_events::recorder m_recorder;
+};
+
+static ast_dumper *dumper;
+
+static void start_unit_callback (void *, void *)
+{
+  gcc_assert (!dumper);
+  gcc_assert (g);
+
+  dumper = new ast_dumper (g->get_channels ().ast_events_channel);
+}
+
+static void finish_unit_callback (void *, void *)
+{
+  gcc_assert (dumper);
+  dumper->dump_as_diagnostic ();
+  delete dumper;
+  dumper = nullptr;
+}
+
+int plugin_init (struct plugin_name_args *,
+		 struct plugin_gcc_version *)
+{
+  register_callback ("start_unit", PLUGIN_START_UNIT,
+		     &start_unit_callback, nullptr);
+  register_callback ("finish_unit", PLUGIN_FINISH_UNIT,
+		     &finish_unit_callback, nullptr);
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index deef8e8570c..5cd089a2ed0 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -155,6 +155,8 @@ set plugin_test_list [list \
     { dump_plugin.cc \
 	  dump-1.c \
 	  dump-2.c } \
+    { ast_dump_plugin.cc \
+	  ast-1.c } \
     { analyzer_gil_plugin.cc \
 	  gil-1.c } \
     { analyzer_known_fns_plugin.cc \
diff --git a/gcc/topics/ast-events-recorder.h b/gcc/topics/ast-events-recorder.h
new file mode 100644
index 00000000000..9100ccfadf5
--- /dev/null
+++ b/gcc/topics/ast-events-recorder.h
@@ -0,0 +1,79 @@
+/* FIXME.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+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_TOPICS_AST_EVENTS_RECORDER_H
+#define GCC_TOPICS_AST_EVENTS_RECORDER_H
+
+#include "topics/ast-events.h"
+
+namespace gcc {
+namespace topics {
+namespace ast_events {
+
+/* An ast_events::subscriber that constructs a tree of saved_node.  */
+
+class recorder : public subscriber
+{
+public:
+  struct saved_node
+  {
+    saved_node (const char *kind, location_t start_loc)
+    : m_kind (kind),
+      m_start_loc (start_loc)
+    {}
+
+    const char *m_kind;
+    location_t m_start_loc;
+    std::vector<std::unique_ptr<saved_node>> m_children;
+  };
+
+  recorder ()
+  : m_root_node (nullptr, UNKNOWN_LOCATION)
+  {
+    m_stack.push_back (&m_root_node);
+  }
+
+  void on_message (const ast_events::begin_node &m) final override
+  {
+    auto prev_top_of_stack = m_stack.back ();
+    gcc_assert (prev_top_of_stack);
+    auto new_node = std::make_unique<saved_node> (m.kind, m.loc);
+    m_stack.push_back (new_node.get ());
+    prev_top_of_stack->m_children.push_back (std::move (new_node));
+  }
+
+  void on_message (const ast_events::end_node &) final override
+  {
+    gcc_assert (!m_stack.empty ());
+    m_stack.pop_back ();
+    // FIXME: also capture the endloc in each saved_node?
+  }
+
+private:
+public: // FIXME
+  saved_node m_root_node;
+private:
+  std::vector<saved_node *> m_stack;  // borrowed ptrs
+};
+
+} // namespace gcc::topics::ast_events
+} // namespace gcc::topics
+} // namespace gcc
+
+#endif /* GCC_TOPICS_AST_EVENTS_RECORDER_H */
diff --git a/gcc/topics/ast-events.h b/gcc/topics/ast-events.h
new file mode 100644
index 00000000000..ba7ebe42ff2
--- /dev/null
+++ b/gcc/topics/ast-events.h
@@ -0,0 +1,55 @@
+/* ast-events.h - pub/sub messages about parsing.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+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_TOPICS_AST_EVENTS_H
+#define GCC_TOPICS_AST_EVENTS_H
+
+#include "pub-sub.h"
+
+namespace gcc {
+namespace topics {
+
+/* A topic for messages relating to AST events when parsing.  */
+
+namespace ast_events {
+
+struct begin_node
+{
+  const char *kind;
+  location_t loc;
+};
+
+struct end_node
+{
+  const char *kind;
+  location_t loc;
+};
+
+struct subscriber
+{
+  virtual ~subscriber () {}
+  virtual void on_message (const begin_node &) {}
+  virtual void on_message (const end_node &) {}
+};
+
+} // namespace gcc::topics::ast_events
+} // namespace gcc::topics
+} // namespace gcc
+
+#endif /* GCC_TOPICS_AST_EVENTS_H */
diff --git a/gcc/tree-diagnostic-ast.cc b/gcc/tree-diagnostic-ast.cc
new file mode 100644
index 00000000000..444ef6da705
--- /dev/null
+++ b/gcc/tree-diagnostic-ast.cc
@@ -0,0 +1,126 @@
+/* FIXME.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+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_MAP
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "version.h"
+#include "tree.h"
+#include "diagnostic.h"
+#include "tree-logical-location.h"
+#include "diagnostics/client-data-hooks.h"
+#include "diagnostics/sarif-sink.h"
+#include "langhooks.h"
+#include "plugin.h"
+#include "timevar.h"
+#include "tree-diagnostic-sink-extensions.h"
+#include "tree-pass.h"
+#include "function.h"
+#include "topics/ast-events-recorder.h"
+#include "topics/pass-events.h"
+#include "diagnostics/digraphs.h"
+#include "diagnostics/dumping.h"
+#include "context.h"
+#include "channels.h"
+
+class compiler_capture_ast : public diagnostics::sink::extension
+{
+public:
+  using saved_node = gcc::topics::ast_events::recorder::saved_node;
+  using digraph = diagnostics::digraphs::digraph;
+  using digraph_node = diagnostics::digraphs::node;
+
+  class lazy_digraph : public lazily_created<digraph>
+  {
+  public:
+    lazy_digraph (const saved_node &root_node)
+      : m_root_node (root_node),
+      m_next_node_id (0)
+    {
+    }
+
+    std::unique_ptr<digraph>
+    create_object () const
+    {
+      auto g = std::make_unique<digraph> ();
+      g->set_graph_kind ("ast");
+      for (auto &saved_child : m_root_node.m_children)
+	add_graph_node (*saved_child, *g, nullptr);
+      return g;
+    }
+
+  private:
+    void add_graph_node (const saved_node &saved,
+			 digraph &digraph_,
+			 digraph_node *parent_digraph_node) const
+    {
+      std::string node_id
+	= std::string ("node") + std::to_string (m_next_node_id++);
+      auto new_digraph_node
+	= std::make_unique<digraph_node> (digraph_, std::move (node_id));
+      new_digraph_node->set_label (saved.m_kind);
+      new_digraph_node->set_physical_loc (saved.m_start_loc);
+      // TODO: get logical locations?
+      for (auto &saved_child : saved.m_children)
+	add_graph_node (*saved_child, digraph_, new_digraph_node.get ());
+      if (parent_digraph_node)
+	parent_digraph_node->add_child (std::move (new_digraph_node));
+      else
+	digraph_.add_node (std::move (new_digraph_node));
+    }
+
+    const saved_node &m_root_node;
+    mutable int m_next_node_id;
+  };
+
+  compiler_capture_ast (diagnostics::sink &sink)
+  : diagnostics::sink::extension (sink)
+  {
+    /* Record the AST events.  */
+    g->get_channels ().ast_events_channel.add_subscriber (m_recorder);
+  }
+
+  void
+  dump (FILE *out, int indent) const
+  {
+    diagnostics::dumping::emit_heading (out, indent, "compiler_capture_ast");
+  }
+
+  void finalize () final override
+  {
+    lazy_digraph g (m_recorder.m_root_node);
+    get_sink ().report_global_digraph (g);
+  }
+
+private:
+  gcc::topics::ast_events::recorder m_recorder;
+};
+
+// class compiler_extension_factory : public extension_factory
+
+std::unique_ptr<diagnostics::sink::extension>
+compiler_extension_factory::make_ast_extension (diagnostics::sink &sink) const
+{
+  return std::make_unique<compiler_capture_ast> (sink);
+}
diff --git a/gcc/tree-diagnostic-sink-extensions.h b/gcc/tree-diagnostic-sink-extensions.h
index 8653fc7e78a..59b3af046d5 100644
--- a/gcc/tree-diagnostic-sink-extensions.h
+++ b/gcc/tree-diagnostic-sink-extensions.h
@@ -28,6 +28,9 @@ public:
   std::unique_ptr<diagnostics::sink::extension>
   make_analyzer_graphs_extension (diagnostics::sink &sink) const final override;
 
+  std::unique_ptr<diagnostics::sink::extension>
+  make_ast_extension (diagnostics::sink &sink) const final override;
+
   std::unique_ptr<diagnostics::sink::extension>
   make_cfg_extension (diagnostics::sink &sink) const final override;
 };
-- 
2.49.0

