From d434547cb5e14ff5848e249db5332315edd32060 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 17 Oct 2025 17:47:00 -0400
Subject: [PATCH 32/48] FIXME: WIP on include-graph={yes,no}

---
 gcc/Makefile.in                       |   1 +
 gcc/c-family/c-opts.cc                |   8 +
 gcc/channels.h                        |   2 +
 gcc/doc/invoke.texi                   |   8 +
 gcc/opts-diagnostic.cc                |  10 +-
 gcc/opts-diagnostic.h                 |   3 +
 gcc/topics/cpp-events.h               |  52 ++++
 gcc/tree-diagnostic-include-graph.cc  | 402 ++++++++++++++++++++++++++
 gcc/tree-diagnostic-sink-extensions.h |   3 +
 9 files changed, 488 insertions(+), 1 deletion(-)
 create mode 100644 gcc/topics/cpp-events.h
 create mode 100644 gcc/tree-diagnostic-include-graph.cc

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 0ab7abafde8..b00e4679447 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1755,6 +1755,7 @@ OBJS = \
 	tree-diagnostic-ast.o \
 	tree-diagnostic-cfg.o \
 	tree-diagnostic-client-data-hooks.o \
+	tree-diagnostic-include-graph.o \
 	tree-dump.o \
 	tree-eh.o \
 	tree-emutls.o \
diff --git a/gcc/c-family/c-opts.cc b/gcc/c-family/c-opts.cc
index 9fad71a1eb1..9562d9d6ab1 100644
--- a/gcc/c-family/c-opts.cc
+++ b/gcc/c-family/c-opts.cc
@@ -18,6 +18,7 @@ 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_VECTOR
 #include "config.h"
 #include "system.h"
@@ -45,6 +46,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "file-prefix-map.h"    /* add_*_prefix_map()  */
 #include "context.h"
 #include "diagnostics/text-sink.h"
+#include "channels.h"
+#include "topics/cpp-events.h"
 
 #ifndef DOLLARS_IN_IDENTIFIERS
 # define DOLLARS_IN_IDENTIFIERS true
@@ -1497,6 +1500,11 @@ c_common_finish (void)
 		     fdeps_file ? fdeps_file : out_fname);
     }
 
+  /* Notify any subscribers that preprocessing is done.  */
+  if (auto cpp_channel = g->get_channels ().cpp_events_channel.get_if_active ())
+    cpp_channel->publish
+      (gcc::topics::cpp_events::after_preprocessing {parse_in, line_table});
+
   /* For performance, avoid tearing down cpplib's internal structures
      with cpp_destroy ().  */
   cpp_finish (parse_in, deps_stream, fdeps_stream);
diff --git a/gcc/channels.h b/gcc/channels.h
index c46066cd3fc..96f53e47890 100644
--- a/gcc/channels.h
+++ b/gcc/channels.h
@@ -30,6 +30,7 @@ namespace gcc {
 namespace topics {
   namespace analyzer_events { struct subscriber; }
   namespace ast_events { struct subscriber; }
+  namespace cpp_events { struct subscriber; }
   namespace pass_events { struct subscriber; }
 } // namespace gcc::topics
 
@@ -39,6 +40,7 @@ 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::cpp_events::subscriber> cpp_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 c5b67607ec3..5d86b805b4e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -6519,6 +6519,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 include-graph=@r{[}yes@r{|}no@r{]}
+If @code{include-graph=yes} for a sink, then GCC will attempt to send
+information to that sink about the directed graph of @code{#include}
+directives for the translation unit.
+Text sinks ignore the information.  SARIF sinks will add the graph within
+@code{theRun.graphs}.  HTML sinks will generate SVG displaying the graph.
+The precise form of the information is subject to change without notice.
+
 @end table
 
 For example,
diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc
index 7e364b97ebc..421f11623f5 100644
--- a/gcc/opts-diagnostic.cc
+++ b/gcc/opts-diagnostic.cc
@@ -51,7 +51,8 @@ public:
   gcc_extra_keys ()
   : m_analyzer_graphs (false),
   : m_ast (false),
-    m_cfgs (false)
+    m_cfgs (false),
+    m_include_graph (false)
   {
   }
   enum result
@@ -65,6 +66,8 @@ public:
       return parse_bool_value (ctxt, key, value, m_ast);
     if (key == "cfgs")
       return parse_bool_value (ctxt, key, value, m_cfgs);
+    if (key == "include-graph")
+      return parse_bool_value (ctxt, key, value, m_include_graph);
     return result::unrecognized;
   }
 
@@ -74,11 +77,13 @@ public:
     out_known_keys.safe_push ("analyzer-graphs");
     out_known_keys.safe_push ("ast");
     out_known_keys.safe_push ("cfgs");
+    out_known_keys.safe_push ("include-graph");
   }
 
   bool m_analyzer_graphs;
   bool m_ast;
   bool m_cfgs;
