From c7372bb32354cc1853dd79ec7cd2f0c33b6c1c20 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 7 May 2019 10:37:30 -0400
Subject: [PATCH 22/54] FIXME: initial implementation of state purging

---
 gcc/analyzer/constraints.cc                       | 225 +++++++
 gcc/analyzer/constraints.h                        |  17 +
 gcc/analyzer/impl-4.cc                            | 719 ++++++++++++++++++++--
 gcc/analyzer/supergraph.cc                        |  43 +-
 gcc/analyzer/supergraph.h                         |  24 +-
 gcc/common.opt                                    |   4 +
 gcc/testsuite/gcc.dg/analyzer/loop.c              |   3 +
 gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c |   3 +
 8 files changed, 985 insertions(+), 53 deletions(-)

diff --git a/gcc/analyzer/constraints.cc b/gcc/analyzer/constraints.cc
index 249e9de..3e28c70 100644
--- a/gcc/analyzer/constraints.cc
+++ b/gcc/analyzer/constraints.cc
@@ -1203,6 +1203,114 @@ path_state::calc_escaped_p (path_var pv)
   return false;
 }
 
+/* Delete any information about path_var instances identified by P.
+   Such instances are removed from equivalence classes, and any
+   redundant ECs and constraints are also removed.  */
+
+void
+path_state::purge (const purge_criteria &p)
+{
+  /* Delete any path_vars identified by P within the various equivalence
+     classes.  */
+  for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+    {
+      equiv_class *ec = m_equiv_classes[ec_idx];
+
+      int i;
+      path_var *pv;
+      bool delete_ec = false;
+      FOR_EACH_VEC_ELT (ec->m_vars, i, pv)
+	{
+	  if (p.should_purge_p (*pv))
+	    if (ec->del (*pv))
+	      if (!ec->m_constant)
+		delete_ec = true;
+	}
+
+      if (delete_ec)
+	{
+	  delete ec;
+	  m_equiv_classes.ordered_remove (ec_idx);
+
+	  /* Update the constraints, potentially removing some.  */
+	  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+	    {
+	      constraint *c = &m_constraints[con_idx];
+
+	      /* Remove constraints that refer to the deleted EC.  */
+	      if (c->m_lhs == ec_idx
+		  || c->m_rhs == ec_idx)
+		{
+		  m_constraints.ordered_remove (con_idx);
+		}
+	      else
+		{
+		  /* Renumber constraints that refer to ECs that have
+		     had their idx changed.  */
+		  c->m_lhs.update_for_removal (ec_idx);
+		  c->m_rhs.update_for_removal (ec_idx);
+
+		  con_idx++;
+		}
+	    }
+	}
+      else
+	ec_idx++;
+    }
+
+  /* Now delete any constraints that are purely between constants.  */
+  for (unsigned con_idx = 0; con_idx < m_constraints.length (); )
+    {
+      constraint *c = &m_constraints[con_idx];
+      if (m_equiv_classes[c->m_lhs.m_idx]->m_vars.length () == 0
+	  && m_equiv_classes[c->m_rhs.m_idx]->m_vars.length () == 0)
+	m_constraints.ordered_remove (con_idx);
+      else
+	{
+	  con_idx++;
+	}
+    }
+
+  /* Finally, delete any ECs that purely contain constants and aren't
+     referenced by any constraints.  */
+  for (unsigned ec_idx = 0; ec_idx < m_equiv_classes.length (); )
+    {
+      equiv_class *ec = m_equiv_classes[ec_idx];
+      if (ec->m_vars.length () == 0)
+	{
+	  bool has_constraint = false;
+	  for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+	       con_idx++)
+	    {
+	      constraint *c = &m_constraints[con_idx];
+	      if (c->m_lhs.m_idx == ec_idx
+		  || c->m_rhs.m_idx == ec_idx)
+		{
+		  has_constraint = true;
+		  break;
+		}
+	    }
+	  if (!has_constraint)
+	    {
+	      delete ec;
+	      m_equiv_classes.ordered_remove (ec_idx);
+
+	      /* Renumber constraints that refer to ECs that have
+		 had their idx changed.  */
+	      for (unsigned con_idx = 0; con_idx < m_constraints.length ();
+		   con_idx++)
+		{
+		  constraint *c = &m_constraints[con_idx];
+		  c->m_lhs.update_for_removal (ec_idx);
+		  c->m_rhs.update_for_removal (ec_idx);
+		}
+	      continue;
+	    }
+	}
+      ec_idx++;
+    }
+}
+
 // FIXME:
 void
 debug (const path_state &ps)
