diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index 18786b5..50e8caf 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -161,6 +161,7 @@ equiv_class::add (svalue_id sid, const constraint_manager &cm)
   if (tree cst = cm.maybe_get_constant (sid))
     {
       gcc_assert (m_constant == NULL_TREE);
+      gcc_assert (CONSTANT_CLASS_P (cst));
       m_constant = cst;
       m_cst_sid = sid;
     }
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
index 5f0d3ca..0ceba72 100644
--- a/gcc/analyzer/constraint-manager.h
+++ b/gcc/analyzer/constraint-manager.h
@@ -101,6 +101,8 @@ public:
   bool m_escaped;
 };
 
+/* FIXME.  */
+
 enum constraint_op
 {
   CONSTRAINT_NE,
diff --git a/gcc/analyzer/hardcoded-state-machine.cc b/gcc/analyzer/hardcoded-state-machine.cc
index 0713d3b..084d0e3 100644
--- a/gcc/analyzer/hardcoded-state-machine.cc
+++ b/gcc/analyzer/hardcoded-state-machine.cc
@@ -108,6 +108,12 @@ state_machine::get_state_name (state_t s) const
   return m_state_names[s];
 }
 
+void
+state_machine::validate (state_t s) const
+{
+  gcc_assert (s < m_state_names.length ());
+}
+
 ////////////////////////////////////////////////////////////////////////////
 
 label_text
diff --git a/gcc/analyzer/hardcoded-state-machine.h b/gcc/analyzer/hardcoded-state-machine.h
index ff9065f..6606411 100644
--- a/gcc/analyzer/hardcoded-state-machine.h
+++ b/gcc/analyzer/hardcoded-state-machine.h
@@ -74,6 +74,8 @@ public:
      States that need leak detection should return false.  */
   virtual bool can_purge_p (state_t s) const = 0;
 
+  void validate (state_t s) const;
+
 protected:
   state_t add_state (const char *name);
 
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index 85de7af..34e896f 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -51,6 +51,7 @@
 #include "ssa-iterators.h"
 #include "region-model.h"
 #include "tree-diagnostic.h"
+#include "constraint-manager.h"
 
 /* Overview:
 
@@ -535,66 +536,69 @@ public:
 
 using impl_4::pod_hash_traits;
 
-template <> struct default_hash_traits<path_var>
-: public pod_hash_traits<path_var>
+template <> struct default_hash_traits<svalue_id>
+: public pod_hash_traits<svalue_id>
 {
 };
 
 template <>
 inline hashval_t
-pod_hash_traits<path_var>::hash (value_type v)
+pod_hash_traits<svalue_id>::hash (value_type v)
 {
-  return (hashval_t)((intptr_t)v.m_tree >> 3) ^ v.m_stack_depth;
+  return v.as_int ();
 }
 
 template <>
 inline bool
-pod_hash_traits<path_var>::equal (const value_type &existing,
-				  const value_type &candidate)
+pod_hash_traits<svalue_id>::equal (const value_type &existing,
+				   const value_type &candidate)
 {
   return existing == candidate;
 }
 template <>
 inline void
-pod_hash_traits<path_var>::mark_deleted (value_type &v)
+pod_hash_traits<svalue_id>::mark_deleted (value_type &v)
 {
-  v = path_var (reinterpret_cast<tree> (1), 0);
+  v = svalue_id::from_int (-2);
 }
 template <>
 inline void
-pod_hash_traits<path_var>::mark_empty (value_type &v)
+pod_hash_traits<svalue_id>::mark_empty (value_type &v)
 {
-  v = path_var (NULL_TREE, 0);
+  v = svalue_id::null ();
 }
 template <>
 inline bool
-pod_hash_traits<path_var>::is_deleted (value_type v)
+pod_hash_traits<svalue_id>::is_deleted (value_type v)
 {
-  return v.m_tree == reinterpret_cast<tree> (1);
+  return v.as_int () == -2;
 }
 template <>
 inline bool
-pod_hash_traits<path_var>::is_empty (value_type v)
+pod_hash_traits<svalue_id>::is_empty (value_type v)
 {
-  return v.m_tree == NULL_TREE;
+  return v.null_p ();
 }
 
 namespace impl_4 {
 
-/* Map from var to state machine state, also capturing the origin of
+/* Map from svalue_id to state machine state, also capturing the origin of
    each state.  */
 
-template<typename Expr_T>
 class sm_state_map
 {
 public:
-  typedef Expr_T expr_t;
-
-  /* An entry in a hash_map.  */
+  /* An entry in the hash_map.  */
   struct entry_t
   {
+    /* Default ctor needed by hash_map::empty.  */
+    entry_t ()
+    : m_state (0), m_origin (svalue_id::null ())
+    {
+    }
+
     entry_t (state_machine::state_t state,
-	     expr_t origin)
+	     svalue_id origin)
     : m_state (state), m_origin (origin)
     {}
 
@@ -609,14 +613,15 @@ public:
     }
 
     state_machine::state_t m_state;
-    expr_t m_origin;
+    svalue_id m_origin;
   };
-  typedef hash_map <expr_t, entry_t> map_t;
+  typedef hash_map <svalue_id, entry_t> map_t;
   typedef typename map_t::iterator iterator_t;
 
   sm_state_map *clone () const;
 
   void print (const state_machine &sm, pretty_printer *pp) const;
+  void dump (const state_machine &sm) const;
   std::string to_str (const state_machine &sm) const;
 
   bool is_empty_p () const;
@@ -629,17 +634,19 @@ public:
     return !(*this == other);
   }
 
-  state_machine::state_t get_state (expr_t pv) const;
-  expr_t get_origin (expr_t pv) const;
+  state_machine::state_t get_state (svalue_id sid) const;
+  svalue_id get_origin (svalue_id sid) const;
 
-  void set_state (path_state *ps, expr_t pv, state_machine::state_t state,
-		  expr_t origin);
-  void set_state (const psn::equiv_class &ec,
+  void set_state (region_model *model,
+		  svalue_id sid,
+		  state_machine::state_t state,
+		  svalue_id origin);
+  void set_state (const equiv_class &ec,
 		  state_machine::state_t state,
-		  expr_t origin);
-  void impl_set_state (expr_t pv,
+		  svalue_id origin);
+  void impl_set_state (svalue_id sid,
 		       state_machine::state_t state,
-		       expr_t origin);
+		       svalue_id origin);
 
   void purge_stack_frame (int callee_stack_depth);
 
@@ -651,6 +658,15 @@ public:
   int purge (const state_machine &sm,
 	     const psn::purge_criteria &p);
 
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  int on_svalue_purge (const state_machine &sm,
+		       int sm_idx,
+		       svalue_id first_unused_sid,
+		       region_model_context *ctxt);
+
+  void validate (const state_machine &sm, int num_svalues) const;
+
   iterator_t begin () const { return m_map.begin (); }
   iterator_t end () const { return m_map.end (); }
 
@@ -682,8 +698,9 @@ public:
 	      pretty_printer *pp) const;
   std::string to_str (const extrinsic_state &ext_state) const;
 
-  void dump (const extrinsic_state &ext_state,
-	     pretty_printer *pp) const;
+  void dump_to_pp (const extrinsic_state &ext_state,
+		   pretty_printer *pp) const;
+  void dump (const extrinsic_state &ext_state) const;
 
   psn::equiv_class &get_ec (path_var pv)
   {
@@ -698,14 +715,16 @@ public:
 		const exploded_node &enode,
 		const superedge *succ);
 
-  void validate () const;
+  void remap_svalue_ids (const svalue_id_map &map);
+
+  void validate (const extrinsic_state &ext_state) const;
 
   //private:
   path_state m_path_state;
 
   region_model *m_region_model;
 
-  auto_delete_vec<sm_state_map<path_var> > m_checker_states;
+  auto_delete_vec<sm_state_map> m_checker_states;
 };
 
 /* A <program_point, program_state> pair, used internally by
@@ -1904,18 +1923,16 @@ program_point::get_stmt () const
 
 /* FIXME.  */
 