+  bool m_include_graph;
 };
 
 struct opt_spec_context : public diagnostics::output_spec::dc_spec_context
@@ -123,6 +128,9 @@ handle_gcc_specific_keys (const gcc_extra_keys &gcc_keys,
 
   if (gcc_keys.m_cfgs)
     sink.add_extension (ext_factory.make_cfg_extension (sink));
+
+  if (gcc_keys.m_include_graph)
+    sink.add_extension (ext_factory.make_include_graph_extension (sink));
 }
 
 static std::unique_ptr<diagnostics::sink>
diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h
index 6ce937a23de..80888fbb342 100644
--- a/gcc/opts-diagnostic.h
+++ b/gcc/opts-diagnostic.h
@@ -79,6 +79,9 @@ public:
   virtual std::unique_ptr<diagnostics::sink::extension>
   make_cfg_extension (diagnostics::sink &sink) const = 0;
 
+  virtual std::unique_ptr<diagnostics::sink::extension>
+  make_include_graph_extension (diagnostics::sink &sink) const = 0;
+
   static const gcc_extension_factory *singleton;
 };
 
diff --git a/gcc/topics/cpp-events.h b/gcc/topics/cpp-events.h
new file mode 100644
index 00000000000..7a748211f3a
--- /dev/null
+++ b/gcc/topics/cpp-events.h
@@ -0,0 +1,52 @@
+/* cpp-events.h - pub/sub messages about usage of libcpp.
+   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_CPP_EVENTS_H
+#define GCC_TOPICS_CPP_EVENTS_H
+
+#include "pub-sub.h"
+
+namespace gcc {
+namespace topics {
+
+/* A topic for messages relating to usage of libcpp.  */
+
+namespace cpp_events {
+
+struct after_preprocessing
+{
+  cpp_reader *m_reader;
+  line_maps *m_line_maps;
+};
+
+/* Abstract base class for a subscriber to messages about
+   usage of libcpp.  */
+
+struct subscriber
+{
+  virtual ~subscriber () = default;
+
+  virtual void on_message (const after_preprocessing &) = 0;
+};
+
+} // namespace gcc::topics::cpp_events
+} // namespace gcc::topics
+} // namespace gcc
+
+#endif /* ! GCC_TOPICS_CPP_EVENTS_H */
diff --git a/gcc/tree-diagnostic-include-graph.cc b/gcc/tree-diagnostic-include-graph.cc
new file mode 100644
index 00000000000..3ed8a153fa0
--- /dev/null
+++ b/gcc/tree-diagnostic-include-graph.cc
@@ -0,0 +1,402 @@
+/* Generating a diagnostics graph from the #includes.
+   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/>.  */
+
+// FIXME
+
+#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 "lazily-created.h"
+#include "diagnostics/digraphs.h"
+#include "diagnostics/dumping.h"
+#include "diagnostic.h"
+#include "tree-pass.h"
+#include "custom-sarif-properties/cfg.h"
+#include "tree-diagnostic-sink-extensions.h"
+#include "tree-logical-location.h"
+#include "tree-pass.h"
+#include "function.h"
+#include "topics/pass-events.h"
+#include "diagnostics/digraphs.h"
+#include "diagnostics/sink.h"
+#include "context.h"
+#include "channels.h"
+#include "bitmap.h"
+#include "sbitmap.h"
+#include "cfghooks.h"
+#include "cfganal.h"
+#include "cfgloop.h"
+#include "graph.h"
+#include "basic-block.h"
+#include "cfg.h"
+#include "topics/cpp-events.h"
+#include "line-map.h"
+#include "cpplib.h"
+
+#if 0
+
+namespace {
+  namespace graph_properties = custom_sarif_properties::cfg::graph;
+  namespace node_properties = custom_sarif_properties::cfg::node;
+  namespace edge_properties = custom_sarif_properties::cfg::edge;
+}
+#endif
+
+/* Disable warnings about quoting issues in the pp_xxx calls below
+   that (intentionally) don't follow GCC diagnostic conventions.  */
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wformat-diag"
+#endif
+
+class cpp_includes_diagnostic_digraph
+  : public lazily_created<diagnostics::digraphs::digraph>
+{
+public:
+  cpp_includes_diagnostic_digraph (line_maps *maps)
+  : m_line_maps (maps)
+  {}
+
+  std::unique_ptr<diagnostics::digraphs::digraph>
+  create_object () const final override
+  {
+    auto g = std::make_unique<diagnostics::digraphs::digraph> ();
+    g->set_graph_kind ("includes");
+
+    for (line_map_uint_t i = 0; i < LINEMAPS_ORDINARY_USED (m_line_maps); ++i)
+      {
+	auto map = LINEMAPS_ORDINARY_MAP_AT (m_line_maps, i);
+	if (map->reason != LC_ENTER)
+	  continue;
+
+	auto node
+	  = std::make_unique<diagnostics::digraphs::node>
+	      (*g, std::string (map->to_file));
+	node->set_physical_loc (map->start_location);
+	g->add_node (std::move (node));
+
+	// FIXME: add child nodes for the #include locations; add edges
+      }
+
+    // FIXME: cluster by directory hierarchy
+
+    return g;
+  }
+
+#if 0
+  void
+  add_cluster_for_function (diagnostics::digraphs::digraph &g,
+			    function *fun) const
+  {
+    const char *funcname = function_name (fun);
+    auto cluster = std::make_unique<diagnostics::digraphs::node> (g, funcname);
+    cluster->set_property (node_properties::kind, "function");
+
+    bb_to_node_map node_map;
+    add_cfg_nodes (g, node_map, *cluster, fun);
+    add_cfg_edges (g, node_map, fun);
+    g.add_node (std::move (cluster));
+  }
+#endif
+
+private:
+#if 0
+  typedef std::map<basic_block, diagnostics::digraphs::node *> bb_to_node_map;
+
+  /* Add a basic block BB belonging to the function with FUNCDEF_NO
+     as its unique number.  */
+  void
+  add_cfg_node (diagnostics::digraphs::digraph &g,
+		bb_to_node_map &node_map,
+		diagnostics::digraphs::node &parent_node,
+		int funcdef_no,
+		basic_block bb) const
+  {
+    pretty_printer pp;
+    pp_printf (&pp, "fn_%d_basic_block_%d",
+	       funcdef_no, bb->index);
+    std::string name (pp_formatted_text (&pp));
+    auto bb_node = std::make_unique<diagnostics::digraphs::node> (g, name);
+    node_map.insert ({bb, bb_node.get ()});
+
+    bb_node->set_property (node_properties::kind, "basic_block");
+
+    dump_bb_as_sarif_properties (nullptr, // FIXME
+				 bb_node->ensure_property_bag (),
+				 bb);
+
+    parent_node.add_child (std::move (bb_node));
+  }
+
+  /* Add all successor edges of a basic block BB belonging to the function
+     with FUNCDEF_NO as its unique number.  */
+  void
+  add_cfg_node_succ_edges (diagnostics::digraphs::digraph &g,
+			   bb_to_node_map &node_map,
+			   int /*funcdef_no*/,
+			   basic_block bb) const
+  {
+    edge e;
+    edge_iterator ei;
+    FOR_EACH_EDGE (e, ei, bb->succs)
+      {
+	auto src_node = node_map.find (e->src);
+	gcc_assert (src_node != node_map.end ());
+	auto dst_node = node_map.find (e->dest);
+	gcc_assert (dst_node != node_map.end ());
+
+	auto diag_edge
+	  = std::make_unique<diagnostics::digraphs::edge> (g, nullptr,
+							   *(src_node->second),
+							   *(dst_node->second));
+	auto flag_arr = std::make_unique<json::array> ();
+#define DEF_EDGE_FLAG(NAME,IDX) \
+	{ handle_edge_flag (*flag_arr, #NAME, (e->flags & EDGE_##NAME)); }
+#include "cfg-flags.def"
+#undef DEF_EDGE_FLAG
+
+	auto &bag = diag_edge->ensure_property_bag ();
+	bag.set<json::array> (edge_properties::flags.m_key.get (),
+			      std::move (flag_arr));
+
+	if (e->probability.initialized_p ())
+	  diag_edge->set_property (edge_properties::probability_pc,
+				   (e->probability.to_reg_br_prob_base ()
+				    * 100 / REG_BR_PROB_BASE));
+
+	g.add_edge (std::move (diag_edge));
+      }
+  }
+
+  void
+  handle_edge_flag (json::array &flag_arr,
+		    const char *flag_name,
+		    bool value) const
+  {
+    if (value)
+      flag_arr.append_string (flag_name);
+  }
+
+  /* Add all the basic blocks in the CFG in case loops are not available.
+     First compute a topological order of the blocks to get a good ranking of
+     the nodes.  Then, if any nodes are not reachable from ENTRY, add them at
+     the end.  */
+  void
+  add_cfg_nodes_no_loops (diagnostics::digraphs::digraph &g,
+			  bb_to_node_map &node_map,
+			  diagnostics::digraphs::node &parent_node,
+			  function *fun) const
+  {
+    int *rpo = XNEWVEC (int, n_basic_blocks_for_fn (fun));
+    int i, n;
+
+    auto_sbitmap visited (last_basic_block_for_fn (fun));
+    bitmap_clear (visited);
+
+    n = pre_and_rev_post_order_compute_fn (fun, NULL, rpo, true);
+    for (i = n_basic_blocks_for_fn (fun) - n;
+	 i < n_basic_blocks_for_fn (fun); i++)
+      {
+	basic_block bb = BASIC_BLOCK_FOR_FN (fun, rpo[i]);
+	add_cfg_node (g, node_map, parent_node, fun->funcdef_no, bb);
+	bitmap_set_bit (visited, bb->index);
+      }
+    free (rpo);
+
+    if (n != n_basic_blocks_for_fn (fun))
+      {
+	/* Some blocks are unreachable.  We still want to dump them.  */
+	basic_block bb;
+	FOR_ALL_BB_FN (bb, fun)
+	  if (! bitmap_bit_p (visited, bb->index))
+	    add_cfg_node (g, node_map, parent_node, fun->funcdef_no, bb);
+      }
+  }
+
+/* Add all the basic blocks in LOOP.  Add the blocks in breath-first
+   order to get a good ranking of the nodes.  This function is recursive:
+   It first adds inner loops, then the body of LOOP itself.  */
+
+  void
+  add_cfg_nodes_for_loop (diagnostics::digraphs::digraph &g,
+			  bb_to_node_map &node_map,
+			  diagnostics::digraphs::node *parent_node,
+			  int funcdef_no,
+			  class loop *loop) const
+  {
+    namespace loop_properties = custom_sarif_properties::cfg::loop;
+
+    gcc_assert (parent_node);
+    diagnostics::digraphs::node &orig_parent_node = *parent_node;
+
+    unsigned int i;
+    std::unique_ptr<diagnostics::digraphs::node> loop_node;
+
+    if (loop->header != NULL
+	&& loop->latch != EXIT_BLOCK_PTR_FOR_FN (cfun))
+      {
+	pretty_printer pp;
+	pp_printf (&pp, "fun_%d_loop_%d", funcdef_no, loop->num);
+	std::string name (pp_formatted_text (&pp));
+	loop_node
+	  = std::make_unique<diagnostics::digraphs::node> (g, name);
+	parent_node = loop_node.get ();
+	loop_node->set_property (node_properties::kind, "loop");
+	loop_node->set_property (loop_properties::num, loop->num);
+	loop_node->set_property (loop_properties::depth, loop_depth (loop));
+      }
+
+    for (class loop *inner = loop->inner; inner; inner = inner->next)
+      add_cfg_nodes_for_loop (g, node_map, parent_node, funcdef_no, inner);
+
+    if (loop->header == NULL)
+      return;
+
+    basic_block *body;
+    if (loop->latch == EXIT_BLOCK_PTR_FOR_FN (cfun))
+      body = get_loop_body (loop);
+    else
+      body = get_loop_body_in_bfs_order (loop);
+
+    for (i = 0; i < loop->num_nodes; i++)
+      {
+	basic_block bb = body[i];
+	if (bb->loop_father == loop)
+	  add_cfg_node (g, node_map, *parent_node, funcdef_no, bb);
+      }
+
+    free (body);
+
+    if (loop->latch != EXIT_BLOCK_PTR_FOR_FN (cfun))
+      {
+	gcc_assert (loop_node);
+	orig_parent_node.add_child (std::move (loop_node));
+      }
+  }
+
+  void
+  add_cfg_nodes (diagnostics::digraphs::digraph &g,
+		 bb_to_node_map &node_map,
+		 diagnostics::digraphs::node &parent_node,
+		 function *fun) const
+  {
+    /* ???  The loop and dominance APIs are dependent on fun == cfun.  */
+    if (fun == cfun && loops_for_fn (fun))
+      add_cfg_nodes_for_loop (g, node_map, &parent_node, fun->funcdef_no,
+			      get_loop (fun, 0));
+    else
+      add_cfg_nodes_no_loops (g, node_map, parent_node, fun);
+  }
+
+  void
+  add_cfg_edges (diagnostics::digraphs::digraph &g,
+		 bb_to_node_map &node_map,
+		 function *fun) const
+  {
+    basic_block bb;
+
+    /* Save EDGE_DFS_BACK flag to dfs_back.  */
+    auto_bitmap dfs_back;
+    edge e;
+    edge_iterator ei;
+    unsigned int idx = 0;
+    FOR_EACH_BB_FN (bb, fun)
+      FOR_EACH_EDGE (e, ei, bb->succs)
+      {
+	if (e->flags & EDGE_DFS_BACK)
+	  bitmap_set_bit (dfs_back, idx);
+	idx++;
+      }
+
+    mark_dfs_back_edges (fun);
+    FOR_ALL_BB_FN (bb, fun)
+      add_cfg_node_succ_edges (g, node_map, fun->funcdef_no, bb);
+
+    /* Restore EDGE_DFS_BACK flag from dfs_back.  */
+    idx = 0;
+    FOR_EACH_BB_FN (bb, fun)
+      FOR_EACH_EDGE (e, ei, bb->succs)
+      {
+	if (bitmap_bit_p (dfs_back, idx))
+	  e->flags |= EDGE_DFS_BACK;
+	else
+	  e->flags &= ~EDGE_DFS_BACK;
+	idx++;
+      }
+  }
+
+  function *m_fun;
+  opt_pass *m_pass;
+#endif
+  line_maps *m_line_maps;
+};
+
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic pop
+#endif
+
+namespace cpp_events = gcc::topics::cpp_events;
+
+class compiler_capture_include_graph : public diagnostics::sink::extension
+{
+public:
+  compiler_capture_include_graph (diagnostics::sink &sink)
+  : extension (sink),
+    m_event_subscriber (sink)
+  {
+    g->get_channels ().cpp_events_channel.add_subscriber (m_event_subscriber);
+  }
+
+  void
+  dump (FILE *out, int indent) const
+  {
+    diagnostics::dumping::emit_heading (out, indent,
+					"compiler_capture_include_graph");
+  }
+
+private:
+  class event_subscriber : public cpp_events::subscriber
+  {
+  public:
+    event_subscriber (diagnostics::sink &sink) : m_sink (sink) {}
+    void on_message (const cpp_events::after_preprocessing &m) final override
+    {
+      // We could probably just use the "line_table" global here
+      m_sink.report_global_digraph
+	(cpp_includes_diagnostic_digraph (m.m_line_maps));
+    }
+
+  private:
+    diagnostics::sink &m_sink;
+  } m_event_subscriber;
+};
+
+std::unique_ptr<diagnostics::sink::extension>
+compiler_extension_factory::
+make_include_graph_extension (diagnostics::sink &sink) const
+{
+  return std::make_unique<compiler_capture_include_graph> (sink);
+}
diff --git a/gcc/tree-diagnostic-sink-extensions.h b/gcc/tree-diagnostic-sink-extensions.h
index 59b3af046d5..51dee9b8b2f 100644
--- a/gcc/tree-diagnostic-sink-extensions.h
+++ b/gcc/tree-diagnostic-sink-extensions.h
@@ -33,6 +33,9 @@ public:
 
   std::unique_ptr<diagnostics::sink::extension>
   make_cfg_extension (diagnostics::sink &sink) const final override;
+
+  std::unique_ptr<diagnostics::sink::extension>
+  make_include_graph_extension (diagnostics::sink &sink) const final override;
 };
 
 #endif /* TREE_DIAGNOSTIC_SINK_EXTENSIONS_H */
-- 
2.49.0