@@ -1968,6 +2076,122 @@ test_equality ()
   }
 }
 
+/* A subclass of purge_criteria for selftests: purge a specific path_var.  */
+
+class purge_one_path_var : public purge_criteria
+{
+public:
+  purge_one_path_var (path_var victim) : m_victim (victim) {}
+
+  bool should_purge_p (path_var pv) const FINAL OVERRIDE
+  {
+    return pv == m_victim;
+  }
+
+private:
+  path_var m_victim;
+};
+
+/* A subclass of purge_criteria for selftests: purge all path_var instances.  */
+
+class purge_all_path_vars : public purge_criteria
+{
+public:
+  bool should_purge_p (path_var) const FINAL OVERRIDE
+  {
+    return true;
+  }
+};
+
+/* Check that path_state objects can "forget".  */
+
+static void
+test_purging ()
+{
+  path_var int_42 (build_int_cst (integer_type_node, 42), 0);
+  path_var int_0 (build_int_cst (integer_type_node, 0), 0);
+
+  path_var x (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("x"),
+			  integer_type_node), 0);
+  path_var y (build_decl (UNKNOWN_LOCATION, PARM_DECL,
+			  get_identifier ("y"),
+			  integer_type_node), 0);
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, EQ_EXPR, y);
+    ASSERT_NE (ps0, ps1);
+
+    purge_one_path_var px (x);
+    ps1.purge (px);
+
+    purge_one_path_var py (y);
+    ps1.purge (py);
+
+    ASSERT_EQ (ps0, ps1);
+  }
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, EQ_EXPR, int_42);
+    ASSERT_NE (ps0, ps1);
+
+    purge_one_path_var px (x);
+    ps1.purge (px);
+
+    ASSERT_EQ (ps0, ps1);
+  }
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, GE_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (ps1, x, LE_EXPR, int_42);
+    ASSERT_NE (ps0, ps1);
+
+    purge_one_path_var px (x);
+    ps1.purge (px);
+
+    ASSERT_EQ (ps0, ps1);
+  }
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (ps1, y, NE_EXPR, int_0);
+    ASSERT_NE (ps0, ps1);
+
+    purge_one_path_var px (x);
+    ps1.purge (px);
+    ASSERT_NE (ps0, ps1);
+
+    ASSERT_CONDITION_TRUE (ps1, y, NE_EXPR, int_0);
+  }
+
+  {
+    path_state ps0;
+    path_state ps1;
+
+    ADD_SAT_CONSTRAINT (ps1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (ps1, y, NE_EXPR, int_0);
+    ASSERT_NE (ps0, ps1);
+
+    purge_all_path_vars p;
+    ps1.purge (p);
+    ASSERT_EQ (ps0, ps1);
+  }
+
+}
+
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -1980,6 +2204,7 @@ analyzer_constraints_cc_tests ()
   test_assignment ();
   test_stack_frames ();
   test_equality ();
+  test_purging ();
 }
 
 } // namespace selftest
diff --git a/gcc/analyzer/constraints.h b/gcc/analyzer/constraints.h
index 40243d0..6c19bea 100644
--- a/gcc/analyzer/constraints.h
+++ b/gcc/analyzer/constraints.h
@@ -80,6 +80,15 @@ public:
   int m_stack_depth; // or -1 for globals?
 };
 
+/* Abstract base class for specifying how state should be purged.  */
+
+class purge_criteria
+{
+public:
+  virtual ~purge_criteria () {}
+  virtual bool should_purge_p (path_var pv) const = 0;
+};
+
 /* Optional per-engine extra state (to avoid making path_state a
    template).  */
 
@@ -163,6 +172,12 @@ public:
     return std::string ("ec") + std::to_string (m_idx);
   }
 
+  void update_for_removal (equiv_class_id other)
+  {
+    if (m_idx > other.m_idx)
+      m_idx--;
+  }
+
   unsigned m_idx;
 };
 
@@ -261,6 +276,8 @@ public:
   void recursively_mark_as_escaping (equiv_class_id ec_id);
   bool calc_escaped_p (path_var pv);
 
+  void purge (const purge_criteria &p);
+
   auto_vec<equiv_class *> m_equiv_classes;
   auto_vec<constraint> m_constraints;
   int m_stack_depth;
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index bab8f65..67b2ec1 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -43,7 +43,12 @@
 #include "selftest.h"
 #include "digraph.h"
 #include "constraints.h"