-template <typename Expr_T>
-sm_state_map<Expr_T> *
-sm_state_map<Expr_T>::clone () const
+sm_state_map *
+sm_state_map::clone () const
 {
   return new sm_state_map (*this);
 }
 
 /* FIXME.  */
 
-template <typename Expr_T>
 void
-sm_state_map<Expr_T>::print (const state_machine &sm, pretty_printer *pp) const
+sm_state_map::print (const state_machine &sm, pretty_printer *pp) const
 {
   pp_string (pp, "{");
   for (typename map_t::iterator iter = m_map.begin ();
@@ -1924,22 +1941,34 @@ sm_state_map<Expr_T>::print (const state_machine &sm, pretty_printer *pp) const
     {
       if (iter != m_map.begin ())
 	pp_string (pp, ", ");
+      svalue_id sid = (*iter).first;
+      sid.print (pp);
+
       entry_t e = (*iter).second;
-      pp_printf (pp, "%s: %s (origin: %s)",
-		 (*iter).first.to_str ().c_str (),
+      pp_printf (pp, ": %s (origin: %s)",
 		 sm.get_state_name (e.m_state),
 		 e.m_origin.to_str ().c_str ());
     }
-
-  // TODO
   pp_string (pp, "}");
 }
 
 /* FIXME.  */
 
-template <typename Expr_T>
+DEBUG_FUNCTION void
+sm_state_map::dump (const state_machine &sm) const
+{
+  pretty_printer pp;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  print (sm, &pp);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
+/* FIXME.  */
+
 std::string
-sm_state_map<Expr_T>::to_str (const state_machine &sm) const
+sm_state_map::to_str (const state_machine &sm) const
 {
   pretty_printer pp;
   print (sm, &pp);
@@ -1948,28 +1977,27 @@ sm_state_map<Expr_T>::to_str (const state_machine &sm) const
 
 /* FIXME.  */
 
-template <typename Expr_T>
 bool
-sm_state_map<Expr_T>::is_empty_p () const
+sm_state_map::is_empty_p () const
 {
   return m_map.elements () == 0;
 }
 
 /* FIXME.  */
 
-template <typename Expr_T>
 hashval_t
-sm_state_map<Expr_T>::hash () const
+sm_state_map::hash () const
 {
   inchash::hash hstate;
+
   for (typename map_t::iterator iter = m_map.begin ();
        iter != m_map.end ();
        ++iter)
     {
-      inchash::add_path_var ((*iter).first, hstate);
+      inchash::add ((*iter).first, hstate);
       entry_t e = (*iter).second;
       hstate.add_int (e.m_state);
-      inchash::add_path_var (e.m_origin, hstate);
+      inchash::add (e.m_origin, hstate);
     }
 
   return hstate.end ();
@@ -1977,9 +2005,8 @@ sm_state_map<Expr_T>::hash () const
 
 /* FIXME.  */
 
-template <typename Expr_T>
 bool
-sm_state_map<Expr_T>::operator== (const sm_state_map &other) const
+sm_state_map::operator== (const sm_state_map &other) const
 {
   if (m_map.elements () != other.m_map.elements ())
     return false;
@@ -1988,25 +2015,24 @@ sm_state_map<Expr_T>::operator== (const sm_state_map &other) const
        iter != m_map.end ();
        ++iter)
     {
-      path_var pv = (*iter).first;
+      svalue_id sid = (*iter).first;
       entry_t e = (*iter).second;
-      entry_t *other_slot = const_cast <map_t &> (other.m_map).get (pv);
+      entry_t *other_slot = const_cast <map_t &> (other.m_map).get (sid);
       if (other_slot == NULL)
 	return false;
       if (e != *other_slot)
 	return false;
     }
+
   return true;
 }
 
 /* FIXME.  */
-
-template <typename Expr_T>
 state_machine::state_t
-sm_state_map<Expr_T>::get_state (Expr_T pv) const
+sm_state_map::get_state (svalue_id sid) const
 {
   if (entry_t *slot
-      = const_cast <map_t &> (m_map).get (pv))
+      = const_cast <map_t &> (m_map).get (sid))
     return slot->m_state;
   else
     return 0;
@@ -2014,66 +2040,66 @@ sm_state_map<Expr_T>::get_state (Expr_T pv) const
 
 /* FIXME.  */
 
-template <typename Expr_T>
-Expr_T
-sm_state_map<Expr_T>::get_origin (Expr_T pv) const
+svalue_id
+sm_state_map::get_origin (svalue_id sid) const
 {
   entry_t *slot
-    = const_cast <map_t &> (m_map).get (pv);
+    = const_cast <map_t &> (m_map).get (sid);
   gcc_assert (slot);
   return slot->m_origin;
 }
 
 /* FIXME.  */
 
-template <typename Expr_T>
 void
-sm_state_map<Expr_T>::set_state (path_state *ps,
-			 Expr_T pv,
+sm_state_map::set_state (region_model *model,
+			 svalue_id sid,
 			 state_machine::state_t state,
-			 Expr_T origin)
+			 svalue_id origin)
 {
-  psn::equiv_class &ec = ps->get_equiv_class (pv);
+  if (model == NULL)
+    return;
+  equiv_class &ec = model->get_constraints ()->get_equiv_class (sid);
   set_state (ec, state, origin);
 }
 
 /* FIXME.  */
 
-template <typename Expr_T>
 void
-sm_state_map<Expr_T>::set_state (const psn::equiv_class &ec,
+sm_state_map::set_state (const equiv_class &ec,
 			 state_machine::state_t state,
-			 Expr_T origin)
+			 svalue_id origin)
 {
   int i;
-  Expr_T *pv;
-  FOR_EACH_VEC_ELT (ec.m_vars, i, pv)
-    impl_set_state (*pv, state, origin);
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (ec.m_vars, i, sid)
+    impl_set_state (*sid, state, origin);
 }
 
 /* Set state of PV to STATE, bypassing equivalence classes.  */
 
-template <typename Expr_T>
 void
-sm_state_map<Expr_T>::impl_set_state (Expr_T pv, state_machine::state_t state,
-				      Expr_T origin)
+sm_state_map::impl_set_state (svalue_id sid, state_machine::state_t state,
+			      svalue_id origin)
 {
   /* Special-case state 0 as the default value.  */
   if (state == 0)
     {
-      if (m_map.get (pv))
-	m_map.remove (pv);
+      if (m_map.get (sid))
+	m_map.remove (sid);
       return;
     }
-  m_map.put (pv, entry_t (state, origin));
+  m_map.put (sid, entry_t (state, origin));
 }
 
 /* FIXME.  */
 
-template <typename Expr_T>
 void
-sm_state_map<Expr_T>::purge_stack_frame (int callee_stack_depth)
+sm_state_map::purge_stack_frame (int callee_stack_depth)
 {
+  /* TODO: I *think* this can be empty (and removed); the other purging
+     calls ought to do it for us.  */
+#if 0
   /* FIXME: not sure how to safely remove during a traversal, so for now
      do it in two stages.  */
   auto_vec <Expr_T> keys;
@@ -2087,17 +2113,18 @@ sm_state_map<Expr_T>::purge_stack_frame (int callee_stack_depth)
   FOR_EACH_VEC_ELT (keys, i, pv)
     if (pv->m_stack_depth >= callee_stack_depth)
       m_map.remove (*pv);
+#endif
 }
 
 /* FIXME.  */
+/* It might be known to other state machines, but not to this one.  */
 
-template <>
 void
-sm_state_map<path_var>::purge_for_unknown_fncall (const exploded_graph &eg,
-						  const state_machine &sm,
-						  const gcall *call,
-						  tree fndecl,
-						  path_state *new_ps)
+sm_state_map::purge_for_unknown_fncall (const exploded_graph &eg,
+					const state_machine &sm,
+					const gcall *call,
+					tree fndecl,
+					path_state *new_ps)
 {
   if (fndecl)
     eg.log ("function %qE is unknown to checker %qs",
@@ -2128,22 +2155,28 @@ sm_state_map<path_var>::purge_for_unknown_fncall (const exploded_graph &eg,
 	      continue;
 	}
 
-      set_state (new_ps,
-		 new_ps->make_path_var (gimple_call_arg (call, arg_idx)),
-		 0, path_var (NULL_TREE, 0));
+      // FIXME:
+#if 0
+      set_state (new_ps, gimple_call_arg (call, arg_idx),
+		 0, svalue_id::null ());
+#endif
     }
 
+  // FIXME:
+#if 0
   if (gimple_call_lhs (call))
     set_state (new_ps,
-	       new_ps->make_path_var (gimple_call_lhs (call)),
-	       0, path_var (NULL_TREE, 0));
+	       gimple_call_lhs (call),
+	       0, svalue_id::null ());
+#endif
 }
 
-template <>
 int
-sm_state_map<path_var>::purge (const state_machine &sm,
-			       const psn::purge_criteria &p)
+sm_state_map::purge (const state_machine &sm,
+		     const psn::purge_criteria &p)
 {
+  // FIXME:
+#if 0
   /* FIXME: not sure how to safely remove during a traversal, so for now
      do it in two stages.  */
   auto_vec<path_var> to_remove;
@@ -2162,28 +2195,213 @@ sm_state_map<path_var>::purge (const state_machine &sm,
     m_map.remove (*pv);
 
   return to_remove.length ();
+#else
+  return 0;
+#endif
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::remap_svalue_ids (const svalue_id_map &map)
+{
+  map_t tmp_map;
+
+  /* Build an intermediate map, using the new sids.  */
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      map.update (&sid);
+      map.update (&e.m_origin);
+      tmp_map.put (sid, e);
+    }
+
+  /* Clear the existing values.  */
+  m_map.empty ();
+
+  /* Copy over from intermediate map.  */
+  for (typename map_t::iterator iter = tmp_map.begin ();
+       iter != tmp_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      impl_set_state (sid, e.m_state, e.m_origin);
+    }
+}
+
+/* FIXME.  */
+
+int
+sm_state_map::on_svalue_purge (const state_machine &sm,
+			       int sm_idx,
+			       svalue_id first_unused_sid,
+			       region_model_context *ctxt)
+{
+  /* FIXME: not sure how to safely remove during a traversal, so for now
+     do it in two stages.  */
+  auto_vec<svalue_id> to_remove;
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid ((*iter).first);
+      if (sid.as_int () >= first_unused_sid.as_int ())
+	{
+	  // TODO: complain about leaks here!
+	  entry_t e = (*iter).second;
+	  //gcc_unreachable ();
+
+	  if (!sm.can_purge_p (e.m_state))
+	    {
+	      //gcc_unreachable ();
+	      ctxt->on_state_leak (sm, sm_idx, sid, e.m_state);
+	    }
+
+#if 0
+	  // FIXME: we need a tree, an snode, a stmt, and an sm_ctxt
+	  sm.on_leak (sm_context *sm_ctxt,
+		      const supernode *node,
+		      const gimple *stmt,
+		      tree var,
+		      e.m_state);
+#endif
+
+	  to_remove.safe_push (sid);
+	}
+    }
+
+  int i;
+  svalue_id *sid;
+  FOR_EACH_VEC_ELT (to_remove, i, sid)
+    m_map.remove (*sid);
+
+  return to_remove.length ();
+}
+
+/* FIXME.  */
+
+void
+sm_state_map::validate (const state_machine &sm,
+			int num_svalues) const
+{
+  for (typename map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      svalue_id sid = (*iter).first;
+      entry_t e = (*iter).second;
+
+      gcc_assert (sid.as_int () < num_svalues);
+      sm.validate (e.m_state);
+      gcc_assert (e.m_origin.as_int () < num_svalues);
+    }
 }
 
 ////////////////////////////////////////////////////////////////////////////
 
 /* FIXME.  */
 
+class impl_region_model_context : public region_model_context, public log_user
+{
+ public:
+  impl_region_model_context (exploded_graph &eg,
+			     const exploded_node *enode,
+			     /* FIXME: should we be getting the ECs from the
+				old state, rather than the new?  */
+			     program_state *new_state,
+
+			     stmt_finder *stmt_finder = NULL)
+  : log_user (eg.get_logger ()), m_eg (eg), m_enode (enode),
+    m_new_state (new_state),
+    m_stmt_finder (stmt_finder)
+  {
+  }
+
+  void warn (pending_diagnostic *d) FINAL OVERRIDE
+  {
+    JIT_LOG_FUNC (get_logger ());
+    m_eg.get_diagnostic_manager ().add_diagnostic
+      (m_enode, m_enode->get_supernode (),
+       m_enode->get_stmt (),
+       m_stmt_finder, d);
+  }
+
+  void remap_svalue_ids (const svalue_id_map &map) FINAL OVERRIDE
+  {
+    m_new_state->remap_svalue_ids (map);
+  }
+
+  /* FIXME: does this ever get called?  */
+  bool can_purge_p (svalue_id sid) FINAL OVERRIDE
+  {
+    /* Don't purge vars that have non-purgeable sm state, to avoid
+       generating false "leak" complaints.  */
+    int i;
+    sm_state_map *smap;
+    FOR_EACH_VEC_ELT (m_new_state->m_checker_states, i, smap)
+      {
+	const state_machine &sm = m_eg.get_ext_state ().get_sm (i);
+	if (!sm.can_purge_p (smap->get_state (sid)))
+	  return false;
+      }
+    return true;
+  }
+
+  int on_svalue_purge (svalue_id first_unused_sid) FINAL OVERRIDE
+  {
+    int total = 0;
+    int sm_idx;
+    sm_state_map *smap;
+    FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
+      {
+	const state_machine &sm = m_eg.get_ext_state ().get_sm (sm_idx);
+	total += smap->on_svalue_purge (sm, sm_idx, first_unused_sid, this);
+      }
+    return total;
+  }
+
+  logger *get_logger () FINAL OVERRIDE
+  {
+    return log_user::get_logger ();
+  }
+
+  void on_state_leak (const state_machine &sm,
+		      int sm_idx,
+		      svalue_id sid,
+		      state_machine::state_t state) FINAL OVERRIDE;
+
+  exploded_graph &m_eg;
+  const exploded_node *m_enode;
+  program_state *m_new_state;
+  impl_4::stmt_finder *m_stmt_finder;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
 class impl_sm_context : public sm_context, public log_user
 {
 public:
   impl_sm_context (exploded_graph &eg,
 		   const state_machine &sm,
-		   const exploded_node *node,
+		   const exploded_node *enode,
 
 		   /* FIXME: should we be getting the ECs from the
 		      old state, rather than the new?  */
 		   program_state *new_state,
 
-		   const sm_state_map<path_var> &old_smap,
-		   sm_state_map<path_var> *new_smap,
+		   const sm_state_map *old_smap,
+		   sm_state_map *new_smap,
 
 		   stmt_finder *stmt_finder = NULL)
-  : log_user (eg.get_logger ()), m_eg (eg), m_sm (sm), m_node (node),
+  : log_user (eg.get_logger ()), m_eg (eg), m_sm (sm), m_enode (enode),
     m_new_state (new_state),
     m_old_smap (old_smap), m_new_smap (new_smap),
     m_stmt_finder (stmt_finder)
@@ -2198,9 +2416,22 @@ public:
 		      tree origin) FINAL OVERRIDE
   {
     JIT_LOG_FUNC (get_logger ());
-    int stack_depth = m_node->get_stack_depth ();
-    path_var pv (var, stack_depth);
-    state_machine::state_t current = m_old_smap.get_state (pv);
+    if (m_enode->get_state ().m_region_model == NULL)
+      return;
+    gcc_assert (m_new_state->m_region_model != NULL);
+
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode, NULL/*m_enode->get_state ()*/, NULL);
+    svalue_id var_old_sid
+      = m_enode->get_state ().m_region_model->get_rvalue (var, &old_ctxt);
+
+    impl_region_model_context new_ctxt (m_eg, m_enode, m_new_state, NULL);
+    svalue_id var_new_sid
+      = m_new_state->m_region_model->get_rvalue (var, &new_ctxt);
+    svalue_id origin_new_sid
+      = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt);
+
+    state_machine::state_t current = m_old_smap->get_state (var_old_sid);
     if (current == from)
       {
 	if (get_logger ())
@@ -2209,24 +2440,27 @@ public:
 	       var,
 	       m_sm.get_state_name (from),
 	       m_sm.get_state_name (to));
-	m_new_smap->set_state (&m_new_state->m_path_state, pv, to,
-			       path_var (origin, stack_depth));
+	m_new_smap->set_state (m_new_state->m_region_model, var_new_sid,
+			       to, origin_new_sid);
       }
   }
 
-  void warn_for_state (const supernode *node, const gimple *stmt,
+  void warn_for_state (const supernode *snode, const gimple *stmt,
 		       tree var, state_machine::state_t state,
 		       pending_diagnostic *d) FINAL OVERRIDE
   {
     JIT_LOG_FUNC (get_logger ());
-    int stack_depth = m_node->get_stack_depth ();
     gcc_assert (d); // take ownership
-    path_var pv (var, stack_depth);
-    state_machine::state_t current = m_old_smap.get_state (pv);
+
+    impl_region_model_context old_ctxt
+      (m_eg, m_enode, NULL/*m_enode->get_state ()*/, NULL);
+    svalue_id var_old_sid
+      = m_enode->get_state ().m_region_model->get_rvalue (var, &old_ctxt);
+    state_machine::state_t current = m_old_smap->get_state (var_old_sid);
     if (state == current)
       {
 	m_eg.get_diagnostic_manager ().add_diagnostic
-	  (&m_sm, m_node, node, stmt, m_stmt_finder,
+	  (&m_sm, m_enode, snode, stmt, m_stmt_finder,
 	   var, state, d);
       }
     else
@@ -2235,10 +2469,10 @@ public:
 
   exploded_graph &m_eg;
   const state_machine &m_sm;
-  const exploded_node *m_node;
+  const exploded_node *m_enode;
   program_state *m_new_state;
-  const sm_state_map<path_var> &m_old_smap;
-  sm_state_map<path_var> *m_new_smap;
+  const sm_state_map *m_old_smap;
+  sm_state_map *m_new_smap;
   impl_4::stmt_finder *m_stmt_finder;
 };
 
@@ -2246,6 +2480,34 @@ public:
 
 /* FIXME.  */
 
+void
+impl_region_model_context::on_state_leak (const state_machine &sm,
+					  int sm_idx,
+					  svalue_id sid,
+					  state_machine::state_t state)
+{
+  /* FIXME: m_enode needs to be non-NULL so warn_for_state can look up the
+     state of the sid.  */
+  gcc_assert (m_enode);
+
+  /* FIXME: is it OK to use m_new_state for both?  */
+  impl_sm_context sm_ctxt (m_eg, sm, m_enode, NULL,
+			   m_enode->get_state ().m_checker_states[sm_idx],
+			   m_new_state->m_checker_states[sm_idx],
+			   NULL);
+#if 0
+		   stmt_finder *stmt_finder = NULL)
+#endif
+  /* FIXME: stmt_finder.  */
+
+  sm.on_leak (&sm_ctxt, NULL/* FIXME: snode*/, NULL,
+	      NULL_TREE /* FIXME */, state);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+/* FIXME.  */
+
 program_state::program_state (const extrinsic_state &ext_state)
 : m_path_state (),
   m_region_model (NULL)
@@ -2255,7 +2517,7 @@ program_state::program_state (const extrinsic_state &ext_state)
 
   int num_states = ext_state.m_checkers.length ();
   for (int i = 0; i < num_states; i++)
-    m_checker_states.safe_push (new sm_state_map<path_var> ());
+    m_checker_states.safe_push (new sm_state_map ());
 }
 
 /* FIXME.  */
@@ -2267,7 +2529,7 @@ program_state::program_state (const program_state &other)
 		  : NULL)
 {
   int i;
-  sm_state_map<path_var> *smap;
+  sm_state_map *smap;
   FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
     m_checker_states.safe_push (smap->clone ());
 }
@@ -2289,7 +2551,7 @@ program_state::operator= (const program_state &other)
   gcc_assert (m_checker_states.length () == 0);
 
   int i;
-  sm_state_map<path_var> *smap;
+  sm_state_map *smap;
   FOR_EACH_VEC_ELT (other.m_checker_states, i, smap)
     m_checker_states.safe_push (smap->clone ());
 
@@ -2307,7 +2569,7 @@ program_state::hash () const
     result ^= m_region_model->hash ();
 
   int i;
-  sm_state_map<path_var> *smap;
+  sm_state_map *smap;
   FOR_EACH_VEC_ELT (m_checker_states, i, smap)
     result ^= smap->hash ();
   return result;
@@ -2332,7 +2594,7 @@ program_state::operator== (const program_state &other) const
     return false;
 
   int i;
-  sm_state_map<path_var> *smap;
+  sm_state_map *smap;
   FOR_EACH_VEC_ELT (m_checker_states, i, smap)
     if (!(*smap == *other.m_checker_states[i]))
       return false;
@@ -2356,7 +2618,7 @@ program_state::print (const extrinsic_state &ext_state,
     }
 
   int i;
-  sm_state_map<path_var> *smap;
+  sm_state_map *smap;
   FOR_EACH_VEC_ELT (m_checker_states, i, smap)
     {
       if (!smap->is_empty_p ())
@@ -2382,8 +2644,8 @@ program_state::to_str (const extrinsic_state &ext_state) const
 /* Dump a multiline representation of this state to PP.  */
 
 void
-program_state::dump (const extrinsic_state &ext_state,
-		     pretty_printer *pp) const
+program_state::dump_to_pp (const extrinsic_state &ext_state,
+			   pretty_printer *pp) const
 {
   // FIXME: multiline format?
   pp_printf (pp, "ps: %s", m_path_state.to_str ().c_str ());
@@ -2396,7 +2658,7 @@ program_state::dump (const extrinsic_state &ext_state,
     }
 
   int i;
-  sm_state_map<path_var> *smap;
+  sm_state_map *smap;
   FOR_EACH_VEC_ELT (m_checker_states, i, smap)
     {
       if (!smap->is_empty_p ())
@@ -2408,6 +2670,19 @@ program_state::dump (const extrinsic_state &ext_state,
     }
 }
 
+/* Dump a multiline representation of this state to stderr.  */
+
+DEBUG_FUNCTION void
+program_state::dump (const extrinsic_state &ext_state) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump_to_pp (ext_state, &pp);
+  pp_flush (&pp);
+}
+
 /* Implementation of purge_criteria for using state_purge_map.
 
    Purge state within a path_state using PURGE_MAP for POINT.
@@ -2447,12 +2722,15 @@ public:
 	/* Don't purge vars that have sm state, to avoid
 	   losing leak complaints.  */
 	int i;
-	sm_state_map<path_var> *smap;
+	sm_state_map *smap;
 	FOR_EACH_VEC_ELT (m_old_state.m_checker_states, i, smap)
 	  {
+	    // FIXME:
+#if 0
 	    const state_machine &sm = m_ext_state.get_sm (i);
 	    if (!sm.can_purge_p (smap->get_state (pv)))
 	      return false;
+#endif
 	  }
 
 	return true;
@@ -2502,7 +2780,7 @@ program_state::prune_for_point (exploded_graph &eg,
 
   // FIXME: state maps also
   int i;
-  sm_state_map<path_var> *smap;
+  sm_state_map *smap;
   int num_sm_purged = 0;
   FOR_EACH_VEC_ELT (new_state.m_checker_states, i, smap)
     {
@@ -2562,16 +2840,21 @@ program_state::prune_for_point (exploded_graph &eg,
 	  /* Purge the regions.  Nothing should point to them, and they
 	     should have no children, as they are for SSA names.  */
 	  new_state.m_region_model->purge_regions (purgeable_ssa_regions,
-						   &stats);
+						   &stats,
+						   eg.get_logger ());
 	}
 
       /* Purge unused svalues.  */
-      new_state.m_region_model->purge_svalues (&stats);
+      // FIXME: which enode to use, if any?
+      impl_region_model_context ctxt (eg, NULL /* FIXME: old_state*/,
+				      &new_state, NULL);
+      new_state.m_region_model->purge_svalues (&stats, &ctxt);
 
       eg.log ("num svalues purged: %i", stats.m_num_svalues);
       eg.log ("num regions purged: %i", stats.m_num_regions);
       eg.log ("num equiv_classes purged: %i", stats.m_num_equiv_classes);
       eg.log ("num constraints purged: %i", stats.m_num_constraints);
+      eg.log ("num sm map items purged: %i", stats.m_num_client_items);
     }
 
   return new_state;
@@ -2580,11 +2863,31 @@ program_state::prune_for_point (exploded_graph &eg,
 /* FIXME.  */
 
 void
-program_state::validate () const
+program_state::remap_svalue_ids (const svalue_id_map &map)
+{
+  int i;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    smap->remap_svalue_ids (map);
+}
+
+/* FIXME.  */
+
+void
+program_state::validate (const extrinsic_state &ext_state) const
 {
   m_path_state.validate ();
   if (m_region_model)
-    m_region_model->validate ();
+    {
+      m_region_model->validate ();
+      int sm_idx;
+      sm_state_map *smap;
+      FOR_EACH_VEC_ELT (m_checker_states, sm_idx, smap)
+	{
+	  const state_machine &sm = ext_state.get_sm (sm_idx);
+	  smap->validate (sm, m_region_model->get_num_svalues ());
+	}
+    }
 }
 
 
@@ -2730,7 +3033,7 @@ exploded_node::dump_to_pp (pretty_printer *pp) const
   m_ps.m_point.print (pp, f);
   pp_newline (pp);
 
-  m_ps.m_state.dump (m_ext_state, pp);
+  m_ps.m_state.dump_to_pp (m_ext_state, pp);
   pp_newline (pp);
 }
 
@@ -2773,39 +3076,6 @@ fndecl_has_gimple_body_p (tree fndecl)
 
 /* FIXME.  */
 
-class impl_region_model_context : public region_model_context, public log_user
-{
- public:
-  impl_region_model_context (exploded_graph &eg,
-			     const exploded_node *enode,
-			     /* FIXME: should we be getting the ECs from the
-				old state, rather than the new?  */
-			     program_state *new_state,
-
-			     stmt_finder *stmt_finder = NULL)
-  : log_user (eg.get_logger ()), m_eg (eg), m_enode (enode),
-    m_new_state (new_state),
-    m_stmt_finder (stmt_finder)
-  {
-  }
-
-  void warn (pending_diagnostic *d) FINAL OVERRIDE
-  {
-    JIT_LOG_FUNC (get_logger ());
-    m_eg.get_diagnostic_manager ().add_diagnostic
-      (m_enode, m_enode->get_supernode (),
-       m_enode->get_stmt (),
-       m_stmt_finder, d);
-  }
-
-  exploded_graph &m_eg;
-  const exploded_node *m_enode;
-  program_state *m_new_state;
-  impl_4::stmt_finder *m_stmt_finder;
-};
-
-/* FIXME.  */
-
 program_state
 exploded_node::on_stmt (exploded_graph &eg) const
 {
@@ -2826,33 +3096,6 @@ exploded_node::on_stmt (exploded_graph &eg) const
 
   impl_region_model_context ctxt (eg, this, &new_state, NULL);
 
-  int sm_idx;
-  sm_state_map<path_var> *smap;
-  FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
-    {
-      const state_machine &sm = m_ext_state.get_sm (sm_idx);
-      const sm_state_map<path_var> &old_smap
-	= *old_state.m_checker_states[sm_idx];
-      sm_state_map<path_var> *new_smap = new_state.m_checker_states[sm_idx];
-      impl_sm_context ctxt (eg, sm, this,
-			    &new_state,
-			    old_smap, new_smap);
-      /* Allow the state_machine to handle the stmt.  */
-      if (!sm.on_stmt (&ctxt, point.m_supernode, stmt))
-	{
-	  /* For those stmts that were not handled by the state machine.  */
-	  if (const gcall *call = dyn_cast <const gcall *> (stmt))
-	    {
-	      tree callee_fndecl = gimple_call_fndecl (call);
-	      // TODO: maybe we can be smarter about handling function pointers?
-
-	      if (!fndecl_has_gimple_body_p (callee_fndecl))
-		new_smap->purge_for_unknown_fncall (eg, sm, call, callee_fndecl,
-						    new_ps);
-	    }
-	}
-    }
-
   if (const gassign *assign = dyn_cast <const gassign *> (stmt))
     {
       tree lhs = gimple_assign_lhs (assign);
@@ -2978,6 +3221,33 @@ exploded_node::on_stmt (exploded_graph &eg) const
       on_call_for_region_model (eg, call, old_state, &new_state);
     }
 
+  int sm_idx;
+  sm_state_map *smap;
+  FOR_EACH_VEC_ELT (old_state.m_checker_states, sm_idx, smap)
+    {
+      const state_machine &sm = m_ext_state.get_sm (sm_idx);
+      const sm_state_map *old_smap
+	= old_state.m_checker_states[sm_idx];
+      sm_state_map *new_smap = new_state.m_checker_states[sm_idx];
+      impl_sm_context ctxt (eg, sm, this,
+			    &new_state,
+			    old_smap, new_smap);
+      /* Allow the state_machine to handle the stmt.  */
+      if (!sm.on_stmt (&ctxt, point.m_supernode, stmt))
+	{
+	  /* For those stmts that were not handled by the state machine.  */
+	  if (const gcall *call = dyn_cast <const gcall *> (stmt))
+	    {
+	      tree callee_fndecl = gimple_call_fndecl (call);
+	      // TODO: maybe we can be smarter about handling function pointers?
+
+	      if (!fndecl_has_gimple_body_p (callee_fndecl))
+		new_smap->purge_for_unknown_fncall (eg, sm, call, callee_fndecl,
+						    new_ps);
+	    }
+	}
+    }
+
   return new_state;
 }
 
@@ -3075,8 +3345,10 @@ exploded_node::copy_sm_state (const exploded_graph &eg,
   for (unsigned i = 0; i < ext_state.m_checkers.length (); i++)
     {
       const state_machine &sm = ext_state.get_sm (i);
-      const sm_state_map<path_var> *src_smap = old_state.m_checker_states[i];
-      sm_state_map<path_var> *dst_smap = new_state->m_checker_states[i];
+      const sm_state_map *src_smap = old_state.m_checker_states[i];
+      sm_state_map *dst_smap = new_state->m_checker_states[i];
+      // FIXME:
+#if 0
       state_machine::state_t rhs_state = src_smap->get_state (rhs);
       dst_smap->set_state (&new_state->m_path_state, lhs, rhs_state, rhs);
       if (eg.get_logger ())
@@ -3085,6 +3357,7 @@ exploded_node::copy_sm_state (const exploded_graph &eg,
 		sm.get_state_name (rhs_state),
 		lhs.to_str ().c_str (),
 		rhs.to_str ().c_str ());
+#endif
     }
 }
 
@@ -3270,6 +3543,9 @@ exploded_node::detect_leaks (exploded_graph &eg) const
 {
   JIT_LOG_FUNC_1 (eg.get_logger (), "EN: %i", m_index);
 
+  // FIXME:
+#if 0
+
   const program_state &old_state = get_state ();
 
   /* Work with a temporary copy of the state.  */
@@ -3334,7 +3610,7 @@ exploded_node::detect_leaks (exploded_graph &eg) const
       leak_stmt_finder stmt_finder (eg, best_pv);
 
       int i;
-      sm_state_map<path_var> *smap;
+      sm_state_map *smap;
       FOR_EACH_VEC_ELT (old_state.m_checker_states, i, smap)
 	{
 	  const state_machine &sm = m_ext_state.get_sm (i);
@@ -3347,6 +3623,7 @@ exploded_node::detect_leaks (exploded_graph &eg) const
 		      smap->get_state (best_pv));
 	}
     }
+#endif
 }
 
 // TODO: move this fn
