8sa1-gcc/gcc/analyzer/state-purge.cc
David Malcolm 42c6331325 analyzer: add new supergraph visualization
This patch extends -fdump-analyzer-supergraph so that rather than just
dumping a DUMP_BASE_NAME.supergraph.dot at the start of analysis, it
also dumps a DUMP_BASE_NAME.supergraph-eg.dot at the end.

The new dump file contains a concise dump of the exploded_graph,
organized with respect to the supergraph and its statements.  The
exploded nodes are colorized to show sm-state, but no other state
is shown.  Per exploded_node saved_diagnostics are also shown,
along with feasibility of the paths to reach them.

I've been finding this a useful way of tracking down issues in
exploded_graphs that are sufficiently large that the output of
-fdump-analyzer-exploded-graph becomes unwieldy.

The patch extends feasiblity-testing so that if the exploded_path
for a saved_diagnostic is found to be infeasible, the reason is
saved and written into the saved_diagnostic, so it can be shown in the
dump.  I've found this very useful when tracking down feasibility
issues.

I'm keeping the initial dump file as it's useful when tracking down
ICEs within the analyzer (which would stop the second dump file being
written).

gcc/analyzer/ChangeLog:
	* analyzer.h (class feasibility_problem): New forward decl.
	* diagnostic-manager.cc (saved_diagnostic::saved_diagnostic):
	Initialize new fields m_status, m_epath_length, and m_problem.
	(saved_diagnostic::~saved_diagnostic): Delete m_problem.
	(dedupe_candidate::dedupe_candidate): Convert "sd" param from a
	const ref to a mutable ptr.
	(dedupe_winners::add): Convert "sd" param from a const ref to a
	mutable ptr.  Record the length of the exploded_path.  Record the
	feasibility/infeasibility of sd into sd, capturing a
	feasibility_problem when feasible_p fails, and storing it in sd.
	(diagnostic_manager::emit_saved_diagnostics): Update for pass by
	ptr rather than by const ref.
	* diagnostic-manager.h (class saved_diagnostic): Add new enum
	status.  Add fields m_status, m_epath_length and m_problem.
	(saved_diagnostic::set_feasible): New member function.
	(saved_diagnostic::set_infeasible): New member function.
	(saved_diagnostic::get_feasibility_problem): New accessor.
	(saved_diagnostic::get_status): New accessor.
	(saved_diagnostic::set_epath_length): New member function.
	(saved_diagnostic::get_epath_length): New accessor.
	* engine.cc: Include "gimple-pretty-print.h".
	(exploded_path::feasible_p): Add OUT param and, if non-NULL, write
	a new feasibility_problem to it on failure.
	(viz_callgraph_node::dump_dot): Convert begin_tr calls to
	begin_trtd.  Convert end_tr calls to end_tdtr.
	(class exploded_graph_annotator): New subclass of dot_annotator.
	(impl_run_checkers): Add a second -fdump-analyzer-supergraph dump
	after the analysis runs, using exploded_graph_annotator. dumping
	to DUMP_BASE_NAME.supergraph-eg.dot.
	* exploded-graph.h (exploded_node::get_dot_fillcolor): Make
	public.
	(exploded_path::feasible_p): Add OUT param.
	(class feasibility_problem): New class.
	* state-purge.cc (state_purge_annotator::add_node_annotations):
	Return a bool, add a "within_table" param.
	(print_vec_of_names): Convert begin_tr calls to begin_trtd.
	Convert end_tr calls to end_tdtr.
	(state_purge_annotator::add_stmt_annotations): Add "within_row"
	param.
	* state-purge.h ((state_purge_annotator::add_node_annotations):
	Return a bool, add a "within_table" param.
	(state_purge_annotator::add_stmt_annotations): Add "within_row"
	param.
	* supergraph.cc (supernode::dump_dot): Call add_node_annotations
	twice: as before, passing false for "within_table", then again
	with true when within the TABLE element.  Convert some begin_tr
	calls to begin_trtd, and some end_tr calls to end_tdtr.
	Repeat each add_stmt_annotations call, distinguishing between
	calls that add TRs and those that add TDs to an existing TR.
	Add a call to add_after_node_annotations.
	* supergraph.h (dot_annotator::add_node_annotations): Add a
	"within_table" param.
	(dot_annotator::add_stmt_annotations): Add a "within_row" param.
	(dot_annotator::add_after_node_annotations): New vfunc.

gcc/ChangeLog:
	* doc/invoke.texi (-fdump-analyzer-supergraph): Document that this
	now emits two .dot files.
	* graphviz.cc (graphviz_out::begin_tr): Only emit a TR, not a TD.
	(graphviz_out::end_tr): Only close a TR, not a TD.
	(graphviz_out::begin_td): New.
	(graphviz_out::end_td): New.
	(graphviz_out::begin_trtd): New, replacing the old implementation
	of graphviz_out::begin_tr.
	(graphviz_out::end_tdtr): New, replacing the old implementation
	of graphviz_out::end_tr.
	* graphviz.h (graphviz_out::begin_td): New decl.
	(graphviz_out::end_td): New decl.
	(graphviz_out::begin_trtd): New decl.
	(graphviz_out::end_tdtr): New decl.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/dot-output.c: Check that
	dot-output.c.supergraph-eg.dot is valid.
2020-03-27 10:02:39 -04:00

541 lines
14 KiB
C++

/* Classes for purging state at function_points.
Copyright (C) 2019-2020 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/>. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tree.h"
#include "timevar.h"
#include "tree-ssa-alias.h"
#include "function.h"
#include "basic-block.h"
#include "gimple.h"
#include "stringpool.h"
#include "tree-vrp.h"
#include "gimple-ssa.h"
#include "tree-ssanames.h"
#include "tree-phinodes.h"
#include "options.h"
#include "ssa-iterators.h"
#include "diagnostic-core.h"
#include "gimple-pretty-print.h"
#include "function.h"
#include "analyzer/analyzer.h"
#include "analyzer/call-string.h"
#include "digraph.h"
#include "ordered-hash-map.h"
#include "cfg.h"
#include "gimple-iterator.h"
#include "cgraph.h"
#include "analyzer/supergraph.h"
#include "analyzer/program-point.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/state-purge.h"
#if ENABLE_ANALYZER
/* state_purge_map's ctor. Walk all SSA names in all functions, building
a state_purge_per_ssa_name instance for each. */
state_purge_map::state_purge_map (const supergraph &sg,
logger *logger)
: log_user (logger), m_sg (sg)
{
LOG_FUNC (logger);
auto_timevar tv (TV_ANALYZER_STATE_PURGE);
cgraph_node *node;
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
{
function *fun = node->get_fun ();
if (logger)
log ("function: %s", function_name (fun));
tree name;
unsigned int i;;
FOR_EACH_SSA_NAME (i, name, fun)
{
/* For now, don't bother tracking the .MEM SSA names. */
if (tree var = SSA_NAME_VAR (name))
if (TREE_CODE (var) == VAR_DECL)
if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
continue;
m_map.put (name, new state_purge_per_ssa_name (*this, name, fun));
}
}
}
/* state_purge_map's dtor. */
state_purge_map::~state_purge_map ()
{
for (iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
delete (*iter).second;
}
/* state_purge_per_ssa_name's ctor.
Locate all uses of VAR within FUN.
Walk backwards from each use, marking program points, until
we reach the def stmt, populating m_points_needing_var.
We have to track program points rather than
just stmts since there could be empty basic blocks on the way. */
state_purge_per_ssa_name::state_purge_per_ssa_name (const state_purge_map &map,
tree name,
function *fun)
: m_points_needing_name (), m_name (name), m_fun (fun)
{
LOG_FUNC (map.get_logger ());
if (map.get_logger ())
{
map.log ("SSA name: %qE within %qD", name, fun->decl);
/* Show def stmt. */
const gimple *def_stmt = SSA_NAME_DEF_STMT (name);
pretty_printer pp;
pp_gimple_stmt_1 (&pp, def_stmt, 0, (dump_flags_t)0);
map.log ("def stmt: %s", pp_formatted_text (&pp));
}
auto_vec<function_point> worklist;
/* Add all immediate uses of name to the worklist.
Compare with debug_immediate_uses. */
imm_use_iterator iter;
use_operand_p use_p;
FOR_EACH_IMM_USE_FAST (use_p, iter, name)
{
if (USE_STMT (use_p))
{
const gimple *use_stmt = USE_STMT (use_p);
if (map.get_logger ())
{
pretty_printer pp;
pp_gimple_stmt_1 (&pp, use_stmt, 0, (dump_flags_t)0);
map.log ("used by stmt: %s", pp_formatted_text (&pp));
}
const supernode *snode
= map.get_sg ().get_supernode_for_stmt (use_stmt);
/* If it's a use within a phi node, then we care about
which in-edge we came from. */
if (use_stmt->code == GIMPLE_PHI)
{
for (gphi_iterator gpi
= const_cast<supernode *> (snode)->start_phis ();
!gsi_end_p (gpi); gsi_next (&gpi))
{
gphi *phi = gpi.phi ();
if (phi == use_stmt)
{
/* Find arguments (and thus in-edges) which use NAME. */
for (unsigned arg_idx = 0;
arg_idx < gimple_phi_num_args (phi);
++arg_idx)
{
if (name == gimple_phi_arg (phi, arg_idx)->def)
{
edge in_edge = gimple_phi_arg_edge (phi, arg_idx);
const superedge *in_sedge
= map.get_sg ().get_edge_for_cfg_edge (in_edge);
function_point point
= function_point::before_supernode
(snode, in_sedge);
add_to_worklist (point, &worklist,
map.get_logger ());
m_points_needing_name.add (point);
}
}
}
}
}
else
{
function_point point = before_use_stmt (map, use_stmt);
add_to_worklist (point, &worklist, map.get_logger ());
m_points_needing_name.add (point);
/* We also need to add uses for conditionals and switches,
where the stmt "happens" at the after_supernode, for filtering
the out-edges. */
if (use_stmt == snode->get_last_stmt ())
{
if (map.get_logger ())
map.log ("last stmt in BB");
function_point point
= function_point::after_supernode (snode);
add_to_worklist (point, &worklist, map.get_logger ());
m_points_needing_name.add (point);
}
else
if (map.get_logger ())
map.log ("not last stmt in BB");
}
}
}
/* Process worklist by walking backwards until we reach the def stmt. */
{
log_scope s (map.get_logger (), "processing worklist");
while (worklist.length () > 0)
{
function_point point = worklist.pop ();
process_point (point, &worklist, map);
}
}
if (map.get_logger ())
{
map.log ("%qE in %qD is needed to process:", name, fun->decl);
for (point_set_t::iterator iter = m_points_needing_name.begin ();
iter != m_points_needing_name.end ();
++iter)
{
map.start_log_line ();
map.get_logger ()->log_partial (" point: ");
(*iter).print (map.get_logger ()->get_printer (), format (false));
map.end_log_line ();
}
}
}
/* Return true if the SSA name is needed at POINT. */
bool
state_purge_per_ssa_name::needed_at_point_p (const function_point &point) const
{
return const_cast <point_set_t &> (m_points_needing_name).contains (point);
}
/* Get the function_point representing immediately before USE_STMT.
Subroutine of ctor. */
function_point
state_purge_per_ssa_name::before_use_stmt (const state_purge_map &map,
const gimple *use_stmt)
{
gcc_assert (use_stmt->code != GIMPLE_PHI);
const supernode *supernode
= map.get_sg ().get_supernode_for_stmt (use_stmt);
unsigned int stmt_idx = supernode->get_stmt_index (use_stmt);
return function_point::before_stmt (supernode, stmt_idx);
}
/* Add POINT to *WORKLIST if the point has not already been seen.
Subroutine of ctor. */
void
state_purge_per_ssa_name::add_to_worklist (const function_point &point,
auto_vec<function_point> *worklist,
logger *logger)
{
LOG_FUNC (logger);
if (logger)
{
logger->start_log_line ();
logger->log_partial ("point: '");
point.print (logger->get_printer (), format (false));
logger->log_partial ("' for worklist for %qE", m_name);
logger->end_log_line ();
}
gcc_assert (point.get_function () == m_fun);
if (point.get_from_edge ())
gcc_assert (point.get_from_edge ()->get_kind () == SUPEREDGE_CFG_EDGE);
if (m_points_needing_name.contains (point))
{
if (logger)
logger->log ("already seen for %qE", m_name);
}
else
{
if (logger)
logger->log ("not seen; adding to worklist for %qE", m_name);
m_points_needing_name.add (point);
worklist->safe_push (point);
}
}
/* Process POINT, popped from WORKLIST.
Iterate over predecessors of POINT, adding to WORKLIST. */
void
state_purge_per_ssa_name::process_point (const function_point &point,
auto_vec<function_point> *worklist,
const state_purge_map &map)
{
logger *logger = map.get_logger ();
LOG_FUNC (logger);
if (logger)
{
logger->start_log_line ();
logger->log_partial ("considering point: '");
point.print (logger->get_printer (), format (false));
logger->log_partial ("' for %qE", m_name);
logger->end_log_line ();
}
gimple *def_stmt = SSA_NAME_DEF_STMT (m_name);
const supernode *snode = point.get_supernode ();
switch (point.get_kind ())
{
default:
gcc_unreachable ();
case PK_ORIGIN:
break;
case PK_BEFORE_SUPERNODE:
{
for (gphi_iterator gpi
= const_cast<supernode *> (snode)->start_phis ();
!gsi_end_p (gpi); gsi_next (&gpi))
{
gphi *phi = gpi.phi ();
if (phi == def_stmt)
{
if (logger)
logger->log ("def stmt within phis; terminating");
return;
}
}
/* Add given pred to worklist. */
if (point.get_from_edge ())
{
gcc_assert (point.get_from_edge ()->m_src);
add_to_worklist
(function_point::after_supernode (point.get_from_edge ()->m_src),
worklist, logger);
}
else
{
/* Add any intraprocedually edge for a call. */
if (snode->m_returning_call)
{
cgraph_edge *cedge
= supergraph_call_edge (snode->m_fun,
snode->m_returning_call);
gcc_assert (cedge);
superedge *sedge
= map.get_sg ().get_intraprocedural_edge_for_call (cedge);
gcc_assert (sedge);
add_to_worklist
(function_point::after_supernode (sedge->m_src),
worklist, logger);
}
}
}
break;
case PK_BEFORE_STMT:
{
if (def_stmt == point.get_stmt ())
{
if (logger)
logger->log ("def stmt; terminating");
return;
}
if (point.get_stmt_idx () > 0)
add_to_worklist (function_point::before_stmt
(snode, point.get_stmt_idx () - 1),
worklist, logger);
else
{
/* Add before_supernode to worklist. This captures the in-edge,
so we have to do it once per in-edge. */
unsigned i;
superedge *pred;
FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
add_to_worklist (function_point::before_supernode (snode,
pred),
worklist, logger);
}
}
break;
case PK_AFTER_SUPERNODE:
{
if (snode->m_stmts.length ())
add_to_worklist
(function_point::before_stmt (snode,
snode->m_stmts.length () - 1),
worklist, logger);
else
{
/* Add before_supernode to worklist. This captures the in-edge,
so we have to do it once per in-edge. */
unsigned i;
superedge *pred;
FOR_EACH_VEC_ELT (snode->m_preds, i, pred)
add_to_worklist (function_point::before_supernode (snode,
pred),
worklist, logger);
/* If it's the initial BB, add it, to ensure that we
have "before supernode" for the initial ENTRY block, and don't
erroneously purge SSA names for initial values of parameters. */
if (snode->entry_p ())
{
add_to_worklist
(function_point::before_supernode (snode, NULL),
worklist, logger);
}
}
}
break;
}
}
/* class state_purge_annotator : public dot_annotator. */
/* Implementation of dot_annotator::add_node_annotations vfunc for
state_purge_annotator.
Add an additional record showing which names are purged on entry
to the supernode N. */
bool
state_purge_annotator::add_node_annotations (graphviz_out *gv,
const supernode &n,
bool within_table) const
{
if (m_map == NULL)
return false;
if (within_table)
return false;
pretty_printer *pp = gv->get_pp ();
pp_printf (pp, "annotation_for_node_%i", n.m_index);
pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=\"",
"lightblue");
pp_write_text_to_stream (pp);
// FIXME: passing in a NULL in-edge means we get no hits
function_point before_supernode
(function_point::before_supernode (&n, NULL));
for (state_purge_map::iterator iter = m_map->begin ();
iter != m_map->end ();
++iter)
{
tree name = (*iter).first;
state_purge_per_ssa_name *per_name_data = (*iter).second;
if (per_name_data->get_function () == n.m_fun)
{
if (per_name_data->needed_at_point_p (before_supernode))
pp_printf (pp, "%qE needed here", name);
else
pp_printf (pp, "%qE not needed here", name);
}
pp_newline (pp);
}
pp_string (pp, "\"];\n\n");
pp_flush (pp);
return false;
}
/* Print V to GV as a comma-separated list in braces within a <TR>,
titling it with TITLE.
Subroutine of state_purge_annotator::add_stmt_annotations. */
static void
print_vec_of_names (graphviz_out *gv, const char *title,
const auto_vec<tree> &v)
{
pretty_printer *pp = gv->get_pp ();
tree name;
unsigned i;
gv->begin_trtd ();
pp_printf (pp, "%s: {", title);
FOR_EACH_VEC_ELT (v, i, name)
{
if (i > 0)
pp_string (pp, ", ");
pp_printf (pp, "%qE", name);
}
pp_printf (pp, "}");
pp_write_text_as_html_like_dot_to_stream (pp);
gv->end_tdtr ();
pp_newline (pp);
}
/* Implementation of dot_annotator::add_stmt_annotations for
state_purge_annotator.
Add text showing which names are purged at STMT. */
void
state_purge_annotator::add_stmt_annotations (graphviz_out *gv,
const gimple *stmt,
bool within_row) const
{
if (within_row)
return;
if (m_map == NULL)
return;
if (stmt->code == GIMPLE_PHI)
return;
pretty_printer *pp = gv->get_pp ();
pp_newline (pp);
const supernode *supernode = m_map->get_sg ().get_supernode_for_stmt (stmt);
unsigned int stmt_idx = supernode->get_stmt_index (stmt);
function_point before_stmt
(function_point::before_stmt (supernode, stmt_idx));
auto_vec<tree> needed;
auto_vec<tree> not_needed;
for (state_purge_map::iterator iter = m_map->begin ();
iter != m_map->end ();
++iter)
{
tree name = (*iter).first;
state_purge_per_ssa_name *per_name_data = (*iter).second;
if (per_name_data->get_function () == supernode->m_fun)
{
if (per_name_data->needed_at_point_p (before_stmt))
needed.safe_push (name);
else
not_needed.safe_push (name);
}
}
print_vec_of_names (gv, "needed here", needed);
print_vec_of_names (gv, "not needed here", not_needed);
}
#endif /* #if ENABLE_ANALYZER */