-//#include "tree-ssa-operands.h"
+#include "tree-ssa-operands.h"
+#include "tree-vrp.h"
+#include "gimple-ssa.h"
+#include "tree-ssanames.h"
+#include "tree-phinodes.h"
+#include "ssa-iterators.h"
 
 /* Overview:
 
@@ -102,6 +107,7 @@ class exploded_node;
 class exploded_edge;
 class checker4_path;
 class stmt_finder;
+class state_purge_per_ssa_name;
 
 ////////////////////////////////////////////////////////////////////////////
 
@@ -157,6 +163,24 @@ struct pod_hash_traits : typed_noop_remove<Type>
 
 ////////////////////////////////////////////////////////////////////////////
 
+class format
+{
+public:
+  format (bool newlines) : m_newlines (newlines) {}
+
+  void spacer (pretty_printer *pp) const
+  {
+    if (m_newlines)
+      pp_newline (pp);
+    else
+      pp_space (pp);
+  }
+
+  bool m_newlines;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
 /* A string of return_superedge pointers, representing a call stack
    at a program point.
 
@@ -265,9 +289,9 @@ public:
   {
   }
 
-  void print (pretty_printer *pp) const;
+  void print (pretty_printer *pp, const format &f) const;
   void print_source_line (pretty_printer *pp) const;
-  std::string to_str () const;
+  std::string to_str (const format &f) const;
 
   hashval_t hash () const;
   bool operator== (const program_point &other) const
@@ -358,6 +382,126 @@ public:
   call_string m_call_string;
 };
 
+} // namespace impl_4
+
+using impl_4::pod_hash_traits;
+using impl_4::program_point;
+
+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;
+}
+
+namespace impl_4 {
+
+////////////////////////////////////////////////////////////////////////////
+
+/* 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 hash_map<tree, state_purge_per_ssa_name *> map_t;
+  typedef map_t::iterator iterator;
+
+  state_purge_map (const supergraph &sg, logger *logger);
+
+  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;
+  }
+
+  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;
+
+  /* FIXME: this stores points, but ignores interprocedural issues;
+     everything will be at depth 0.  */
+  /* 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;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
 /* Data shared by all program_state instances.  */
 
 class extrinsic_state
@@ -710,10 +854,12 @@ class exploded_graph : public digraph, public log_user
 public:
   exploded_graph (const supergraph &sg, logger *logger,
 		  const extrinsic_state &ext_state,
+		  const state_purge_map *purge_map,
 		  int verbosity);
 
   const supergraph &get_supergraph () const { return m_sg; }
   const extrinsic_state &get_ext_state () const { return m_ext_state; }
+  const state_purge_map *get_purge_map () const { return m_purge_map; }
 
   exploded_node *get_origin () const { return m_origin; }
 
@@ -752,6 +898,8 @@ private:
 
   const extrinsic_state &m_ext_state;
 
+  const state_purge_map *const m_purge_map;
+
   const int m_verbosity;
 
   /* Stats.  */
@@ -1462,11 +1610,11 @@ call_string::calc_recursion_depth () const
 /* FIXME.  */
 
 void
-program_point::print (pretty_printer *pp) const
+program_point::print (pretty_printer *pp, const format &f) const
 {
   pp_string (pp, "callstring: ");
   m_call_string.print (pp);
-  pp_newline (pp);
+  f.spacer (pp);
 
   switch (m_kind)
     {
@@ -1475,7 +1623,6 @@ program_point::print (pretty_printer *pp) const
 
     case PK_ORIGIN:
       pp_printf (pp, "origin");
-      pp_newline (pp);
       break;
 
     case PK_BEFORE_SUPERNODE:
@@ -1486,7 +1633,7 @@ program_point::print (pretty_printer *pp) const
 	else
 	  pp_printf (pp, "before SN: %i (NULL from-edge)",
 		     m_supernode->m_index);
-	pp_newline (pp);
+	f.spacer (pp);
 	for (gphi_iterator gpi
 	       = const_cast<supernode *>(get_supernode ())->start_phis ();
 	     !gsi_end_p (gpi); gsi_next (&gpi))
@@ -1494,7 +1641,6 @@ program_point::print (pretty_printer *pp) const
 	    gphi *phi = gpi.phi ();
 	    pp_gimple_stmt_1 (pp, const_cast <gphi *> (phi),
 			      0, (dump_flags_t)0);
-	    pp_newline (pp);
 	  }
       }
       break;