@@ -3529,13 +3806,13 @@ program_state::on_edge (exploded_graph &eg,
   /* Pass the edge to the various state machines to see if any transitions
      or warnings occur.  */
   int i;
-  sm_state_map<path_var> *old_smap;
+  sm_state_map *old_smap;
   FOR_EACH_VEC_ELT (old_state.m_checker_states, i, old_smap)
     {
       const state_machine &sm = eg.get_ext_state ().get_sm (i);
       impl_sm_context ctxt (eg, sm, &enode,
 			    this,
-			    *old_state.m_checker_states[i],
+			    old_state.m_checker_states[i],
 			    m_checker_states[i]);
       sm.on_edge (&ctxt, last_stmt, succ);
 
@@ -4101,11 +4378,13 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
       for (unsigned i = 0; i < ext_state.m_checkers.length (); i++)
 	{
 	  const state_machine &sm = ext_state.get_sm (i);
-	  const sm_state_map<path_var> &src_smap
+	  const sm_state_map &src_smap
 	    = *src_state.m_checker_states[i];
-	  const sm_state_map<path_var> &dst_smap
+	  const sm_state_map &dst_smap
 	    = *dst_state.m_checker_states[i];
-	  for (sm_state_map<path_var>::iterator_t iter = dst_smap.begin ();
+	  // FIXME:
+#if 0
+	  for (sm_state_map::iterator_t iter = dst_smap.begin ();
 	       iter != dst_smap.end ();
 	       ++iter)
 	    {
@@ -4142,6 +4421,7 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
 					     (*iter).second.m_origin.m_tree));
 		}
 	    }
+#endif
 	}
     }
 
