blob: 0e28d4d9b3497a4dc322d6db75f2c9dc62c822f6 [file] [log] [blame]
/* "Supergraph" classes that combine CFGs and callgraph into one digraph.
Copyright (C) 2019-2025 Free Software Foundation, Inc.
Contributed by David Malcolm <dmalcolm@redhat.com>.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#define INCLUDE_DEQUE
#include "analyzer/common.h"
#include "timevar.h"
#include "gimple-pretty-print.h"
#include "ordered-hash-map.h"
#include "options.h"
#include "cgraph.h"
#include "cfg.h"
#include "digraph.h"
#include "tree-cfg.h"
#include "cfganal.h"
#include "except.h"
#include "diagnostics/file-cache.h"
#include "analyzer/supergraph.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/region-model.h"
#include "analyzer/exploded-graph.h"
#if ENABLE_ANALYZER
namespace ana {
/* Get the function of the ultimate alias target being called at EDGE,
if any. */
function *
get_ultimate_function_for_cgraph_edge (cgraph_edge *edge)
{
cgraph_node *ultimate_node = edge->callee->ultimate_alias_target ();
if (!ultimate_node)
return nullptr;
return ultimate_node->get_fun ();
}
/* class saved_uids.
In order to ensure consistent results without relying on the ordering
of pointer values we assign a uid to each gimple stmt, globally unique
across all functions.
Normally, the stmt uids are a scratch space that each pass can freely
assign its own values to. However, in the case of LTO, the uids are
used to associate call stmts with callgraph edges between the WPA phase
(where the analyzer runs in LTO mode) and the LTRANS phase; if the
analyzer changes them in the WPA phase, it leads to errors when
streaming the code back in at LTRANS.
lto_prepare_function_for_streaming has code to renumber the stmt UIDs
when the code is streamed back out, but for some reason this isn't
called for clones.
Hence, as a workaround, this class has responsibility for tracking
the original uids and restoring them once the pass is complete
(in the supergraph dtor). */
/* Give STMT a globally unique uid, storing its original uid so it can
later be restored. */
void
saved_uids::make_uid_unique (gimple *stmt)
{
unsigned next_uid = m_old_stmt_uids.length ();
unsigned old_stmt_uid = stmt->uid;
stmt->uid = next_uid;
m_old_stmt_uids.safe_push
(std::pair<gimple *, unsigned> (stmt, old_stmt_uid));
}
/* Restore the saved uids of all stmts. */
void
saved_uids::restore_uids () const
{
unsigned i;
std::pair<gimple *, unsigned> *pair;
FOR_EACH_VEC_ELT (m_old_stmt_uids, i, pair)
pair->first->uid = pair->second;
}
/* When building the supergraph, should STMT be handled
along each out-edge in the CFG, or as separate superedge
"within" the BB. */
static bool
control_flow_stmt_p (const gimple &stmt)
{
switch (gimple_code (&stmt))
{
case GIMPLE_COND:
case GIMPLE_EH_DISPATCH:
case GIMPLE_GOTO:
case GIMPLE_SWITCH:
return true;
case GIMPLE_ASM:
case GIMPLE_ASSIGN:
case GIMPLE_CALL:
case GIMPLE_DEBUG:
case GIMPLE_LABEL:
case GIMPLE_NOP:
case GIMPLE_PREDICT:
case GIMPLE_RESX:
case GIMPLE_RETURN:
return false;
/* We don't expect to see any other statement kinds in the analyzer. */
default:
internal_error ("unexpected gimple stmt code: %qs",
gimple_code_name[gimple_code (&stmt)]);
break;
}
}
/* supergraph's ctor. Walk the callgraph, building supernodes for each
CFG basic block, splitting the basic blocks at statements. Join
together the supernodes with interprocedural superedges as appropriate.
Assign UIDs to the gimple stmts. */
supergraph::supergraph (region_model_manager &mgr,
logger *logger)
: m_next_snode_id (0)
{
auto_timevar tv (TV_ANALYZER_SUPERGRAPH);
LOG_FUNC (logger);
/* For each BB, if present, the stmt that terminates it. */
typedef ordered_hash_map<basic_block, gimple *> bb_to_stmt_t;
bb_to_stmt_t control_stmt_ending_bbs;
/* First pass: make supernodes (and assign UIDs to the gimple stmts). */
{
/* Sort the cgraph_nodes? */
cgraph_node *node;
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
{
function *fun = node->get_fun ();
log_nesting_level log_sentinel (logger, "function: %qD", fun->decl);
/* Ensure that EDGE_DFS_BACK is correct for every CFG edge in
the supergraph (by doing it per-function). */
auto_cfun cfun_sentinel (fun);
mark_dfs_back_edges ();
const int start_id = m_nodes.length ();
basic_block bb;
FOR_ALL_BB_FN (bb, fun)
if (gimple *final_control_stmt
= populate_for_basic_block (bb, fun, logger))
control_stmt_ending_bbs.put (bb, final_control_stmt);
const unsigned num_snodes = m_nodes.length () - start_id;
m_function_to_num_snodes.put (fun, num_snodes);
if (logger)
{
const int end_id = m_nodes.length () - 1;
logger->log ("SN: %i...%i: function %qD",
start_id, end_id, fun->decl);
}
}
}
/* Second pass: make superedges between basic blocks. */
{
/* Make superedges for CFG edges. */
for (bb_to_node_t::iterator iter = m_bb_to_final_node.begin ();
iter != m_bb_to_final_node.end ();
++iter)
{
basic_block bb = (*iter).first;
supernode *src_supernode = (*iter).second;
gimple *control_stmt_ending_bb = nullptr;
if (auto control_stmt_iter = control_stmt_ending_bbs.get (bb))
control_stmt_ending_bb = *control_stmt_iter;
::edge cfg_edge;
int idx;
if (bb->succs)
FOR_EACH_VEC_ELT (*bb->succs, idx, cfg_edge)
{
basic_block dest_cfg_block = cfg_edge->dest;
supernode *dest_supernode
= *m_bb_to_initial_node.get (dest_cfg_block);
add_sedges_for_cfg_edge (src_supernode,
dest_supernode,
cfg_edge,
control_stmt_ending_bb,
mgr,
logger);
}
}
}
}
/* Create a run of supernodes and superedges for the BB within FUN
expressing all of the stmts apart from the final control flow stmt (if any).
Return the control stmt that ends this bb, if any. */
gimple *
supergraph::populate_for_basic_block (basic_block bb,
function *fun,
logger *logger)
{
log_nesting_level sentinel (logger, "bb %i", bb->index);
supernode *initial_snode_in_bb = add_node (fun, bb, logger);
m_bb_to_initial_node.put (bb, initial_snode_in_bb);
if (bb->index == ENTRY_BLOCK)
/* Use the decl's location, rather than fun->function_start_locus,
which leads to more readable output. */
initial_snode_in_bb->m_loc = DECL_SOURCE_LOCATION (fun->decl);
else if (bb->index == EXIT_BLOCK)
initial_snode_in_bb->m_loc = fun->function_end_locus;
else if (gsi_end_p (gsi_start_bb (bb)))
{
/* BB has no stmts, and isn't the ENTRY or EXIT node.
Try to find a source location for it. */
if (bb->succs->length () == 1)
{
auto outedge = (*bb->succs)[0];
if (useful_location_p (outedge->goto_locus))
{
/* We have an empty basic block with one out-edge,
perhaps part of an empty infinite loop. */
if (logger)
logger->log ("using location 0x%lx from outedge",
outedge->goto_locus);
initial_snode_in_bb->m_loc = outedge->goto_locus;
}
}
}
initial_snode_in_bb->m_state_merger_node = true;
gimple *final_control_flow_stmt = nullptr;
/* Create a run of supernodes for the stmts in BB,
connected by stmt_superedge. */
gimple_stmt_iterator gsi;
supernode *prev_snode_in_bb = initial_snode_in_bb;
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
{
gimple *next_stmt = gsi_stmt (gsi);
if (logger)
{
logger->start_log_line ();
logger->log_partial ("next_stmt: ");
pp_gimple_stmt_1 (logger->get_printer (), next_stmt,
0, (dump_flags_t)0);
logger->end_log_line ();
}
prev_snode_in_bb->m_loc = get_stmt_location (next_stmt, fun);
prev_snode_in_bb->m_stmt_loc = next_stmt->location;
m_node_for_stmt.insert ({next_stmt, prev_snode_in_bb});
m_stmt_uids.make_uid_unique (next_stmt);
if (auto glabel_ = dyn_cast<const glabel *>(next_stmt))
{
/* Associate the GIMPLE_LABEL with its snode. */
prev_snode_in_bb->m_label = gimple_label_label (glabel_);
/* Only create an snode for the label if it has location
information. */
if (glabel_->location == UNKNOWN_LOCATION)
continue;
}
// handle control flow stmts on the edges
if (control_flow_stmt_p (*next_stmt))
{
final_control_flow_stmt = next_stmt;
break;
}
supernode *snode_after_next_stmt = add_node (fun, bb, logger);
if (prev_snode_in_bb)
{
std::unique_ptr<operation> op;
switch (gimple_code (next_stmt))
{
default:
gcc_unreachable ();
break;
case GIMPLE_ASM:
op = std::make_unique<gasm_op>
(*as_a <const gasm *> (next_stmt));
break;
case GIMPLE_ASSIGN:
op = std::make_unique<gassign_op>
(*as_a <const gassign *> (next_stmt));
break;
case GIMPLE_CALL:
op = call_and_return_op::make
(*as_a <const gcall *> (next_stmt));
break;
case GIMPLE_PREDICT:
op = std::make_unique<predict_op> (*next_stmt);
break;
case GIMPLE_RESX:
op = std::make_unique<resx_op>
(*as_a <const gresx *> (next_stmt));
break;
case GIMPLE_RETURN:
op = std::make_unique<greturn_op>
(*as_a <const greturn *> (next_stmt));
break;
case GIMPLE_DEBUG:
case GIMPLE_LABEL:
case GIMPLE_NOP:
/* Treat all of these as no-ops within analyzer; though
perhaps we care about their locations. */
break;
}
superedge *sedge
= new superedge (prev_snode_in_bb,
snode_after_next_stmt,
std::move (op),
nullptr);
add_edge (sedge);
}
prev_snode_in_bb = snode_after_next_stmt;
}
m_bb_to_final_node.put (bb, prev_snode_in_bb);
return final_control_flow_stmt;
}
/* supergraph's dtor. Reset stmt uids. */
supergraph::~supergraph ()
{
m_stmt_uids.restore_uids ();
}
/* Dump this graph in .dot format to PP, using DUMP_ARGS.
Cluster the supernodes by function, then by BB from original CFG. */
void
supergraph::dump_dot_to_pp (pretty_printer *pp,
const dump_args_t &dump_args) const
{
graphviz_out gv (pp);
pp_string (pp, "digraph \"");
pp_write_text_to_stream (pp);
pp_string (pp, "supergraph");
pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
pp_string (pp, "\" {\n");
gv.indent ();
gv.println ("overlap=false;");
gv.println ("compound=true;");
/* TODO: maybe (optionally) sub-subdivide by TU, for LTO; see also:
https://gcc-python-plugin.readthedocs.io/en/latest/_images/sample-supergraph.png
*/
/* Break out the supernodes into clusters by function. */
{
cgraph_node *node;
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
{
function *fun = node->get_fun ();
gcc_assert (fun);
auto_cfun sentinel (fun);
const char *funcname = function_name (fun);
gv.println ("subgraph \"cluster_%s\" {",
funcname);
gv.indent ();
pp_printf (pp,
("style=\"dashed\";"
" color=\"black\";"
" label=\"%s\";\n"),
funcname);
if (loops_for_fn (fun))
dump_dot_to_gv_for_loop (gv, dump_args, get_loop (fun, 0), fun);
else
{
basic_block bb;
FOR_ALL_BB_FN (bb, fun)
dump_dot_to_gv_for_bb (gv, dump_args, bb, fun);
}
/* Terminate per-function "subgraph" */
gv.outdent ();
gv.println ("}");
}
}
/* Superedges. */
int i;
superedge *e;
FOR_EACH_VEC_ELT (m_edges, i, e)
e->dump_dot (&gv, dump_args);
if (dump_args.m_node_annotator)
dump_args.m_node_annotator->add_extra_objects (&gv);
/* Terminate "digraph" */
gv.outdent ();
gv.println ("}");
}
/* Recursively dump all the snodes within LOOP and the loops
within it. */
void
supergraph::dump_dot_to_gv_for_loop (graphviz_out &gv,
const dump_args_t &dump_args,
class loop *loop,
function *fun) const
{
pretty_printer *pp = gv.get_pp ();
basic_block *body;
unsigned int i;
// Adapted from graph.cc:draw_cfg_nodes_for_loop
const char *fillcolors[3] = { "grey88", "grey77", "grey66" };
if (loop->header != NULL
&& loop->latch != EXIT_BLOCK_PTR_FOR_FN (cfun))
pp_printf (pp,
"\tsubgraph cluster_%d_%d {\n"
"\tstyle=\"filled\";\n"
"\tcolor=\"darkgreen\";\n"
"\tfillcolor=\"%s\";\n"
"\tlabel=\"loop %d\";\n"
"\tlabeljust=l;\n"
"\tpenwidth=2;\n",
fun->funcdef_no, loop->num,
fillcolors[(loop_depth (loop) - 1) % 3],
loop->num);
// Recurse
for (class loop *inner = loop->inner; inner; inner = inner->next)
dump_dot_to_gv_for_loop (gv, dump_args, inner, fun);
if (loop->header == NULL)
return;
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)
dump_dot_to_gv_for_bb (gv, dump_args, bb, fun);
}
free (body);
if (loop->latch != EXIT_BLOCK_PTR_FOR_FN (cfun))
pp_printf (pp, "\t}\n");
}
/* Dump all the snodes from BB. */
void
supergraph::dump_dot_to_gv_for_bb (graphviz_out &gv,
const dump_args_t &dump_args,
basic_block bb,
function *fun) const
{
pretty_printer *pp = gv.get_pp ();
if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
{
const char *funcname = function_name (fun);
gv.println ("subgraph \"cluster_%s_bb_%i\" {",
funcname, bb->index);
gv.indent ();
pp_printf (pp,
("style=\"dashed\";"
" color=\"black\";"
" label=\"bb: %i\";\n"),
bb->index);
}
// TODO: maybe keep an index per-function/per-bb to speed this up???
int i;
supernode *n;
FOR_EACH_VEC_ELT (m_nodes, i, n)
if (n->m_fun == fun && n->m_bb == bb)
n->dump_dot (&gv, dump_args);
if (dump_args.m_flags & SUPERGRAPH_DOT_SHOW_BBS)
{
/* Terminate per-bb "subgraph" */
gv.outdent ();
gv.println ("}");
}
}
/* Dump this graph in .dot format to FP, using DUMP_ARGS. */
void
supergraph::dump_dot_to_file (FILE *fp, const dump_args_t &dump_args) const
{
std::unique_ptr<pretty_printer> pp (global_dc->clone_printer ());
pp_show_color (pp.get ()) = 0;
/* %qE in logs for SSA_NAMEs should show the ssa names, rather than
trying to prettify things by showing the underlying var. */
pp_format_decoder (pp.get ()) = default_tree_printer;
pp->set_output_stream (fp);
dump_dot_to_pp (pp.get (), dump_args);
pp_flush (pp.get ());
}
/* Dump this graph in .dot format to PATH, using DUMP_ARGS. */
void
supergraph::dump_dot (const char *path, const dump_args_t &dump_args) const
{
FILE *fp = fopen (path, "w");
dump_dot_to_file (fp, dump_args);
fclose (fp);
}
/* Return a new json::object of the form
{"nodes" : [objs for snodes],
"edges" : [objs for sedges]}. */
std::unique_ptr<json::object>
supergraph::to_json () const
{
auto sgraph_obj = std::make_unique<json::object> ();
/* Nodes. */
{
auto nodes_arr = std::make_unique<json::array> ();
unsigned i;
supernode *n;
FOR_EACH_VEC_ELT (m_nodes, i, n)
nodes_arr->append (n->to_json ());
sgraph_obj->set ("nodes", std::move (nodes_arr));
}
/* Edges. */
{
auto edges_arr = std::make_unique<json::array> ();
unsigned i;
superedge *n;
FOR_EACH_VEC_ELT (m_edges, i, n)
edges_arr->append (n->to_json ());
sgraph_obj->set ("edges", std::move (edges_arr));
}
return sgraph_obj;
}
/* Create a supernode within BB within FUN and add it to this supergraph. */
supernode *
supergraph::add_node (function *fun, basic_block bb, logger *logger)
{
supernode *n = new supernode (fun, bb, m_next_snode_id++);
m_snode_by_id.push_back (n);
m_nodes.safe_push (n);
if (logger)
logger->log ("created SN %i", n->m_id);
return n;
}
void
supergraph::delete_nodes (const std::set<supernode *> &snodes_to_delete)
{
/* Remove nodes from m_nodes. */
unsigned read_index, write_index;
supernode **elem_ptr;
VEC_ORDERED_REMOVE_IF
(m_nodes, read_index, write_index, elem_ptr,
snodes_to_delete.find (*elem_ptr) != snodes_to_delete.end ()
);
/* Remove nodes from m_snode_by_id, and delete them. */
for (auto iter : snodes_to_delete)
{
gcc_assert (iter->m_preds.length () == 0);
gcc_assert (iter->m_succs.length () == 0);
m_snode_by_id[iter->m_id] = nullptr;
delete iter;
}
}
/* Create a chain of nodes and edges in this supergraph from SRC to DEST
to handle CFG_EDGE in the underlying CFG:
+---+
|SRC|
+---+
|
| optional edge for ctrlflow_stmt (if CTRLFLOW_STMT is non-null)
| (e.g. checking conditions on a GIMPLE_COND)
|
V
+------+
|(node)|
+------+
|
| optional edge for any phi nodes in the destination basic block
|
V
+------+
|(node)|
+------+
|
| optional edge for state merging at CFG join points
|
V
+----+
|DEST|
+----+
adding nodes where necessary between the edges, and adding a no-op edge
for the case where there is no CTRLFLOW_STMT, phi nodes, or
state merging. */
void
supergraph::add_sedges_for_cfg_edge (supernode *src,
supernode *dest,
::edge cfg_edge,
gimple *ctrlflow_stmt,
region_model_manager &mgr,
logger *logger)
{
log_nesting_level sentinel (logger,
"edge: bb %i -> bb %i",
cfg_edge->src->index,
cfg_edge->dest->index);
std::unique_ptr<operation> ctrlflow_op;
if (ctrlflow_stmt)
switch (gimple_code (ctrlflow_stmt))
{
default:
gcc_unreachable ();
break;
case GIMPLE_COND:
ctrlflow_op = std::make_unique<gcond_edge_op>
(cfg_edge,
*as_a <gcond *> (ctrlflow_stmt));
break;
case GIMPLE_EH_DISPATCH:
ctrlflow_op
= eh_dispatch_edge_op::make (src, dest,
cfg_edge,
*as_a <geh_dispatch *> (ctrlflow_stmt));
break;
case GIMPLE_GOTO:
{
ctrlflow_op = std::make_unique<ggoto_edge_op>
(cfg_edge,
*as_a <ggoto *> (ctrlflow_stmt),
dest->m_label);
}
break;
case GIMPLE_SWITCH:
ctrlflow_op = std::make_unique<switch_case_op>
(*src->get_function (),
cfg_edge,
*as_a <gswitch *> (ctrlflow_stmt),
*mgr.get_range_manager ());
break;
}
else
{
if (cfg_edge->flags & EDGE_ABNORMAL)
/* Don't create superedges for such CFG edges (though
computed gotos are handled by the GIMPLE_GOTO clause above). */
return;
}
/* Determine a location to use for any snodes within the CFG edge. */
location_t dest_loc = dest->m_loc;
if (useful_location_p (cfg_edge->goto_locus))
dest_loc = cfg_edge->goto_locus;
/* If the dest is a control flow join point, then for each CFG in-edge
add an extra snode/sedge before DEST and route to it.
We hope this will help state-merging keep the
different in-edges separately. */
if (cfg_edge->dest->preds->length () > 1
&& cfg_edge->dest->index != EXIT_BLOCK)
{
auto extra_snode = add_node (src->get_function (),
cfg_edge->dest,
logger);
extra_snode->m_loc = dest_loc;
extra_snode->m_preserve_p = true;
extra_snode->m_state_merger_node = true;
add_edge (new superedge (extra_snode, dest, nullptr, nullptr));
dest = extra_snode;
}
std::unique_ptr<operation> phi_op;
if (phi_nodes (cfg_edge->dest))
phi_op = phis_for_edge_op::maybe_make (cfg_edge);
if (phi_op)
{
superedge *edge_for_phis_op;
if (ctrlflow_op)
{
/* We have two ops; add an edge for each:
SRC --{ctrlflow_op}--> before_phi_nodes --{phi_op}--> DEST. */
supernode *before_phi_nodes
= add_node (src->get_function (), cfg_edge->src, logger);
before_phi_nodes->m_loc = dest_loc;
add_edge (new superedge (src, before_phi_nodes,
std::move (ctrlflow_op),
cfg_edge));
edge_for_phis_op = new superedge (before_phi_nodes, dest,
std::move (phi_op),
cfg_edge);
}
else
/* We just have a phi_op; add: SRC --{phi_op}--> DEST. */
edge_for_phis_op
= new superedge (src, dest, std::move (phi_op), cfg_edge);
add_edge (edge_for_phis_op);
m_edges_for_phis.insert ({cfg_edge, edge_for_phis_op});
}
else
/* We don't have a phi op, create this edge:
SRC --{ctrlflow_op}--> DEST
where ctrlflow_op might be nullptr (for a no-op edge). */
add_edge (new superedge (src, dest, std::move (ctrlflow_op), cfg_edge));
}
// class supernode : public dnode<supergraph_traits>
/* Implementation of dnode::dump_dot vfunc for supernodes.
Write a cluster for the node, and within it a .dot node showing
the phi nodes and stmts. Call into any node annotator from ARGS to
potentially add other records to the cluster. */
void
supernode::dump_dot (graphviz_out *gv, const dump_args_t &args) const
{
pretty_printer *pp = gv->get_pp ();
gv->write_indent ();
dump_dot_id (pp);
pp_printf (pp,
" [shape=none,margin=0,style=filled,label=<");
pp_string (pp, "<TABLE BORDER=\"0\">");
pp_write_text_to_stream (pp);
pp_printf (pp, "<TR><TD>sn: %i (bb: %i)",
m_id, m_bb->index);
if (args.m_eg)
pp_printf (pp, "; scc: %i", args.m_eg->get_scc_id (*this));
pp_string (pp, "</TD></TR>");
pp_newline (pp);
if (m_preserve_p)
{
pp_string (pp, "<TR><TD>(preserve)</TD></TR>");
pp_newline (pp);
}
if (m_state_merger_node)
{
pp_string (pp, "<TR><TD BGCOLOR=\"#ec7a08\">"
"<FONT COLOR=\"white\">STATE MERGER</FONT></TD></TR>");
pp_newline (pp);
}
if (entry_p ())
{
pp_string (pp, "<TR><TD>ENTRY</TD></TR>");
pp_newline (pp);
}
else if (exit_p ())
{
pp_string (pp, "<TR><TD>EXIT</TD></TR>");
pp_newline (pp);
}
/* Source location. */
/* Highlight nodes where we're missing source location information.
Ideally this all gets fixed up by supergraph::fixup_locations. */
if (m_loc == UNKNOWN_LOCATION)
pp_string (pp, "<TR><TD>UNKNOWN_LOCATION</TD></TR>");
else if (get_pure_location (m_loc) == UNKNOWN_LOCATION)
{
pp_printf (pp, "<TR><TD>location: 0x%lx</TD></TR>", m_loc);
pp_string (pp, "<TR><TD>UNKNOWN_LOCATION</TD></TR>");
}
else
{
/* Show the source location, but skip it for the case where it's
the same as all previous snodes (and there's a single in-edge). */
bool show_location = true;
location_t prev_loc = UNKNOWN_LOCATION;
if (m_preds.length () == 1)
{
prev_loc = m_preds[0]->m_src->m_loc;
if (prev_loc == m_loc)
show_location = false;
}
if (show_location)
{
pp_printf (pp, "<TR><TD>location: 0x%lx</TD></TR>", m_loc);
/* Print changes to the expanded location (or all of it if
we have multiple in-edges). */
auto prev_exploc = expand_location (prev_loc);
auto exploc = expand_location (m_loc);
if ((exploc.file != prev_exploc.file)
&& exploc.file)
{
pp_string (pp, "<TR><TD>");
pp_flush (pp);
pp_printf (pp, "%s", exploc.file);
/* Escape, to handle cases like "<built-in>". */
pp_write_text_as_html_like_dot_to_stream (pp);
pp_printf (pp, ":%i:</TD></TR>", exploc.line);
}
if (exploc.line != prev_exploc.line)
if (const diagnostics::char_span line
= global_dc->get_file_cache ().get_source_line (exploc.file,
exploc.line))
{
/* Print the source line. */
pp_string (pp, "<TR><TD>");
pp_string (pp, "<TABLE BORDER=\"0\"><TR>");
// Line number:
pp_printf (pp, ("<TD ALIGN=\"RIGHT\" BGCOLOR=\"#0088ce\">"
"<FONT COLOR=\"white\"> %i</FONT></TD>"),
exploc.line);
// Line contents:
pp_string (pp, "<TD ALIGN=\"LEFT\" BGCOLOR=\"white\">");
pp_flush (pp);
for (size_t i = 0; i < line.length (); ++i)
pp_character (pp, line[i]);
pp_write_text_as_html_like_dot_to_stream (pp);
pp_string (pp, "</TD>");
pp_string (pp, "</TR></TABLE>");
pp_string (pp, "</TD></TR>");
}
}
}
pp_flush (pp);
if (args.m_node_annotator)
args.m_node_annotator->add_node_annotations (gv, *this);
pp_printf (pp, "<TR><TD>m_stmt_loc: 0x%lx</TD></TR>", m_stmt_loc);
pp_string (pp, "</TABLE>>];\n\n");
pp_flush (pp);
}
/* Write an ID for this node to PP, for use in .dot output. */
void
supernode::dump_dot_id (pretty_printer *pp) const
{
pp_printf (pp, "node_%i", m_id);
}
/* Return a new json::object of the form
{"id": int,
"fun": optional str
"bb_idx": int}. */
std::unique_ptr<json::object>
supernode::to_json () const
{
auto snode_obj = std::make_unique<json::object> ();
snode_obj->set_integer ("id", m_id);
snode_obj->set_integer ("bb_idx", m_bb->index);
if (function *fun = get_function ())
snode_obj->set_string ("fun", function_name (fun));
return snode_obj;
}
/* Dump this superedge to PP. */
void
superedge::dump (pretty_printer *pp) const
{
pp_printf (pp, "edge: SN: %i -> SN: %i", m_src->m_id, m_dest->m_id);
label_text desc (get_description (false));
if (strlen (desc.get ()) > 0)
{
pp_space (pp);
pp_string (pp, desc.get ());
}
}
/* Dump this superedge to stderr. */
DEBUG_FUNCTION void
superedge::dump () const
{
tree_dump_pretty_printer pp (stderr);
dump (&pp);
pp_newline (&pp);
}
/* Implementation of dedge::dump_dot for superedges.
Write a .dot edge to GV representing this superedge. */
void
superedge::dump_dot (graphviz_out *gv, const dump_args_t &) const
{
const char *style = "\"solid,bold\"";
const char *color = "black";
int weight = 10;
const char *constraint = "true";
/* Adapted from graph.cc:draw_cfg_node_succ_edges. */
if (::edge cfg_edge = get_any_cfg_edge ())
{
if (cfg_edge->flags & EDGE_FAKE)
{
style = "dotted";
color = "green";
weight = 0;
}
else if (cfg_edge->flags & EDGE_DFS_BACK)
{
style = "\"dotted,bold\"";
color = "blue";
weight = 10;
}
else if (cfg_edge->flags & EDGE_FALLTHRU)
{
color = "blue";
weight = 100;
}
if (cfg_edge->flags & EDGE_ABNORMAL)
color = "red";
}
gv->write_indent ();
pretty_printer *pp = gv->get_pp ();
m_src->dump_dot_id (pp);
pp_string (pp, " -> ");
m_dest->dump_dot_id (pp);
pp_printf (pp,
(" [style=%s, color=%s, weight=%d, constraint=%s,"
" headlabel=\""),
style, color, weight, constraint);
pp_flush (pp);
dump_label_to_pp (pp, false);
pp_write_text_as_dot_label_to_stream (pp, false);
pp_printf (pp, "\"];\n");
}
/* Return a new json::object of the form
{"src_id": int, the index of the source supernode,
"dst_id": int, the index of the destination supernode} */
std::unique_ptr<json::object>
superedge::to_json () const
{
auto sedge_obj = std::make_unique<json::object> ();
sedge_obj->set_integer ("src_id", m_src->m_id);
sedge_obj->set_integer ("dst_id", m_dest->m_id);
return sedge_obj;
}
/* Return true iff this edge needs to be preserved during simplification. */
bool
superedge::preserve_p () const
{
if (m_cfg_edge)
if (m_cfg_edge->flags & (EDGE_EH | EDGE_DFS_BACK))
{
/* We use EDGE_EH in get_eh_outedge, and EDGE_DFS_BACK
for detecting infinite loops. */
return true;
}
return false;
}
/* Build a description of this superedge (e.g. "true" for the true
edge of a conditional, or "case 42:" for a switch case).
If USER_FACING is false, the result also contains any underlying
CFG edge flags. e.g. " (flags FALLTHRU | DFS_BACK)". */
label_text
superedge::get_description (bool user_facing) const
{
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
dump_label_to_pp (&pp, user_facing);
return label_text::take (xstrdup (pp_formatted_text (&pp)));
}
void
superedge::dump_label_to_pp (pretty_printer *pp, bool user_facing) const
{
if (get_op ())
get_op ()->print_as_edge_label (pp, user_facing);
else
pp_printf (pp, "no-op");
if (user_facing)
return;
if (::edge cfg_edge = get_any_cfg_edge ())
{
if (cfg_edge->flags)
{
pp_string (pp, " (flags ");
bool seen_flag = false;
#define DEF_EDGE_FLAG(NAME,IDX) \
do { \
if (cfg_edge->flags & EDGE_##NAME) \
{ \
if (seen_flag) \
pp_string (pp, " | "); \
pp_printf (pp, "%s", (#NAME)); \
seen_flag = true; \
} \
} while (0);
#include "cfg-flags.def"
#undef DEF_EDGE_FLAG
pp_string (pp, ")");
}
if (cfg_edge->goto_locus > BUILTINS_LOCATION)
pp_printf (pp, " (has goto_locus: 0x%lx)", cfg_edge->goto_locus);
}
}
bool
superedge::supports_bulk_merge_p () const
{
if (!m_op)
return true;
return m_op->supports_bulk_merge_p ();
}
} // namespace ana
#endif /* #if ENABLE_ANALYZER */