@@ -1502,26 +1648,25 @@ program_point::print (pretty_printer *pp) const
     case PK_BEFORE_STMT:
       pp_printf (pp, "before (SN: %i stmt: %i): ", m_supernode->m_index,
 		 m_stmt_idx);
-      pp_newline (pp);
+      f.spacer (pp);
       pp_gimple_stmt_1 (pp, const_cast <gimple *> (get_stmt ()),
 			0, (dump_flags_t)0);
-      pp_newline (pp);
-      print_source_line (pp);
+      if (f.m_newlines)
+	print_source_line (pp);
       break;
 
     case PK_AFTER_STMT:
       pp_printf (pp, "after (SN: %i stmt: %i): ", m_supernode->m_index,
 		 m_stmt_idx);
-      pp_newline (pp);
+      f.spacer (pp);
       pp_gimple_stmt_1 (pp, const_cast <gimple *> (get_stmt ()),
 			0, (dump_flags_t)0);
-      pp_newline (pp);
-      print_source_line (pp);
+      if (f.m_newlines)
+	print_source_line (pp);
       break;
 
     case PK_AFTER_SUPERNODE:
       pp_printf (pp, "after SN: %i", m_supernode->m_index);
-      pp_newline (pp);
       break;
     }
 }
@@ -1558,10 +1703,10 @@ program_point::print_source_line (pretty_printer *pp) const
 /* FIXME.  */
 
 std::string
-program_point::to_str () const
+program_point::to_str (const format &f) const
 {
   pretty_printer pp;
-  print (&pp);
+  print (&pp, f);
   return std::string (pp_formatted_text (&pp));
 }
 
@@ -1954,6 +2099,62 @@ program_state::to_str (const extrinsic_state &ext_state) const
   return std::string (pp_formatted_text (&pp));
 }
 
+/* Implementation of purge_criteria for using state_purge_map.
+
+   Purge state within a path_state using PURGE_MAP for POINT.
+   Use the sm-states within OLD_STATE to avoid purging path_vars
+   that have any sm-state.  */
+
+class purge_at_point_criteria : public purge_criteria
+{
+public:
+  purge_at_point_criteria (const program_point &point,
+			   const state_purge_map *purge_map,
+			   const program_state &old_state)
+  : m_point (point),
+    m_flat_point (point),
+    m_purge_map (purge_map),
+    m_old_state (old_state)
+  {
+    m_flat_point.m_call_string = call_string ();
+  }
+
+  bool should_purge_p (path_var pv) const FINAL OVERRIDE
+  {
+    if (TREE_CODE (pv.m_tree) != SSA_NAME)
+      return false;
+    if (pv.m_stack_depth != m_point.get_stack_depth ())
+      return false;
+
+    if (!m_purge_map)
+      return false;
+
+    const state_purge_per_ssa_name &per_var_data
+      = m_purge_map->get_data_for_ssa_name (pv.m_tree);
+    if (!per_var_data.needed_at_point_p (m_flat_point))
+      {
+	/* Don't purge vars that have sm state, to avoid
+	   losing leak complaints.  */
+	int i;
+	sm_state_map *smap;
+	FOR_EACH_VEC_ELT (m_old_state.m_checker_states, i, smap)
+	  {
+	    if (smap->get_state (pv) != 0)
+	      return false;
+	  }
+
+	return true;
+      }
+    return false;
+  }
+
+private:
+  const program_point &m_point;
+  program_point m_flat_point;
+  const state_purge_map *m_purge_map;
+  const program_state &m_old_state;
+};
+
 /* FIXME.
    Generate a simpler version of THIS, discarding state that's no longer
    relevant at POINT.
@@ -1965,31 +2166,34 @@ program_state
 program_state::prune_for_point (const exploded_graph &eg,
 				const program_point &point) const
 {
-  /* FIXME: for now a hack: purge all states at exit nodes (which won't work
-     interprocedurally).  */
-#if 0
-  if (const supernode *supernode = point.get_supernode ())
-    if (supernode->return_p ())
-      return program_state (eg.m_ext_state);
-#endif
+  function *fun = point.get_function ();
+  if (!fun)
+    return *this;
+
+  /* The points in the state map don't have call strings, so flatten
+     accordingly.  */
+  program_point flat_point (point);
+  flat_point.m_call_string = call_string ();
+
+  program_state new_state (*this);
 
-  // FIXME: what about leak detection, though?
+  purge_at_point_criteria criteria (point, eg.get_purge_map (),
+				    *this);
+  new_state.m_path_state.purge (criteria);
 
 #if 0
