(root)/
gcc-13.2.0/
gcc/
analyzer/
state-purge.cc
/* Classes for purging state at function_points.
   Copyright (C) 2019-2023 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"
#define INCLUDE_MEMORY
#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 "analyzer/analyzer.h"
#include "analyzer/call-string.h"
#include "analyzer/supergraph.h"
#include "analyzer/program-point.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/state-purge.h"
#include "analyzer/store.h"
#include "analyzer/region-model.h"
#include "gimple-walk.h"
#include "cgraph.h"

#if ENABLE_ANALYZER

/* Given NODE at an access, determine if this access is within
   a decl that could be consider for purging, and if so, return the decl.  */

static tree
get_candidate_for_purging (tree node)
{
  tree iter = node;
  while (1)
    switch (TREE_CODE (iter))
      {
      default:
	return NULL_TREE;

      case ADDR_EXPR:
      case MEM_REF:
      case COMPONENT_REF:
	iter = TREE_OPERAND (iter, 0);
	continue;

      case VAR_DECL:
	if (is_global_var (iter))
	  return NULL_TREE;
	else
	  return iter;

      case PARM_DECL:
      case RESULT_DECL:
	return iter;
      }
}

/* Class-based handler for walk_stmt_load_store_addr_ops at a particular
   function_point, for populating the worklists within a state_purge_map.  */

class gimple_op_visitor : public log_user
{
public:
  gimple_op_visitor (state_purge_map *map,
		     const function_point &point,
		     function *fun)
  : log_user (map->get_logger ()),
    m_map (map),
    m_point (point),
    m_fun (fun)
  {}

  bool on_load (gimple *stmt, tree base, tree op)
  {
    LOG_FUNC (get_logger ());
    if (get_logger ())
      {
	pretty_printer pp;
	pp_gimple_stmt_1 (&pp, stmt, 0, (dump_flags_t)0);
	log ("on_load: %s; base: %qE, op: %qE",
	     pp_formatted_text (&pp), base, op);
      }
    if (tree node = get_candidate_for_purging (base))
      add_needed (node);
    return true;
  }

  bool on_store (gimple *stmt, tree base, tree op)
  {
    LOG_FUNC (get_logger ());
    if (get_logger ())
      {
	pretty_printer pp;
	pp_gimple_stmt_1 (&pp, stmt, 0, (dump_flags_t)0);
	log ("on_store: %s; base: %qE, op: %qE",
	     pp_formatted_text (&pp), base, op);
      }
    return true;
  }

  bool on_addr (gimple *stmt, tree base, tree op)
  {
    LOG_FUNC (get_logger ());
    if (get_logger ())
      {
	pretty_printer pp;
	pp_gimple_stmt_1 (&pp, stmt, 0, (dump_flags_t)0);
	log ("on_addr: %s; base: %qE, op: %qE",
	     pp_formatted_text (&pp), base, op);
      }
    if (TREE_CODE (op) != ADDR_EXPR)
      return true;
    if (tree node = get_candidate_for_purging (base))
      {
	add_needed (node);
	add_pointed_to (node);
      }
    return true;
  }

private:
  void add_needed (tree decl)
  {
    gcc_assert (get_candidate_for_purging (decl) == decl);
    state_purge_per_decl &data
      = get_or_create_data_for_decl (decl);
    data.add_needed_at (m_point);

    /* Handle calls: if we're seeing a use at a call, then add a use at the
       "after-supernode" point (in case of interprocedural call superedges).  */
    if (m_point.final_stmt_p ())
      data.add_needed_at (m_point.get_next ());
  }

  void add_pointed_to (tree decl)
  {
    gcc_assert (get_candidate_for_purging (decl) == decl);
    get_or_create_data_for_decl (decl).add_pointed_to_at (m_point);
  }

  state_purge_per_decl &
  get_or_create_data_for_decl (tree decl)
  {
    return m_map->get_or_create_data_for_decl (m_fun, decl);
  }

  state_purge_map *m_map;
  const function_point &m_point;
  function *m_fun;
};

static bool
my_load_cb  (gimple *stmt, tree base, tree op, void *user_data)
{
  gimple_op_visitor *x = (gimple_op_visitor *)user_data;
  return x->on_load (stmt, base, op);
}

