https://gcc.gnu.org/g:e20eee3897ae8cd0f2212dad0710d64df8f1a956
commit r16-6665-ge20eee3897ae8cd0f2212dad0710d64df8f1a956 Author: David Malcolm <[email protected]> Date: Fri Jan 9 15:54:15 2026 -0500 diagnostics: add optional CFG dumps to SARIF/HTML output sinks 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]> Diff: --- 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(-) diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 8878eaa9c84b..4bd05a60cf20 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1749,6 +1749,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 \ @@ -1869,6 +1870,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 \ @@ -1878,6 +1880,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 f9cffb168c09..4596ab2ed854 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 ac09bebdca92..e2e81396b5b2 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 5ab3c076bf32..93bda48cab65 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 000000000000..f03a39d6b780 --- /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 000000000000..04776e55f330 --- /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 000000000000..f1b0b239a074 --- /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 000000000000..030187f1a790 --- /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 000000000000..5213abf68fc6 --- /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 71423733fdf5..9063f7cc7f36 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 54482dda720d..682d980b6abc 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 a51ef257bd6a..33cbb8c24132 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 4e869faa8ced..78743df7f1db 100644 --- a/gcc/diagnostics/sarif-sink.cc +++ b/gcc/diagnostics/sarif-sink.cc @@ -97,6 +97,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 @@ -802,6 +808,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); @@ -1948,6 +1958,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. */ @@ -4048,6 +4081,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 62e1bb64e8c5..bf33037f963f 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 18eddea96318..b756a49d0471 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 525d8fec5dc3..46d537058701 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -6303,7 +6303,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 @@ -6313,7 +6314,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 @@ -6343,7 +6344,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 @@ -6383,7 +6384,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 @@ -6432,6 +6433,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 935e2b387aeb..4b27b9701736 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 84a48d1a705d..8013f2d59095 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 fb775eb054aa..02ff8659fb21 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 0a3ce3c44393..41abcfc63b2e 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 16ff57ca97d2..01d3a975f62f 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 6b5f6dcc898c..df03c39b10cd 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 5a8fc6120e77..f60628244489 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 a03c7b3e707c..f496ef04912c 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 2b953755410b..6323fd588016 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 07b5a1801d38..976a060be7f8 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 34145c8e4dd0..5b6ad578302a 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 000000000000..4cd5234dacb0 --- /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 000000000000..0dfca4b4586a --- /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 000000000000..99740655b613 --- /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 649c8558d94a..f9a71f0a8be4 100644 --- a/gcc/tree-cfg.cc +++ b/gcc/tree-cfg.cc @@ -9362,6 +9362,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 000000000000..dad7fa6d43f1 --- /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 000000000000..bd77b1a562f3 --- /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 1ec6af0530a6..06d7bcc7c9b0 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; }