-  {
-    function *fun = point.get_function ();
-    if (fun)
-      {
-	auto_cfun sentinel (fun);
-	debug_immediate_uses ();
-	/* FIXME: maybe do the pruning here: we only know what state will be
-	   important when in conjunction with a program_point.  */
-      }
-  }
+  // FIXME: state maps also
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    {
+      smap->purge (criteria);
+    }
+  // FIXME: what about leaks?
 #endif
 
+  return new_state;
 
-  return *this;
 }
 
 ////////////////////////////////////////////////////////////////////////////
@@ -2010,7 +2214,8 @@ exploded_node::dump_dot (graphviz_out *gv) const
   pp_printf (pp, "EN: %i", m_index);
   pp_newline (pp);
 
-  m_ps.m_point.print (pp);
+  format f (true);
+  m_ps.m_point.print (pp, f);
   pp_newline (pp);
 
   m_ps.m_state.print (m_ext_state, pp);
@@ -2824,8 +3029,10 @@ exploded_edge::dump_dot (graphviz_out *gv) const
 
 exploded_graph::exploded_graph (const supergraph &sg, logger *logger,
 				const extrinsic_state &ext_state,
+				const state_purge_map *purge_map,
 				int verbosity)
 : log_user (logger), m_sg (sg), m_ext_state (ext_state),
+  m_purge_map (purge_map),
   m_verbosity (verbosity),
   m_node_reuse_count (0)
 {
@@ -2862,10 +3069,13 @@ exploded_graph::get_or_create_node (const program_point &point,
   JIT_LOG_FUNC (get_logger ());
   if (get_logger ())
     {
-      log ("point: %s", point.to_str ().c_str ());
+      format f (false);
+      log ("point: %s", point.to_str (f).c_str ());
       log ("state: %s", state.to_str (m_ext_state).c_str ());
     }
 
+  auto_cfun sentinel (point.get_function ());
+
   /* Prune state to try to improve the chances of a cache hit,
      avoiding generating redundant nodes.  */
   program_state pruned_state = state.prune_for_point (*this, point);
@@ -2890,8 +3100,9 @@ exploded_graph::get_or_create_node (const program_point &point,
 
   if (get_logger ())
     {
+      format f (false);
       log ("created EN: %i", node->m_index);
-      log ("point: %s", point.to_str ().c_str ());
+      log ("point: %s", point.to_str (f).c_str ());
       log ("pruned_state: %s", pruned_state.to_str (m_ext_state).c_str ());
     }
 
@@ -2957,8 +3168,9 @@ exploded_graph::process_node (exploded_node *node)
   const program_state &state = node->get_state ();
   if (get_logger ())
     {
+      format f (false);
       log ("point: %s, state: %s",
-	   point.to_str ().c_str (),
+	   point.to_str (f).c_str (),
 	   state.to_str (m_ext_state).c_str ());
     }
 
@@ -3986,6 +4198,410 @@ private:
 
 ////////////////////////////////////////////////////////////////////////////
 
+/* FIXME.  */
+
+state_purge_map::state_purge_map (const supergraph &sg,
+				  logger *logger)
+: log_user (logger), m_sg (sg)
+{
+  JIT_LOG_FUNC (logger);
+
+  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_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)
+{
+  JIT_LOG_FUNC (map.get_logger ());
+
+  map.log ("SSA name: %qE", var);
+  if (map.get_logger ())
+    {
+      /* Show def stmt.  */
+      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.
+     FIXME: worklist can't be a vec<program_point>; we can't have a
+     vec<program_point> because vec<T> require T to be a POD.
+     Hence (annoyingly) we 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))
+	{
+	  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.m_sg.get_node_for_block (use_stmt->bb);
+
+	  /* 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.m_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 ())
+		{
+		  program_point point
+		    = program_point::after_supernode (snode, call_string ());
+		  add_to_worklist (point, &worklist, map.get_logger ());
+		  m_points_needing_var.add (point);
+		}
+	    }
+	}
+    }
+
+  /* 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 is needed to process:", var);
+      for (point_set_t::iterator iter = m_points_needing_var.begin ();
+	   iter != m_points_needing_var.end ();
+	   ++iter)
+	{
+	  format f (false);
+	  map.log ("  point: %s", (*iter).to_str (f).c_str ());
+	}
+    }
+}
+
+/* FIXME.  */
+
+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);
+}
+
+/* FIXME.  */
+
+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.m_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 ());
+    }
+}
+
+/* FIXME.  */
+
+void
+state_purge_per_ssa_name::add_to_worklist (const program_point &point,
+					   auto_vec<program_point *> *worklist,
+					   logger *logger)
+{
+  JIT_LOG_FUNC (logger);
+  if (logger)
+    {
+      format f (false);
+      logger->log ("point: %s", point.to_str (f).c_str ());
+    }
+
+  if (m_points_needing_var.contains (point))
+    {
+      if (logger)
+	logger->log ("already seen");
+    }
+  else
+    {
+      if (logger)
+	logger->log ("not seen; adding to worklist");
+      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)
+{
+  JIT_LOG_FUNC (logger);
+  if (logger)
+    {
+      format f (false);
+      logger->log ("considering point: %s", point.to_str (f).c_str ());
+    }
+
+  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->m_src)
+	  add_to_worklist
+	    (program_point::after_supernode (point.m_from_edge->m_src,
+					     call_string ()),
+	     worklist, logger);
+      }
+      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::after_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_STMT:
+      add_to_worklist (program_point::before_stmt (snode,
+						   point.m_stmt_idx,
+						   call_string ()),
+		       worklist, logger);
+      break;
+
+    case PK_AFTER_SUPERNODE:
+      {
+	if (snode->m_stmts.length ())
+	  add_to_worklist
+	    (program_point::after_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);
+	  }
+      }
+      break;
+    }
+}
+
+/* FIXME.  */
+
+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 ATTRIBUTE_UNUSED,
+			     const supernode &n ATTRIBUTE_UNUSED)
+    const FINAL OVERRIDE
+  {
+    if (m_map == NULL)
+      return;
+
+#if 0
+    //pp_printf (pp, "FIXME: node annotation");
+    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);
+
+    // TODO: which in-edge?
+    program_point before_supernode
+      (program_point::before_supernode (&n, 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;
+	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);
+	pp_newline (pp);
+      }
+
+    pp_right_brace (pp);
+
+    pp_string (pp, "\"];\n\n");
+    pp_flush (pp);
+#endif
+  }
+
+  void add_stmt_annotations (pretty_printer *pp ATTRIBUTE_UNUSED,
+			     const gimple *stmt ATTRIBUTE_UNUSED)
+    const FINAL OVERRIDE
+  {
+    if (m_map == NULL)
+      return;
+
+    pp_newline (pp);
+    pp_printf (pp, "FIXME: stmt annotation");
+    pp_newline (pp);
+
+    if (stmt->code == GIMPLE_PHI)
+      return;
+
+    const supernode *supernode = m_map->m_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 ()));
+
+    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))
+	  pp_printf (pp, "%qE needed here", var);
+	else
+	  pp_printf (pp, "%qE not needed here", var);
+	pp_newline (pp);
+      }
+  }
+
+private:
+  const state_purge_map *m_map;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
 #if 0
 /* FIXME.  */
 