static bool
my_store_cb  (gimple *stmt, tree base, tree op, void *user_data)
{
  gimple_op_visitor *x = (gimple_op_visitor *)user_data;
  return x->on_store (stmt, base, op);
}

static bool
my_addr_cb  (gimple *stmt, tree base, tree op, void *user_data)
{
  gimple_op_visitor *x = (gimple_op_visitor *)user_data;
  return x->on_addr (stmt, base, op);
}

/* state_purge_map's ctor.  Walk all SSA names in all functions, building
   a state_purge_per_ssa_name instance for each.
   Also, walk all loads and address-taken ops of local variables, building
   a state_purge_per_decl as appropriate.  */

state_purge_map::state_purge_map (const supergraph &sg,
				  region_model_manager *mgr,
				  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_ssa_map.put (name, new state_purge_per_ssa_name (*this, name, fun));
      }
  }

  /* Find all uses of local vars.
     We iterate through all function points, finding loads, stores, and
     address-taken operations on locals, building a pair of worklists.  */
  for (auto snode : sg.m_nodes)
    {
      if (logger)
	log ("SN: %i", snode->m_index);
      /* We ignore m_returning_call and phi nodes.  */
      gimple *stmt;
      unsigned i;
      FOR_EACH_VEC_ELT (snode->m_stmts, i, stmt)
	{
	  function_point point (function_point::before_stmt (snode, i));
	  gimple_op_visitor v (this, point, snode->get_function ());
	  walk_stmt_load_store_addr_ops (stmt, &v,
					 my_load_cb, my_store_cb, my_addr_cb);
	}
    }

  /* Now iterate through the m_decl_map, processing each pair of worklists.  */
  for (state_purge_map::decl_iterator iter = begin_decls ();
       iter != end_decls ();
       ++iter)
    {
      state_purge_per_decl *per_decl_data = (*iter).second;
      per_decl_data->process_worklists (*this, mgr);
    }
}

/* state_purge_map's dtor.  */

state_purge_map::~state_purge_map ()
{
  for (auto iter : m_ssa_map)
    delete iter.second;
  for (auto iter : m_decl_map)
    delete iter.second;
}

/* Get the state_purge_per_decl for local DECL within FUN, creating it
   if necessary.  */

state_purge_per_decl &
state_purge_map::get_or_create_data_for_decl (function *fun, tree decl)
{
  if (state_purge_per_decl **slot
      = const_cast <decl_map_t&> (m_decl_map).get (decl))
    return **slot;
  state_purge_per_decl *result = new state_purge_per_decl (*this, decl, fun);
  m_decl_map.put (decl, result);
  return *result;
}

/* class state_purge_per_ssa_name : public state_purge_per_tree.  */

/* 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)
: state_purge_per_tree (fun), m_points_needing_name (), m_name (name)
{
  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);
      /* Log m_points_needing_name, sorting it to avoid churn when comparing
	 dumps.  */
      auto_vec<function_point> points;
      for (point_set_t::iterator iter = m_points_needing_name.begin ();
	   iter != m_points_needing_name.end ();
	   ++iter)
	points.safe_push (*iter);
      points.qsort (function_point::cmp_ptr);
      unsigned i;
      function_point *point;
      FOR_EACH_VEC_ELT (points, i, point)
	{
	  map.start_log_line ();
	  map.get_logger ()->log_partial ("  point: ");
	  point->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 () == get_function ());
  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);
    }
}

/* Return true iff NAME is used by any of the phi nodes in SNODE
   when processing the in-edge with PHI_ARG_IDX.  */

static bool
name_used_by_phis_p (tree name, const supernode *snode,
		     size_t phi_arg_idx)
{
  gcc_assert (TREE_CODE (name) == SSA_NAME);

  for (gphi_iterator gpi
	 = const_cast<supernode *> (snode)->start_phis ();
       !gsi_end_p (gpi); gsi_next (&gpi))
    {
      gphi *phi = gpi.phi ();
      if (gimple_phi_arg_def (phi, phi_arg_idx) == name)
	return true;
    }
  return false;
}