@@ -4461,13 +4741,17 @@ exploded_graph::get_or_create_node (const program_point &point,
 
   auto_cfun sentinel (point.get_function ());
 
-  state.validate ();
+  state.validate (get_ext_state ());
+
+  //state.dump (get_ext_state ());
 
   /* 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);
 
-  pruned_state.validate ();
+  pruned_state.validate (get_ext_state ());
+
+  //pruned_state.dump (get_ext_state ());
 
   if (get_logger ())
     {
@@ -5941,76 +6225,116 @@ namespace selftest {
 static void
 test_sm_state_map ()
 {
-  tree x = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+  tree x = build_decl (UNKNOWN_LOCATION, VAR_DECL,
 		       get_identifier ("x"),
 		       integer_type_node);
-  tree y = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+  tree y = build_decl (UNKNOWN_LOCATION, VAR_DECL,
 		       get_identifier ("y"),
 		       integer_type_node);
-  tree z = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+  tree z = build_decl (UNKNOWN_LOCATION, VAR_DECL,
 		       get_identifier ("z"),
 		       integer_type_node);
-  path_var x0 (x, 0);
-  path_var y0 (y, 0);
-  path_var z0 (z, 0);
 
-  /* Test setting states on path_var instances directly.  */
+  /* Test setting states on svalue_id instances directly.  */
   {
-    impl_4::sm_state_map<path_var> map;
+    region_model model;
+    svalue_id sid_x = model.get_rvalue (x, NULL);
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    impl_4::sm_state_map map;
     ASSERT_TRUE (map.is_empty_p ());
-    ASSERT_EQ (map.get_state (x0), 0);
+    ASSERT_EQ (map.get_state (sid_x), 0);
 
-    map.impl_set_state (x0, 42, z0);
-    ASSERT_EQ (map.get_state (x0), 42);
-    ASSERT_EQ (map.get_origin (x0), z0);
-    ASSERT_EQ (map.get_state (y0), 0);
+    map.impl_set_state (sid_x, 42, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 42);
+    ASSERT_EQ (map.get_origin (sid_x), sid_z);
+    ASSERT_EQ (map.get_state (sid_y), 0);
     ASSERT_FALSE (map.is_empty_p ());
 
-    map.impl_set_state (y0, 0, z0);
-    ASSERT_EQ (map.get_state (y0), 0);
+    map.impl_set_state (sid_y, 0, sid_z);
+    ASSERT_EQ (map.get_state (sid_y), 0);
 
-    map.impl_set_state (x0, 0, z0);
-    ASSERT_EQ (map.get_state (x0), 0);
+    map.impl_set_state (sid_x, 0, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 0);
     ASSERT_TRUE (map.is_empty_p ());
   }
 
   /* Test setting states via equivalence classes.  */
   {
-    impl_4::sm_state_map<path_var> map;
+    region_model model;
+    svalue_id sid_x = model.get_rvalue (x, NULL);
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    impl_4::sm_state_map map;
     ASSERT_TRUE (map.is_empty_p ());
-    ASSERT_EQ (map.get_state (x0), 0);
-    ASSERT_EQ (map.get_state (y0), 0);
+    ASSERT_EQ (map.get_state (sid_x), 0);
+    ASSERT_EQ (map.get_state (sid_y), 0);
 
-    path_state ps;
-    ps.add_constraint (x0, EQ_EXPR, y0);
+    model.add_constraint (x, EQ_EXPR, y, NULL);
 
     /* Setting x to a state should also update y, as they
        are in the same equivalence class.  */
-    map.set_state (&ps, x0, 5, z0);
-    ASSERT_EQ (map.get_state (x0), 5);
-    ASSERT_EQ (map.get_state (y0), 5);
-    ASSERT_EQ (map.get_origin (x0), z0);
-    ASSERT_EQ (map.get_origin (y0), z0);
+    map.set_state (&model, sid_x, 5, sid_z);
+    ASSERT_EQ (map.get_state (sid_x), 5);
+    ASSERT_EQ (map.get_state (sid_y), 5);
+    ASSERT_EQ (map.get_origin (sid_x), sid_z);
+    ASSERT_EQ (map.get_origin (sid_y), sid_z);
   }
 
   /* Test equality and hashing.  */
   {
-    impl_4::sm_state_map<path_var> map0;
-    impl_4::sm_state_map<path_var> map1;
-    impl_4::sm_state_map<path_var> map2;
+    region_model model;
+    svalue_id sid_x = model.get_rvalue (x, NULL);
+    svalue_id sid_y = model.get_rvalue (y, NULL);
+    svalue_id sid_z = model.get_rvalue (z, NULL);
+
+    impl_4::sm_state_map map0;
+    impl_4::sm_state_map map1;
+    impl_4::sm_state_map map2;
 
     ASSERT_EQ (map0.hash (), map1.hash ());
     ASSERT_EQ (map0, map1);
 
-    map1.impl_set_state (y0, 5, z0);
+    map1.impl_set_state (sid_y, 5, sid_z);
     ASSERT_NE (map0.hash (), map1.hash ());
     ASSERT_NE (map0, map1);
 
     /* Make the same change to map2.  */
-    map2.impl_set_state (y0, 5, z0);
+    map2.impl_set_state (sid_y, 5, sid_z);
     ASSERT_EQ (map1.hash (), map2.hash ());
     ASSERT_EQ (map1, map2);
   }
+
+  /* Test sm_state_map::remap_svalue_ids.  */
+  {
+    impl_4::sm_state_map map;
+    svalue_id sid_0 = svalue_id::from_int (0);
+    svalue_id sid_1 = svalue_id::from_int (1);
+    svalue_id sid_2 = svalue_id::from_int (2);
+
+    map.impl_set_state (sid_0, 42, sid_2);
+    ASSERT_EQ (map.get_state (sid_0), 42);
+    ASSERT_EQ (map.get_origin (sid_0), sid_2);
+    ASSERT_EQ (map.get_state (sid_1), 0);
+    ASSERT_EQ (map.get_state (sid_2), 0);
+
+    /* Apply a remapping to the IDs.  */
+    svalue_id_map remapping (3);
+    remapping.put (sid_0, sid_1);
+    remapping.put (sid_1, sid_2);
+    remapping.put (sid_2, sid_0);
+    map.remap_svalue_ids (remapping);
+
+    /* Verify that the IDs have been remapped.  */
+    ASSERT_EQ (map.get_state (sid_1), 42);
+    ASSERT_EQ (map.get_origin (sid_1), sid_0);
+    ASSERT_EQ (map.get_state (sid_2), 0);
+    ASSERT_EQ (map.get_state (sid_0), 0);
+  }
+
+  // TODO: coverage for purging
 }
 
 /* FIXME.  */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index f2fb924..44d6ccd 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -74,7 +74,11 @@ class impl_constraint_manager : public constraint_manager
     svalue *svalue = m_model->get_svalue (sid);
     if (constant_svalue *cst_svalue = svalue->dyn_cast_constant_svalue ())
       {
-	return cst_svalue->get_constant ();
+	tree cst = cst_svalue->get_constant ();
+	/* This could be a constant or a decl; constraint_manager is only
+	   expecting actual constants.  */
+	if (CONSTANT_CLASS_P (cst))
+	  return cst;
       }
     return NULL_TREE;
   }
