view gcc/analyzer/state-purge.cc @ 145:1830386684a0

gcc-9.2.0
author anatofuz
date Thu, 13 Feb 2020 11:34:05 +0900
parents
children
line wrap: on
line source

/* 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.  */

void
state_purge_annotator::add_node_annotations (graphviz_out *gv,
					     const supernode &n) const
{
  if (m_map == NULL)
    return;

  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);
}

/* 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_tr ();
  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_tr ();
  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) const
{
  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 */