/* 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))
	  {
	    gcc_assert (point.get_from_edge ());
	    const cfg_superedge *cfg_sedge
	      = point.get_from_edge ()->dyn_cast_cfg_superedge ();
	    gcc_assert (cfg_sedge);

	    gphi *phi = gpi.phi ();
	    /* Are we at the def-stmt for m_name?  */
	    if (phi == def_stmt)
	      {
		if (name_used_by_phis_p (m_name, snode,
					 cfg_sedge->get_phi_arg_idx ()))
		  {
		    if (logger)
		      logger->log ("name in def stmt used within phis;"
				   " continuing");
		  }
		else
		  {
		    if (logger)
		      logger->log ("name in def stmt not used 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)
	    {
	      gcall *returning_call = snode->m_returning_call;
	      cgraph_edge *cedge
		  = supergraph_call_edge (snode->m_fun,
					  returning_call);
	      if(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);
	        }
	      else
	        {
	          supernode *callernode 
	            = map.get_sg ().get_supernode_for_stmt (returning_call);

	          gcc_assert (callernode);
	          add_to_worklist 
	            (function_point::after_supernode (callernode),
		     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_per_decl : public state_purge_per_tree.  */

/* state_purge_per_decl's ctor.  */

state_purge_per_decl::state_purge_per_decl (const state_purge_map &map,
					    tree decl,
					    function *fun)
: state_purge_per_tree (fun),
  m_decl (decl)
{
  /* The RESULT_DECL is always needed at the end of its function. */
  if (TREE_CODE (decl) == RESULT_DECL)
    {
      supernode *exit_snode = map.get_sg ().get_node_for_function_exit (fun);
      add_needed_at (function_point::after_supernode (exit_snode));
    }
}

/* Mark the value of the decl (or a subvalue within it) as being needed
   at POINT.  */

void
state_purge_per_decl::add_needed_at (const function_point &point)
{
  m_points_needing_decl.add (point);
}

/* Mark that a pointer to the decl (or a region within it) is taken
   at POINT.  */

void
state_purge_per_decl::add_pointed_to_at (const function_point &point)
{
  m_points_taking_address.add (point);
}

/* Process the worklists for this decl:
   (a) walk backwards from points where we know the value of the decl
   is needed, marking points until we get to a stmt that fully overwrites
   the decl.
   (b) walk forwards from points where the address of the decl is taken,
   marking points as potentially needing the value of the decl.  */

void
state_purge_per_decl::process_worklists (const state_purge_map &map,
					 region_model_manager *mgr)
{
  logger *logger = map.get_logger ();
  LOG_SCOPE (logger);
  if (logger)
    logger->log ("decl: %qE within %qD", m_decl, get_fndecl ());

  /* Worklist for walking backwards from uses.  */
  {
    auto_vec<function_point> worklist;
    point_set_t seen;

    /* Add all uses of the decl to the worklist.  */
    for (auto iter : m_points_needing_decl)
      worklist.safe_push (iter);

    region_model model (mgr);
    model.push_frame (get_function (), NULL, NULL);

    /* Process worklist by walking backwards until we reach a stmt
       that fully overwrites the decl.  */
    {
      log_scope s (logger, "processing worklist");
      while (worklist.length () > 0)
	{
	  function_point point = worklist.pop ();
	  process_point_backwards (point, &worklist, &seen, map, model);
	}
    }
  }

  /* Worklist for walking forwards from address-taken points.  */
  {
    auto_vec<function_point> worklist;
    point_set_t seen;

    /* Add all uses of the decl to the worklist.  */
    for (auto iter : m_points_taking_address)
      {
	worklist.safe_push (iter);

	/* Add to m_points_needing_decl (now that we traversed
	   it above for the backward worklist).  */
	m_points_needing_decl.add (iter);
      }

    /* Process worklist by walking forwards. */
    {
      log_scope s (logger, "processing worklist");
      while (worklist.length () > 0)
	{
	  function_point point = worklist.pop ();
	  process_point_forwards (point, &worklist, &seen, map);
	}
    }
  }
}

/* Add POINT to *WORKLIST if the point is not already in *seen.
   Add newly seen points to *SEEN and to m_points_needing_name.  */

void
state_purge_per_decl::add_to_worklist (const function_point &point,
				       auto_vec<function_point> *worklist,
				       point_set_t *seen,
				       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_decl);
      logger->end_log_line ();
    }

  gcc_assert (point.get_function () == get_function ());
  if (point.get_from_edge ())
    gcc_assert (point.get_from_edge ()->get_kind () == SUPEREDGE_CFG_EDGE);

  if (seen->contains (point))
    {
      if (logger)
	logger->log ("already seen for %qE", m_decl);
    }
  else
    {
      if (logger)
	logger->log ("not seen; adding to worklist for %qE", m_decl);
      m_points_needing_decl.add (point);
      seen->add (point);
      worklist->safe_push (point);
    }
}

/* Determine if REG_A and REG_B express writing to exactly the same
   set of bits.
   For example, when "s.field" is the only field of "s", and there's no
   padding, this should return true.  */

static bool
same_binding_p (const region *reg_a, const region *reg_b,
		store_manager *store_mgr)
{
  if (reg_a->get_base_region () != reg_b->get_base_region ())
    return false;
  if (reg_a->empty_p ())
    return false;
  const binding_key *bind_key_a = binding_key::make (store_mgr, reg_a);
  if (reg_b->empty_p ())
    return false;
  const binding_key *bind_key_b = binding_key::make (store_mgr, reg_b);
  return bind_key_a == bind_key_b;
}

/* Return true if STMT fully overwrites DECL.  */

static bool
fully_overwrites_p (const gimple *stmt, tree decl,
		    const region_model &model)
{
  if (tree lhs = gimple_get_lhs (stmt))
    {
      /* Determine if LHS fully overwrites DECL.
	 We can't just check for equality; consider the case of
	 "s.field = EXPR;" where the stmt writes to the only field
	 of "s", and there's no padding.  */
      const region *lhs_reg = model.get_lvalue (lhs, NULL);
      const region *decl_reg = model.get_lvalue (decl, NULL);
      if (same_binding_p (lhs_reg, decl_reg,
			  model.get_manager ()->get_store_manager ()))
	return true;
    }
  return false;
}

/* Process POINT, popped from *WORKLIST.
   Iterate over predecessors of POINT, adding to *WORKLIST and *SEEN,
   until we get to a stmt that fully overwrites the decl.  */

void
state_purge_per_decl::
process_point_backwards (const function_point &point,
			 auto_vec<function_point> *worklist,
			 point_set_t *seen,
			 const state_purge_map &map,
			 const region_model &model)
{
  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_decl);
      logger->end_log_line ();
    }
  const supernode *snode = point.get_supernode ();

  switch (point.get_kind ())
    {
    default:
      gcc_unreachable ();

    case PK_ORIGIN:
      break;

    case PK_BEFORE_SUPERNODE:
      {
	/* 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, seen, logger);
	  }
	else
	  {
	    /* Add any intraprocedually edge for a call.  */
	    if (snode->m_returning_call)
	    {
	      gcall *returning_call = snode->m_returning_call;
	      cgraph_edge *cedge
		  = supergraph_call_edge (snode->m_fun,
					  returning_call);
	      if(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, seen, logger);
		}
	      else
		{
		  supernode *callernode
		    = map.get_sg ().get_supernode_for_stmt (returning_call);

		  gcc_assert (callernode);
		  add_to_worklist
		    (function_point::after_supernode (callernode),
		     worklist, seen, logger);
		}
	    }
	  }
      }
      break;

    case PK_BEFORE_STMT:
      {
	/* This is somewhat equivalent to how the SSA case handles
	   def-stmts.  */
	if (fully_overwrites_p (point.get_stmt (), m_decl, model)
	    /* ...but we mustn't be at a point that also consumes the
	       current value of the decl when it's generating the new
	       value, for cases such as
		  struct st s;
		  s = foo ();
		  s = bar (s);
	       where we want to make sure that we don't stop at the:
		  s = bar (s);
	       since otherwise we would erroneously purge the state of "s"
	       after:
		  s = foo ();
	    */
	    && !m_points_needing_decl.contains (point))
	  {
	    if (logger)
	      logger->log ("stmt fully overwrites %qE; terminating", m_decl);
	    return;
	  }
	if (point.get_stmt_idx () > 0)
	  add_to_worklist (function_point::before_stmt
			     (snode, point.get_stmt_idx () - 1),
			   worklist, seen, 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, seen, 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, seen, 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, seen, logger);
	  }
      }
      break;
    }

}