@@ -247,6 +251,17 @@ id_map<T>::dump () const
   pp_flush (&pp);
 }
 
+/* FIXME.  */
+
+template <typename T>
+std::string
+id_map<T>::to_str () const
+{
+  pretty_printer pp;
+  dump_to_pp (&pp);
+  return pp_formatted_text (&pp);
+}
+
 /* Update *ID from the old value to its new value in this map.  */
 
 template <typename T>
@@ -1263,10 +1278,10 @@ stack_region::pop_frame (region_model *model, purge_stats *stats,
 
   model->delete_region_and_descendents (frame_rid,
 					POISON_KIND_POPPED_STACK,
-					stats);
+					stats, ctxt->get_logger ());
 
   /* Delete unused svalues, but don't delete the return value.  */
-  model->purge_svalues (stats, &result_sid);
+  model->purge_svalues (stats, ctxt, &result_sid);
 
   model->validate ();
 
@@ -2088,8 +2103,8 @@ region_model::on_call (const gcall *call, region_model_context *ctxt)
 	      purge_stats stats;
 	      delete_region_and_descendents (pointee_rid,
 					     POISON_KIND_FREED,
-					     &stats);
-	      purge_svalues (&stats, NULL);
+					     &stats, ctxt->get_logger ());
+	      purge_svalues (&stats, ctxt);
 	      validate ();
 	      // TODO: do anything with stats?
 	    }