@@ -4012,13 +4628,24 @@ impl_run_checkers (logger *logger)
   /* Create the supergraph.  */
   supergraph sg (logger);
 
+  state_purge_map *purge_map = NULL;
+
+  if (flag_analyzer_state_purge)
+      purge_map = new state_purge_map (sg, logger);
+
   if (1)
     {
+      state_purge_annotator a (purge_map);
       char *filename = concat (dump_base_name, ".supergraph.dot", NULL);
-      sg.dump_dot (filename, (enum supergraph_dot_flags)0);
+      sg.dump_dot (filename, (enum supergraph_dot_flags)0, &a);
       free (filename);
     }
 
+#if 0
+  // FIXME: early exit (while debugging state_purge_map)
+  return;
+#endif
+
   // TODO: selftest for auto_delete_vec
   auto_delete_vec <state_machine> checkers;
   make_checkers (checkers, logger);
@@ -4030,11 +4657,12 @@ impl_run_checkers (logger *logger)
       FOR_EACH_VEC_ELT (checkers, i, sm)
 	logger->log ("checkers[%i]: %s", i, sm->get_name ());
     }
-  //engine e (sg, sm, logger, analyzer_verbosity);
 
-  // TODO: add initial nodes
-  impl_4::exploded_graph eg (sg, logger, checkers, analyzer_verbosity);
+  /* The exploded graph.  */
+  impl_4::exploded_graph eg (sg, logger, checkers, purge_map,
+			     analyzer_verbosity);
 