/* Process POINT, popped from *WORKLIST.
   Iterate over successors of POINT, adding to *WORKLIST and *SEEN.  */

void
state_purge_per_decl::
process_point_forwards (const function_point &point,
			auto_vec<function_point> *worklist,
			point_set_t *seen,
			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_decl);
      logger->end_log_line ();
    }
  const supernode *snode = point.get_supernode ();

  switch (point.get_kind ())
    {
    default:
    case PK_ORIGIN:
      gcc_unreachable ();

    case PK_BEFORE_SUPERNODE:
      {
	function_point next = point.get_next ();
	add_to_worklist (next, worklist, seen, logger);
      }
      break;

    case PK_BEFORE_STMT:
      {
	/* Perhaps we should stop at a clobber of the decl,
	   but that ought to purge state for us explicitly.  */
	function_point next = point.get_next ();
	add_to_worklist (next, worklist, seen, logger);
      }
      break;

    case PK_AFTER_SUPERNODE:
      {
	/* Look at interprocedural out-edges.  */
	unsigned i;
	superedge *succ;
	FOR_EACH_VEC_ELT (snode->m_succs, i, succ)
	  {
	    enum edge_kind kind = succ->get_kind ();
	    if (kind == SUPEREDGE_CFG_EDGE
		|| kind == SUPEREDGE_INTRAPROCEDURAL_CALL)
	      add_to_worklist (function_point::before_supernode (succ->m_dest,
								 succ),
			       worklist, seen, logger);
	  }
      }
      break;
    }
}