@@ -2306,6 +2321,9 @@ region_model::get_lvalue (tree expr, region_model_context *ctxt)
 svalue_id
 region_model::get_rvalue (tree expr, region_model_context *ctxt)
 {
+  if (expr == NULL_TREE)
+    return svalue_id::null ();
+
   switch (TREE_CODE (expr))
     {
     default:
@@ -2332,6 +2350,8 @@ region_model::get_rvalue (tree expr, region_model_context *ctxt)
     case STRING_CST:
       return get_svalue_for_constant (expr);
 
+    case COMPONENT_REF:
+    case MEM_REF:
     case SSA_NAME:
     case VAR_DECL:
     case PARM_DECL:
@@ -3033,8 +3053,13 @@ private:
    lvalue and rvalue lookups implicitly relative to a "current" frame).  */
 
 void
-region_model::purge_svalues (purge_stats *stats, svalue_id *known_used_sid)
+region_model::purge_svalues (purge_stats *stats,
+			     region_model_context *ctxt,
+			     svalue_id *known_used_sid)
 {
+  // FIXME: might want to avoid a vfunc call just to do logging here:
+  logger *logger = ctxt ? ctxt->get_logger () : NULL;
+
   //svalue_id_set used (*this);
   auto_sbitmap used (m_svalues.length ());
   bitmap_clear (used); // FIXME: is this needed?
@@ -3074,6 +3099,16 @@ region_model::purge_svalues (purge_stats *stats, svalue_id *known_used_sid)
       }
   }
 
