From 45dd5b1636939a3cb182af38dcf6fc0356fa55dd Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 23 Oct 2019 10:14:16 -0400
Subject: [PATCH 47/97] {HAS FIXMES, NEEDS BLURB}: analyzer: new files:
 state-purge.{cc|h}

gcc/ChangeLog:
	* analyzer/state-purge.cc: New file.
	* analyzer/state-purge.h: New file.
---
 gcc/analyzer/state-purge.cc | 490 ++++++++++++++++++++++++++++++++++++++++++++
 gcc/analyzer/state-purge.h  | 165 +++++++++++++++
 2 files changed, 655 insertions(+)
 create mode 100644 gcc/analyzer/state-purge.cc
 create mode 100644 gcc/analyzer/state-purge.h

diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc
new file mode 100644
index 0000000..e342a3a
--- /dev/null
+++ b/gcc/analyzer/state-purge.cc
@@ -0,0 +1,490 @@
+/* FIXME.
+   Copyright (C) 2019 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 "gcc-plugin.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "timevar.h"
+#include "tree-ssa-alias.h"
+#include "gimple.h"
+#include "stringpool.h"
+#include "tree-vrp.h"
+#include "gimple-ssa.h"
+#include "tree-ssanames.h"
+#include "tree-phinodes.h"
+#include "ssa-iterators.h"
+#include "gimple-pretty-print.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/state-purge.h"
+
+/* 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_client_timevar tv ("state_purge_map ctor");
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+  {
+    function *fun = node->get_fun ();
+    if (logger)
+      log ("function: %s", function_name (fun));
+    tree var;
+    unsigned int i;;
+    FOR_EACH_SSA_NAME (i, var, fun)
+      m_map.put (var, new state_purge_per_ssa_name (*this, var, 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 var,
+						    function *fun)
+: m_var (var)
+{
+  LOG_FUNC (map.get_logger ());
+
+  if (map.get_logger ())
+    {
+      map.log ("SSA name: %qE within %qD", var, fun->decl);
+
+      /* Show def stmt.  */
+      const gimple *def_stmt = SSA_NAME_DEF_STMT (var);
+      pretty_printer pp;
+      pp_gimple_stmt_1 (&pp, def_stmt, 0, (dump_flags_t)0);
+      map.log ("def stmt: %s", pp_formatted_text (&pp));
+    }
+
+  /* The worklist for the traversal.
+     We can't have a vec<program_point> here because vec<T> require T to be
+     a POD.  Hence (annoyingly) we use vec<program_point *> and dynamically
+     allocate.  */
+  auto_vec<program_point *> worklist;
+
+  /* Add all immediate uses of var 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, var)
+    {
+      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 VAR.  */
+		      for (unsigned arg_idx = 0;
+			   arg_idx < gimple_phi_num_args (phi);
+			   ++arg_idx)
+			{
+			  if (var == 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);
+			      program_point point
+				= program_point::before_supernode
+				(snode, in_sedge, call_string ());
+			      add_to_worklist (point, &worklist,
+					       map.get_logger ());
+			      m_points_needing_var.add (point);
+			    }
+			}
+		    }
+		}
+	    }
+	  else
+	    {
+	      program_point point = before_use_stmt (map, use_stmt);
+	      add_to_worklist (point, &worklist, map.get_logger ());
+	      m_points_needing_var.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");
+		  program_point point
+		    = program_point::after_supernode (snode, call_string ());
+		  add_to_worklist (point, &worklist, map.get_logger ());
+		  m_points_needing_var.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)
+      {
+	program_point *point_ptr = worklist.pop ();
+	program_point point (*point_ptr);
+	process_point (*point_ptr, &worklist, map.get_logger ());
+	delete point_ptr;
+    }
+  }
+
+  if (map.get_logger ())
+    {
+      map.log ("%qE in %qD is needed to process:", var, fun->decl);
+      for (point_set_t::iterator iter = m_points_needing_var.begin ();
+	   iter != m_points_needing_var.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 program_point &point) const
+{
+  return const_cast <point_set_t &> (m_points_needing_var).contains (point);
+}
+
+/* Get the program_point representing immediately before USE_STMT.
+   Subroutine of ctor.  */
+
+program_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); // FIXME: for now
+#if 0
+  if (stmt->code == GIMPLE_PHI)
+    {
+      const supernode *supernode = sg.get_node_for_block (stmt->bb);
+      return before_supernode (supernode, from_edge, call_string);
+    }
+  else
+#endif
+    {
+      const supernode *supernode
+	= map.get_sg ().get_supernode_for_stmt (use_stmt);
+      unsigned int stmt_idx = supernode->get_stmt_index (use_stmt);
+      return program_point::before_stmt (supernode, stmt_idx, call_string ());
+    }
+}
+
+/* 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 program_point &point,
+					   auto_vec<program_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_var);
+      logger->end_log_line ();
+    }
+
+  if (m_points_needing_var.contains (point))
+    {
+      if (logger)
+	logger->log ("already seen for %qE", m_var);
+    }
+  else
+    {
+      if (logger)
+	logger->log ("not seen; adding to worklist for %qE", m_var);
+      worklist->safe_push (new program_point (point));
+    }
+}
+
+/* Process POINT, popped from WORKLIST.
+   Iterate over predecessors of POINT, adding to WORKLIST.  */
+
+void
+state_purge_per_ssa_name::process_point (const program_point &point,
+					 auto_vec<program_point *> *worklist,
+					 logger *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_var);
+      logger->end_log_line ();
+    }
+
+  gimple *def_stmt = SSA_NAME_DEF_STMT (m_var);
+
+  m_points_needing_var.add (point);
+
+  const supernode *snode = point.get_supernode ();
+
+  switch (point.m_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.m_from_edge)
+	  {
+	    if (point.m_from_edge->m_src)
+	      add_to_worklist
+		(program_point::after_supernode (point.m_from_edge->m_src,
+						 call_string ()),
+		 worklist, logger);
+	  }
+	else
+	  gcc_assert (snode->entry_p ());
+      }
+      break;
+
+    case PK_BEFORE_STMT:
+      {
+	if (def_stmt == point.get_stmt ())
+	  {
+	    if (logger)
+	      logger->log ("def stmt; terminating");
+	    return;
+	  }
+	if (point.m_stmt_idx > 0)
+	  add_to_worklist (program_point::before_stmt (snode,
+						       point.m_stmt_idx - 1,
+						       call_string ()),
+			   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 (program_point::before_supernode (snode,
+							      pred,
+							      call_string ()),
+			     worklist, logger);
+	}
+      }
+      break;
+
+    case PK_AFTER_SUPERNODE:
+      {
+	if (snode->m_stmts.length ())
+	  add_to_worklist
+	    (program_point::before_stmt (snode,
+					 snode->m_stmts.length () - 1,
+					 call_string ()),
+	     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 (program_point::before_supernode (snode,
+								pred,
+								call_string ()),
+			       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
+		  (program_point::before_supernode (snode,
+						    NULL,
+						    call_string ()),
+		   worklist, logger);
+	      }
+	  }
+      }
+      break;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* class state_purge_annotator  : public dot_annotator.  */
+
+void
+state_purge_annotator::add_node_annotations (pretty_printer *pp,
+					     const supernode &n) const
+{
+  if (m_map == NULL)
+    return;
+
+   pp_printf (pp, "annotation_for_node_%i", n.m_index);
+   pp_printf (pp, " [shape=%s,style=filled,fillcolor=%s,label=\"",
+       "record", "lightblue");
+   pp_left_brace (pp);
+   pp_write_text_to_stream (pp);
+
+   program_point before_supernode
+     (program_point::before_supernode (&n, NULL, call_string ()));
+
+   for (state_purge_map::iterator iter = m_map->begin ();
+	iter != m_map->end ();
+	++iter)
+     {
+       tree var = (*iter).first;
+       state_purge_per_ssa_name *per_var_data = (*iter).second;
+PUSH_IGNORE_WFORMAT
+       if (per_var_data->needed_at_point_p (before_supernode))
+	 pp_printf (pp, "%qE needed here", var);
+       else
+	 pp_printf (pp, "%qE not needed here", var);
+POP_IGNORE_WFORMAT
+       pp_newline (pp);
+     }
+
+   pp_right_brace (pp);
+
+   pp_string (pp, "\"];\n\n");
+   pp_flush (pp);
+}
+
+void
+state_purge_annotator::add_stmt_annotations (pretty_printer *pp,
+					     const gimple *stmt) const
+{
+  if (m_map == NULL)
+    return;
+
+  if (stmt->code == GIMPLE_PHI)
+    return;
+
+  pp_newline (pp);
+
+  const supernode *supernode = m_map->get_sg ().get_supernode_for_stmt (stmt);
+  unsigned int stmt_idx = supernode->get_stmt_index (stmt);
+  program_point before_stmt
+    (program_point::before_stmt (supernode, stmt_idx, call_string ()));
+
+  auto_vec<tree> needed;
+  auto_vec<tree> not_needed;
+  for (state_purge_map::iterator iter = m_map->begin ();
+       iter != m_map->end ();
+       ++iter)
+    {
+      tree var = (*iter).first;
+      state_purge_per_ssa_name *per_var_data = (*iter).second;
+      if (per_var_data->needed_at_point_p (before_stmt))
+	needed.safe_push (var);
+      else
+	not_needed.safe_push (var);
+    }
+
+  tree var;
+  unsigned i;
+  pp_printf (pp, "needed here: {");
+  FOR_EACH_VEC_ELT (needed, i, var)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qE", var);
+POP_IGNORE_WFORMAT
+    }
+  pp_printf (pp, "}");
+  pp_newline (pp);
+  pp_printf (pp, "not needed here: {");
+  FOR_EACH_VEC_ELT (not_needed, i, var)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+PUSH_IGNORE_WFORMAT
+      pp_printf (pp, "%qE", var);
+POP_IGNORE_WFORMAT
+    }
+  pp_newline (pp);
+}
diff --git a/gcc/analyzer/state-purge.h b/gcc/analyzer/state-purge.h
new file mode 100644
index 0000000..849dea0
--- /dev/null
+++ b/gcc/analyzer/state-purge.h
@@ -0,0 +1,165 @@
+/* FIXME.
+   Copyright (C) 2019 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/>.  */
+
+#ifndef GCC_ANALYZER_STATE_PURGE_H
+#define GCC_ANALYZER_STATE_PURGE_H
+
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/program-point.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+template <> struct default_hash_traits<program_point>
+: public pod_hash_traits<program_point>
+{
+};
+
+template <>
+inline hashval_t
+pod_hash_traits<program_point>::hash (value_type v)
+{
+  return v.hash ();
+}
+
+template <>
+inline bool
+pod_hash_traits<program_point>::equal (const value_type &existing,
+                                 const value_type &candidate)
+{
+  return existing == candidate;
+}
+template <>
+inline void
+pod_hash_traits<program_point>::mark_deleted (value_type &v)
+{
+  v = program_point::deleted ();
+}
+template <>
+inline void
+pod_hash_traits<program_point>::mark_empty (value_type &v)
+{
+  v = program_point::empty ();
+}
+template <>
+inline bool
+pod_hash_traits<program_point>::is_deleted (value_type v)
+{
+  return v.m_kind == PK_DELETED;
+}
+template <>
+inline bool
+pod_hash_traits<program_point>::is_empty (value_type v)
+{
+  return v.m_kind == PK_EMPTY;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/* The result of analyzing which SSA names can be purged from state at
+   different points in the program, so that we can simplify program_state
+   objects, in the hope of reducing state-blowup.  */
+
+class state_purge_map : public log_user
+{
+public:
+  typedef ordered_hash_map<tree, state_purge_per_ssa_name *> map_t;
+  typedef map_t::iterator iterator;
+
+  state_purge_map (const supergraph &sg, logger *logger);
+  ~state_purge_map ();
+
+  const state_purge_per_ssa_name &get_data_for_ssa_name (tree var) const
+  {
+    gcc_assert (TREE_CODE (var) == SSA_NAME);
+    state_purge_per_ssa_name **slot
+      = const_cast <map_t&> (m_map).get (var);
+    return **slot;
+  }
+
+  const supergraph &get_sg () const { return m_sg; }
+
+  iterator begin () const { return m_map.begin (); }
+  iterator end () const { return m_map.end (); }
+
+private:
+  const supergraph &m_sg;
+  map_t m_map;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+/* The part of a state_purge_map relating to a specific SSA name.
+
+   The result of analyzing a given SSA name, recording which
+   program_points need to retain state information about it to handle
+   their successor states, so that we can simplify program_state objects,
+   in the hope of reducing state-blowup.  */
+
+class state_purge_per_ssa_name
+{
+public:
+  state_purge_per_ssa_name (const state_purge_map &map,
+			    tree var,
+			    function *fun);
+
+  bool needed_at_point_p (const program_point &point) const;
+
+  /* This stores points, but ignores interprocedural issues;
+     everything will be at depth 0.
+     TODO: should we build a program_point graph first???  */
+
+private:
+  static program_point before_use_stmt (const state_purge_map &map,
+					const gimple *use_stmt);
+
+  void add_to_worklist (const program_point &point,
+			auto_vec<program_point *> *worklist,
+			logger *logger);
+
+  void process_point (const program_point &point,
+		      auto_vec<program_point *> *worklist,
+		      logger *logger);
+
+  typedef hash_set<program_point> point_set_t;
+  point_set_t m_points_needing_var;
+  tree m_var;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* Subclass of dot_annotator for use by -fdump-analyzer-state-purge.
+   Annotate the .dot output with state-purge information.  */
+
+class state_purge_annotator : public dot_annotator
+{
+public:
+  state_purge_annotator (const state_purge_map *map) : m_map (map) {}
+
+  void add_node_annotations (pretty_printer *pp, const supernode &n)
+    const FINAL OVERRIDE;
+
+  void add_stmt_annotations (pretty_printer *pp,const gimple *stmt)
+    const FINAL OVERRIDE;
+
+private:
+  const state_purge_map *m_map;
+};
+
+#endif /* GCC_ANALYZER_STATE_PURGE_H */
-- 
1.8.5.3