/* Return true if the decl is needed at POINT.  */

bool
state_purge_per_decl::needed_at_point_p (const function_point &point) const
{
  return const_cast <point_set_t &> (m_points_needing_decl).contains (point);
}

/* 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);

   /* Different in-edges mean different names need purging.
      Determine which points to dump.  */
   auto_vec<function_point> points;
   if (n.entry_p () || n.m_returning_call)
     points.safe_push (function_point::before_supernode (&n, NULL));
   else
     for (auto inedge : n.m_preds)
       points.safe_push (function_point::before_supernode (&n, inedge));
   points.safe_push (function_point::after_supernode (&n));

   for (auto & point : points)
     {
       point.print (pp, format (true));
       pp_newline (pp);
       print_needed (gv, point, false);
       pp_newline (pp);
     }

   pp_string (pp, "\"];\n\n");
   pp_flush (pp);
   return false;
}

/* Print V to GV as a comma-separated list in braces, titling it with TITLE.
   If WITHIN_TABLE is true, print it within a <TR>

   Subroutine of state_purge_annotator::print_needed.  */

static void
print_vec_of_names (graphviz_out *gv, const char *title,
		    const auto_vec<tree> &v, bool within_table)
{
  pretty_printer *pp = gv->get_pp ();
  tree name;
  unsigned i;
  if (within_table)
    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, "}");
  if (within_table)
    {
      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));

  print_needed (gv, before_stmt, true);
}

/* Get the decls and SSA names needed and not-needed at POINT, and
   print them to GV.
   If WITHIN_TABLE is true, print them within <TR> elements.  */

void
state_purge_annotator::print_needed (graphviz_out *gv,
				     const function_point &point,
				     bool within_table) const
{
  auto_vec<tree> needed;
  auto_vec<tree> not_needed;
  for (state_purge_map::ssa_iterator iter = m_map->begin_ssas ();
       iter != m_map->end_ssas ();
       ++iter)
    {
      tree name = (*iter).first;
      state_purge_per_ssa_name *per_name_data = (*iter).second;
      if (per_name_data->get_function () == point.get_function ())
	{
	  if (per_name_data->needed_at_point_p (point))
	    needed.safe_push (name);
	  else
	    not_needed.safe_push (name);
	}
    }
  for (state_purge_map::decl_iterator iter = m_map->begin_decls ();
       iter != m_map->end_decls ();
       ++iter)
    {
      tree decl = (*iter).first;
      state_purge_per_decl *per_decl_data = (*iter).second;
      if (per_decl_data->get_function () == point.get_function ())
	{
	  if (per_decl_data->needed_at_point_p (point))
	    needed.safe_push (decl);
	  else
	    not_needed.safe_push (decl);
	}
    }

  print_vec_of_names (gv, "needed here", needed, within_table);
  print_vec_of_names (gv, "not needed here", not_needed, within_table);
}

#endif /* #if ENABLE_ANALYZER */