+  /* Don't purge SIDs that have non-purgeable sm_state_map
+     information.  */
+  if (ctxt)
+    for (unsigned i = 0; i < m_svalues.length (); i++)
+      {
+	if (!bitmap_bit_p (used, i))
+	  if (!ctxt->can_purge_p (svalue_id::from_int (i)))
+	    bitmap_set_bit (used, i);
+      }
+
   /* Build a mapping from old-sid to new-sid so that we can preserve
      order of the used IDs and move all redundant ones to the end.
      Iterate though svalue IDs, adding used ones to the front of
@@ -3085,9 +3120,17 @@ region_model::purge_svalues (purge_stats *stats, svalue_id *known_used_sid)
     {
       svalue_id src (svalue_id::from_int (i));
       if (bitmap_bit_p (used, i))
-	map.put (src, svalue_id::from_int (next_used_new_sid++));
+	{
+	  if (logger)
+	    logger->log ("sv%i is used", next_used_new_sid);
+	  map.put (src, svalue_id::from_int (next_used_new_sid++));
+	}
       else
-	map.put (src, svalue_id::from_int (--after_next_unused_new_sid));
+	{
+	  if (logger)
+	    logger->log ("sv%i is unused", next_used_new_sid);
+	  map.put (src, svalue_id::from_int (--after_next_unused_new_sid));
+	}
     }
   /* The two insertion points should have met.  */
   gcc_assert (next_used_new_sid == after_next_unused_new_sid);
@@ -3095,10 +3138,23 @@ region_model::purge_svalues (purge_stats *stats, svalue_id *known_used_sid)
   /* Now walk the regions and the constraints, remapping sids,
      so that all the redundant svalues are at the end.  */
   remap_svalue_ids (map);
+  if (logger)
+    logger->log ("map: %s", map.to_str ().c_str ());
+
+  /* Notify any client about the remapping and pending deletion.  */
+  // FIXME: potentially this could trigger leak warnings
+  if (ctxt)
+    {
+      ctxt->remap_svalue_ids (map);
+      stats->m_num_client_items +=
+	ctxt->on_svalue_purge (svalue_id::from_int (next_used_new_sid));
+    }
 
   /* Drop the redundant svalues from the end of the vector.  */
   while (m_svalues.length () > next_used_new_sid)
     {
+      if (logger)
+	logger->log ("deleting sv%i", m_svalues.length () - 1);
       delete m_svalues.pop ();
       stats->m_num_svalues++;
     }