+  /* Add entrypoints to the graph for externally-callable functions.  */
   cgraph_node *node;
   FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
   {
@@ -4048,6 +4676,7 @@ impl_run_checkers (logger *logger)
     eg.add_function_entry (fun);
   }
 
+  /* Now process the worklist, exploring the <point, state> graph.  */
   eg.process_worklist ();
 
   if (1)
@@ -4060,6 +4689,8 @@ impl_run_checkers (logger *logger)
     }
 
   eg.log_stats ();
+
+  delete purge_map;
 }
 
 } // namespace impl_4
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
index 31609e2..a3324b1 100644
--- a/gcc/analyzer/supergraph.cc
+++ b/gcc/analyzer/supergraph.cc
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3.  If not see
 #include <string>
 #include "supergraph.h"
 #include "analyzer-logging.h"
+#include "tree-diagnostic.h"
 
 /* Get the cgraph_edge, but only if there's an underlying function body.  */
 
@@ -105,6 +106,7 @@ supergraph::supergraph (logger *logger)
 	    {
 	      gimple *stmt = gsi_stmt (gsi);
 	      node_for_stmts->m_stmts.safe_push (stmt);
+	      m_stmt_to_node_t.put (stmt, node_for_stmts);
 	      if (cgraph_edge *edge = supergraph_call_edge (fun, stmt))
 		{
 		  m_cgraph_edge_to_caller_prev_node.put(edge, node_for_stmts);
@@ -143,7 +145,9 @@ supergraph::supergraph (logger *logger)
 	      basic_block dest_cfg_block = cfg_edge->dest;
 	      supernode *dest_supernode
 		= *m_bb_to_initial_node.get (dest_cfg_block);
-	      add_cfg_edge (src_supernode, dest_supernode, cfg_edge, idx);
+	      cfg_superedge *cfg_sedge
+		= add_cfg_edge (src_supernode, dest_supernode, cfg_edge, idx);
+	      m_cfg_edge_to_cfg_superedge.put (cfg_edge, cfg_sedge);
 	    }
       }
 
@@ -316,10 +320,16 @@ void
 supergraph::dump_dot_to_file (FILE *fp, enum supergraph_dot_flags flags,
 			      const dot_annotator *a) const
 {
-  pretty_printer pp;
-  pp.buffer->stream = fp;
-  dump_dot_to_pp (&pp, flags, a);
-  pp_flush (&pp);
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_show_color (pp) = 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) = default_tree_printer;
+
+  pp->buffer->stream = fp;
+  dump_dot_to_pp (pp, flags, a);
+  pp_flush (pp);
+  delete pp;
 }
 
 void
@@ -341,16 +351,19 @@ supergraph::add_node (function *fun, basic_block bb, gcall *returning_call,
   return n;
 }
 
-superedge *
+cfg_superedge *
 supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx)
 {
   /* Special-case switch edges.  */
   // FIXME: see ana::at_stmt::add_unreached_successors_to_worklist
   gimple *stmt = src->get_last_stmt ();
+  cfg_superedge *new_edge;
   if (stmt && stmt->code == GIMPLE_SWITCH)
-    return add_edge (new switch_cfg_superedge (src, dest, e, idx));
+    new_edge = new switch_cfg_superedge (src, dest, e, idx);
   else
-    return add_edge (new cfg_superedge (src, dest, e));
+    new_edge = new cfg_superedge (src, dest, e);
+  add_edge (new_edge);
+  return new_edge;
 }
 
 call_superedge *
@@ -375,6 +388,7 @@ superedge *
 supergraph::add_edge (superedge *e)
 {
   m_edges.safe_push (e);
+  e->m_dest->m_preds.safe_push (e);
   e->m_src->m_succs.safe_push (e);
   return e;
 }
@@ -440,6 +454,8 @@ supernode::dump_dot (graphviz_out *gv,
     {
       gimple *stmt = gsi_stmt (gpi);
       pp_gimple_stmt_1 (pp, stmt, 0, (dump_flags_t)0);
+      if (a)
+	a->add_stmt_annotations (pp, stmt);
       pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
       pp_newline (pp);
     }
@@ -561,6 +577,17 @@ supernode::get_end_location () const
   return UNKNOWN_LOCATION;
 }
 
