This patch adds a new key/value pair "cfgs={yes,no}" to diagnostics
sinks, "no" by default.
If set to "yes" for a SARIF sink, then GCC will add the internal state
of the CFG for all functions after each pertinent optimization pass in
graph form to theRun.graphs in the SARIF output.
If set to "yes" for an HTML sink, the generated HTML will contain SVG
displaying the graphs, adapted from code in graph.cc
Text sinks ignore it.
The SARIF output is thus a machine-readable serialization of (some of)
GCC's intermediate representation (as JSON), but it's much less than
GCC-XML used to provide. The precise form of the information is
documented as subject to change without notice.
Currently it shows both gimple statements and RTL instructions,
depending on the pass. My hope is that it should be possible to write a
"cfg-grep" tool that can read the SARIF and automatically identify
in which pass a particular piece of our IR appeared or disappeared,
for tracking down bugs in our optimization passes.
Implementation-wise:
* this uses the publish-subscribe mechanism from the earlier patch, by
having the diagnostics sink subscribe to pass_events::after_pass
messages from the pass_events_channel.
* the patch adds a new hook to cfghooks.h for dumping a basic block
into a SARIF property bag
gcc/ChangeLog:
* Makefile.in (OBJS): Add tree-diagnostic-cfg.o.
(OBJS-libcommon): Add custom-sarif-properties/cfg.o,
diagnostics/digraphs-to-dot.o, and
diagnostics/digraphs-to-dot-from-cfg.o.
* cfghooks.cc: Define INCLUDE_VECTOR. Add includes of
"diagnostics/sarif-sink.h" and "custom-sarif-properties/cfg.h".
(dump_bb_as_sarif_properties): New.
* cfghooks.h (diagnostics::sarif_builder): New forward decl.
(json::object): New forward decl.
(cfg_hooks::dump_bb_as_sarif_properties): New callback field.
(dump_bb_as_sarif_properties): New decl.
* cfgrtl.cc (rtl_cfg_hooks): Populate the new callback
field with rtl_dump_bb_as_sarif_properties.
(cfg_layout_rtl_cfg_hooks): Likewise.
* custom-sarif-properties/cfg.cc: New file.
* custom-sarif-properties/cfg.h: New file.
* diagnostics/digraphs-to-dot-from-cfg.cc: New file, partly
adapted from gcc/graph.cc.
* diagnostics/digraphs-to-dot.cc: New file.
* diagnostics/digraphs-to-dot.h: New file, based on material in...
* diagnostics/digraphs.cc: Include
"diagnostics/digraphs-to-dot.h".
(class conversion_to_dot): Rework and move to above.
(make_dot_graph_from_diagnostic_graph): Likewise.
(make_dot_node_from_digraph_node): Likewise.
(make_dot_edge_from_digraph_edge): Likewise.
(conversion_to_dot::get_dot_id_for_node): Likewise.
(conversion_to_dot::has_edges_p): Likewise.
(digraph::make_dot_graph): Use to_dot::converter::make and invoke
the result to make the dot graph.
* diagnostics/digraphs.h (digraph:get_all_nodes): New accessor.
* diagnostics/html-sink.cc
(html_builder::m_per_logical_loc_graphs): New field.
(html_builder::add_graph_for_logical_loc): New.
(html_sink::report_digraph_for_logical_location): New.
* diagnostics/sarif-sink.cc (sarif_array_of_unique::get_element):
New.
(sarif_builder::report_digraph_for_logical_location): New.
(sarif_sink::report_digraph_for_logical_location): New.
* diagnostics/sink.h: Include "diagnostics/logical-locations.h".
(sink::report_digraph_for_logical_location): New vfunc.
* diagnostics/text-sink.h
(text_sink::report_digraph_for_logical_location): New.
* doc/invoke.texi (fdiagnostics-add-output): Clarify wording.
Distinguish between scheme-specific vs GCC-specific keys, and add
"cfgs" as the first example of the latter.
* gimple-pretty-print.cc: Include "cfghooks.h", "json.h", and
"custom-sarif-properties/cfg.h".
(gimple_dump_bb_as_sarif_properties): New.
* gimple-pretty-print.h (diagnostics::sarif_builder): New forward
decl.
(json::object): Likewise.
(gimple_dump_bb_as_sarif_properties): New.
* graphviz.cc (get_compass_pt_from_string): New
* graphviz.h (get_compass_pt_from_string): New decl.
* libsarifreplay.cc (sarif_replayer::handle_graph_object): Fix
overlong line.
* opts-common.cc: Define INCLUDE_VECTOR.
* opts-diagnostic.cc: Define INCLUDE_LIST. Include
"diagnostics/sarif-sink.h", "tree-diagnostic-sink-extensions.h",
"opts-diagnostic.h", and "pub-sub.h".
(class gcc_extra_keys): New class.
(opt_spec_context::opt_spec_context): Add "client_keys" param and
pass to dc_spec_context.
(handle_gcc_specific_keys): New.
(try_to_make_sink): New.
(gcc_extension_factory::singleton): New.
(handle_OPT_fdiagnostics_add_output_): Rework to use
try_to_make_sink.
(handle_OPT_fdiagnostics_set_output_): Likewise.
* opts-diagnostic.h: Include "diagnostics/sink.h".
(class gcc_extension_factory): New.
* opts.cc: Define INCLUDE_LIST.
* print-rtl.cc: Include "dumpfile.h", "cfghooks.h", "json.h", and
"custom-sarif-properties/cfg.h".
(rtl_dump_bb_as_sarif_properties): New.
* print-rtl.h (diagnostics::sarif_builder): New forward decl.
(json::object): Likewise.
(rtl_dump_bb_as_sarif_properties): New decl.
* tree-cfg.cc (gimple_cfg_hooks): Use
gimple_dump_bb_as_sarif_properties for new callback field.
* tree-diagnostic-cfg.cc: New file, based on material in graph.cc.
* tree-diagnostic-sink-extensions.h: New file.
* tree-diagnostic.cc: Define INCLUDE_LIST. Include
"tree-diagnostic-sink-extensions.h".
(compiler_ext_factory): New.
(tree_diagnostics_defaults): Set gcc_extension_factory::singleton
to be compiler_ext_factory.
gcc/testsuite/ChangeLog:
* gcc.dg/diagnostic-cfgs-html.py: New test.
* gcc.dg/diagnostic-cfgs-sarif.py: New test.
* gcc.dg/diagnostic-cfgs.c: New test.
Signed-off-by: David Malcolm <[email protected]>
---
gcc/Makefile.in | 4 +
gcc/cfghooks.cc | 30 ++
gcc/cfghooks.h | 10 +
gcc/cfgrtl.cc | 2 +
gcc/custom-sarif-properties/cfg.cc | 69 ++++
gcc/custom-sarif-properties/cfg.h | 64 +++
gcc/diagnostics/digraphs-to-dot-from-cfg.cc | 323 +++++++++++++++
gcc/diagnostics/digraphs-to-dot.cc | 202 +++++++++
gcc/diagnostics/digraphs-to-dot.h | 84 ++++
gcc/diagnostics/digraphs.cc | 137 +-----
gcc/diagnostics/digraphs.h | 6 +
gcc/diagnostics/html-sink.cc | 30 ++
gcc/diagnostics/sarif-sink.cc | 40 ++
gcc/diagnostics/sink.h | 5 +
gcc/diagnostics/text-sink.h | 7 +
gcc/doc/invoke.texi | 23 +-
gcc/gimple-pretty-print.cc | 84 ++++
gcc/gimple-pretty-print.h | 7 +
gcc/graphviz.cc | 52 +++
gcc/graphviz.h | 3 +
gcc/libsarifreplay.cc | 5 +-
gcc/opts-common.cc | 1 +
gcc/opts-diagnostic.cc | 93 ++++-
gcc/opts-diagnostic.h | 14 +
gcc/opts.cc | 1 +
gcc/print-rtl.cc | 25 ++
gcc/print-rtl.h | 7 +
gcc/testsuite/gcc.dg/diagnostic-cfgs-html.py | 21 +
gcc/testsuite/gcc.dg/diagnostic-cfgs-sarif.py | 84 ++++
gcc/testsuite/gcc.dg/diagnostic-cfgs.c | 18 +
gcc/tree-cfg.cc | 1 +
gcc/tree-diagnostic-cfg.cc | 390 ++++++++++++++++++
gcc/tree-diagnostic-sink-extensions.h | 32 ++
gcc/tree-diagnostic.cc | 5 +
34 files changed, 1721 insertions(+), 158 deletions(-)
create mode 100644 gcc/custom-sarif-properties/cfg.cc
create mode 100644 gcc/custom-sarif-properties/cfg.h
create mode 100644 gcc/diagnostics/digraphs-to-dot-from-cfg.cc
create mode 100644 gcc/diagnostics/digraphs-to-dot.cc
create mode 100644 gcc/diagnostics/digraphs-to-dot.h
create mode 100644 gcc/testsuite/gcc.dg/diagnostic-cfgs-html.py
create mode 100644 gcc/testsuite/gcc.dg/diagnostic-cfgs-sarif.py
create mode 100644 gcc/testsuite/gcc.dg/diagnostic-cfgs.c
create mode 100644 gcc/tree-diagnostic-cfg.cc
create mode 100644 gcc/tree-diagnostic-sink-extensions.h
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index f09915780192c..2e822cca9df75 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1742,6 +1742,7 @@ OBJS = \
tree-data-ref.o \
tree-dfa.o \
tree-diagnostic.o \
+ tree-diagnostic-cfg.o \
tree-diagnostic-client-data-hooks.o \
tree-dump.o \
tree-eh.o \
@@ -1862,6 +1863,7 @@ OBJS = \
# Objects in libcommon.a, potentially used by all host binaries and with
# no target dependencies.
OBJS-libcommon = \
+ custom-sarif-properties/cfg.o \
custom-sarif-properties/digraphs.o \
custom-sarif-properties/state-graphs.o \
diagnostic-global-context.o \
@@ -1871,6 +1873,8 @@ OBJS-libcommon = \
diagnostics/context.o \
diagnostics/digraphs.o \
diagnostics/dumping.o \
+ diagnostics/digraphs-to-dot.o \
+ diagnostics/digraphs-to-dot-from-cfg.o \
diagnostics/file-cache.o \
diagnostics/output-spec.o \
diagnostics/html-sink.o \
diff --git a/gcc/cfghooks.cc b/gcc/cfghooks.cc
index 01169e22fbb94..32b5dd2f2b1a5 100644
--- a/gcc/cfghooks.cc
+++ b/gcc/cfghooks.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_VECTOR
#include "config.h"
#include "system.h"
#include "coretypes.h"
@@ -34,6 +35,8 @@ along with GCC; see the file COPYING3. If not see
#include "cfgloop.h"
#include "sreal.h"
#include "profile.h"
+#include "diagnostics/sarif-sink.h"
+#include "custom-sarif-properties/cfg.h"
/* Disable warnings about missing quoting in GCC diagnostics. */
#if __GNUC__ >= 10
@@ -352,6 +355,33 @@ dump_bb_for_graph (pretty_printer *pp, basic_block bb)
cfg_hooks->dump_bb_for_graph (pp, bb);
}
+void
+dump_bb_as_sarif_properties (diagnostics::sarif_builder *builder,
+ json::object &output_bag,
+ basic_block bb)
+{
+ if (!cfg_hooks->dump_bb_for_graph)
+ internal_error ("%s does not support dump_bb_as_sarif_properties",
+ cfg_hooks->name);
+ namespace bb_property_names = custom_sarif_properties::cfg::basic_block;
+ if (bb->index == ENTRY_BLOCK)
+ output_bag.set_string (bb_property_names::kind, "entry");
+ else if (bb->index == EXIT_BLOCK)
+ output_bag.set_string (bb_property_names::kind, "exit");
+ else if (BB_PARTITION (bb) == BB_HOT_PARTITION)
+ output_bag.set_string (bb_property_names::kind, "hot");
+ else if (BB_PARTITION (bb) == BB_COLD_PARTITION)
+ output_bag.set_string (bb_property_names::kind, "cold");
+ if (bb->count.initialized_p ())
+ {
+ pretty_printer pp;
+ pp_printf (&pp, "%" PRId64, bb->count.to_gcov_type ());
+ output_bag.set_string (bb_property_names::count,
+ pp_formatted_text (&pp));
+ }
+ cfg_hooks->dump_bb_as_sarif_properties (builder, output_bag, bb);
+}
+
/* Dump the complete CFG to FILE. FLAGS are the TDF_* flags in dumpfile.h. */
void
dump_flow_info (FILE *file, dump_flags_t flags)
diff --git a/gcc/cfghooks.h b/gcc/cfghooks.h
index ec0c63296968f..a4311989d0e09 100644
--- a/gcc/cfghooks.h
+++ b/gcc/cfghooks.h
@@ -23,6 +23,9 @@ along with GCC; see the file COPYING3. If not see
#include "predict.h"
+namespace diagnostics { class sarif_builder; }
+namespace json { class object; }
+
/* Structure to gather statistic about profile consistency, per pass.
An array of this structure, indexed by pass static number, is allocated
in passes.cc. The structure is defined here so that different CFG modes
@@ -81,6 +84,10 @@ struct cfg_hooks
bool (*verify_flow_info) (void);
void (*dump_bb) (FILE *, basic_block, int, dump_flags_t);
void (*dump_bb_for_graph) (pretty_printer *, basic_block);
+ void
+ (*dump_bb_as_sarif_properties) (diagnostics::sarif_builder *,
+ json::object &,
+ basic_block);
/* Basic CFG manipulation. */
@@ -216,6 +223,9 @@ checking_verify_flow_info (void)
extern void dump_bb (FILE *, basic_block, int, dump_flags_t);
extern void dump_bb_for_graph (pretty_printer *, basic_block);
+extern void dump_bb_as_sarif_properties (diagnostics::sarif_builder *,
+ json::object &,
+ basic_block);
extern void dump_flow_info (FILE *, dump_flags_t);
extern edge redirect_edge_and_branch (edge, basic_block);
diff --git a/gcc/cfgrtl.cc b/gcc/cfgrtl.cc
index 1b4f78aa4b827..975784a0d96f8 100644
--- a/gcc/cfgrtl.cc
+++ b/gcc/cfgrtl.cc
@@ -5393,6 +5393,7 @@ struct cfg_hooks rtl_cfg_hooks = {
rtl_verify_flow_info,
rtl_dump_bb,
rtl_dump_bb_for_graph,
+ rtl_dump_bb_as_sarif_properties,
rtl_create_basic_block,
rtl_redirect_edge_and_branch,
rtl_redirect_edge_and_branch_force,
@@ -5435,6 +5436,7 @@ struct cfg_hooks cfg_layout_rtl_cfg_hooks = {
rtl_verify_flow_info_1,
rtl_dump_bb,
rtl_dump_bb_for_graph,
+ rtl_dump_bb_as_sarif_properties,
cfg_layout_create_basic_block,
cfg_layout_redirect_edge_and_branch,
cfg_layout_redirect_edge_and_branch_force,
diff --git a/gcc/custom-sarif-properties/cfg.cc
b/gcc/custom-sarif-properties/cfg.cc
new file mode 100644
index 0000000000000..f03a39d6b780d
--- /dev/null
+++ b/gcc/custom-sarif-properties/cfg.cc
@@ -0,0 +1,69 @@
+/* Extra properties for digraphs in SARIF property bags.
+ 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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "json.h"
+#include "custom-sarif-properties/cfg.h"
+
+namespace cfg = custom_sarif_properties::cfg;
+
+#define GRAPH_PREFIX "gcc/cfg/graph/"
+const json::string_property cfg::graph::pass_name
+ (GRAPH_PREFIX "pass_name");
+const json::integer_property cfg::graph::pass_number
+ (GRAPH_PREFIX "pass_number");
+#undef GRAPH_PREFIX
+
+#define NODE_PREFIX "gcc/cfg/node/"
+const json::string_property cfg::node::kind
+ (NODE_PREFIX "kind");
+#undef NODE_PREFIX
+
+// For node kind: "loop":
+#define LOOP_PREFIX "gcc/cfg/loop/"
+const json::integer_property cfg::loop::num (LOOP_PREFIX "num");
+const json::integer_property cfg::loop::depth (LOOP_PREFIX "depth");
+#undef LOOP_PREFIX
+
+// For node kind: "basic_block":
+#define BB_PREFIX "gcc/cfg/basic_block/"
+
+const json::string_property cfg::basic_block::kind (BB_PREFIX "kind");
+const json::integer_property cfg::basic_block::index (BB_PREFIX "index");
+const json::string_property cfg::basic_block::count (BB_PREFIX "count");
+
+const json::array_of_string_property cfg::basic_block::gimple::phis
+ (BB_PREFIX "gimple/phis");
+const json::array_of_string_property cfg::basic_block::gimple::stmts
+ (BB_PREFIX "gimple/stmts");
+
+const json::array_of_string_property cfg::basic_block::rtl::insns
+ (BB_PREFIX "rtl/insns");
+
+#undef BB_PREFIX
+
+#define EDGE_PREFIX "gcc/cfg/edge/"
+const json::array_of_string_property cfg::edge::flags
+ (EDGE_PREFIX "flags");
+const json::integer_property cfg::edge::probability_pc
+ (EDGE_PREFIX "probability_pc");
+#undef EDGE_PREFIX
diff --git a/gcc/custom-sarif-properties/cfg.h
b/gcc/custom-sarif-properties/cfg.h
new file mode 100644
index 0000000000000..04776e55f330d
--- /dev/null
+++ b/gcc/custom-sarif-properties/cfg.h
@@ -0,0 +1,64 @@
+/* Extra properties for CFGs in SARIF property bags.
+ 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_CUSTOM_SARIF_PROPERTIES_CFG_H
+#define GCC_CUSTOM_SARIF_PROPERTIES_CFG_H
+
+/* SARIF property names relating to GCC's CFGs. */
+
+namespace custom_sarif_properties {
+ namespace cfg {
+ namespace graph {
+ extern const json::string_property pass_name;
+ extern const json::integer_property pass_number;
+ }
+
+ // node kinds: "function", "loop", or "basic_block"
+
+ // For node_kind: "loop":
+ namespace loop {
+ extern const json::integer_property num;
+ extern const json::integer_property depth;
+ }
+ // For node_kind: "basic_block":
+ namespace basic_block {
+ extern const json::string_property kind;
+ extern const json::integer_property index;
+ extern const json::string_property count; // profile info
+ namespace gimple {
+ extern const json::array_of_string_property phis;
+ extern const json::array_of_string_property stmts;
+ }
+ namespace rtl {
+ extern const json::array_of_string_property insns;
+ }
+ }
+
+ namespace node {
+ extern const json::string_property kind;
+ }
+ namespace edge {
+ extern const json::array_of_string_property flags;
+ extern const json::integer_property probability_pc;
+ }
+ }
+}
+
+#endif /* ! GCC_CUSTOM_SARIF_PROPERTIES_CFG_H */
diff --git a/gcc/diagnostics/digraphs-to-dot-from-cfg.cc
b/gcc/diagnostics/digraphs-to-dot-from-cfg.cc
new file mode 100644
index 0000000000000..f1b0b239a074f
--- /dev/null
+++ b/gcc/diagnostics/digraphs-to-dot-from-cfg.cc
@@ -0,0 +1,323 @@
+/* Presentation tweaks for generating .dot from digraphs for GCC CFGs.
+ 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_ALGORITHM
+#define INCLUDE_MAP
+#define INCLUDE_SET
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+
+#include "graphviz.h"
+#include "xml.h"
+#include "xml-printer.h"
+#include "diagnostics/digraphs.h"
+#include "diagnostics/digraphs-to-dot.h"
+#include "diagnostics/sarif-sink.h"
+#include "custom-sarif-properties/cfg.h"
+
+#include "selftest.h"
+
+namespace diagnostics {
+namespace digraphs {
+namespace to_dot {
+
+namespace {
+ namespace node_properties = custom_sarif_properties::cfg::node;
+ namespace edge_properties = custom_sarif_properties::cfg::edge;
+}
+
+class converter_from_cfg : public converter
+{
+public:
+ std::unique_ptr<dot::graph>
+ make_dot_graph_from_diagnostic_graph (const digraph &dg) final override
+ {
+ auto dot_graph
+ = converter::make_dot_graph_from_diagnostic_graph (dg);
+
+ /* Add an invisible edge from ENTRY to EXIT, to improve the
+ graph layout. */
+ if (const digraphs::node *entry_node = get_entry_node (dg))
+ if (const digraphs::node *exit_node = get_exit_node (dg))
+ {
+ auto extra_edge_stmt
+ = std::make_unique<dot::edge_stmt>
+ (get_node_id_for_node (*entry_node, "s"),
+ get_node_id_for_node (*exit_node, "n"));
+ extra_edge_stmt->set_attr (dot::id ("style"), dot::id ("invis"));
+ extra_edge_stmt->set_attr (dot::id ("constraint"), dot::id ("true"));
+ dot_graph->m_stmt_list.add_stmt (std::move (extra_edge_stmt));
+ }
+
+ return dot_graph;
+ }
+
+ void
+ add_any_subgraph_attrs (const digraph_node &input_node,
+ dot::subgraph &output_subgraph) final override
+ {
+ if (const char *kind = input_node.get_property (node_properties::kind))
+ {
+ if (strcmp (kind, "function") == 0)
+ {
+ }
+ else if (strcmp (kind, "loop") == 0)
+ {
+ namespace loop_property_names
+ = custom_sarif_properties::cfg::loop;
+ long num;
+ if (input_node.maybe_get_property (loop_property_names::num, num))
+ {
+ pretty_printer pp;
+ pp_printf (&pp, "loop %li", num);
+ output_subgraph.add_attr (dot::id ("label"),
+ dot::id (pp_formatted_text (&pp)));
+ }
+ long depth;
+ if (input_node.maybe_get_property (loop_property_names::depth,
+ depth))
+ {
+ const char *fillcolors[3] = { "grey88", "grey77", "grey66" };
+ output_subgraph.add_attr
+ (dot::id ("fillcolor"),
+ dot::id (fillcolors[(depth - 1) % 3]));
+ }
+ output_subgraph.add_attr (dot::id ("style"), dot::id ("filled"));
+ output_subgraph.add_attr (dot::id ("color"), dot::id ("darkgreen"));
+ output_subgraph.add_attr (dot::id ("labeljust"), dot::id ("l"));
+ output_subgraph.add_attr (dot::id ("penwidth"), dot::id ("2"));
+ }
+ }
+ }
+
+ void
+ add_any_node_attrs (const digraph_node &input_node,
+ dot::node_stmt &output_node) final override
+ {
+ if (const char *node_kind = input_node.get_property
(node_properties::kind))
+ if (strcmp (node_kind, "basic_block") == 0)
+ {
+ namespace bb_property_names
+ = custom_sarif_properties::cfg::basic_block;
+ const char *shape = nullptr;
+ const char *fillcolor = "lightgrey";
+ const char *label = nullptr;
+ if (const char *bb_kind
+ = input_node.get_property (bb_property_names::kind))
+ {
+ if (strcmp (bb_kind, "entry") == 0)
+ {
+ shape = "Mdiamond";
+ fillcolor = "white";
+ label = "ENTRY";
+ }
+ else if (strcmp (bb_kind, "exit") == 0)
+ {
+ shape = "Mdiamond";
+ fillcolor = "white";
+ label = "EXIT";
+ }
+ else if (strcmp (bb_kind, "hot") == 0)
+ fillcolor = "lightpink";
+ else if (strcmp (bb_kind, "cold") == 0)
+ fillcolor = "lightblue";
+ }
+
+ if (shape)
+ output_node.set_attr (dot::id ("shape"), dot::id (shape));
+ else
+ {
+ output_node.set_attr (dot::id ("shape"), dot::id ("none"));
+ output_node.set_attr (dot::id ("margin"), dot::id ("0"));
+ }
+ output_node.set_attr (dot::id ("fillcolor"), dot::id (fillcolor));
+ if (label)
+ output_node.set_label (dot::id (label));
+ else
+ {
+ // Create node with table
+ xml::element table ("table", false);
+ xml::printer xp (table);
+ xp.set_attr ("border", "0");
+ xp.set_attr ("cellborder", "1");
+ xp.set_attr ("cellspacing", "0");
+
+ long bb_index;
+ if (input_node.maybe_get_property (bb_property_names::index,
+ bb_index))
+ {
+ xp.push_tag ("tr", true);
+ xp.push_tag ("td", true);
+ xp.set_attr ("align", "left");
+ pretty_printer pp;
+ pp_printf (&pp, "<bb %li>:", bb_index);
+ xp.add_text_from_pp (pp);
+ xp.pop_tag ("td");
+ xp.pop_tag ("tr");
+ }
+
+ if (json::array *arr
+ = input_node.get_property (bb_property_names::gimple::phis))
+ print_rows_for_strings (*arr, xp);
+
+ if (json::array *arr
+ = input_node.get_property
+ (bb_property_names::gimple::stmts))
+ print_rows_for_strings (*arr, xp);
+
+ if (json::array *arr
+ = input_node.get_property (bb_property_names::rtl::insns))
+ print_rows_for_strings (*arr, xp);
+
+ // xml must be done by now
+
+ output_node.m_attrs.add (dot::id ("label"),
+ dot::id (table));
+ }
+ }
+ }
+
+ virtual void
+ add_any_edge_attrs (const digraph_edge &input_edge,
+ dot::edge_stmt &output_edge) final override
+ {
+ namespace edge_properties = custom_sarif_properties::cfg::edge;
+
+ const char *style = "solid,bold";
+ const char *color = "black";
+ int weight = 10;
+
+ if (edge_has_flag (input_edge, "FAKE"))
+ {
+ style = "dotted";
+ color = "green";
+ weight = 0;
+ }
+ if (edge_has_flag (input_edge, "DFS_BACK"))
+ {
+ style = "dotted,bold";
+ color = "blue";
+ weight = 10;
+ }
+ else if (edge_has_flag (input_edge, "FALLTHRU"))
+ weight = 100;
+ else if (edge_has_flag (input_edge, "TRUE_VALUE"))
+ color = "forestgreen";
+ else if (edge_has_flag (input_edge, "FALSE_VALUE"))
+ color = "darkorange";
+
+ if (edge_has_flag (input_edge, "ABNORMAL"))
+ color = "red";
+
+ output_edge.set_attr (dot::id ("style"), dot::id (style));
+ output_edge.set_attr (dot::id ("color"), dot::id (color));
+ output_edge.set_attr (dot::id ("weight"),
+ dot::id (std::to_string (weight)));
+ output_edge.set_attr (dot::id ("constraint"),
+ dot::id ((edge_has_flag (input_edge, "FAKE")
+ || edge_has_flag (input_edge, "DFS_BACK"))
+ ? "false" : "true"));
+
+ long probability_pc;
+ if (input_edge.maybe_get_property (edge_properties::probability_pc,
+ probability_pc))
+ {
+ pretty_printer pp;
+ pp_printf (&pp, "[%li%%]", probability_pc);
+ output_edge.set_label (dot::id (pp_formatted_text (&pp)));
+ }
+ }
+
+ private:
+ bool
+ edge_has_flag (const digraph_edge &input_edge,
+ const char *flag_name) const
+ {
+ auto flags = input_edge.get_property (edge_properties::flags);
+ for (auto iter : *flags)
+ if (auto str = iter->dyn_cast_string ())
+ if (!strcmp (flag_name, str->get_string ()))
+ return true;
+ return false;
+ }
+
+ void
+ print_rows_for_strings (json::array &arr,
+ xml::printer &xp)
+ {
+ for (auto iter : arr)
+ if (auto js_str = iter->dyn_cast_string ())
+ {
+ xp.push_tag ("tr", true);
+ xp.push_tag ("td", true);
+ xp.set_attr ("align", "left");
+ xp.add_text (js_str->get_string ());
+ xp.pop_tag ("td");
+ xp.pop_tag ("tr");
+ }
+ }
+
+ const node *
+ get_bb_node_by_kind (const digraph &dg, const char *kind) const
+ {
+ for (auto &iter : dg.get_all_nodes ())
+ {
+ const node &input_node = *iter.second;
+ if (const char *node_kind = input_node.get_property
(node_properties::kind))
+ if (strcmp (node_kind, "basic_block") == 0)
+ {
+ namespace bb_property_names
+ = custom_sarif_properties::cfg::basic_block;
+ if (const char *bb_kind
+ = input_node.get_property (bb_property_names::kind))
+ {
+ if (strcmp (bb_kind, kind) == 0)
+ return &input_node;
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ const node *
+ get_entry_node (const digraph &dg) const
+ {
+ return get_bb_node_by_kind (dg, "entry");
+ }
+
+ const node *
+ get_exit_node (const digraph &dg) const
+ {
+ return get_bb_node_by_kind (dg, "exit");
+ }
+};
+
+std::unique_ptr<converter>
+make_converter_from_cfg ()
+{
+ return std::make_unique<converter_from_cfg> ();
+}
+
+} // namespace to_dot
+} // namespace digraphs
+} // namespace diagnostics
diff --git a/gcc/diagnostics/digraphs-to-dot.cc
b/gcc/diagnostics/digraphs-to-dot.cc
new file mode 100644
index 0000000000000..030187f1a790f
--- /dev/null
+++ b/gcc/diagnostics/digraphs-to-dot.cc
@@ -0,0 +1,202 @@
+/* Converting directed graphs to dot.
+ 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_ALGORITHM
+#define INCLUDE_MAP
+#define INCLUDE_SET
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+
+#include "graphviz.h"
+#include "xml.h"
+#include "xml-printer.h"
+#include "diagnostics/digraphs.h"
+#include "diagnostics/digraphs-to-dot.h"
+#include "diagnostics/sarif-sink.h"
+
+#include "selftest.h"
+
+namespace diagnostics {
+namespace digraphs {
+namespace to_dot {
+
+using digraph = diagnostics::digraphs::digraph;
+using digraph_node = diagnostics::digraphs::node;
+using digraph_edge = diagnostics::digraphs::edge;
+
+// class conversion_to_dot
+
+std::unique_ptr<dot::graph>
+converter::make_dot_graph_from_diagnostic_graph (const digraph &input_graph)
+{
+ auto output_graph = std::make_unique<dot::graph> ();
+
+ if (const char *description = input_graph.get_description ())
+ output_graph->m_stmt_list.add_attr (dot::id ("label"),
+ dot::id (description));
+
+ const int num_nodes = input_graph.get_num_nodes ();
+ const int num_edges = input_graph.get_num_edges ();
+
+ /* Determine which nodes have in-edges and out-edges. */
+ for (int i = 0; i < num_edges; ++i)
+ {
+ const digraph_edge &input_edge = input_graph.get_edge (i);
+ m_nodes_with_edges.insert (&input_edge.get_src_node ());
+ m_nodes_with_edges.insert (&input_edge.get_dst_node ());
+ }
+
+ for (int i = 0; i < num_nodes; ++i)
+ {
+ const digraph_node &input_node = input_graph.get_node (i);
+ auto dot_node_stmt = make_dot_node_from_digraph_node (input_node);
+ output_graph->m_stmt_list.add_stmt (std::move (dot_node_stmt));
+ }
+
+ for (int i = 0; i < num_edges; ++i)
+ {
+ const digraph_edge &input_edge = input_graph.get_edge (i);
+ auto dot_edge_stmt = make_dot_edge_from_digraph_edge (input_edge);
+ output_graph->m_stmt_list.add_stmt (std::move (dot_edge_stmt));
+ }
+
+ return output_graph;
+}
+
+std::unique_ptr<dot::stmt>
+converter::
+make_dot_node_from_digraph_node (const diagnostics::digraphs::node &input_node)
+{
+ dot::id dot_id (get_dot_id_for_node (input_node));
+
+ /* For now, we can only do either edges or children, not both
+ ...but see https://graphviz.org/docs/attrs/compound/ */
+
+ if (has_edges_p (input_node))
+ {
+ auto output_node
+ = std::make_unique<dot::node_stmt> (std::move (dot_id));
+ m_node_map[&input_node] = output_node.get ();
+ if (const char *label = input_node.get_label ())
+ output_node->set_label (dot::id (label));
+ add_any_node_attrs (input_node, *output_node);
+ return output_node;
+ }
+ else
+ {
+ auto output_node = std::make_unique<dot::subgraph> (std::move (dot_id));
+ m_node_map[&input_node] = output_node.get ();
+ if (const char *label = input_node.get_label ())
+ output_node->add_attr (dot::id ("label"), dot::id (label));
+ add_any_subgraph_attrs (input_node, *output_node);
+ const int num_children = input_node.get_num_children ();
+ for (int i = 0; i < num_children; ++i)
+ {
+ const digraph_node &input_child = input_node.get_child (i);
+ auto dot_child_stmt = make_dot_node_from_digraph_node (input_child);
+ output_node->m_stmt_list.add_stmt (std::move (dot_child_stmt));
+ }
+ return output_node;
+ }
+}
+
+std::unique_ptr<dot::edge_stmt>
+converter::
+make_dot_edge_from_digraph_edge (const digraph_edge &input_edge)
+{
+ const digraph_node &src_dnode = input_edge.get_src_node ();
+ const digraph_node &dst_dnode = input_edge.get_dst_node ();
+ auto output_edge
+ = std::make_unique<dot::edge_stmt> (get_node_id_for_node (src_dnode),
+ get_node_id_for_node (dst_dnode));
+ if (const char *label = input_edge.get_label ())
+ output_edge->set_label (dot::id (label));
+ add_any_edge_attrs (input_edge, *output_edge);
+ return output_edge;
+}
+
+dot::id
+converter::get_dot_id_for_node (const digraph_node &input_node)
+{
+ if (has_edges_p (input_node))
+ return input_node.get_id ();
+ else
+ return std::string ("cluster_") + input_node.get_id ();
+}
+
+dot::node_id
+converter::get_node_id_for_node (const digraph_node &input_node,
+ const char *compass_point)
+{
+ dot::id id = get_dot_id_for_node (input_node);
+ if (compass_point)
+ {
+ enum dot::compass_pt pt;
+ if (dot::get_compass_pt_from_string (compass_point, pt))
+ return dot::node_id (id, pt);
+ }
+ return dot::node_id (id);
+}
+
+bool
+converter::has_edges_p (const digraph_node &input_node)
+{
+ return m_nodes_with_edges.find (&input_node) != m_nodes_with_edges.end ();
+}
+
+void
+converter::add_any_subgraph_attrs (const digraph_node &/*input_node*/,
+ dot::subgraph &/*output_subgraph*/)
+{
+ // No-op
+}
+
+void
+converter::add_any_node_attrs (const digraph_node &/*input_node*/,
+ dot::node_stmt &/*output_node*/)
+{
+ // No-op
+}
+
+void
+converter::add_any_edge_attrs (const digraph_edge &/*input_edge*/,
+ dot::edge_stmt &/*output_edge*/)
+{
+ // No-op
+}
+
+std::unique_ptr<converter>
+converter::make (const diagnostics::digraphs::digraph &dg)
+{
+ if (const char *graph_kind = dg.get_graph_kind ())
+ {
+ // Try to find a suitable converter subclass and use it
+ if (strcmp (graph_kind, "cfg") == 0)
+ return make_converter_from_cfg ();
+ }
+ return std::make_unique<converter> ();
+}
+
+} // namespace to_dot
+} // namespace digraphs
+} // namespace diagnostics
diff --git a/gcc/diagnostics/digraphs-to-dot.h
b/gcc/diagnostics/digraphs-to-dot.h
new file mode 100644
index 0000000000000..5213abf68fc62
--- /dev/null
+++ b/gcc/diagnostics/digraphs-to-dot.h
@@ -0,0 +1,84 @@
+/* Converting directed graphs to dot.
+ 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_DIAGNOSTICS_DIGRAPHS_TO_DOT_H
+#define GCC_DIAGNOSTICS_DIGRAPHS_TO_DOT_H
+
+#include "diagnostics/digraphs.h"
+#include "graphviz.h"
+
+namespace diagnostics {
+namespace digraphs {
+namespace to_dot {
+
+using digraph = diagnostics::digraphs::digraph;
+using digraph_node = diagnostics::digraphs::node;
+using digraph_edge = diagnostics::digraphs::edge;
+
+class converter
+{
+public:
+ static std::unique_ptr<converter>
+ make (const digraph &dg);
+
+ virtual std::unique_ptr<dot::graph>
+ make_dot_graph_from_diagnostic_graph (const digraph &);
+
+ std::unique_ptr<dot::stmt>
+ make_dot_node_from_digraph_node (const digraph_node &);
+
+ std::unique_ptr<dot::edge_stmt>
+ make_dot_edge_from_digraph_edge (const digraph_edge &);
+
+ dot::id
+ get_dot_id_for_node (const digraph_node &);
+
+ dot::node_id
+ get_node_id_for_node (const digraph_node &,
+ const char *compass_point = nullptr);
+
+ bool
+ has_edges_p (const digraph_node &);
+
+ virtual void
+ add_any_subgraph_attrs (const digraph_node &input_node,
+ dot::subgraph &output_subgraph);
+
+ virtual void
+ add_any_node_attrs (const digraph_node &input_node,
+ dot::node_stmt &output_node);
+
+ virtual void
+ add_any_edge_attrs (const digraph_edge &input_edge,
+ dot::edge_stmt &output_edge);
+
+private:
+ std::set<const digraph_node *> m_nodes_with_edges;
+ std::map<const digraph_node *, dot::stmt *> m_node_map;
+};
+
+extern std::unique_ptr<converter>
+make_converter_from_cfg ();
+
+} // namespace to_dot
+} // namespace digraphs
+} // namespace diagnostics
+
+#endif /* ! GCC_DIAGNOSTICS_DIGRAPHS_TO_DOT_H */
diff --git a/gcc/diagnostics/digraphs.cc b/gcc/diagnostics/digraphs.cc
index 59a9af09d3a62..09bfb92f0b393 100644
--- a/gcc/diagnostics/digraphs.cc
+++ b/gcc/diagnostics/digraphs.cc
@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see
#include "graphviz.h"
#include "diagnostics/digraphs.h"
+#include "diagnostics/digraphs-to-dot.h"
#include "diagnostics/sarif-sink.h"
#include "custom-sarif-properties/digraphs.h"
@@ -39,138 +40,6 @@ using digraph_edge = diagnostics::digraphs::edge;
namespace properties = custom_sarif_properties::digraphs;
-namespace {
-
-class conversion_to_dot
-{
-public:
- std::unique_ptr<dot::graph>
- make_dot_graph_from_diagnostic_graph (const digraph &);
-
- std::unique_ptr<dot::stmt>
- make_dot_node_from_digraph_node (const digraph_node &);
-
- std::unique_ptr<dot::edge_stmt>
- make_dot_edge_from_digraph_edge (const digraph_edge &);
-
- dot::id
- get_dot_id_for_node (const digraph_node &);
-
- bool
- has_edges_p (const digraph_node &);
-
-private:
- std::set<const digraph_node *> m_nodes_with_edges;
- std::map<const digraph_node *, dot::stmt *> m_node_map;
-};
-
-} // anonymous namespace
-
-// class conversion_to_dot
-
-std::unique_ptr<dot::graph>
-conversion_to_dot::
-make_dot_graph_from_diagnostic_graph (const diagnostics::digraphs::digraph
&input_graph)
-{
- auto output_graph = std::make_unique<dot::graph> ();
-
- if (const char *description = input_graph.get_description ())
- output_graph->m_stmt_list.add_attr (dot::id ("label"),
- dot::id (description));
-
- const int num_nodes = input_graph.get_num_nodes ();
- const int num_edges = input_graph.get_num_edges ();
-
- /* Determine which nodes have in-edges and out-edges. */
- for (int i = 0; i < num_edges; ++i)
- {
- const digraph_edge &input_edge = input_graph.get_edge (i);
- m_nodes_with_edges.insert (&input_edge.get_src_node ());
- m_nodes_with_edges.insert (&input_edge.get_dst_node ());
- }
-
- for (int i = 0; i < num_nodes; ++i)
- {
- const digraph_node &input_node = input_graph.get_node (i);
- auto dot_node_stmt = make_dot_node_from_digraph_node (input_node);
- output_graph->m_stmt_list.add_stmt (std::move (dot_node_stmt));
- }
-
- for (int i = 0; i < num_edges; ++i)
- {
- const digraph_edge &input_edge = input_graph.get_edge (i);
- auto dot_edge_stmt = make_dot_edge_from_digraph_edge (input_edge);
- output_graph->m_stmt_list.add_stmt (std::move (dot_edge_stmt));
- }
-
- return output_graph;
-}
-
-std::unique_ptr<dot::stmt>
-conversion_to_dot::
-make_dot_node_from_digraph_node (const diagnostics::digraphs::node &input_node)
-{
- dot::id dot_id (get_dot_id_for_node (input_node));
-
- /* For now, we can only do either edges or children, not both
- ...but see https://graphviz.org/docs/attrs/compound/ */
-
- if (has_edges_p (input_node))
- {
- auto output_node
- = std::make_unique<dot::node_stmt> (std::move (dot_id));
- m_node_map[&input_node] = output_node.get ();
- if (const char *label = input_node.get_label ())
- output_node->set_label (dot::id (label));
- return output_node;
- }
- else
- {
- auto output_node = std::make_unique<dot::subgraph> (std::move (dot_id));
- m_node_map[&input_node] = output_node.get ();
- if (const char *label = input_node.get_label ())
- output_node->add_attr (dot::id ("label"), dot::id (label));
- const int num_children = input_node.get_num_children ();
- for (int i = 0; i < num_children; ++i)
- {
- const digraph_node &input_child = input_node.get_child (i);
- auto dot_child_stmt = make_dot_node_from_digraph_node (input_child);
- output_node->m_stmt_list.add_stmt (std::move (dot_child_stmt));
- }
- return output_node;
- }
-}
-
-std::unique_ptr<dot::edge_stmt>
-conversion_to_dot::
-make_dot_edge_from_digraph_edge (const digraph_edge &input_edge)
-{
- const digraph_node &src_dnode = input_edge.get_src_node ();
- const digraph_node &dst_dnode = input_edge.get_dst_node ();
- auto output_edge
- = std::make_unique<dot::edge_stmt>
- (get_dot_id_for_node (src_dnode),
- get_dot_id_for_node (dst_dnode));
- if (const char *label = input_edge.get_label ())
- output_edge->set_label (dot::id (label));
- return output_edge;
-}
-
-dot::id
-conversion_to_dot::get_dot_id_for_node (const digraph_node &input_node)
-{
- if (has_edges_p (input_node))
- return input_node.get_id ();
- else
- return std::string ("cluster_") + input_node.get_id ();
-}
-
-bool
-conversion_to_dot::has_edges_p (const digraph_node &input_node)
-{
- return m_nodes_with_edges.find (&input_node) != m_nodes_with_edges.end ();
-}
-
// class object
/* String properties. */
@@ -299,8 +168,8 @@ digraph::make_json_sarif_graph () const
std::unique_ptr<dot::graph>
digraph::make_dot_graph () const
{
- conversion_to_dot converter;
- return converter.make_dot_graph_from_diagnostic_graph (*this);
+ auto converter = to_dot::converter::make (*this);
+ return converter->make_dot_graph_from_diagnostic_graph (*this);
}
std::unique_ptr<digraph>
diff --git a/gcc/diagnostics/digraphs.h b/gcc/diagnostics/digraphs.h
index 485a18917ca0a..9bf0a3fc96e60 100644
--- a/gcc/diagnostics/digraphs.h
+++ b/gcc/diagnostics/digraphs.h
@@ -226,6 +226,12 @@ class digraph : public object
const char *get_graph_kind () const;
void set_graph_kind (const char *);
+ const std::map<std::string, node *> &
+ get_all_nodes () const
+ {
+ return m_id_to_node_map;
+ }
+
private:
void
add_node_id (std::string node_id, node &new_node)
diff --git a/gcc/diagnostics/html-sink.cc b/gcc/diagnostics/html-sink.cc
index 99d3b9d5dab54..1248560938b55 100644
--- a/gcc/diagnostics/html-sink.cc
+++ b/gcc/diagnostics/html-sink.cc
@@ -137,6 +137,8 @@ public:
html_sink_buffer *buffer);
void emit_diagram (const diagram &d);
void emit_global_graph (const lazily_created<digraphs::digraph> &);
+ void add_graph_for_logical_loc (const lazily_created<digraphs::digraph> &,
+ logical_locations::key);
void end_group ();
@@ -213,6 +215,7 @@ private:
logical_locations::key m_last_logical_location;
location_t m_last_location;
expanded_location m_last_expanded_location;
+ std::map<logical_locations::key, xml::element *> m_per_logical_loc_graphs;
};
static std::unique_ptr<xml::element>
@@ -1323,6 +1326,26 @@ html_builder::emit_global_graph (const
lazily_created<digraphs::digraph> &ldg)
add_graph (dg, *m_body_element);
}
+void
+html_builder::
+add_graph_for_logical_loc (const lazily_created<digraphs::digraph> &ldg,
+ logical_locations::key logical_loc)
+{
+ gcc_assert (m_body_element);
+
+ auto iter = m_per_logical_loc_graphs.find (logical_loc);
+ if (iter == m_per_logical_loc_graphs.end ())
+ {
+ auto logical_loc_element = make_div ("gcc-logical-location");
+ iter = m_per_logical_loc_graphs.insert ({logical_loc,
+ logical_loc_element.get ()}).first;
+ m_body_element->add_child (std::move (logical_loc_element));
+ }
+
+ auto &dg = ldg.get_or_create ();
+ add_graph (dg, *iter->second);
+}
+
/* Implementation of "end_group_cb" for HTML output. */
void
@@ -1449,6 +1472,13 @@ public:
m_builder.emit_global_graph (ldg);
}
+ void
+ report_digraph_for_logical_location (const lazily_created<digraphs::digraph>
&ldg,
+ logical_locations::key logical_loc)
final override
+ {
+ m_builder.add_graph_for_logical_loc (ldg, logical_loc);
+ }
+
const xml::document &get_document () const
{
return m_builder.get_document ();
diff --git a/gcc/diagnostics/sarif-sink.cc b/gcc/diagnostics/sarif-sink.cc
index 76aec2a886c52..0595074829f5b 100644
--- a/gcc/diagnostics/sarif-sink.cc
+++ b/gcc/diagnostics/sarif-sink.cc
@@ -92,6 +92,12 @@ class sarif_array_of_unique : public json::array
obj->set_integer ("index", idx);
}
+ JsonElementType *
+ get_element (size_t i) const
+ {
+ return static_cast<JsonElementType *> ((*this)[i]);
+ }
+
private:
struct comparator_t {
bool operator () (const json::value *a, const json::value *b) const
@@ -797,6 +803,10 @@ public:
void
report_global_digraph (const lazily_created<digraphs::digraph> &);
+ void
+ report_digraph_for_logical_location (const lazily_created<digraphs::digraph>
&ldg,
+ logical_locations::key);
+
std::unique_ptr<sarif_result> take_current_result ()
{
return std::move (m_cur_group_result);
@@ -1943,6 +1953,29 @@ report_global_digraph (const
lazily_created<digraphs::digraph> &ldg)
m_run_graphs->append (make_sarif_graph (dg, this, nullptr));
}
+void
+sarif_builder::
+report_digraph_for_logical_location (const lazily_created<digraphs::digraph>
&ldg,
+ logical_locations::key logical_loc)
+{
+ /* Adding the graph to the logical location itself would break consolidation
+ of logical locations, as the objects would no longer be equal.
+ So we add the graph to the per-run graphs, but add a logicalLocation
+ property to it. */
+
+ auto &dg = ldg.get_or_create ();
+
+ /* Presumably the location manager must be nullptr; see
+ https://github.com/oasis-tcs/sarif-spec/issues/712 */
+ auto graph_obj = make_sarif_graph (dg, this, nullptr);
+
+ auto &bag = graph_obj->get_or_create_properties ();
+ bag.set ("logicalLocation",
+ make_minimal_sarif_logical_location (logical_loc));
+
+ m_run_graphs->append (std::move (graph_obj));
+}
+
/* Create a top-level object, and add it to all the results
(and other entities) we've seen so far, moving ownership
to the object. */
@@ -4043,6 +4076,13 @@ public:
m_builder.report_global_digraph (ldg);
}
+ void
+ report_digraph_for_logical_location (const lazily_created<digraphs::digraph>
&ldg,
+ logical_locations::key key) final
override
+ {
+ m_builder.report_digraph_for_logical_location (ldg, key);
+ }
+
sarif_builder &get_builder () { return m_builder; }
size_t num_results () const { return m_builder.num_results (); }
diff --git a/gcc/diagnostics/sink.h b/gcc/diagnostics/sink.h
index a2094e9f5a560..57ffb5085a5a1 100644
--- a/gcc/diagnostics/sink.h
+++ b/gcc/diagnostics/sink.h
@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3. If not see
#define GCC_DIAGNOSTICS_SINK_H
#include "diagnostic.h"
+#include "diagnostics/logical-locations.h"
namespace diagnostics {
@@ -101,6 +102,10 @@ public:
virtual void
report_global_digraph (const lazily_created<digraphs::digraph> &) = 0;
+ virtual void
+ report_digraph_for_logical_location (const lazily_created<digraphs::digraph>
&,
+ logical_locations::key) = 0;
+
context &get_context () const { return m_context; }
pretty_printer *get_printer () const { return m_printer.get (); }
diff --git a/gcc/diagnostics/text-sink.h b/gcc/diagnostics/text-sink.h
index f280e72cb4e0f..4de121c2fe810 100644
--- a/gcc/diagnostics/text-sink.h
+++ b/gcc/diagnostics/text-sink.h
@@ -85,6 +85,13 @@ public:
// no-op for text
}
+ void
+ report_digraph_for_logical_location (const lazily_created<digraphs::digraph>
&,
+ logical_locations::key) final override
+ {
+ // no-op for text
+ }
+
/* Helpers for writing lang-specific starters/finalizers for text output. */
char *build_prefix (const diagnostic_info &) const;
void report_current_module (location_t where);
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 27a9e2e5e93a1..6ab18851c76cf 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -6311,7 +6311,8 @@ by @code{:} and one or more @var{KEY}=@var{VALUE} pairs,
in this form:
etc.
Schemes, keys, or values with a name prefixed ``experimental'' may change
-or be removed without notice.
+or be removed without notice. Keys can be per-scheme, or related to GCC
+as a whole.
@var{SCHEME} can be
@@ -6321,7 +6322,7 @@ or be removed without notice.
@item text
Emit diagnostics to stderr using GCC's classic text output format.
-Supported keys are:
+Supported keys for the @code{text} scheme are:
@table @gcctabopt
@@ -6351,7 +6352,7 @@ This exists for use by GCC developers.
@item sarif
Emit diagnostics to a file in SARIF format.
-Supported keys are:
+Supported keys for the @code{sarif} scheme are:
@table @gcctabopt
@@ -6391,7 +6392,7 @@ Emit diagnostics to a file in HTML format. This scheme
is experimental,
and may go away in future GCC releases. The keys and details of the output
are also subject to change.
-Supported keys are:
+Supported keys for the @code{experimental-html} scheme are:
@table @gcctabopt
@@ -6440,6 +6441,20 @@ also show a SARIF representation of the state.
@end table
+As well as scheme-specific keys, the following GCC-related key is usable
+on sinks of any scheme:
+
+@table @gcctabopt
+
+@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.
+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.
+
+@end table
+
For example,
@smallexample
diff --git a/gcc/gimple-pretty-print.cc b/gcc/gimple-pretty-print.cc
index 6929cd0bca13c..24bf84800eeb0 100644
--- a/gcc/gimple-pretty-print.cc
+++ b/gcc/gimple-pretty-print.cc
@@ -44,6 +44,9 @@ along with GCC; see the file COPYING3. If not see
#include "asan.h"
#include "cfgloop.h"
#include "gimple-range.h"
+#include "cfghooks.h"
+#include "json.h"
+#include "custom-sarif-properties/cfg.h"
/* Disable warnings about quoting issues in the pp_xxx calls below
that (intentionally) don't follow GCC diagnostic conventions. */
@@ -3204,6 +3207,87 @@ gimple_dump_bb_for_graph (pretty_printer *pp,
basic_block bb)
pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
}
+void
+gimple_dump_bb_as_sarif_properties (diagnostics::sarif_builder *,
+ json::object &output_bag,
+ basic_block bb)
+{
+ namespace bb_properties = custom_sarif_properties::cfg::basic_block;
+ output_bag.set_integer (bb_properties::index, bb->index);
+
+ auto phi_arr = std::make_unique<json::array> ();
+ for (gphi_iterator gsi = gsi_start_phis (bb); !gsi_end_p (gsi);
+ gsi_next (&gsi))
+ {
+ gphi *phi = gsi.phi ();
+ if (!virtual_operand_p (gimple_phi_result (phi))
+ || (dump_flags & TDF_VOPS))
+ {
+ pretty_printer pp;
+ pp_gimple_stmt_1 (&pp, phi, 0, dump_flags);
+ phi_arr->append_string (pp_formatted_text (&pp));
+ }
+ }
+ output_bag.set_array_of_string (bb_properties::gimple::phis,
+ std::move (phi_arr));
+
+ auto stmt_arr = std::make_unique<json::array> ();
+ for (gimple_stmt_iterator gsi = gsi_start_bb (bb); !gsi_end_p (gsi);
+ gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ pretty_printer the_pp;
+ pretty_printer *pp = &the_pp;
+ pp_gimple_stmt_1 (pp, stmt, 0, dump_flags);
+ pp_newline (pp);
+ if (gsi_one_before_end_p (gsi))
+ {
+ /* Compare with gcond part of dump_implicit_edges. */
+ if (safe_is_a <gcond *> (stmt)
+ && (EDGE_COUNT (bb->succs) == 2))
+ {
+ const int indent = 0;
+ edge true_edge, false_edge;
+
+ extract_true_false_edges_from_block (bb, &true_edge, &false_edge);
+ INDENT (indent + 2);
+ pp_cfg_jump (pp, true_edge, dump_flags);
+ newline_and_indent (pp, indent);
+ pp_string (pp, "else");
+ newline_and_indent (pp, indent + 2);
+ pp_cfg_jump (pp, false_edge, dump_flags);
+ pp_newline (pp);
+ }
+ }
+ stmt_arr->append_string (pp_formatted_text (pp));
+ }
+
+ /* Compare with dump_implicit_edges. */
+ {
+ /* If there is a fallthru edge, we may need to add an artificial
+ goto to the dump. */
+ edge e = find_fallthru_edge (bb->succs);
+
+ if (e && (e->dest != bb->next_bb || (dump_flags & TDF_GIMPLE)))
+ {
+ pretty_printer the_pp;
+ pretty_printer *pp = &the_pp;
+ INDENT (0);
+
+ if ((dump_flags & TDF_LINENO)
+ && e->goto_locus != UNKNOWN_LOCATION)
+ dump_location (pp, e->goto_locus);
+
+ pp_cfg_jump (pp, e, dump_flags);
+ pp_newline (pp);
+ stmt_arr->append_string (pp_formatted_text (pp));
+ }
+ }
+
+ output_bag.set_array_of_string (bb_properties::gimple::stmts,
+ std::move (stmt_arr));
+}
+
#if __GNUC__ >= 10
# pragma GCC diagnostic pop
#endif
diff --git a/gcc/gimple-pretty-print.h b/gcc/gimple-pretty-print.h
index 37c76dc7826e4..931247cba1504 100644
--- a/gcc/gimple-pretty-print.h
+++ b/gcc/gimple-pretty-print.h
@@ -23,6 +23,9 @@ along with GCC; see the file COPYING3. If not see
#include "tree-pretty-print.h"
+namespace diagnostics { class sarif_builder; }
+namespace json { class object; }
+
/* In gimple-pretty-print.cc */
extern void debug_gimple_stmt (gimple *);
extern void debug_gimple_seq (gimple_seq);
@@ -35,6 +38,10 @@ extern void pp_gimple_stmt_1 (pretty_printer *, const gimple
*, int,
dump_flags_t);
extern void gimple_dump_bb (FILE *, basic_block, int, dump_flags_t);
extern void gimple_dump_bb_for_graph (pretty_printer *, basic_block);
+extern void
+gimple_dump_bb_as_sarif_properties (diagnostics::sarif_builder *,
+ json::object &,
+ basic_block);
extern void dump_ssaname_info_to_file (FILE *, tree, int);
extern void percent_G_format (text_info *);
diff --git a/gcc/graphviz.cc b/gcc/graphviz.cc
index f8cedc023b86e..01f96919cad07 100644
--- a/gcc/graphviz.cc
+++ b/gcc/graphviz.cc
@@ -353,6 +353,58 @@ kv_stmt::print (writer &w) const
m_kv.print (w);
}
+bool
+get_compass_pt_from_string (const char *str, enum compass_pt &out)
+{
+ if (strcmp (str, "n") == 0)
+ {
+ out = compass_pt::n;
+ return true;
+ }
+ if (strcmp (str, "ne") == 0)
+ {
+ out = compass_pt::ne;
+ return true;
+ }
+ if (strcmp (str, "e") == 0)
+ {
+ out = compass_pt::e;
+ return true;
+ }
+ if (strcmp (str, "se") == 0)
+ {
+ out = compass_pt::se;
+ return true;
+ }
+ if (strcmp (str, "s") == 0)
+ {
+ out = compass_pt::s;
+ return true;
+ }
+ if (strcmp (str, "sw") == 0)
+ {
+ out = compass_pt::sw;
+ return true;
+ }
+ if (strcmp (str, "w") == 0)
+ {
+ out = compass_pt::w;
+ return true;
+ }
+ if (strcmp (str, "nw") == 0)
+ {
+ out = compass_pt::nw;
+ return true;
+ }
+ if (strcmp (str, "c") == 0)
+ {
+ out = compass_pt::c;
+ return true;
+ }
+
+ return false;
+}
+
// struct node_id
void
diff --git a/gcc/graphviz.h b/gcc/graphviz.h
index 9a0fe6f55d3a6..f1be2d2a753d6 100644
--- a/gcc/graphviz.h
+++ b/gcc/graphviz.h
@@ -250,6 +250,9 @@ enum class compass_pt
/* "_" clashes with intl macro */
};
+bool
+get_compass_pt_from_string (const char *str, enum compass_pt &out);
+
/* port : ':' ID [ ':' compass_pt ]
| ':' compass_pt
*/
diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc
index e8fc6d0d170cb..c4805b11fffc2 100644
--- a/gcc/libsarifreplay.cc
+++ b/gcc/libsarifreplay.cc
@@ -2303,8 +2303,9 @@ sarif_replayer::handle_graph_object (const json::object
&graph_json_obj,
id_map edge_id_map;
if (auto properties = maybe_get_property_bag (graph_json_obj))
- private_diagnostic_graph_set_property_bag (*out_graph.m_inner,
- properties->clone_as_object ());
+ private_diagnostic_graph_set_property_bag
+ (*out_graph.m_inner,
+ properties->clone_as_object ());
// ยง3.39.2: MAY contain a "description" property
const property_spec_ref description_prop
diff --git a/gcc/opts-common.cc b/gcc/opts-common.cc
index 379402e68a630..96db04f88601f 100644
--- a/gcc/opts-common.cc
+++ b/gcc/opts-common.cc
@@ -18,6 +18,7 @@ along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#define INCLUDE_STRING
+#define INCLUDE_VECTOR
#include "config.h"
#include "system.h"
#include "intl.h"
diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc
index a230c2119a9f6..ba451e4c99493 100644
--- a/gcc/opts-diagnostic.cc
+++ b/gcc/opts-diagnostic.cc
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3. If not see
#include "config.h"
#define INCLUDE_ARRAY
+#define INCLUDE_LIST
#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
@@ -33,13 +34,43 @@ along with GCC; see the file COPYING3. If not see
#include "diagnostic.h"
#include "diagnostics/output-spec.h"
#include "diagnostics/logging.h"
+#include "diagnostics/sarif-sink.h"
#include "opts.h"
#include "options.h"
+#include "tree-diagnostic-sink-extensions.h"
+#include "opts-diagnostic.h"
+#include "pub-sub.h"
/* Decls. */
namespace {
+class gcc_extra_keys : public diagnostics::output_spec::key_handler
+{
+public:
+ gcc_extra_keys ()
+ : m_cfgs (false)
+ {
+ }
+ enum result
+ maybe_handle_kv (const diagnostics::output_spec::context &ctxt,
+ const std::string &key,
+ const std::string &value) final override
+ {
+ if (key == "cfgs")
+ return parse_bool_value (ctxt, key, value, m_cfgs);
+ return result::unrecognized;
+ }
+
+ void
+ get_keys (auto_vec<const char *> &out_known_keys) const final override
+ {
+ out_known_keys.safe_push ("cfgs");
+ }
+
+ bool m_cfgs;
+};
+
struct opt_spec_context : public diagnostics::output_spec::dc_spec_context
{
public:
@@ -48,10 +79,11 @@ public:
line_maps *location_mgr,
location_t loc,
const char *option_name,
- const char *option_value)
+ const char *option_value,
+ diagnostics::output_spec::key_handler *client_keys)
: dc_spec_context (option_name,
option_value,
- nullptr,
+ client_keys,
location_mgr,
dc,
location_mgr,
@@ -70,8 +102,45 @@ public:
const gcc_options &m_opts;
};
+static void
+handle_gcc_specific_keys (const gcc_extra_keys &gcc_keys,
+ diagnostics::sink &sink,
+ const gcc_extension_factory &ext_factory)
+{
+ if (gcc_keys.m_cfgs)
+ sink.add_extension (ext_factory.make_cfg_extension (sink));
+}
+
+static std::unique_ptr<diagnostics::sink>
+try_to_make_sink (const gcc_options &opts,
+ diagnostics::context &dc,
+ const char *option_name,
+ const char *unparsed_spec,
+ location_t loc)
+{
+ gcc_assert (line_table);
+
+ gcc_extra_keys gcc_keys;
+ opt_spec_context ctxt (opts, dc, line_table, loc, option_name, unparsed_spec,
+ &gcc_keys);
+ auto sink = ctxt.parse_and_make_sink (dc);
+ if (!sink)
+ return nullptr;
+
+ sink->set_main_input_filename (opts.x_main_input_filename);
+
+ if (auto ext_factory = gcc_extension_factory::singleton)
+ handle_gcc_specific_keys (gcc_keys, *sink, *ext_factory);
+
+ return sink;
+}
+
} // anon namespace
+// class gcc_extension_factory
+
+const gcc_extension_factory *gcc_extension_factory::singleton;
+
void
handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts,
diagnostics::context &dc,
@@ -79,18 +148,12 @@ handle_OPT_fdiagnostics_add_output_ (const gcc_options
&opts,
location_t loc)
{
gcc_assert (unparsed_spec);
- gcc_assert (line_table);
const char *const option_name = "-fdiagnostics-add-output=";
DIAGNOSTICS_LOG_SCOPE_PRINTF2 (dc.get_logger (),
"handling: %s%s", option_name, unparsed_spec);
- opt_spec_context ctxt (opts, dc, line_table, loc, option_name,
unparsed_spec);
- auto sink = ctxt.parse_and_make_sink (dc);
- if (!sink)
- return;
-
- sink->set_main_input_filename (opts.x_main_input_filename);
- dc.add_sink (std::move (sink));
+ if (auto sink = try_to_make_sink (opts, dc, option_name, unparsed_spec, loc))
+ dc.add_sink (std::move (sink));
}
void
@@ -100,16 +163,10 @@ handle_OPT_fdiagnostics_set_output_ (const gcc_options
&opts,
location_t loc)
{
gcc_assert (unparsed_spec);
- gcc_assert (line_table);
const char *const option_name = "-fdiagnostics-set-output=";
DIAGNOSTICS_LOG_SCOPE_PRINTF2 (dc.get_logger (),
"handling: %s%s", option_name, unparsed_spec);
- opt_spec_context ctxt (opts, dc, line_table, loc, option_name,
unparsed_spec);
- auto sink = ctxt.parse_and_make_sink (dc);
- if (!sink)
- return;
-
- sink->set_main_input_filename (opts.x_main_input_filename);
- dc.set_sink (std::move (sink));
+ if (auto sink = try_to_make_sink (opts, dc, option_name, unparsed_spec, loc))
+ dc.set_sink (std::move (sink));
}
diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h
index 25ade867ef24c..4254d23cb8bdf 100644
--- a/gcc/opts-diagnostic.h
+++ b/gcc/opts-diagnostic.h
@@ -20,6 +20,8 @@ along with GCC; see the file COPYING3. If not see
#ifndef GCC_OPTS_DIAGNOSTIC_H
#define GCC_OPTS_DIAGNOSTIC_H
+#include "diagnostics/sink.h"
+
/* Abstract subclass of diagnostics::option_id_manager for gcc options. */
class gcc_diagnostic_option_id_manager : public diagnostics::option_id_manager
@@ -61,6 +63,18 @@ private:
void *m_opts;
};
+class gcc_extension_factory
+{
+public:
+ virtual ~gcc_extension_factory () {}
+
+ virtual std::unique_ptr<diagnostics::sink::extension>
+ make_cfg_extension (diagnostics::sink &sink) const = 0;
+
+ static const gcc_extension_factory *singleton;
+};
+
+
extern void
handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts,
diagnostics::context &dc,
diff --git a/gcc/opts.cc b/gcc/opts.cc
index ceb1e0f445b15..0fb156728c8fc 100644
--- a/gcc/opts.cc
+++ b/gcc/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"
diff --git a/gcc/print-rtl.cc b/gcc/print-rtl.cc
index 033f7e7aab0da..1b1b3c93bb8de 100644
--- a/gcc/print-rtl.cc
+++ b/gcc/print-rtl.cc
@@ -51,8 +51,13 @@ along with GCC; see the file COPYING3. If not see
#include "pretty-print.h"
#endif
+#include "dumpfile.h"
+#include "cfghooks.h"
#include "print-rtl.h"
#include "rtl-iter.h"
+#include "json.h"
+
+#include "custom-sarif-properties/cfg.h"
/* Disable warnings about quoting issues in the pp_xxx calls below
that (intentionally) don't follow GCC diagnostic conventions. */
@@ -2146,6 +2151,26 @@ rtl_dump_bb_for_graph (pretty_printer *pp, basic_block
bb)
}
}
+
+void
+rtl_dump_bb_as_sarif_properties (diagnostics::sarif_builder *,
+ json::object &output_bag,
+ basic_block bb)
+{
+ /* TODO: inter-bb stuff. */
+ auto json_insn_arr = std::make_unique<json::array> ();
+ rtx_insn *insn;
+ FOR_BB_INSNS (bb, insn)
+ {
+ pretty_printer pp;
+ print_insn_with_notes (&pp, insn);
+ json_insn_arr->append_string (pp_formatted_text (&pp));
+ }
+ output_bag.set_array_of_string
+ (custom_sarif_properties::cfg::basic_block::rtl::insns,
+ std::move (json_insn_arr));
+}
+
/* Pretty-print pattern X of some insn in non-verbose mode.
Return a string pointer to the pretty-printer buffer.
diff --git a/gcc/print-rtl.h b/gcc/print-rtl.h
index 7eacd097b774b..aa23508539924 100644
--- a/gcc/print-rtl.h
+++ b/gcc/print-rtl.h
@@ -24,6 +24,9 @@ along with GCC; see the file COPYING3. If not see
#include "bitmap.h"
#endif /* #ifndef GENERATOR_FILE */
+namespace diagnostics { class sarif_builder; }
+namespace json { class object; }
+
class rtx_reuse_manager;
/* A class for writing rtx to a FILE *. */
@@ -90,6 +93,10 @@ extern void print_insn (pretty_printer *pp, const rtx_insn
*x, int verbose);
extern void print_insn_with_notes (pretty_printer *, const rtx_insn *);
extern void rtl_dump_bb_for_graph (pretty_printer *, basic_block);
+extern void
+rtl_dump_bb_as_sarif_properties (diagnostics::sarif_builder *,
+ json::object &,
+ basic_block);
extern const char *str_pattern_slim (const_rtx);
extern void print_rtx_function (FILE *file, function *fn, bool compact);
diff --git a/gcc/testsuite/gcc.dg/diagnostic-cfgs-html.py
b/gcc/testsuite/gcc.dg/diagnostic-cfgs-html.py
new file mode 100644
index 0000000000000..4cd5234dacb0c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/diagnostic-cfgs-html.py
@@ -0,0 +1,21 @@
+from htmltest import *
+
+import pytest
+
[email protected](scope='function', autouse=True)
+def html_tree():
+ return html_tree_from_env()
+
+def test_results(html_tree):
+ root = html_tree.getroot ()
+ assert root.tag == make_tag('html')
+
+ head = root.find('xhtml:head', ns)
+ assert head is not None
+
+ body = root.find('xhtml:body', ns)
+ assert body is not None
+
+ logical_loc = body.find("./xhtml:div[@class='gcc-logical-location']", ns)
+ assert len(logical_loc)
+ logical_loc.find('xhtml:svg', ns)
diff --git a/gcc/testsuite/gcc.dg/diagnostic-cfgs-sarif.py
b/gcc/testsuite/gcc.dg/diagnostic-cfgs-sarif.py
new file mode 100644
index 0000000000000..0dfca4b4586aa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/diagnostic-cfgs-sarif.py
@@ -0,0 +1,84 @@
+from sarif import *
+
+import pytest
+
[email protected](scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def get_graph_by_description(sarif, desc):
+ runs = sarif['runs']
+ run = runs[0]
+ graphs = run['graphs']
+ for g in graphs:
+ if g['description']['text'] == desc:
+ return g
+
+def test_graphs(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+ graphs = run['graphs']
+
+ # We expect many CFGs, but let's not hardcode the number
+ assert len(graphs) > 20
+
+def test_ssa(sarif):
+ ssa = get_graph_by_description(sarif, 'test: ssa')
+ assert ssa["properties"]["gcc/digraphs/graph/kind"] == "cfg"
+ assert ssa["properties"]["gcc/cfg/graph/pass_name"] == "ssa"
+ assert type(ssa["properties"]["gcc/cfg/graph/pass_number"]) == int
+ assert ssa["properties"]["logicalLocation"]["fullyQualifiedName"] == "test"
+ nodes = ssa["nodes"]
+ assert len(nodes) == 1
+ root_node = nodes[0]
+ assert root_node['id'] == 'test'
+ assert root_node['properties']["gcc/cfg/node/kind"] == "function"
+
+ assert len(root_node['children']) >= 3
+ entry = root_node['children'][0]
+ assert entry['properties']["gcc/cfg/node/kind"] == "basic_block"
+ assert entry['properties']["gcc/cfg/basic_block/kind"] == "entry"
+ assert entry['properties']["gcc/cfg/basic_block/index"] == 0
+ assert entry['properties']["gcc/cfg/basic_block/gimple/phis"] == []
+ assert entry['properties']["gcc/cfg/basic_block/gimple/stmts"] == []
+
+ exit = root_node['children'][1]
+ assert exit['properties']["gcc/cfg/node/kind"] == "basic_block"
+ assert exit['properties']["gcc/cfg/basic_block/kind"] == "exit"
+ assert exit['properties']["gcc/cfg/basic_block/index"] == 1
+ assert exit['properties']["gcc/cfg/basic_block/gimple/phis"] == []
+ assert exit['properties']["gcc/cfg/basic_block/gimple/stmts"] == []
+
+ block = root_node['children'][2]
+ assert block['properties']["gcc/cfg/node/kind"] == "basic_block"
+ assert block['properties']["gcc/cfg/basic_block/index"] == 2
+ assert block['properties']["gcc/cfg/basic_block/gimple/phis"] == []
+ cond = block['properties']["gcc/cfg/basic_block/gimple/stmts"][0]
+ assert cond.startswith("if (i_")
+
+ return_block = root_node['children'][-1]
+ phis = return_block['properties']["gcc/cfg/basic_block/gimple/phis"]
+ assert len(phis) == 1
+ assert '= PHI' in phis[0]
+
+ edges = ssa["edges"]
+ assert len(edges) >= 4
+
+def test_expand(sarif):
+ ssa = get_graph_by_description(sarif, 'test: expand')
+ assert ssa["properties"]["gcc/digraphs/graph/kind"] == "cfg"
+ assert ssa["properties"]["logicalLocation"]["fullyQualifiedName"] == "test"
+ nodes = ssa["nodes"]
+ assert len(nodes) == 1
+ root_node = nodes[0]
+ assert root_node['id'] == 'test'
+ assert root_node['properties']["gcc/cfg/node/kind"] == "function"
+
+ assert len(root_node['children']) >= 3
+ entry = root_node['children'][0]
+ assert entry['properties']["gcc/cfg/node/kind"] == "basic_block"
+ assert entry['properties']["gcc/cfg/basic_block/kind"] == "entry"
+ assert entry['properties']["gcc/cfg/basic_block/rtl/insns"] == []
+
+ edges = ssa["edges"]
+ assert len(edges) >= 4
diff --git a/gcc/testsuite/gcc.dg/diagnostic-cfgs.c
b/gcc/testsuite/gcc.dg/diagnostic-cfgs.c
new file mode 100644
index 0000000000000..99740655b613a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/diagnostic-cfgs.c
@@ -0,0 +1,18 @@
+/* Verify that the "cfgs=yes" option to diagnostic sinks works. */
+
+/* { dg-do compile } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:cfgs=yes" } */
+/* { dg-additional-options
"-fdiagnostics-add-output=experimental-html:javascript=no,cfgs=yes" } */
+
+int
+test (int i, int j, int k)
+{
+ if (i)
+ return j + k;
+ else
+ return j - k;
+}
+
+/* { dg-final { verify-sarif-file } }
+ { dg-final { run-sarif-pytest diagnostic-cfgs.c "diagnostic-cfgs-sarif.py"
} }
+ { dg-final { run-html-pytest diagnostic-cfgs.c "diagnostic-cfgs-html.py" }
} */
diff --git a/gcc/tree-cfg.cc b/gcc/tree-cfg.cc
index 62513045c6f0b..a0aba061c1f70 100644
--- a/gcc/tree-cfg.cc
+++ b/gcc/tree-cfg.cc
@@ -9333,6 +9333,7 @@ struct cfg_hooks gimple_cfg_hooks = {
gimple_verify_flow_info,
gimple_dump_bb, /* dump_bb */
gimple_dump_bb_for_graph, /* dump_bb_for_graph */
+ gimple_dump_bb_as_sarif_properties,
create_bb, /* create_basic_block */
gimple_redirect_edge_and_branch, /* redirect_edge_and_branch */
gimple_redirect_edge_and_branch_force, /* redirect_edge_and_branch_force */
diff --git a/gcc/tree-diagnostic-cfg.cc b/gcc/tree-diagnostic-cfg.cc
new file mode 100644
index 0000000000000..dad7fa6d43f1a
--- /dev/null
+++ b/gcc/tree-diagnostic-cfg.cc
@@ -0,0 +1,390 @@
+/* Generating diagnostics graphs from GCC CFGs.
+ 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_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"
+
+
+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;
+}
+
+/* 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 cfg_diagnostic_digraph
+ : public lazily_created<diagnostics::digraphs::digraph>
+{
+public:
+ cfg_diagnostic_digraph (function *fun,
+ opt_pass *pass)
+ : m_fun (fun), m_pass (pass)
+ {}
+
+ std::unique_ptr<diagnostics::digraphs::digraph>
+ create_object () const final override
+ {
+ auto g = std::make_unique<diagnostics::digraphs::digraph> ();
+ g->set_graph_kind ("cfg");
+
+ pretty_printer pp;
+ pp_printf (&pp, "%s: %s", function_name (m_fun), m_pass->name);
+ g->set_description (pp_formatted_text (&pp));
+
+ g->set_property (graph_properties::pass_name, m_pass->name);
+ g->set_property (graph_properties::pass_number,
m_pass->static_pass_number);
+
+ add_cluster_for_function (*g, m_fun);
+ return g;
+ }
+
+ 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));
+ }
+
+private:
+ 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,
+ 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;
+};
+
+#if __GNUC__ >= 10
+# pragma GCC diagnostic pop
+#endif
+
+namespace pass_events = gcc::topics::pass_events;
+
+/* A diagnostics::sink::extension which subscribes to pass_events
+ and responds to "after_pass" events by adding a diagnostics digraph
+ for the CFG for the relevant function. */
+
+class compiler_capture_cfgs : public diagnostics::sink::extension
+{
+public:
+ compiler_capture_cfgs (diagnostics::sink &sink)
+ : extension (sink),
+ m_event_subscriber (sink)
+ {
+ g->get_channels ().pass_events_channel.add_subscriber (m_event_subscriber);
+ }
+
+ void
+ dump (FILE *out, int indent) const
+ {
+ diagnostics::dumping::emit_heading (out, indent, "compiler_capture_cfgs");
+ }
+
+private:
+ class event_subscriber : public pass_events::subscriber
+ {
+ public:
+ event_subscriber (diagnostics::sink &sink) : m_sink (sink) {}
+ void on_message (const pass_events::before_pass &) final override
+ {
+ }
+ void on_message (const pass_events::after_pass &m) final override
+ {
+ if (m.fun
+ && m.fun->cfg
+ && m.pass->static_pass_number > 0)
+ m_sink.report_digraph_for_logical_location
+ (cfg_diagnostic_digraph (m.fun, m.pass),
+ tree_logical_location_manager::key_from_tree (m.fun->decl));
+ }
+
+ private:
+ diagnostics::sink &m_sink;
+ } m_event_subscriber;
+};
+
+std::unique_ptr<diagnostics::sink::extension>
+compiler_extension_factory::make_cfg_extension (diagnostics::sink &sink) const
+{
+ return std::make_unique<compiler_capture_cfgs> (sink);
+}
diff --git a/gcc/tree-diagnostic-sink-extensions.h
b/gcc/tree-diagnostic-sink-extensions.h
new file mode 100644
index 0000000000000..bd77b1a562f3d
--- /dev/null
+++ b/gcc/tree-diagnostic-sink-extensions.h
@@ -0,0 +1,32 @@
+/* Compiler-specific implementation of GCC extensions to diagnostic output.
+ 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 TREE_DIAGNOSTIC_SINK_EXTENSIONS_H
+#define TREE_DIAGNOSTIC_SINK_EXTENSIONS_H
+
+#include "opts-diagnostic.h"
+
+class compiler_extension_factory : public gcc_extension_factory
+{
+public:
+ std::unique_ptr<diagnostics::sink::extension>
+ make_cfg_extension (diagnostics::sink &sink) const final override;
+};
+
+#endif /* TREE_DIAGNOSTIC_SINK_EXTENSIONS_H */
diff --git a/gcc/tree-diagnostic.cc b/gcc/tree-diagnostic.cc
index 4cf742d047d93..588ad1120191d 100644
--- a/gcc/tree-diagnostic.cc
+++ b/gcc/tree-diagnostic.cc
@@ -19,6 +19,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"
@@ -32,6 +33,7 @@ along with GCC; see the file COPYING3. If not see
#include "langhooks.h"
#include "intl.h"
#include "diagnostics/text-sink.h"
+#include "tree-diagnostic-sink-extensions.h"
/* Prints out, if necessary, the name of the current function
that caused an error. */
@@ -174,6 +176,8 @@ set_inlining_locations (const diagnostics::context &,
*ao = (tree)diagnostic->m_iinfo.m_ao;
}
+static const compiler_extension_factory compiler_ext_factory;
+
/* Sets CONTEXT to use language independent diagnostics. */
void
tree_diagnostics_defaults (diagnostics::context *context)
@@ -183,4 +187,5 @@ tree_diagnostics_defaults (diagnostics::context *context)
context->set_format_decoder (default_tree_printer);
context->set_set_locations_callback (set_inlining_locations);
context->set_client_data_hooks (make_compiler_data_hooks ());
+ gcc_extension_factory::singleton = &compiler_ext_factory;
}
--
2.26.3