@@ -3178,7 +3234,8 @@ region_model::remap_region_ids (const region_id_map &map)
 
 void
 region_model::purge_regions (const region_id_set &set_to_purge,
-			     purge_stats *stats)
+			     purge_stats *stats,
+			     logger *logger)
 {
   /* Build a mapping from old-rid to new-rid so that we can preserve
      order of the used IDs and move all redundant ones to the end.
@@ -3248,7 +3305,8 @@ region_model::get_descendents (region_id rid, region_id_set *out) const
 void
 region_model::delete_region_and_descendents (region_id rid,
 					     enum poison_kind pkind,
-					     purge_stats *stats)
+					     purge_stats *stats,
+					     logger *logger)
 {
   /* Find all child and descendent regions.  */
   region_id_set descendents (this);
@@ -3258,7 +3316,7 @@ region_model::delete_region_and_descendents (region_id rid,
   poison_any_pointers_to_bad_regions (descendents, pkind);
 
   /* Delete all such regions.  */
-  purge_regions (descendents, stats);
+  purge_regions (descendents, stats, logger);
 }
 
 /* Find any pointers to regions within BAD_REGIONS; convert them to
@@ -3489,7 +3547,7 @@ test_purge_svalues ()
 
   /* The redundant unknown values should have been purged.  */
   purge_stats purged;
-  model.purge_svalues (&purged);
+  model.purge_svalues (&purged, NULL);
   ASSERT_EQ (purged.m_num_svalues, 3);
 
   /* and the redundant constraint on an old, unknown value for x should
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index e3515ad..3e1b809 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -21,6 +21,8 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ANALYZER_REGION_MODEL_H
 #define GCC_ANALYZER_REGION_MODEL_H
 
+#include "hardcoded-state-machine.h"
+
 /* Implemention of the region-based ternary model described in:
      "A Memory Model for Static Analysis of C Programs"
       (Zhongxing Xu, Ted Kremenek, and Jian Zhang)
@@ -128,6 +130,7 @@ class id_map
   T get_src_for_dst (T dst) const;
   void dump_to_pp (pretty_printer *pp) const;
   void dump () const;
+  std::string to_str () const;
   void update (T *) const;
 
  private:
@@ -185,18 +188,21 @@ struct purge_stats
   : m_num_svalues (0),
     m_num_regions (0),
     m_num_equiv_classes (0),
-    m_num_constraints (0)
+    m_num_constraints (0),
+    m_num_client_items (0)
   {}
 
   int m_num_svalues;
   int m_num_regions;
   int m_num_equiv_classes;
   int m_num_constraints;
+  int m_num_client_items;
 };
 
 ////////////////////////////////////////////////////////////////////////////
 
-/* FIXME.  */
+/* An enum for discriminating between the different concrete subclasses
+   of svalue.  */
 
 enum svalue_kind
 {
@@ -206,7 +212,7 @@ enum svalue_kind
   SK_UNKNOWN
 };
 
-/* FIXME.  */
+/* An abstract base class representing a value held by a region of memory.  */
 
 class svalue
 {
@@ -351,11 +357,14 @@ public:
     FINAL OVERRIDE;
 };
 
-/* FIXME.  */
+/* An enum describing a particular kind of "poisoned" value.  */
 
 enum poison_kind
 {
+  /* For use to describe uninitialized memory.  */
   POISON_KIND_UNINIT,
+
+  /* For use to describe freed memory.  */
   POISON_KIND_FREED,
 
   /* For use on pointers to regions within popped stack frames.  */
@@ -396,7 +405,8 @@ public:
 
 ////////////////////////////////////////////////////////////////////////////
 
-/* FIXME.  */
+/* An enum for discriminating between the different concrete subclasses
+   of region.  */
 
 enum region_kind
 {
@@ -700,7 +710,9 @@ public:
 /* A region_model encapsulates a representation of the state of memory, with
    a tree of regions, along with their associated values.
    The representation is graph-like because values can be pointers to
-   regions.  */
+   regions.
+   It also stores a constraint_manager, capturing relationships between
+   the values.  */
 
 class region_model
 {
@@ -791,11 +803,14 @@ class region_model
   tree maybe_get_constant (svalue_id sid);
 
   void purge_svalues (purge_stats *out,
+		      region_model_context *ctxt,
 		      svalue_id *known_used_sid = NULL);
   void remap_svalue_ids (const svalue_id_map &map);
   void remap_region_ids (const region_id_map &map);
 
-  void purge_regions (const region_id_set &set, purge_stats *stats);
+  void purge_regions (const region_id_set &set,
+		      purge_stats *stats,
+		      logger *logger);
 
   int get_num_svalues () const { return m_svalues.length (); }
 
@@ -809,7 +824,8 @@ class region_model
 
   void delete_region_and_descendents (region_id rid,
 				      enum poison_kind pkind,
-				      purge_stats *stats);
+				      purge_stats *stats,
+				      logger *logger);
 
  private:
   void update_for_call_superedge (const call_superedge &call_edge,
@@ -836,12 +852,45 @@ class region_model
 
 /* Some region_model activity could lead to warnings (e.g. attempts to use an
    uninitialized value).  This abstract base class encapsulates an interface
-   for the region model to use when emitting such warnings.  */
+   for the region model to use when emitting such warnings.
+
+   It also provides an interface for being notified about svalue_ids being
+   remapped, and being deleted.
+
+   Having this as an abstract base class allows us to support the various
+   operations needed by program_state in the analyzer within region_model,
+   whilst keeping them somewhat modularized.  */
 
 class region_model_context
 {
  public:
   virtual void warn (pending_diagnostic *d) = 0;
+
+  /* Hook for clients that store svalue_id instances, so that they
+     can remap their IDs when the underlying region_model renumbers
+     the IDs.  */
+  virtual void remap_svalue_ids (const svalue_id_map &map) = 0;
+
+  /* Return true if if's OK to purge SID when simplifying state.
+     Subclasses can return false for values that have sm state,
+     to avoid generating "leak" false positives.  */
+  virtual bool can_purge_p (svalue_id sid) = 0;
+
+  /* Hook for clients to be notified when a range of SIDs have
+     been purged, so that they can purge state relating to those
+     values (and potentially emit warnings about leaks).
+     All SIDs from FIRST_PURGED_SID numerically upwards are being
+     purged.
+     The return values is a count of how many items of data the client
+     has purged (potentially for use in selftests).  */
+  virtual int on_svalue_purge (svalue_id first_purged_sid) = 0;
+
+  virtual logger *get_logger () = 0;
+
+  virtual void on_state_leak (const state_machine &sm,
+			      int sm_idx,
+			      svalue_id sid,
+			      state_machine::state_t state) = 0;
 };
 
 namespace inchash
@@ -857,7 +906,7 @@ extern void debug (const region_model &rmodel);
 namespace selftest {
 
 /* An implementation of region_model_context for use in selftests, which
-   stores the pending_diagnostic instances passed to it.  */
+   stores any pending_diagnostic instances passed to it.  */
 
 class test_region_model_context : public region_model_context
 {
@@ -867,9 +916,35 @@ public:
     m_diagnostics.safe_push (d);
   }
 
+  void remap_svalue_ids (const svalue_id_map &) FINAL OVERRIDE
+  {
+    /* Empty.  */
+  }
+
+  bool can_purge_p (svalue_id) FINAL OVERRIDE
+  {
+    return true;
+  }
+
+  int on_svalue_purge (svalue_id) FINAL OVERRIDE
+  {
+    /* Empty.  */
+    return 0;
+  }
+
+  logger *get_logger () FINAL OVERRIDE { return NULL; }
+
+  void on_state_leak (const state_machine &,
+		      int,
+		      svalue_id,
+		      state_machine::state_t) FINAL OVERRIDE
+  {
+  }
+
   unsigned get_num_diagnostics () const { return m_diagnostics.length (); }
 
 private:
+  /* Implicitly delete any diagnostics in the dtor.  */
   auto_delete_vec<pending_diagnostic> m_diagnostics;
 };
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/file-2.c b/gcc/testsuite/gcc.dg/analyzer/file-2.c
index c81cf22..498cb03 100644
--- a/gcc/testsuite/gcc.dg/analyzer/file-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/file-2.c
@@ -9,11 +9,11 @@ void test (const char *path)
 {
   struct foo f;
   f.m_f = fopen (path, "r");
+
   if (!f.m_f)
-    return; /* { dg-bogus "leak of FILE 'f.m_f'" "" { xfail *-*-* } } */
-  // FIXME: there's another one of these, with UNKNOWN_LOCATION
+    return; /* { dg-bogus "leak of FILE" } */
 
   fclose (f.m_f);
-  fclose (f.m_f); /* { dg-warning "double 'fclose' of FILE 'f'" "" { xfail *-*-* } } */
-  /* TODO(xfail): need to make state machines use region model.  */
+  fclose (f.m_f); /* { dg-warning "double 'fclose' of FILE '<unknown>'" } */
+  /* TODO: use a better expression than just '<unknown>'.  */
 }
diff --git a/notes.txt b/notes.txt
index 4f4674f..efa3017 100644
--- a/notes.txt
+++ b/notes.txt
@@ -1,3 +1,6 @@
+Notes
+=====
+
 We need a path: we need to explain to the user what the problem is, and to
 persuade them that there really is a problem.  Hence having a path isn't
 just an incidental detail of the analyzer; it's required.
@@ -8,7 +11,8 @@ Paths ought to be:
 ...but it's not clear to what extent we can enforce this.
 
 
-Useful debugging tips:
+Useful debugging tips
+=====================
 
 Add:
   __analyzer_break ();
@@ -20,3 +24,11 @@ __analyzer_dump ();
 
 will dump the copious information about the analyzer's state each time it
 reaches the call in its traversal of the source.
+
+
+User-facing Docs (putting them here to avoid the tedious texinfo rebuild times):
+================================================================================
+
+-fanalyzer
+Enables a more detailed, but much more time-consuming analysis pass.
+