+unsigned int
+supernode::get_stmt_index (const gimple *stmt) const
+{
+  unsigned i;
+  gimple *iter_stmt;
+  FOR_EACH_VEC_ELT (m_stmts, i, iter_stmt)
+    if (iter_stmt == stmt)
+      return i;
+  gcc_unreachable ();
+}
+
 void
 superedge::dump_dot (graphviz_out *gv,
 		     enum supergraph_dot_flags flags ATTRIBUTE_UNUSED) const
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
index c497de6..f362d69 100644
--- a/gcc/analyzer/supergraph.h
+++ b/gcc/analyzer/supergraph.h
@@ -114,6 +114,18 @@ public:
 	      (m_cgraph_edge_to_return_superedge).get (edge));
   }
 
+  cfg_superedge *get_edge_for_cfg_edge (edge e) const
+  {
+    return (*const_cast <cfg_edge_to_cfg_superedge_t &>
+	      (m_cfg_edge_to_cfg_superedge).get (e));
+  }
+
+  supernode *get_supernode_for_stmt (const gimple *stmt) const
+  {
+    return (*const_cast <stmt_to_node_t &>(m_stmt_to_node_t).get
+	    (const_cast <gimple *> (stmt)));
+  }
+
   void dump_dot_to_pp (pretty_printer *pp, enum supergraph_dot_flags flags,
 		       const dot_annotator *a = NULL) const;
   void dump_dot_to_file (FILE *fp, enum supergraph_dot_flags flags,
@@ -132,7 +144,7 @@ public:
 private:
   supernode *add_node (function *fun, basic_block bb, gcall *returning_call,
 		       gimple_seq phi_nodes);
-  superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx);
+  cfg_superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx);
   call_superedge *add_call_superedge (supernode *src, supernode *dest,
 				      cgraph_edge *cedge);
   return_superedge *add_return_superedge (supernode *src, supernode *dest,
@@ -152,6 +164,10 @@ private:
   cgraph_edge_to_node_t m_cgraph_edge_to_caller_prev_node;
   cgraph_edge_to_node_t m_cgraph_edge_to_caller_next_node;
 
+  typedef hash_map<::edge, cfg_superedge *>
+    cfg_edge_to_cfg_superedge_t;
+  cfg_edge_to_cfg_superedge_t m_cfg_edge_to_cfg_superedge;
+
   typedef hash_map<cgraph_edge *, call_superedge *>
     cgraph_edge_to_call_superedge_t;
   cgraph_edge_to_call_superedge_t m_cgraph_edge_to_call_superedge;
@@ -159,6 +175,9 @@ private:
   typedef hash_map<cgraph_edge *, return_superedge *>
     cgraph_edge_to_return_superedge_t;
   cgraph_edge_to_return_superedge_t m_cgraph_edge_to_return_superedge;
+
+  typedef hash_map<gimple *, supernode *> stmt_to_node_t;
+  stmt_to_node_t m_stmt_to_node_t;
 };
 
 /* FIXME.  */
@@ -222,11 +241,14 @@ class supernode
     return dyn_cast<gcall *> (stmt);
   }
 
+  unsigned int get_stmt_index (const gimple *stmt) const;
+
   function * const m_fun; // alternatively could be stored as runs of indices within the supergraph
   const basic_block m_bb;
   gcall * const m_returning_call; // for handling the result of a returned call
   gimple_seq m_phi_nodes; // ptr to that of the underlying BB, for the first supernode for the BB
   auto_vec<gimple *> m_stmts;
+  auto_vec<superedge *> m_preds;
   auto_vec<superedge *> m_succs;
   const int m_index; /* unique within the supergraph as a whole.  */
 };
diff --git a/gcc/common.opt b/gcc/common.opt
index c59063d..6559137 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1000,6 +1000,10 @@ fanalyzer-impl=
 Common Joined RejectNegative UInteger Var(flag_analyzer_impl)
 FIXME
 
+fanalyzer-state-purge
+Common Var(flag_analyzer_state_purge) Init(1)
+FIXME
+
 fargument-alias
 Common Ignore
 Does nothing. Preserved for backward compatibility.
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop.c b/gcc/testsuite/gcc.dg/analyzer/loop.c
index 23294f2..aeba702 100644
--- a/gcc/testsuite/gcc.dg/analyzer/loop.c
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -1,3 +1,6 @@
+// FIXME:
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
 void test(void)
 {
   int i;
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c
index 062273d..18ba6bd 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-many-paths.c
@@ -1,3 +1,6 @@
+// FIXME:
+/* { dg-additional-options "-fno-analyzer-state-purge" } */
+
 #include <stdlib.h>
 
 extern int foo (void);
-- 
1.8.5.3

