From cdcc1755e1926e3aed5f583937707cf100ed9077 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Sun, 12 Apr 2020 09:58:15 -0400
Subject: [PATCH 023/159] FIXME: WIP on revamp_state 2: region_model2

---
 gcc/Makefile.in                    |    1 +
 gcc/analyzer/analyzer-selftests.cc |    1 +
 gcc/analyzer/analyzer-selftests.h  |    1 +
 gcc/analyzer/analyzer.h            |   14 +
 gcc/analyzer/region-model2.cc      | 8236 ++++++++++++++++++++++++++++
 gcc/analyzer/region-model2.h       | 1847 +++++++
 gcc/doc/analyzer.texi              |   11 +-
 7 files changed, 10104 insertions(+), 7 deletions(-)
 create mode 100644 gcc/analyzer/region-model2.cc
 create mode 100644 gcc/analyzer/region-model2.h

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 147c111ae5c..7357c0c886b 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1233,6 +1233,7 @@ ANALYZER_OBJS = \
 	analyzer/program-point.o \
 	analyzer/program-state.o \
 	analyzer/region-model.o \
+	analyzer/region-model2.o \
 	analyzer/revamp-state.o \
 	analyzer/sm.o \
 	analyzer/sm-file.o \
diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
index 2e80069b4e7..03426ead60d 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -57,6 +57,7 @@ run_analyzer_selftests ()
   analyzer_program_point_cc_tests ();
   analyzer_program_state_cc_tests ();
   analyzer_region_model_cc_tests ();
+  analyzer_region_model2_cc_tests ();
   analyzer_revamp_state_cc_tests ();
   analyzer_sm_file_cc_tests ();
   analyzer_sm_signal_cc_tests ();
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
index d3fe380e157..a8e115c01df 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -40,6 +40,7 @@ extern void analyzer_immutable_hash_map_cc_tests ();
 extern void analyzer_program_point_cc_tests ();
 extern void analyzer_program_state_cc_tests ();
 extern void analyzer_region_model_cc_tests ();
+extern void analyzer_region_model2_cc_tests ();
 extern void analyzer_revamp_state_cc_tests ();
 extern void analyzer_sm_file_cc_tests ();
 extern void analyzer_sm_signal_cc_tests ();
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 95cec3fdad1..6a37c0a3262 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -53,6 +53,20 @@ class equiv_class;
 struct model_merger;
 struct svalue_id_merger_mapping;
 struct canonicalization;
+
+class svalue2;
+  class region_svalue2;
+  class constant_svalue2;
+  class poisoned_svalue2;
+  class unknown_svalue2;
+  class setjmp_svalue2;
+class region2;
+  class map_region2;
+  class array_region2;
+  class symbolic_region2;
+class region_model2;
+class region_model2_context;
+
 class pending_diagnostic;
 class state_change_event;
 class checker_path;
diff --git a/gcc/analyzer/region-model2.cc b/gcc/analyzer/region-model2.cc
new file mode 100644
index 00000000000..ad0bacee148
--- /dev/null
+++ b/gcc/analyzer/region-model2.cc
@@ -0,0 +1,8236 @@
+/* Classes for modeling the state of memory.
+   Copyright (C) 2019-2020 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "diagnostic-core.h"
+#include "graphviz.h"
+#include "options.h"
+#include "cgraph.h"
+#include "tree-dfa.h"
+#include "stringpool.h"
+#include "convert.h"
+#include "target.h"
+#include "fold-const.h"
+#include "tree-pretty-print.h"
+#include "diagnostic-color.h"
+#include "diagnostic-metadata.h"
+#include "tristate.h"
+#include "bitmap.h"
+#include "selftest.h"
+#include "function.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "ordered-hash-map.h"
+#include "options.h"
+#include "cgraph.h"
+#include "cfg.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "sbitmap.h"
+#include "analyzer/region-model2.h"
+//#include "analyzer/constraint-manager.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/sm.h"
+#include "diagnostic-event-id.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/analyzer-selftests.h"
+#include "stor-layout.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* Dump T to PP in language-independent form, for debugging/logging/dumping
+   purposes.  */
+
+static void
+dump_tree (pretty_printer *pp, tree t)
+{
+  dump_generic_node (pp, t, 0, TDF_SLIM, 0);
+}
+
+/* Dump T to PP in language-independent form in quotes, for
+   debugging/logging/dumping purposes.  */
+
+void
+dump_quoted_tree (pretty_printer *pp, tree t)
+{
+  pp_begin_quote (pp, pp_show_color (pp));
+  dump_tree (pp, t);
+  pp_end_quote (pp, pp_show_color (pp));
+}
+
+/* Equivalent to pp_printf (pp, "%qT", t), to avoid nesting pp_printf
+   calls within other pp_printf calls.
+
+   default_tree_printer handles 'T' and some other codes by calling
+     dump_generic_node (pp, t, 0, TDF_SLIM, 0);
+   dump_generic_node calls pp_printf in various places, leading to
+   garbled output.
+
+   Ideally pp_printf could be made to be reentrant, but in the meantime
+   this function provides a workaround.  */
+
+static void
+print_quoted_type (pretty_printer *pp, tree t)
+{
+  pp_begin_quote (pp, pp_show_color (pp));
+  dump_generic_node (pp, t, 0, TDF_SLIM, 0);
+  pp_end_quote (pp, pp_show_color (pp));
+}
+
+/* Dump this path_var to PP (which must support %E for trees).
+
+   Express the stack depth using an "@DEPTH" suffix, so e.g. given
+     void foo (int j);
+     void bar (int i)
+     {
+       foo (i);
+     }
+   then:
+   - the "i" in "bar" would be "(i @ 0)"
+   - the "j" in "foo" would be "(j @ 1)".  */
+
+void
+path_var::dump (pretty_printer *pp) const
+{
+  if (m_tree == NULL_TREE)
+    pp_string (pp, "NULL");
+  if (CONSTANT_CLASS_P (m_tree))
+    pp_printf (pp, "%qE", m_tree);
+  else
+    pp_printf (pp, "(%qE @ %i)", m_tree, m_stack_depth);
+}
+
+/* For use in printing a comma-separated list.  */
+
+static void
+dump_separator (pretty_printer *pp, bool *is_first)
+{
+  if (!*is_first)
+    pp_string (pp, ", ");
+  *is_first = false;
+}
+
+#if 0
+/* Concrete subclass of constraint_manager that wires it up to a region_model2
+   (whilst allowing the constraint_manager and region_model2 to be somewhat
+   at arms length).
+   TODO: revisit this; maybe put the region_model2 * into the constraint_manager
+   base class.  */
+
+class impl_constraint_manager : public constraint_manager
+{
+ public:
+  impl_constraint_manager (region_model2 *model)
+  : constraint_manager (),
+    m_model (model)
+  {}
+
+  impl_constraint_manager (const impl_constraint_manager &other,
+			   region_model2 *model)
+  : constraint_manager (other),
+    m_model (model)
+  {}
+
+  constraint_manager *clone (region_model2 *model) const
+  {
+    return new impl_constraint_manager (*this, model);
+  }
+
+  tree maybe_get_constant (svalue2 *sval) const FINAL OVERRIDE
+  {
+    svalue2 *svalue2 = m_model->get_svalue2 (sid);
+    return svalue2->maybe_get_constant ();
+  }
+
+  svalue2_id get_sid_for_constant (tree cst) const FINAL OVERRIDE
+  {
+    gcc_assert (CONSTANT_CLASS_P (cst));
+    return m_model->get_rvalue (cst, NULL);
+  }
+
+  int get_num_svalue2s () const FINAL OVERRIDE
+  {
+    return m_model->get_num_svalue2s ();
+  }
+
+ private:
+  region_model2 *m_model;
+};
+#endif
+
+/* class svalue2 and its various subclasses.  */
+
+/* class svalue2.  */
+
+/* Generate a hash value for this svalue2.  Most of the work is done by the
+   add_to_hash vfunc.  */
+
+hashval_t
+svalue2::hash () const
+{
+  inchash::hash hstate;
+  if (m_type)
+    hstate.add_int (TYPE_UID (m_type));
+  add_to_hash (hstate);
+  return hstate.end ();
+}
+
+/* Print this svalue2 to PP.  */
+#if 0
+void
+svalue2::print (const region_model2 &model,
+		pretty_printer *pp) const
+{
+  this_sid.print (pp);
+  pp_string (pp, ": {");
+
+  if (m_type)
+    {
+      gcc_assert (TYPE_P (m_type));
+      pp_string (pp, "type: ");
+      print_quoted_type (pp, m_type);
+      pp_string (pp, ", ");
+    }
+
+  /* vfunc.  */
+  print_details (model, this_sid, pp);
+
+  pp_string (pp, "}");
+}
+#endif
+
+/* Dump this svalue2 in the form of a .dot record to PP.  */
+
+#if 0
+void
+svalue2::dump_dot_to_pp (const region_model2 &model,
+			pretty_printer *pp) const
+{
+  this_sval.dump_node_name_to_pp (pp);
+  pp_printf (pp, " [label=\"");
+  pp_write_text_to_stream (pp);
+  this_sval.print (pp);
+  pp_string (pp, ": {");
+  print (model, this_sval, pp);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "}\"];");
+  pp_newline (pp);
+}
+#endif
+
+/* If this svalue2 is a constant_svalue2, return the underlying tree constant.
+   Otherwise return NULL_TREE.  */
+
+tree
+svalue2::maybe_get_constant () const
+{
+  if (const constant_svalue2 *cst_sval = dyn_cast_constant_svalue2 ())
+    return cst_sval->get_constant ();
+  else
+    return NULL_TREE;
+}
+
+/* class region_svalue2 : public svalue2.  */
+
+/* Compare the fields of this region_svalue2 with OTHER, returning true
+   if they are equal.
+   For use by svalue2::operator==.  */
+#if 0
+bool
+region_svalue2::compare_fields (const region_svalue2 &other) const
+{
+  return m_reg == other.m_reg;
+}
+#endif
+
+/* Implementation of svalue2::add_to_hash vfunc for region_svalue2.  */
+
+#if 0
+void
+region_svalue2::add_to_hash (inchash::hash &hstate) const
+{
+  inchash::add (m_reg, hstate);
+}
+#endif
+/* Implementation of svalue2::print_details vfunc for region_svalue2.  */
+
+#if 0
+void
+region_svalue2::print_details (const region_model2 &model ATTRIBUTE_UNUSED,
+			      svalue2_id this_sval ATTRIBUTE_UNUSED,
+			      pretty_printer *pp) const
+{
+  if (m_reg.null_p ())
+    pp_string (pp, "NULL");
+  else
+    {
+      pp_string (pp, "&");
+      m_reg.print (pp);
+    }
+}
+#endif
+
+/* Implementation of svalue2::dump_dot_to_pp for region_svalue2.  */
+
+#if 0
+void
+region_svalue2::dump_dot_to_pp (const region_model2 &model,
+			       svalue2_id this_sval,
+			       pretty_printer *pp) const
+{
+  svalue2::dump_dot_to_pp (model, this_sval, pp);
+
+  /* If non-NULL, add an edge to the pointed-to region2.  */
+  if (!m_reg.null_p ())
+    {
+      this_sval.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_reg.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+  }
+}
+#endif
+
+#if 0
+/* Merge REGION2_SVAL_A and REGION2_SVAL_B using MERGER, writing the result
+   into *MERGED_SVAL.  */
+
+void
+region_svalue2::merge_values (const region_svalue2 &region2_sval_a,
+			     const region_svalue2 &region2_sval_b,
+			     svalue2_id *merged_sval,
+			     tree type,
+			     model_merger *merger)
+{
+  region2_id a_reg = region2_sval_a.get_pointee ();
+  region2_id b_reg = region2_sval_b.get_pointee ();
+
+  /* Both are non-NULL. */
+  gcc_assert (!a_reg.null_p () && !b_reg.null_p ());
+
+  /* Have these ptr-values already been merged?  */
+
+  region2_id a_reg_in_m
+    = merger->m_map_region2s_from_a_to_m.get_dst_for_src (a_reg);
+  region2_id b_reg_in_m
+    = merger->m_map_region2s_from_b_to_m.get_dst_for_src (b_reg);
+
+  /* "null_p" here means "we haven't seen this ptr-value before".
+     If we've seen one but not the other, or we have different
+     region2s, then the merged ptr has to be "unknown".  */
+  if (a_reg_in_m != b_reg_in_m)
+    {
+      svalue2 *merged_sval = new unknown_svalue2 (type);
+      *merged_sval = merger->m_merged_model->add_svalue2 (merged_sval);
+      return;
+    }
+
+  /* Have we seen this yet?  If so, reuse the value.  */
+  if (!a_reg_in_m.null_p ())
+    {
+      *merged_sval
+	= merger->m_merged_model->get_or_create_ptr_svalue2 (type, a_reg_in_m);
+      return;
+    }
+
+  /* Otherwise we have A/B region2s that haven't been referenced yet.  */
+
+  /* Are the region2s the "same", when seen from the tree point-of-view.
+     If so, create a merged pointer to it.  */
+  path_var pv_a = merger->m_model_a->get_representative_path_var (a_reg);
+  path_var pv_b = merger->m_model_b->get_representative_path_var (b_reg);
+  if (pv_a.m_tree
+      && pv_a == pv_b)
+    {
+      region2_id merged_pointee_reg
+	= merger->m_merged_model->get_lvalue (pv_a, NULL);
+      *merged_sval
+	= merger->m_merged_model->get_or_create_ptr_svalue2 (type,
+							    merged_pointee_reg);
+      merger->record_region2s (a_reg, b_reg, merged_pointee_reg);
+      return;
+    }
+
+  /* Handle an A/B pair of ptrs that both point at heap region2s.
+     If they both have a heap region2 in the merger model, merge them.  */
+  region2 *region2_a = merger->m_model_a->get_region2 (a_reg);
+  region2 *region2_b = merger->m_model_b->get_region2 (b_reg);
+  region2_id a_parent_reg = region2_a->get_parent ();
+  region2_id b_parent_reg = region2_b->get_parent ();
+  region2 *parent_region2_a = merger->m_model_a->get_region2 (a_parent_reg);
+  region2 *parent_region2_b = merger->m_model_b->get_region2 (b_parent_reg);
+  if (parent_region2_a
+      && parent_region2_b
+      && parent_region2_a->get_kind () == RK_HEAP
+      && parent_region2_b->get_kind () == RK_HEAP)
+    {
+	/* We have an A/B pair of ptrs that both point at heap region2s.  */
+	/* presumably we want to see if each A/B heap region2 already
+	   has a merged region2, and, if so, is it the same one.  */
+	// This check is above
+
+	region2_id merged_pointee_reg
+	  = merger->m_merged_model->add_new_malloc_region2 ();
+	*merged_sval
+	  = merger->m_merged_model->get_or_create_ptr_svalue2
+	     (type, merged_pointee_reg);
+	merger->record_region2s (a_reg, b_reg, merged_pointee_reg);
+	return;
+    }
+
+  /* Two different non-NULL pointers?  Merge to unknown.  */
+  svalue2 *merged_sval = new unknown_svalue2 (type);
+  *merged_sval = merger->m_merged_model->add_svalue2 (merged_sval);
+  return;
+}
+#endif
+
+/* Evaluate the condition LHS OP RHS.
+   Subroutine of region_model2::eval_condition for when we have a pair of
+   pointers.  */
+#if 0
+tristate
+region_svalue2::eval_condition (region_svalue2 *lhs,
+				enum tree_code op,
+				region_svalue2 *rhs)
+{
+  /* See if they point to the same region2.  */
+  /* TODO: what about child region2s where the child is the first child
+     (or descendent)?  */
+  region2_id lhs_reg = lhs->get_pointee ();
+  region2_id rhs_reg = rhs->get_pointee ();
+  switch (op)
+    {
+    default:
+      gcc_unreachable ();
+
+    case EQ_EXPR:
+      if (lhs_reg == rhs_reg)
+	return tristate::TS_TRUE;
+      else
+	return tristate::TS_FALSE;
+      break;
+
+    case NE_EXPR:
+      if (lhs_reg != rhs_reg)
+	return tristate::TS_TRUE;
+      else
+	return tristate::TS_FALSE;
+      break;
+
+    case GE_EXPR:
+    case LE_EXPR:
+      if (lhs_reg == rhs_reg)
+	return tristate::TS_TRUE;
+      break;
+
+    case GT_EXPR:
+    case LT_EXPR:
+      if (lhs_reg == rhs_reg)
+	return tristate::TS_FALSE;
+      break;
+    }
+
+  return tristate::TS_UNKNOWN;
+}
+#endif
+
+/* class constant_svalue2 : public svalue2.  */
+
+/* Compare the fields of this constant_svalue2 with OTHER, returning true
+   if they are equal.
+   For use by svalue2::operator==.  */
+#if 0
+bool
+constant_svalue2::compare_fields (const constant_svalue2 &other) const
+{
+  return m_cst_expr == other.m_cst_expr;
+}
+#endif
+
+/* Implementation of svalue2::add_to_hash vfunc for constant_svalue2.  */
+
+void
+constant_svalue2::add_to_hash (inchash::hash &hstate) const
+{
+  inchash::add_expr (m_cst_expr, hstate);
+}
+
+/* Merge the CST_SVAL_A and CST_SVAL_B using MERGER, writing the id of
+   the resulting svalue2 into *MERGED_SVAL.  */
+#if 0
+void
+constant_svalue2::merge_values (const constant_svalue2 &cst_sval_a,
+			       const constant_svalue2 &cst_sval_b,
+			       svalue2_id *merged_sval,
+			       model_merger *merger)
+{
+  tree cst_a = cst_sval_a.get_constant ();
+  tree cst_b = cst_sval_b.get_constant ();
+  svalue2 *merged_sval;
+  if (cst_a == cst_b)
+    {
+      /* If they are the same constant, merge as that constant value.  */
+      merged_sval = new constant_svalue2 (cst_a);
+    }
+  else
+    {
+      /* Otherwise, we have two different constant values.
+	 Merge as an unknown value.
+	 TODO: impose constraints on the value?
+	 (maybe just based on A, to avoid infinite chains)  */
+      merged_sval = new unknown_svalue2 (TREE_TYPE (cst_a));
+    }
+  *merged_sval = merger->m_merged_model->add_svalue2 (merged_sval);
+}
+#endif
+
+/* Evaluate the condition LHS OP RHS.
+   Subroutine of region_model2::eval_condition for when we have a pair of
+   constants.  */
+#if 0
+tristate
+constant_svalue2::eval_condition (constant_svalue2 *lhs,
+				 enum tree_code op,
+				 constant_svalue2 *rhs)
+{
+  tree lhs_const = lhs->get_constant ();
+  tree rhs_const = rhs->get_constant ();
+
+  gcc_assert (CONSTANT_CLASS_P (lhs_const));
+  gcc_assert (CONSTANT_CLASS_P (rhs_const));
+
+  /* Check for comparable types.  */
+  if (types_compatible_p (TREE_TYPE (lhs_const), TREE_TYPE (rhs_const)))
+    {
+      tree comparison
+	= fold_binary (op, boolean_type_node, lhs_const, rhs_const);
+      if (comparison == boolean_true_node)
+	return tristate (tristate::TS_TRUE);
+      if (comparison == boolean_false_node)
+	return tristate (tristate::TS_FALSE);
+    }
+  return tristate::TS_UNKNOWN;
+}
+#endif
+
+/* Implementation of svalue2::print_details vfunc for constant_svalue2.  */
+#if 0
+void
+constant_svalue2::print_details (const region_model2 &model ATTRIBUTE_UNUSED,
+				 pretty_printer *pp) const
+{
+  pp_printf (pp, "%qE", m_cst_expr);
+}
+#endif
+
+/* Implementation of svalue2::get_child_sval vfunc for constant_svalue2.  */
+#if 0
+svalue2_id
+constant_svalue2::get_child_sval (region2 *parent ATTRIBUTE_UNUSED,
+				region2 *child,
+				region_model2 &model,
+				region_model2_context *ctxt ATTRIBUTE_UNUSED)
+{
+  /* TODO: handle the all-zeroes case by returning an all-zeroes of the
+     child type.  */
+
+  /* Otherwise, we don't have a good way to get a child value out of a
+     constant.
+
+     Handle this case by using an unknown value.  */
+  svalue2 *unknown_sval = new unknown_svalue2 (child->get_type ());
+  return model.add_svalue2 (unknown_sval);
+}
+#endif
+
+/* class unknown_svalue2 : public svalue2.  */
+
+/* Compare the fields of this unknown_svalue2 with OTHER, returning true
+   if they are equal.
+   For use by svalue2::operator==.  */
+#if 0
+bool
+unknown_svalue2::compare_fields (const unknown_svalue2 &) const
+{
+  /* I *think* we want to return true here, in that when comparing
+     two region2 models, we want two peer unknown_svalue2 instances
+     to be the "same".  */
+  return true;
+}
+#endif
+
+/* Implementation of svalue2::add_to_hash vfunc for unknown_svalue2.  */
+
+void
+unknown_svalue2::add_to_hash (inchash::hash &) const
+{
+  /* Empty.  */
+}
+
+/* Implementation of svalue2::print_details vfunc for unknown_svalue2.  */
+#if 0
+void
+unknown_svalue2::print_details (const region_model2 &model ATTRIBUTE_UNUSED,
+			       pretty_printer *pp) const
+{
+  pp_string (pp, "unknown");
+}
+#endif
+
+/* Get a string for KIND for use in debug dumps.  */
+
+const char *
+poison_kind_to_str (enum poison_kind kind)
+{
+  switch (kind)
+    {
+    default:
+      gcc_unreachable ();
+    case POISON_KIND_UNINIT:
+      return "uninit";
+    case POISON_KIND_FREED:
+      return "freed";
+    case POISON_KIND_POPPED_STACK:
+      return "popped stack";
+    }
+}
+
+/* class poisoned_svalue2 : public svalue2.  */
+
+/* Compare the fields of this poisoned_svalue2 with OTHER, returning true
+   if they are equal.
+   For use by svalue2::operator==.  */
+#if 0
+bool
+poisoned_svalue2::compare_fields (const poisoned_svalue2 &other) const
+{
+  return m_kind == other.m_kind;
+}
+#endif
+
+/* Implementation of svalue2::add_to_hash vfunc for poisoned_svalue2.  */
+
+void
+poisoned_svalue2::add_to_hash (inchash::hash &hstate) const
+{
+  hstate.add_int (m_kind);
+}
+
+/* Implementation of svalue2::print_details vfunc for poisoned_svalue2.  */
+#if 0
+void
+poisoned_svalue2::print_details (const region_model2 &model ATTRIBUTE_UNUSED,
+				pretty_printer *pp) const
+{
+  pp_printf (pp, "poisoned: %s", poison_kind_to_str (m_kind));
+}
+#endif
+
+/* class setjmp_svalue2's implementation is in engine.cc, so that it can use
+   the declaration of exploded_node.  */
+
+/* class region2 and its various subclasses.  */
+
+/* Get a string for KIND for use in debug dumps.  */
+
+const char *
+region2::kind_to_str (enum kind kind)
+{
+  switch (kind)
+    {
+    default:
+      gcc_unreachable ();
+    case RK_PRIMITIVE:
+      return "primitive";
+    case RK_STRUCT:
+      return "struct";
+    case RK_UNION:
+      return "union";
+    case RK_ARRAY:
+      return "array";
+    case RK_FRAME:
+      return "frame";
+    case RK_GLOBALS:
+      return "globals";
+    case RK_CODE:
+      return "code";
+    case RK_FUNCTION:
+      return "function";
+    case RK_STACK:
+      return "stack";
+    case RK_HEAP:
+      return "heap";
+    case RK_ROOT:
+      return "root";
+    case RK_SYMBOLIC:
+      return "symbolic";
+    }
+}
+
+/* class region2.  */
+
+/* Equality operator for region2.
+   After comparing base class fields and kind, the rest of the
+   comparison is handled off to a "compare_fields" member function
+   specific to the appropriate subclass.  */
+#if 0
+bool
+region2::operator== (const region2 &other) const
+{
+  if (m_parent_reg != other.m_parent_reg)
+    return false;
+  if (m_sval_id != other.m_sval_id)
+    return false;
+  if (m_type != other.m_type)
+    return false;
+
+  enum region2_kind this_kind = get_kind ();
+  enum region2_kind other_kind = other.get_kind ();
+  if (this_kind != other_kind)
+    return false;
+
+  /* Compare views.  */
+  if (m_view_regs.length () != other.m_view_regs.length ())
+    return false;
+  int i;
+  region2_id *rid;
+  FOR_EACH_VEC_ELT (m_view_regs, i, rid)
+    if (! (*rid == other.m_view_regs[i]))
+      return false;
+
+  switch (this_kind)
+    {
+    default:
+      gcc_unreachable ();
+    case RK_PRIMITIVE:
+      {
+#if 1
+	return true;
+#else
+	const primitive_region2 &this_sub
+	  = (const primitive_region2 &)*this;
+	const primitive_region2 &other_sub
+	  = (const primitive_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+#endif
+      }
+    case RK_STRUCT:
+      {
+	const struct_region2 &this_sub
+	  = (const struct_region2 &)*this;
+	const struct_region2 &other_sub
+	  = (const struct_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_UNION:
+      {
+	const union_region2 &this_sub
+	  = (const union_region2 &)*this;
+	const union_region2 &other_sub
+	  = (const union_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_ARRAY:
+      {
+	const array_region2 &this_sub
+	  = (const array_region2 &)*this;
+	const array_region2 &other_sub
+	  = (const array_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_FRAME:
+      {
+	const frame_region2 &this_sub
+	  = (const frame_region2 &)*this;
+	const frame_region2 &other_sub
+	  = (const frame_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_GLOBALS:
+      {
+	const globals_region2 &this_sub
+	  = (const globals_region2 &)*this;
+	const globals_region2 &other_sub
+	  = (const globals_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_CODE:
+      {
+	const code_region2 &this_sub
+	  = (const code_region2 &)*this;
+	const code_region2 &other_sub
+	  = (const code_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_FUNCTION:
+      {
+	const function_region2 &this_sub
+	  = (const function_region2 &)*this;
+	const function_region2 &other_sub
+	  = (const function_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_STACK:
+      {
+	const stack_region2 &this_sub
+	  = (const stack_region2 &)*this;
+	const stack_region2 &other_sub
+	  = (const stack_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_ROOT:
+      {
+	const root_region2 &this_sub
+	  = (const root_region2 &)*this;
+	const root_region2 &other_sub
+	  = (const root_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_SYMBOLIC:
+      {
+	const symbolic_region2 &this_sub
+	  = (const symbolic_region2 &)*this;
+	const symbolic_region2 &other_sub
+	  = (const symbolic_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    case RK_HEAP:
+      {
+	const heap_region2 &this_sub
+	  = (const heap_region2 &)*this;
+	const heap_region2 &other_sub
+	  = (const heap_region2 &)other;
+	return this_sub.compare_fields (other_sub);
+      }
+    }
+}
+#endif
+
+/* Set this region2's value to RHS_SVAL (or potentially a variant of it,
+   for some kinds of casts).  */
+#if 0
+void
+region2::set_value (region_model2 &model, region2_id this_reg, svalue2_id rhs_sval,
+		   region_model2_context *ctxt)
+{
+  /* Handle some kinds of casting.  */
+  if (m_type)
+    {
+      svalue2 *sval = model.get_svalue2 (rhs_sval);
+      if (sval->get_type ())
+	rhs_sval = model.maybe_cast (m_type, rhs_sval, ctxt);
+
+      sval = model.get_svalue2 (rhs_sval);
+      if (sval->get_type ())
+	gcc_assert (m_type == sval->get_type ());
+    }
+
+  m_sval_id = rhs_sval;
+
+  /* Update views.
+     If this is a view, it becomes its parent's active view.
+     If there was already an active views, invalidate its value; otherwise
+     if the parent itself had a value, invalidate it.
+     If it's not a view, then deactivate any view that is active on this
+     region2.  */
+  {
+    if (m_is_view)
+      become_active_view (model, this_reg);
+    else
+      {
+	deactivate_any_active_view (model);
+	gcc_assert (m_active_view_reg.null_p ());
+      }
+  }
+}
+#endif
+
+#if 0
+/* Make this region2 (with id THIS_REG) the "active" view of its parent.
+   Any other active view has its value set to "unknown" and descendent values
+   cleared.
+   If there wasn't an active view, then set the parent's value to unknown, and
+   clear its descendent values (apart from this view).  */
+void
+region2::become_active_view (region_model2 &model, region2_id this_reg)
+{
+  gcc_assert (m_is_view);
+
+  region2 *parent_reg = model.get_region2 (m_parent_reg);
+  gcc_assert (parent_reg);
+
+  region2_id old_active_view_reg = parent_reg->m_active_view_reg;
+
+  if (old_active_view_reg == this_reg)
+    {
+      /* Already the active view: do nothing.  */
+      return;
+    }
+
+  /* We have a change of active view.  */
+  parent_reg->m_active_view_reg = this_reg;
+
+  if (old_active_view_reg.null_p ())
+    {
+      /* No previous active view, but the parent and its other children
+	 might have values.
+	 If so, invalidate those values - but not that of the new view.  */
+      region2_id_set below_region2 (&model);
+      model.get_descendents (m_parent_reg, &below_region2, this_reg);
+      for (unsigned i = 0; i < model.get_num_region2s (); i++)
+	{
+	  region2_id rid (region2_id::from_int (i));
+	  if (below_region2.region2_p (rid))
+	    {
+	      region2 *other_reg = model.get_region2 (rid);
+	      other_reg->m_sval_id = svalue2_id::null ();
+	    }
+	}
+      region2 *parent = model.get_region2 (m_parent_reg);
+      parent->m_sval_id
+	= model.add_svalue2 (new unknown_svalue2 (parent->get_type ()));
+    }
+  else
+    {
+      /* If there was an active view, invalidate it.  */
+      region2 *old_active_view = model.get_region2 (old_active_view_reg);
+      old_active_view->deactivate_view (model, old_active_view_reg);
+    }
+}
+
+/* If this region2 (with id THIS_REG) has an active view, deactivate it,
+   clearing m_active_view_reg.  */
+
+void
+region2::deactivate_any_active_view (region_model2 &model)
+{
+  if (m_active_view_reg.null_p ())
+    return;
+  region2 *view = model.get_region2 (m_active_view_reg);
+  view->deactivate_view (model, m_active_view_reg);
+  m_active_view_reg = region2_id::null ();
+}
+
+/* Clear any values for region2s below THIS_REG.
+   Set the view's value to unknown.  */
+
+void
+region2::deactivate_view (region_model2 &model, region2_id this_view_reg)
+{
+  gcc_assert (is_view_p ());
+
+  /* Purge values from old_active_this_view_reg and all its
+     descendents.  Potentially we could use a poison value
+     for this, but let's use unknown for now.  */
+  region2_id_set below_view (&model);
+  model.get_descendents (this_view_reg, &below_view, region2_id::null ());
+
+  for (unsigned i = 0; i < model.get_num_region2s (); i++)
+    {
+      region2_id rid (region2_id::from_int (i));
+      if (below_view.region2_p (rid))
+	{
+	  region2 *other_reg = model.get_region2 (rid);
+	  other_reg->m_sval_id = svalue2_id::null ();
+	}
+    }
+
+  m_sval_id = model.add_svalue2 (new unknown_svalue2 (get_type ()));
+}
+#endif
+
+/* Get a value for this region2, either its value if it has one,
+   or, failing that, "inherit" a value from first ancestor with a
+   non-null value.
+
+   For example, when getting the value for a local variable within
+   a stack frame that doesn't have one, the frame doesn't have a value
+   either, but the stack as a whole will have an "uninitialized" poison
+   value, so inherit that.  */
+#if 0
+svalue2_id
+region2::get_value (region_model2 &model, bool non_null,
+		   region_model2_context *ctxt)
+{
+  /* If this region2 has a value, use it. */
+  if (!m_sval_id.null_p ())
+    return m_sval_id;
+
+  /* Otherwise, "inherit" value from first ancestor with a
+     non-null value. */
+
+  region2 *parent = model.get_region2 (m_parent_reg);
+  if (parent)
+    {
+      svalue2_id inherited_sval
+	= parent->get_inherited_child_sval (this, model, ctxt);
+      if (!inherited_sval.null_p ())
+	return inherited_sval;
+    }
+
+  /* If a non-null value has been requested, then generate
+     a new unknown value.  Store it, so that repeated reads from this
+     region2 will yield the same unknown value.  */
+  if (non_null)
+    {
+      svalue2_id unknown_sval = model.add_svalue2 (new unknown_svalue2 (m_type));
+      m_sval_id = unknown_sval;
+      return unknown_sval;
+    }
+
+  return svalue2_id::null ();
+}
+
+/* Get a value for CHILD, inheriting from this region2.
+
+   Recurse, so this region2 will inherit a value if it doesn't already
+   have one.  */
+
+svalue2_id
+region2::get_inherited_child_sval (region2 *child,
+				 region_model2 &model,
+				 region_model2_context *ctxt)
+{
+  if (m_sval_id.null_p ())
+    {
+      /* Recurse.  */
+      if (!m_parent_reg.null_p ())
+	{
+	  region2 *parent = model.get_region2 (m_parent_reg);
+	  m_sval_id = parent->get_inherited_child_sval (this, model, ctxt);
+	}
+    }
+
+  if (!m_sval_id.null_p ())
+    {
+      /* Clone the parent's value, so that attempts to update it
+	 (e.g giving a specific value to an inherited "uninitialized"
+	 value) touch the child, and not the parent.  */
+      svalue2 *this_value = model.get_svalue2 (m_sval_id);
+      svalue2_id new_child_sval
+	= this_value->get_child_sval (this, child, model, ctxt);
+      if (ctxt)
+	ctxt->on_inherited_svalue2 (m_sval_id, new_child_sval);
+      child->m_sval_id = new_child_sval;
+      return new_child_sval;
+    }
+
+  return svalue2_id::null ();
+}
+#endif
+
+/* Copy from SRC_REG to DST_REG, using CTXT for any issues that occur.
+   Copy across any value for the region2, and handle structs, unions
+   and arrays recursively.  */
+#if 0
+void
+region_model2::copy_region2 (region2_id dst_reg, region2_id src_reg,
+			   region_model2_context *ctxt)
+{
+  gcc_assert (!dst_reg.null_p ());
+  gcc_assert (!src_reg.null_p ());
+  if (dst_reg == src_reg)
+    return;
+  region2 *dst_reg = get_region2 (dst_reg);
+  region2 *src_reg = get_region2 (src_reg);
+
+  /* Copy across any value for the src region2 itself.  */
+  svalue2 *sval = src_reg->get_value (*this, true, ctxt);
+  set_value (dst_reg, sid, ctxt);
+
+  if (dst_reg->get_kind () != src_reg->get_kind ())
+    return;
+
+  /* Copy across child region2s for structs, unions, and arrays.  */
+  switch (dst_reg->get_kind ())
+    {
+    case RK_PRIMITIVE:
+      return;
+    case RK_STRUCT:
+      {
+	struct_region2 *dst_sub = as_a <struct_region2 *> (dst_reg);
+	struct_region2 *src_sub = as_a <struct_region2 *> (src_reg);
+	copy_struct_region2 (dst_reg, dst_sub, src_sub, ctxt);
+      }
+      return;
+    case RK_UNION:
+      {
+	union_region2 *src_sub = as_a <union_region2 *> (src_reg);
+	copy_union_region2 (dst_reg, src_sub, ctxt);
+      }
+      return;
+    case RK_FRAME:
+    case RK_GLOBALS:
+    case RK_CODE:
+    case RK_FUNCTION:
+      return;
+    case RK_ARRAY:
+      {
+	array_region2 *dst_sub = as_a <array_region2 *> (dst_reg);
+	array_region2 *src_sub = as_a <array_region2 *> (src_reg);
+	copy_array_region2 (dst_reg, dst_sub, src_sub, ctxt);
+      }
+      return;
+    case RK_STACK:
+    case RK_HEAP:
+    case RK_ROOT:
+    case RK_SYMBOLIC:
+      return;
+    }
+}
+
+/* Subroutine of region_model2::copy_region2 for copying the child
+   region2s for a struct.  */
+
+void
+region_model2::copy_struct_region2 (region2_id dst_reg,
+				  struct_region2 *dst_reg,
+				  struct_region2 *src_reg,
+				  region_model2_context *ctxt)
+{
+  for (map_region2::iterator_t iter = src_reg->begin ();
+       iter != src_reg->end (); ++iter)
+    {
+      tree src_key = (*iter).first;
+      region2_id src_field_reg = (*iter).second;
+      region2 *src_field_reg = get_region2 (src_field_reg);
+      region2_id dst_field_reg
+	= dst_reg->get_or_create (this, dst_reg, src_key,
+				  src_field_reg->get_type (), ctxt);
+      copy_region2 (dst_field_reg, src_field_reg, ctxt);
+    }
+}
+
+/* Subroutine of region_model2::copy_region2 for copying the active
+   child region2 for a union.  */
+
+void
+region_model2::copy_union_region2 (region2_id dst_reg,
+				 union_region2 *src_reg,
+				 region_model2_context *ctxt)
+{
+  region2_id src_active_view_reg = src_reg->get_active_view ();
+  if (src_active_view_reg.null_p ())
+    return;
+  region2 *src_active_view = get_region2 (src_active_view_reg);
+  tree type = src_active_view->get_type ();
+  region2_id dst_active_view_reg = get_or_create_view (dst_reg, type, ctxt);
+  copy_region2 (dst_active_view_reg, src_active_view_reg, ctxt);
+}
+
+/* Subroutine of region_model2::copy_region2 for copying the child
+   region2s for an array.  */
+
+void
+region_model2::copy_array_region2 (region2_id dst_reg,
+				 array_region2 *dst_reg,
+				 array_region2 *src_reg,
+				 region_model2_context *ctxt)
+{
+  for (array_region2::iterator_t iter = src_reg->begin ();
+       iter != src_reg->end (); ++iter)
+    {
+      array_region2::key_t src_key = (*iter).first;
+      region2_id src_field_reg = (*iter).second;
+      region2 *src_field_reg = get_region2 (src_field_reg);
+      region2_id dst_field_reg
+	= dst_reg->get_or_create (this, dst_reg, src_key,
+				  src_field_reg->get_type (), ctxt);
+      copy_region2 (dst_field_reg, src_field_reg, ctxt);
+    }
+}
+#endif
+
+/* Generate a hash value for this region2.  The work is done by the
+   add_to_hash vfunc.  */
+
+hashval_t
+region2::hash () const
+{
+  inchash::hash hstate;
+  add_to_hash (hstate);
+  return hstate.end ();
+}
+
+/* Print a one-liner representation of this region2 to PP, assuming
+   that this region2 is within MODEL and its id is THIS_REG.  */
+#if 0
+void
+region2::print (const region_model2 &model,
+	       region2_id this_reg,
+	       pretty_printer *pp) const
+{
+  this_reg.print (pp);
+  pp_string (pp, ": {");
+
+  /* vfunc.  */
+  print_fields (model, this_reg, pp);
+
+  pp_string (pp, "}");
+}
+#endif
+
+/* Base class implementation of region2::dump_dot_to_pp vfunc.  */
+#if 0
+void
+region2::dump_dot_to_pp (const region_model2 &model,
+			region2_id this_reg,
+			pretty_printer *pp) const
+{
+  this_reg.dump_node_name_to_pp (pp);
+  pp_printf (pp, " [shape=none,margin=0,style=filled,fillcolor=%s,label=\"",
+	     "lightgrey");
+  pp_write_text_to_stream (pp);
+  print (model, this_reg, pp);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "\"];");
+  pp_newline (pp);
+
+  /* Add edge to svalue2.  */
+  if (!m_sval_id.null_p ())
+    {
+      this_reg.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_sval_id.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+
+  /* Add edge to parent.  */
+  if (!m_parent_reg.null_p ())
+    {
+      this_reg.dump_node_name_to_pp (pp);
+      pp_string (pp, " -> ");
+      m_parent_reg.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+}
+#endif
+
+/* Dump a tree-like ASCII-art representation of this region2 to PP.  */
+#if 0
+void
+region2::dump_to_pp (const region_model2 &model,
+		    region2_id this_reg,
+		    pretty_printer *pp,
+		    const char *prefix,
+		    bool is_last_child) const
+{
+  print (model, this_reg, pp);
+  pp_newline (pp);
+
+  const char *new_prefix;
+  if (!m_parent_reg.null_p ())
+    new_prefix = ACONCAT ((prefix, is_last_child ? "  " : "|  ", NULL));
+  else
+    new_prefix = prefix;
+
+  const char *begin_color = colorize_start (pp_show_color (pp), "note");
+  const char *end_color = colorize_stop (pp_show_color (pp));
+  char *field_prefix
+    = ACONCAT ((begin_color, new_prefix, "|:", end_color, NULL));
+
+  if (!m_sval_id.null_p ())
+    {
+      pp_printf (pp, "%s sval: ", field_prefix);
+      model.get_svalue2 (m_sval_id)->print (model, m_sval_id, pp);
+      pp_newline (pp);
+    }
+  if (m_type)
+    {
+      pp_printf (pp, "%s type: ", field_prefix);
+      print_quoted_type (pp, m_type);
+      pp_newline (pp);
+    }
+
+  /* Find the children.  */
+
+  auto_vec<region2_id> child_regs;
+  unsigned i;
+  for (unsigned i = 0; i < model.get_num_region2s (); ++i)
+    {
+      region2_id rid = region2_id::from_int (i);
+      region2 *child = model.get_region2 (rid);
+      if (child->m_parent_reg == this_reg)
+	child_regs.safe_push (rid);
+    }
+
+  /* Print the children, using dump_child_label to label them.  */
+
+  region2_id *child_reg;
+  FOR_EACH_VEC_ELT (child_regs, i, child_reg)
+    {
+      is_last_child = (i == child_regs.length () - 1);
+      if (!this_reg.null_p ())
+	{
+	  const char *tail = is_last_child ? "`-" : "|-";
+	  pp_printf (pp, "%r%s%s%R", "note", new_prefix, tail);
+	}
+      dump_child_label (model, this_reg, *child_reg, pp);
+      model.get_region2 (*child_reg)->dump_to_pp (model, *child_reg, pp,
+						 new_prefix,
+						 is_last_child);
+    }
+}
+#endif
+
+/* Base implementation of region2::dump_child_label vfunc.  */
+#if 0
+void
+region2::dump_child_label (const region_model2 &model,
+			  region2_id this_reg ATTRIBUTE_UNUSED,
+			  region2_id child_reg,
+			  pretty_printer *pp) const
+{
+  region2 *child = model.get_region2 (child_reg);
+  if (child->m_is_view)
+    {
+      gcc_assert (TYPE_P (child->get_type ()));
+      if (m_active_view_reg == child_reg)
+	pp_string (pp, "active ");
+      else
+	pp_string (pp, "inactive ");
+      pp_string (pp, "view as ");
+      print_quoted_type (pp, child->get_type ());
+      pp_string (pp, ": ");
+    }
+}
+#endif
+
+/* Base implementation of region2::validate vfunc.
+   Assert that the fields of "region2" are valid; subclasses should
+   chain up their implementation to this one.  */
+#if 0
+void
+region2::validate (const region_model2 &model) const
+{
+  m_parent_reg.validate (model);
+  m_sval_id.validate (model);
+  unsigned i;
+  region2_id *view_reg;
+  FOR_EACH_VEC_ELT (m_view_regs, i, view_reg)
+    {
+      gcc_assert (!view_reg->null_p ());
+      view_reg->validate (model);
+    }
+  m_active_view_reg.validate (model);
+}
+#endif
+
+#if 0
+/* Add a new region2 with id VIEW_REG as a view of this region2.  */
+
+void
+region2::add_view (region2_id view_reg, region_model2 *model)
+{
+  gcc_assert (!view_reg.null_p ());
+  region2 *new_view = model->get_region2 (view_reg);
+  new_view->m_is_view = true;
+  gcc_assert (!new_view->m_parent_reg.null_p ());
+  gcc_assert (new_view->m_sval_id.null_p ());
+
+  //gcc_assert (new_view->get_type () != NULL_TREE);
+  // TODO: this can sometimes be NULL, when viewing through a (void *)
+
+  // TODO: the type ought to not be present yet
+
+  m_view_regs.safe_push (view_reg);
+}
+
+/* Look for a view of type TYPE of this region2, returning its id if found,
+   or null otherwise.  */
+
+region2_id
+region2::get_view (tree type, region_model2 *model) const
+{
+  unsigned i;
+  region2_id *view_reg;
+  FOR_EACH_VEC_ELT (m_view_regs, i, view_reg)
+    {
+      region2 *view = model->get_region2 (*view_reg);
+      gcc_assert (view->m_is_view);
+      if (view->get_type () == type)
+	return *view_reg;
+    }
+  return region2_id::null ();
+}
+#endif
+
+/* region2's ctor.  */
+
+region2::region2 (region2 *parent, tree type)
+: m_parent (parent), m_type (type)
+{
+  gcc_assert (type == NULL_TREE || TYPE_P (type));
+}
+
+/* region2's copy ctor.  */
+#if 0
+region2::region2 (const region2 &other)
+: m_parent_reg (other.m_parent_reg), m_sval_id (other.m_sval_id),
+  m_type (other.m_type), m_view_regs (other.m_view_regs.length ()),
+  m_is_view (other.m_is_view), m_active_view_reg (other.m_active_view_reg)
+{
+  int i;
+  region2_id *rid;
+  FOR_EACH_VEC_ELT (other.m_view_regs, i, rid)
+    m_view_regs.quick_push (*rid);
+}
+#endif
+
+/* Base implementation of region2::add_to_hash vfunc; subclasses should
+   chain up to this.  */
+
+void
+region2::add_to_hash (inchash::hash &hstate) const
+{
+  hstate.add_ptr (m_parent);
+  hstate.add_ptr (m_type);
+}
+
+/* Base implementation of region2::print_fields vfunc.  */
+#if 0
+void
+region2::print_fields (const region_model2 &model ATTRIBUTE_UNUSED,
+		       pretty_printer *pp) const
+{
+  pp_printf (pp, "kind: %qs", region2_kind_to_str (get_kind ()));
+
+  pp_string (pp, ", parent: ");
+  m_parent_reg.print (pp);
+
+  pp_printf (pp, ", sval: ");
+  m_sval_id.print (pp);
+
+  if (m_type)
+    {
+      pp_printf (pp, ", type: ");
+      print_quoted_type (pp, m_type);
+    }
+}
+#endif
+
+/* Determine if a pointer to this region2 must be non-NULL.
+
+   Generally, pointers to region2s must be non-NULL, but pointers
+   to symbolic_region2s might, in fact, be NULL.
+
+   This allows us to simulate functions like malloc and calloc with:
+   - only one "outcome" from each statement,
+   - the idea that the pointer is on the heap if non-NULL
+   - the possibility that the pointer could be NULL
+   - the idea that successive values returned from malloc are non-equal
+   - to be able to zero-fill for calloc.  */
+#if 0
+bool
+region2::non_null_p (const region_model2 &model) const
+{
+  /* Look through views to get at the underlying region2.  */
+  if (is_view_p ())
+    return model.get_region2 (m_parent_reg)->non_null_p (model);
+
+  /* Are we within a symbolic_region2?  If so, it could be NULL.  */
+  if (const symbolic_region2 *sym_reg = dyn_cast_symbolic_region2 ())
+    {
+      if (sym_reg->m_possibly_null)
+	return false;
+    }
+
+  return true;
+}
+#endif
+
+/* class primitive_region2 : public region2.  */
+
+/* Implementation of region2::clone vfunc for primitive_region2.  */
+#if 0
+region2 *
+primitive_region2::clone () const
+{
+  return new primitive_region2 (*this);
+}
+#endif
+
+/* class map_region2 : public region2.  */
+#if 0
+/* map_region2's copy ctor.  */
+
+map_region2::map_region2 (const map_region2 &other)
+: region2 (other),
+  m_map (other.m_map)
+{
+}
+#endif
+
+/* Compare the fields of this map_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+map_region2::compare_fields (const map_region2 &other) const
+{
+  if (m_map.elements () != other.m_map.elements ())
+    return false;
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      region2_id e = (*iter).second;
+      region2_id *other_slot = const_cast <map_t &> (other.m_map).get (key);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+  return true;
+}
+#endif
+
+/* Implementation of region2::print_fields vfunc for map_region2.  */
+#if 0
+void
+map_region2::print_fields (const region_model2 &model,
+			  region2_id this_reg,
+			  pretty_printer *pp) const
+{
+  region2::print_fields (model, this_reg, pp);
+  pp_string (pp, ", map: {");
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      tree expr = (*iter).first;
+      region2_id child_reg = (*iter).second;
+      dump_quoted_tree (pp, expr);
+      pp_string (pp, ": ");
+      child_reg.print (pp);
+    }
+  pp_string (pp, "}");
+}
+#endif
+
+/* Implementation of region2::validate vfunc for map_region2.  */
+#if 0
+void
+map_region2::validate (const region_model2 &model) const
+{
+  region2::validate (model);
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      region2_id child_reg = (*iter).second;
+      child_reg.validate (model);
+    }
+}
+#endif
+
+/* Implementation of region2::dump_dot_to_pp vfunc for map_region2.  */
+#if 0
+void
+map_region2::dump_dot_to_pp (const region_model2 &model,
+			    region2_id this_reg,
+			    pretty_printer *pp) const
+{
+  region2::dump_dot_to_pp (model, this_reg, pp);
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      // TODO: add nodes/edges to label things
+
+      tree expr = (*iter).first;
+      region2_id child_reg = (*iter).second;
+
+      pp_printf (pp, "rid_label_%i [label=\"", child_reg.as_int ());
+      pp_write_text_to_stream (pp);
+      pp_printf (pp, "%qE", expr);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+      pp_string (pp, "\"];");
+      pp_newline (pp);
+
+      pp_printf (pp, "rid_label_%i", child_reg.as_int ());
+      pp_string (pp, " -> ");
+      child_reg.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+}
+#endif
+
+/* Implementation of region2::dump_child_label vfunc for map_region2.  */
+#if 0
+void
+map_region2::dump_child_label (const region_model2 &model,
+			      region2_id this_reg,
+			      region2_id child_reg,
+			      pretty_printer *pp) const
+{
+  region2::dump_child_label (model, this_reg, child_reg, pp);
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (child_reg == (*iter).second)
+	{
+	  tree key = (*iter).first;
+	  dump_quoted_tree (pp, key);
+	  pp_string (pp, ": ");
+	}
+    }
+}
+#endif
+
+/* Look for a child region2 for KEY within this map_region2.
+   If it doesn't already exist, create a child map_region2, using TYPE for
+   its type.
+   Return the region2_id of the child (whether pre-existing, or
+   newly-created).
+   Notify CTXT if we don't know how to handle TYPE.  */
+#if 0
+region2 *
+map_region2::get_or_create (region_model2 *model,
+			   tree key,
+			   tree type,
+			   region_model2_context *ctxt)
+{
+  gcc_assert (key);
+  gcc_assert (valid_key_p (key));
+  region2 **slot = m_map.get (key);
+  if (slot)
+    return *slot;
+  region2 *child_reg = model->add_region_for_type (type, ctxt);
+  m_map.put (key, child_reg);
+  return child_reg;
+}
+#endif
+
+#if 0
+/* Get the region2_id for the child region2 for KEY within this
+   MAP_REGION2, or NULL if there is no such child region2.  */
+
+region2_id *
+map_region2::get (tree key)
+{
+  gcc_assert (key);
+  gcc_assert (valid_key_p (key));
+  region2_id *slot = m_map.get (key);
+  return slot;
+}
+#endif
+
+/* Implementation of region2::add_to_hash vfunc for map_region2.  */
+
+void
+map_region2::add_to_hash (inchash::hash &hstate) const
+{
+  region2::add_to_hash (hstate);
+  // TODO
+}
+
+/* Remove the binding of KEY to its child region2 (but not the
+   child region2 itself).
+   For use when purging unneeded SSA names.  */
+#if 0
+void
+map_region2::unbind (tree key)
+{
+  gcc_assert (key);
+  gcc_assert (valid_key_p (key));
+  m_map.remove (key);
+}
+#endif
+
+/* Look for a child region2 with id CHILD_REG within this map_region2.
+   If one is found, return its tree key, otherwise return NULL_TREE.  */
+#if 0
+tree
+map_region2::get_tree_for_child_region2 (region2_id child_reg) const
+{
+  // TODO: do we want to store an inverse map?
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      region2_id r = (*iter).second;
+      if (r == child_reg)
+	return key;
+    }
+
+  return NULL_TREE;
+}
+
+/* Look for a child region2 CHILD within this map_region2.
+   If one is found, return its tree key, otherwise return NULL_TREE.  */
+
+tree
+map_region2::get_tree_for_child_region2 (region2 *child,
+				       const region_model2 &model) const
+{
+  // TODO: do we want to store an inverse map?
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      region2_id r = (*iter).second;
+      if (model.get_region2 (r) == child)
+	return key;
+    }
+
+  return NULL_TREE;
+}
+#endif
+
+/* Comparator for trees to impose a deterministic ordering on
+   T1 and T2.  */
+
+static int
+tree_cmp (const_tree t1, const_tree t2)
+{
+  gcc_assert (t1);
+  gcc_assert (t2);
+
+  /* Test tree codes first.  */
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return TREE_CODE (t1) - TREE_CODE (t2);
+
+  /* From this point on, we know T1 and T2 have the same tree code.  */
+
+  if (DECL_P (t1))
+    {
+      if (DECL_NAME (t1) && DECL_NAME (t2))
+	return strcmp (IDENTIFIER_POINTER (DECL_NAME (t1)),
+		       IDENTIFIER_POINTER (DECL_NAME (t2)));
+      else
+	{
+	  if (DECL_NAME (t1))
+	    return -1;
+	  else if (DECL_NAME (t2))
+	    return 1;
+	  else
+	    return DECL_UID (t1) - DECL_UID (t2);
+	}
+    }
+
+  switch (TREE_CODE (t1))
+    {
+    case SSA_NAME:
+      {
+	if (SSA_NAME_VAR (t1) && SSA_NAME_VAR (t2))
+	  {
+	    int var_cmp = tree_cmp (SSA_NAME_VAR (t1), SSA_NAME_VAR (t2));
+	    if (var_cmp)
+	      return var_cmp;
+	    return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
+	  }
+	else
+	  {
+	    if (SSA_NAME_VAR (t1))
+	      return -1;
+	    else if (SSA_NAME_VAR (t2))
+	      return 1;
+	    else
+	      return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
+	  }
+      }
+      break;
+
+    case INTEGER_CST:
+      return tree_int_cst_compare (t1, t2);
+
+    case REAL_CST:
+      {
+	const real_value *rv1 = TREE_REAL_CST_PTR (t1);
+	const real_value *rv2 = TREE_REAL_CST_PTR (t2);
+	if (real_compare (UNORDERED_EXPR, rv1, rv2))
+	  {
+	    /* Impose an arbitrary order on NaNs relative to other NaNs
+	       and to non-NaNs.  */
+	    if (int cmp_isnan = real_isnan (rv1) - real_isnan (rv2))
+	      return cmp_isnan;
+	    if (int cmp_issignaling_nan
+		  = real_issignaling_nan (rv1) - real_issignaling_nan (rv2))
+	      return cmp_issignaling_nan;
+	    return real_isneg (rv1) - real_isneg (rv2);
+	  }
+	if (real_compare (LT_EXPR, rv1, rv2))
+	  return -1;
+	if (real_compare (GT_EXPR, rv1, rv2))
+	  return 1;
+	return 0;
+      }
+
+    case STRING_CST:
+      return strcmp (TREE_STRING_POINTER (t1),
+		     TREE_STRING_POINTER (t2));
+
+    default:
+      gcc_unreachable ();
+      break;
+    }
+
+  gcc_unreachable ();
+
+  return 0;
+}
+
+/* qsort comparator for trees to impose a deterministic ordering on
+   P1 and P2.  */
+
+static int
+tree_cmp (const void *p1, const void *p2)
+{
+  const_tree t1 = *(const_tree const *)p1;
+  const_tree t2 = *(const_tree const *)p2;
+
+  return tree_cmp (t1, t2);
+}
+
+/* Attempt to merge MAP_REGION2_A and MAP_REGION2_B into MERGED_MAP_REGION2,
+   which has region2_id MERGED_REG, using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+#if 0
+bool
+map_region2::can_merge_p (const map_region2 *map_region2_a,
+			 const map_region2 *map_region2_b,
+			 map_region2 *merged_map_region2,
+			 region2_id merged_reg,
+			 model_merger *merger)
+{
+  for (map_t::iterator iter = map_region2_a->m_map.begin ();
+       iter != map_region2_a->m_map.end ();
+       ++iter)
+    {
+      tree key_a = (*iter).first;
+      region2_id rid_a = (*iter).second;
+
+      if (const region2_id *slot_b
+	  = const_cast<map_region2 *>(map_region2_b)->m_map.get (key_a))
+	{
+	  region2_id rid_b = *slot_b;
+
+	  region2 *child_region2_a = merger->get_region2_a <region2> (rid_a);
+	  region2 *child_region2_b = merger->get_region2_b <region2> (rid_b);
+
+	  gcc_assert (child_region2_a->get_type ()
+		      == child_region2_b->get_type ());
+
+	  gcc_assert (child_region2_a->get_kind ()
+		      == child_region2_b->get_kind ());
+
+	  region2_id child_merged_reg
+	    = merged_map_region2->get_or_create (merger->m_merged_model,
+						merged_reg,
+						key_a,
+						child_region2_a->get_type (),
+						NULL);
+
+	  region2 *child_merged_region2
+	    = merger->m_merged_model->get_region2 (child_merged_reg);
+
+	  /* Consider values.  */
+	  svalue2_id child_a_sval = child_region2_a->get_value_direct ();
+	  svalue2_id child_b_sval = child_region2_b->get_value_direct ();
+	  svalue2_id child_merged_sval;
+	  if (!merger->can_merge_values_p (child_a_sval, child_b_sval,
+					   &child_merged_sval))
+	    return false;
+	  if (!child_merged_sval.null_p ())
+	    child_merged_region2->set_value (*merger->m_merged_model,
+					    child_merged_reg,
+					    child_merged_sval,
+					    NULL);
+
+	  if (map_region2 *map_region2_a = child_region2_a->dyn_cast_map_region2 ())
+	    {
+	      /* Recurse.  */
+	      if (!can_merge_p (map_region2_a,
+				as_a <map_region2 *> (child_region2_b),
+				as_a <map_region2 *> (child_merged_region2),
+				child_merged_reg,
+				merger))
+		return false;
+	    }
+
+	}
+      else
+	{
+	  /* TODO: region2 is present in A, but absent in B.  */
+	}
+    }
+
+  /* TODO: check for keys in B that aren't in A.  */
+
+  return true;
+}
+#endif
+
+/* For debugging purposes: look for a child region2 for a decl named
+   IDENTIFIER (or an SSA_NAME for such a decl), returning its value,
+   or svalue2_id::null if none are found.  */
+#if 0
+svalue2_id
+map_region2::get_value_by_name (tree identifier,
+			       const region_model2 &model) const
+{
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      tree key = (*iter).first;
+      if (TREE_CODE (key) == SSA_NAME)
+	if (SSA_NAME_VAR (key))
+	  key = SSA_NAME_VAR (key);
+      if (DECL_P (key))
+	if (DECL_NAME (key) == identifier)
+	  {
+	    region2_id rid = (*iter).second;
+	    region2 *region2 = model.get_region2 (rid);
+	    return region2->get_value (const_cast<region_model2 &>(model),
+				      false, NULL);
+	  }
+    }
+  return svalue2_id::null ();
+}
+#endif
+
+/* class struct_or_union_region2 : public map_region2.  */
+
+/* Implementation of map_region2::valid_key_p vfunc for
+   struct_or_union_region2.  */
+
+bool
+struct_or_union_region2::valid_key_p (tree key) const
+{
+  return TREE_CODE (key) == FIELD_DECL;
+}
+
+/* Compare the fields of this struct_or_union_region2 with OTHER, returning
+   true if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+struct_or_union_region2::compare_fields (const struct_or_union_region2 &other)
+  const
+{
+  return map_region2::compare_fields (other);
+}
+#endif
+
+/* class struct_region2 : public struct_or_union_region2.  */
+
+/* Compare the fields of this struct_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+struct_region2::compare_fields (const struct_region2 &other) const
+{
+  return struct_or_union_region2::compare_fields (other);
+}
+#endif
+
+/* class union_region2 : public struct_or_union_region2.  */
+
+/* Compare the fields of this union_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+union_region2::compare_fields (const union_region2 &other) const
+{
+  return struct_or_union_region2::compare_fields (other);
+}
+#endif
+
+/* class frame_region2 : public map_region2.  */
+
+/* Compare the fields of this frame_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+frame_region2::compare_fields (const frame_region2 &other) const
+{
+  if (!map_region2::compare_fields (other))
+    return false;
+  if (m_fun != other.m_fun)
+    return false;
+  if (m_depth != other.m_depth)
+    return false;
+  return true;
+}
+#endif
+
+/* Implementation of map_region2::valid_key_p vfunc for frame_region2.  */
+
+bool
+frame_region2::valid_key_p (tree key) const
+{
+  // TODO: could also check that VAR_DECLs are locals
+  return (TREE_CODE (key) == PARM_DECL
+	  || TREE_CODE (key) == VAR_DECL
+	  || TREE_CODE (key) == SSA_NAME
+	  || TREE_CODE (key) == RESULT_DECL);
+}
+
+/* Implementation of region2::print_fields vfunc for frame_region2.  */
+#if 0
+void
+frame_region2::print_fields (const region_model2 &model,
+			    pretty_printer *pp) const
+{
+  map_region2::print_fields (model, this_reg, pp);
+  pp_printf (pp, ", function: %qs, depth: %i", function_name (m_fun), m_depth);
+}
+#endif
+
+/* Implementation of region2::add_to_hash vfunc for frame_region2.  */
+
+void
+frame_region2::add_to_hash (inchash::hash &hstate) const
+{
+  map_region2::add_to_hash (hstate);
+  hstate.add_ptr (m_fun);
+  hstate.add_int (m_depth);
+}
+
+/* class globals_region2 : public scope_region2.  */
+
+/* Compare the fields of this globals_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+globals_region2::compare_fields (const globals_region2 &other) const
+{
+  return map_region2::compare_fields (other);
+}
+#endif
+
+/* Implementation of map_region2::valid_key_p vfunc for globals_region2.  */
+
+bool
+globals_region2::valid_key_p (tree key) const
+{
+  return TREE_CODE (key) == VAR_DECL;
+}
+
+/* class code_region2 : public map_region2.  */
+
+/* Compare the fields of this code_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+code_region2::compare_fields (const code_region2 &other) const
+{
+  return map_region2::compare_fields (other);
+}
+#endif
+
+/* Implementation of map_region2::valid_key_p vfunc for code_region2.  */
+
+bool
+code_region2::valid_key_p (tree key) const
+{
+  return TREE_CODE (key) == FUNCTION_DECL;
+}
+
+/* class array_region2 : public region2.  */
+
+/* array_region2's copy ctor.  */
+
+array_region2::array_region2 (const array_region2 &other)
+: region2 (other),
+  m_map (other.m_map)
+{
+}
+
+/* Get a child region2 for the element with index INDEX_SVAL.  */
+#if 0
+region2 *
+array_region2::get_element (region_model2 *model,
+			   svalue2_id index_sval,
+			   region_model2_context *ctxt)
+{
+  tree element_type = TREE_TYPE (get_type ());
+  svalue2 *index_sval = model->get_svalue2 (index_sval);
+  if (tree cst_index = index_sval->maybe_get_constant ())
+    {
+      key_t key = key_from_constant (cst_index);
+      region2_id element_reg
+	= get_or_create (model, this_reg, key, element_type, ctxt);
+      return element_reg;
+   }
+
+  return model->get_or_create_view (this_reg, element_type, ctxt);
+}
+#endif
+
+/* Compare the fields of this array_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+array_region2::compare_fields (const array_region2 &other) const
+{
+  if (m_map.elements () != other.m_map.elements ())
+    return false;
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      int key = (*iter).first;
+      region2_id e = (*iter).second;
+      region2_id *other_slot = const_cast <map_t &> (other.m_map).get (key);
+      if (other_slot == NULL)
+	return false;
+      if (e != *other_slot)
+	return false;
+    }
+  return true;
+}
+#endif
+
+/* Implementation of region2::print_fields vfunc for array_region2.  */
+#if 0
+void
+array_region2::print_fields (const region_model2 &model,
+			    pretty_printer *pp) const
+{
+  region2::print_fields (model, this_reg, pp);
+  pp_string (pp, ", array: {");
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (iter != m_map.begin ())
+	pp_string (pp, ", ");
+      int key = (*iter).first;
+      region2_id child_reg = (*iter).second;
+      pp_printf (pp, "[%i]: ", key);
+      child_reg.print (pp);
+    }
+  pp_string (pp, "}");
+}
+#endif
+
+/* Implementation of region2::validate vfunc for array_region2.  */
+#if 0
+void
+array_region2::validate (const region_model2 &model) const
+{
+  region2::validate (model);
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      region2_id child_reg = (*iter).second;
+      child_reg.validate (model);
+    }
+}
+#endif
+
+/* Implementation of region2::dump_dot_to_pp vfunc for array_region2.  */
+#if 0
+void
+array_region2::dump_dot_to_pp (const region_model2 &model,
+			      region2_id this_reg,
+			      pretty_printer *pp) const
+{
+  region2::dump_dot_to_pp (model, this_reg, pp);
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      // TODO: add nodes/edges to label things
+
+      int key = (*iter).first;
+      region2_id child_reg = (*iter).second;
+
+      pp_printf (pp, "rid_label_%i [label=\"", child_reg.as_int ());
+      pp_write_text_to_stream (pp);
+      pp_printf (pp, "%qi", key);
+      pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+      pp_string (pp, "\"];");
+      pp_newline (pp);
+
+      pp_printf (pp, "rid_label_%i", child_reg.as_int ());
+      pp_string (pp, " -> ");
+      child_reg.dump_node_name_to_pp (pp);
+      pp_string (pp, ";");
+      pp_newline (pp);
+    }
+}
+
+/* Implementation of region2::dump_child_label vfunc for array_region2.  */
+
+void
+array_region2::dump_child_label (const region_model2 &model,
+			      region2_id this_reg,
+			      region2_id child_reg,
+			      pretty_printer *pp) const
+{
+  region2::dump_child_label (model, this_reg, child_reg, pp);
+
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      if (child_reg == (*iter).second)
+	{
+	  int key = (*iter).first;
+	  pp_printf (pp, "[%i]: ", key);
+	}
+    }
+}
+
+/* Look for a child region2 for KEY within this array_region2.
+   If it doesn't already exist, create a child array_region2, using TYPE for
+   its type.
+   Return the region2_id of the child (whether pre-existing, or
+   newly-created).
+   Notify CTXT if we don't know how to handle TYPE.  */
+
+region2_id
+array_region2::get_or_create (region_model2 *model,
+			     region2_id this_reg,
+			     key_t key,
+			     tree type,
+			     region_model2_context *ctxt)
+{
+  region2_id *slot = m_map.get (key);
+  if (slot)
+    return *slot;
+  region2_id child_reg = model->add_region2_for_type (this_reg, type, ctxt);
+  m_map.put (key, child_reg);
+  return child_reg;
+}
+#endif
+
+/* Get the region2 * for the child region2 for KEY within this
+   ARRAY_REGION2, or NULL if there is no such child region2.  */
+#if 0
+region2 **
+array_region2::get (key_t key)
+{
+  region2_id *slot = m_map.get (key);
+  return slot;
+}
+#endif
+
+/* Implementation of region2::add_to_hash vfunc for array_region2.  */
+
+void
+array_region2::add_to_hash (inchash::hash &hstate) const
+{
+  region2::add_to_hash (hstate);
+  // TODO
+}
+
+/* Look for a child region2 with id CHILD_REG within this array_region2.
+   If one is found, write its key to *OUT and return true,
+   otherwise return false.  */
+#if 0
+bool
+array_region2::get_key_for_child_region2 (region2_id child_reg, key_t *out) const
+{
+  // TODO: do we want to store an inverse map?
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    {
+      key_t key = (*iter).first;
+      region2_id r = (*iter).second;
+      if (r == child_reg)
+	{
+	  *out = key;
+	  return true;
+	}
+    }
+
+  return false;
+}
+
+/* qsort comparator for array_region2's keys.  */
+
+int
+array_region2::key_cmp (const void *p1, const void *p2)
+{
+  key_t i1 = *(const key_t *)p1;
+  key_t i2 = *(const key_t *)p2;
+
+  if (i1 > i2)
+    return 1;
+  else if (i1 < i2)
+    return -1;
+  else
+    return 0;
+}
+#endif
+
+/* Convert constant CST into an array_region2::key_t.  */
+
+array_region2::key_t
+array_region2::key_from_constant (tree cst)
+{
+  gcc_assert (CONSTANT_CLASS_P (cst));
+  wide_int w = wi::to_wide (cst);
+  key_t result = w.to_shwi ();
+  return result;
+}
+
+/* Convert array_region2::key_t KEY into a tree constant.  */
+
+tree
+array_region2::constant_from_key (key_t key)
+{
+  tree array_type = get_type ();
+  tree index_type = TYPE_DOMAIN (array_type);
+  return build_int_cst (index_type, key);
+}
+
+/* class function_region2 : public map_region2.  */
+
+/* Compare the fields of this function_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+function_region2::compare_fields (const function_region2 &other) const
+{
+  return map_region2::compare_fields (other);
+}
+#endif
+
+/* Implementation of map_region2::valid_key_p vfunc for function_region2.  */
+
+bool
+function_region2::valid_key_p (tree key) const
+{
+  return TREE_CODE (key) == LABEL_DECL;
+}
+
+/* class stack_region2 : public region2.  */
+
+/* stack_region2's copy ctor.  */
+#if 0
+stack_region2::stack_region2 (const stack_region2 &other)
+: region2 (other),
+  m_frame_regs (other.m_frame_regs.length ())
+{
+  int i;
+  region2_id *frame_reg;
+  FOR_EACH_VEC_ELT (other.m_frame_regs, i, frame_reg)
+    m_frame_regs.quick_push (*frame_reg);
+}
+#endif
+
+/* Compare the fields of this stack_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+stack_region2::compare_fields (const stack_region2 &other) const
+{
+  if (m_frame_regs.length () != other.m_frame_regs.length ())
+    return false;
+
+  int i;
+  region2_id *frame_reg;
+  FOR_EACH_VEC_ELT (m_frame_regs, i, frame_reg)
+    if (m_frame_regs[i] != other.m_frame_regs[i])
+      return false;
+
+  return true;
+}
+#endif
+
+/* Implementation of region2::print_fields vfunc for stack_region2.  */
+#if 0
+void
+stack_region2::print_fields (const region_model2 &model,
+			    pretty_printer *pp) const
+{
+  region2::print_fields (model, this_reg, pp);
+  // TODO
+}
+#endif
+
+/* Implementation of region2::dump_child_label vfunc for stack_region2.  */
+#if 0
+void
+stack_region2::dump_child_label (const region_model2 &model,
+				region2_id child_reg,
+				pretty_printer *pp) const
+{
+  function *fun = model.get_region2<frame_region2> (child_reg)->get_function ();
+  pp_printf (pp, "frame for %qs: ", function_name (fun));
+}
+#endif
+
+/* Implementation of region2::validate vfunc for stack_region2.  */
+#if 0
+void
+stack_region2::validate (const region_model2 &model) const
+{
+  region2::validate (model);
+  int i;
+  region2_id *frame_reg;
+  FOR_EACH_VEC_ELT (m_frame_regs, i, frame_reg)
+    m_frame_regs[i].validate (model);
+}
+
+/* Push FRAME_REG (for a frame_region2) onto this stack.  */
+
+void
+stack_region2::push_frame (region2_id frame_reg)
+{
+  m_frame_regs.safe_push (frame_reg);
+}
+
+/* Get the region2_id of the top-most frame in this stack, if any.  */
+
+region2_id
+stack_region2::get_current_frame_id () const
+{
+  if (m_frame_regs.length () > 0)
+    return m_frame_regs[m_frame_regs.length () - 1];
+  else
+    return region2_id::null ();
+}
+
+/* Pop the topmost frame_region2 from this stack.
+
+   If RESULT_DST_REG is non-null, copy any return value from the frame
+   into RESULT_DST_REG's region2.
+
+   Purge the frame region2 and all its descendent region2s.
+   Convert any pointers that point into such region2s into
+   POISON_KIND_POPPED_STACK svalue2s.
+
+   If PURGE, then purge all unused svalue2s, with the exception of any
+   returned values.
+
+   Accumulate stats on purged entities into STATS.  */
+
+void
+stack_region2::pop_frame (region_model2 *model, region2_id result_dst_reg,
+			 bool purge, purge_stats *stats,
+			 region_model2_context *ctxt)
+{
+  gcc_assert (m_frame_regs.length () > 0);
+
+  region2_id frame_reg = get_current_frame_id ();
+  frame_region2 *frame = model->get_region2<frame_region2> (frame_reg);
+
+  /* Evaluate the result, within the callee frame.  */
+  svalue2_id_set returned_svals;
+  tree fndecl = frame->get_function ()->decl;
+  tree result = DECL_RESULT (fndecl);
+  if (result && TREE_TYPE (result) != void_type_node)
+    {
+      if (!result_dst_reg.null_p ())
+	{
+	  /* Copy the result to RESULT_DST_REG.  */
+	  model->copy_region2 (result_dst_reg, model->get_lvalue (result, ctxt),
+			      ctxt);
+	}
+      if (purge)
+	{
+	  /* Populate returned_svals, to avoid purging them.  */
+	  region2_id return_reg = model->get_lvalue (result, NULL);
+	  region2_id_set returned_regs (model);
+	  model->get_descendents (return_reg, &returned_regs,
+				  region2_id::null ());
+	  for (unsigned i = 0; i < model->get_num_region2s (); i++)
+	    {
+	      region2_id rid = region2_id::from_int (i);
+	      if (returned_regs.region2_p (rid))
+		{
+		  svalue2 *sval = model->get_region2 (rid)->get_value_direct ();
+		  returned_svals.add_svalue2 (sid);
+		}
+	    }
+	}
+    }
+
+  /* Pop the frame RID.  */
+  m_frame_regs.pop ();
+
+  model->delete_region2_and_descendents (frame_reg,
+					POISON_KIND_POPPED_STACK,
+					stats,
+					ctxt ? ctxt->get_logger () : NULL);
+
+  /* Delete unused svalue2s, but don't delete the return value(s).  */
+  if (purge)
+    model->purge_unused_svalue2s (stats, ctxt, &returned_svals);
+
+  model->validate ();
+}
+#endif
+
+/* Implementation of region2::add_to_hash vfunc for stack_region2.  */
+#if 0
+void
+stack_region2::add_to_hash (inchash::hash &hstate) const
+{
+  region2::add_to_hash (hstate);
+
+  int i;
+  region2_id *frame_reg;
+  FOR_EACH_VEC_ELT (m_frame_regs, i, frame_reg)
+    inchash::add (*frame_reg, hstate);
+}
+#endif
+
+/* Attempt to merge STACK_REGION2_A and STACK_REGION2_B using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+#if 0
+bool
+stack_region2::can_merge_p (const stack_region2 *stack_region2_a,
+			   const stack_region2 *stack_region2_b,
+			   model_merger *merger)
+{
+  if (stack_region2_a->get_num_frames ()
+      != stack_region2_b->get_num_frames ())
+    return false;
+
+  region_model2 *merged_model = merger->m_merged_model;
+
+  region2_id rid_merged_stack
+    = merged_model->get_root_region2 ()->ensure_stack_region2 (merged_model);
+
+  stack_region2 *merged_stack
+    = merged_model->get_region2 <stack_region2> (rid_merged_stack);
+
+  /* First, create all frames in the merged model, without populating them.
+     The merging code assumes that all frames in the merged model already exist,
+     so we have to do this first to handle the case in which a local in an
+     older frame points at a local in a more recent frame.  */
+    for (unsigned i = 0; i < stack_region2_a->get_num_frames (); i++)
+      {
+	region2_id rid_a = stack_region2_a->get_frame_reg (i);
+	frame_region2 *frame_a = merger->get_region2_a <frame_region2> (rid_a);
+
+	region2_id rid_b = stack_region2_b->get_frame_reg (i);
+	frame_region2 *frame_b = merger->get_region2_b <frame_region2> (rid_b);
+
+	if (frame_a->get_function () != frame_b->get_function ())
+	  return false;
+
+	frame_region2 *merged_frame = new frame_region2 (rid_merged_stack,
+						       frame_a->get_function (),
+						       frame_a->get_depth ());
+	region2_id rid_merged_frame = merged_model->add_region2 (merged_frame);
+	merged_stack->push_frame (rid_merged_frame);
+      }
+
+    /* Now populate the frames we created.  */
+    for (unsigned i = 0; i < stack_region2_a->get_num_frames (); i++)
+      {
+	region2_id rid_a = stack_region2_a->get_frame_reg (i);
+	frame_region2 *frame_a = merger->get_region2_a <frame_region2> (rid_a);
+
+	region2_id rid_b = stack_region2_b->get_frame_reg (i);
+	frame_region2 *frame_b = merger->get_region2_b <frame_region2> (rid_b);
+
+	region2_id rid_merged_frame = merged_stack->get_frame_reg (i);
+	frame_region2 *merged_frame
+	  = merged_model->get_region2 <frame_region2> (rid_merged_frame);
+	if (!map_region2::can_merge_p (frame_a, frame_b,
+				      merged_frame, rid_merged_frame,
+				      merger))
+	  return false;
+      }
+
+  return true;
+}
+#endif
+
+/* For debugging purposes: look for a grandchild region2 within one of
+   the child frame region2s, where the grandchild is for a decl named
+   IDENTIFIER (or an SSA_NAME for such a decl):
+
+     stack_region2
+     `-frame_region2
+       `-region2 for decl named IDENTIFIER
+
+   returning its value, or svalue2_id::null if none are found.  */
+#if 0
+svalue2_id
+stack_region2::get_value_by_name (tree identifier,
+				 const region_model2 &model) const
+{
+  int i;
+  region2_id *frame_reg;
+  FOR_EACH_VEC_ELT (m_frame_regs, i, frame_reg)
+    {
+      frame_region2 *frame = model.get_region2<frame_region2> (*frame_reg);
+      svalue2 *sval = frame->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+
+  return svalue2_id::null ();
+}
+#endif
+
+/* class heap_region2 : public region2.  */
+
+/* Compare the fields of this heap_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+#if 0
+bool
+heap_region2::compare_fields (const heap_region2 &) const
+{
+  /* Empty.  */
+  return true;
+}
+#endif
+
+/* class root_region2 : public region2.  */
+
+/* root_region2's default ctor.  */
+
+root_region2::root_region2 ()
+: region2 (NULL, NULL_TREE)
+{
+}
+
+#if 0
+/* Compare the fields of this root_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+
+bool
+root_region2::compare_fields (const root_region2 &other) const
+{
+  if (m_stack_reg != other.m_stack_reg)
+    return false;
+  if (m_globals_reg != other.m_globals_reg)
+    return false;
+  if (m_code_reg != other.m_code_reg)
+    return false;
+  if (m_heap_reg != other.m_heap_reg)
+    return false;
+  return true;
+}
+#endif
+
+/* Implementation of region2::print_fields vfunc for root_region2.  */
+#if 0
+void
+root_region2::print_fields (const region_model2 &model,
+			   pretty_printer *pp) const
+{
+  region2::print_fields (model, this_reg, pp);
+  // TODO
+}
+#endif
+
+/* Implementation of region2::validate vfunc for root_region2.  */
+#if 0
+void
+root_region2::validate (const region_model2 &model) const
+{
+  region2::validate (model);
+  m_stack_reg.validate (model);
+  m_globals_reg.validate (model);
+  m_code_reg.validate (model);
+  m_heap_reg.validate (model);
+}
+#endif
+
+/* Implementation of region2::dump_child_label vfunc for root_region2.  */
+#if 0
+void
+root_region2::dump_child_label (const region_model2 &model ATTRIBUTE_UNUSED,
+			       region2_id child_reg,
+			       pretty_printer *pp) const
+{
+  if (child_reg == m_stack_reg)
+    pp_printf (pp, "stack: ");
+  else if (child_reg == m_globals_reg)
+    pp_printf (pp, "globals: ");
+  else if (child_reg == m_code_reg)
+    pp_printf (pp, "code: ");
+  else if (child_reg == m_heap_reg)
+    pp_printf (pp, "heap: ");
+}
+#endif
+
+/* Create a new frame_region2 for a call to FUN and push it onto
+   the stack.
+
+   If ARG_SVALS is non-NULL, use it to populate the parameters
+   in the new frame.
+   Otherwise, populate them with unknown values.
+
+   Return the region2_id of the new frame.  */
+#if 0
+region2_id
+root_region2::push_frame (region_model2 *model, function *fun,
+			 vec<svalue2_id> *arg_svals,
+			 region_model2_context *ctxt)
+{
+  gcc_assert (fun);
+  /* arg_svals can be NULL.  */
+
+  ensure_stack_region2 (model);
+  stack_region2 *stack = model->get_region2 <stack_region2> (m_stack_reg);
+
+  frame_region2 *region2 = new frame_region2 (m_stack_reg, fun,
+					   stack->get_num_frames ());
+  region2_id frame_reg = model->add_region2 (region2);
+
+  // TODO: unify these cases by building a vec of unknown?
+
+  if (arg_svals)
+    {
+      /* Arguments supplied from a caller frame.  */
+
+      tree fndecl = fun->decl;
+      unsigned idx = 0;
+      for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+	   iter_parm = DECL_CHAIN (iter_parm), ++idx)
+	{
+	  /* If there's a mismatching declaration, the call stmt might
+	     not have enough args.  Handle this case by leaving the
+	     rest of the params as uninitialized.  */
+	  if (idx >= arg_svals->length ())
+	    break;
+	  svalue2_id arg_sval = (*arg_svals)[idx];
+	  region2_id parm_reg
+	    = region2->get_or_create (model, frame_reg, iter_parm,
+				     TREE_TYPE (iter_parm), ctxt);
+	  model->set_value (parm_reg, arg_sval, ctxt);
+
+	  /* Also do it for default SSA name (sharing the same unknown
+	     value).  */
+	  tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+	  if (parm_default_ssa)
+	    {
+	      region2_id defssa_reg
+		= region2->get_or_create (model, frame_reg, parm_default_ssa,
+					 TREE_TYPE (iter_parm), ctxt);
+	      model->set_value (defssa_reg, arg_sval, ctxt);
+	    }
+	}
+    }
+  else
+    {
+      /* No known arguments (a top-level call within the analysis).  */
+
+      /* Params have a defined, unknown value; they should not inherit
+	 from the poisoned uninit value.  */
+      tree fndecl = fun->decl;
+      for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
+	   iter_parm = DECL_CHAIN (iter_parm))
+	{
+	  region2_id parm_reg
+	    = region2->get_or_create (model, frame_reg, iter_parm,
+				     TREE_TYPE (iter_parm), ctxt);
+	  svalue2_id parm_sval
+	    = model->set_to_new_unknown_value (parm_reg, TREE_TYPE (iter_parm),
+					       ctxt);
+
+	  /* Also do it for default SSA name (sharing the same unknown
+	     value).  */
+	  tree parm_default_ssa = ssa_default_def (fun, iter_parm);
+	  if (parm_default_ssa)
+	    {
+	      region2_id defssa_reg
+		= region2->get_or_create (model, frame_reg, parm_default_ssa,
+					 TREE_TYPE (iter_parm), ctxt);
+	      model->get_region2 (defssa_reg)->set_value (*model, defssa_reg,
+							 parm_sval, ctxt);
+	    }
+	}
+    }
+
+  stack->push_frame (frame_reg);
+
+  return frame_reg;
+}
+
+/* Get the region2_id of the top-most frame in this root_region2's stack,
+   if any.  */
+
+region2_id
+root_region2::get_current_frame_id (const region_model2 &model) const
+{
+  stack_region2 *stack = model.get_region2 <stack_region2> (m_stack_reg);
+  if (stack)
+    return stack->get_current_frame_id ();
+  else
+    return region2_id::null ();
+}
+
+/* Pop the topmost frame_region2 from this root_region2's stack;
+   see the comment for stack_region2::pop_frame.  */
+
+void
+root_region2::pop_frame (region_model2 *model, region2_id result_dst_reg,
+			bool purge, purge_stats *out,
+			region_model2_context *ctxt)
+{
+  stack_region2 *stack = model->get_region2 <stack_region2> (m_stack_reg);
+  stack->pop_frame (model, result_dst_reg, purge, out, ctxt);
+}
+
+/* Return the region2_id of the stack region2, creating it if doesn't
+   already exist.  */
+
+region2_id
+root_region2::ensure_stack_region2 (region_model2 *model)
+{
+  if (m_stack_reg.null_p ())
+    {
+      svalue2_id uninit_sval
+	= model->add_svalue2 (new poisoned_svalue2 (POISON_KIND_UNINIT,
+						  NULL_TREE));
+      m_stack_reg
+	= model->add_region2 (new stack_region2 (model->get_root_reg (),
+					       uninit_sval));
+    }
+  return m_stack_reg;
+}
+
+/* Return the stack region2 (which could be NULL).  */
+
+stack_region2 *
+root_region2::get_stack_region2 (const region_model2 *model) const
+{
+  return model->get_region2 <stack_region2> (m_stack_reg);
+}
+
+/* Return the region2_id of the globals region2, creating it if doesn't
+   already exist.  */
+
+region2_id
+root_region2::ensure_globals_region2 (region_model2 *model)
+{
+  if (m_globals_reg.null_p ())
+    m_globals_reg
+      = model->add_region2 (new globals_region2 (model->get_root_reg ()));
+  return m_globals_reg;
+}
+
+/* Return the code region2 (which could be NULL).  */
+
+code_region2 *
+root_region2::get_code_region2 (const region_model2 *model) const
+{
+  return model->get_region2 <code_region2> (m_code_reg);
+}
+
+/* Return the region2_id of the code region2, creating it if doesn't
+   already exist.  */
+
+region2_id
+root_region2::ensure_code_region2 (region_model2 *model)
+{
+  if (m_code_reg.null_p ())
+    m_code_reg
+      = model->add_region2 (new code_region2 (model->get_root_reg ()));
+  return m_code_reg;
+}
+
+/* Return the globals region2 (which could be NULL).  */
+
+globals_region2 *
+root_region2::get_globals_region2 (const region_model2 *model) const
+{
+  return model->get_region2 <globals_region2> (m_globals_reg);
+}
+
+/* Return the region2_id of the heap region2, creating it if doesn't
+   already exist.  */
+
+region2_id
+root_region2::ensure_heap_region2 (region_model2 *model)
+{
+  if (m_heap_reg.null_p ())
+    {
+      svalue2_id uninit_sval
+	= model->add_svalue2 (new poisoned_svalue2 (POISON_KIND_UNINIT,
+						  NULL_TREE));
+      m_heap_reg
+	= model->add_region2 (new heap_region2 (model->get_root_reg (),
+					      uninit_sval));
+    }
+  return m_heap_reg;
+}
+
+/* Return the heap region2 (which could be NULL).  */
+
+heap_region2 *
+root_region2::get_heap_region2 (const region_model2 *model) const
+{
+  return model->get_region2 <heap_region2> (m_heap_reg);
+}
+
+/* Attempt to merge ROOT_REGION2_A and ROOT_REGION2_B into
+   MERGED_ROOT_REGION2 using MERGER.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+root_region2::can_merge_p (const root_region2 *root_region2_a,
+			  const root_region2 *root_region2_b,
+			  root_region2 *merged_root_region2,
+			  model_merger *merger)
+{
+  /* We can only merge if the stacks are sufficiently similar.  */
+  stack_region2 *stack_a = root_region2_a->get_stack_region2 (merger->m_model_a);
+  stack_region2 *stack_b = root_region2_b->get_stack_region2 (merger->m_model_b);
+  if (stack_a && stack_b)
+    {
+      /* If the two models both have a stack, attempt to merge them.  */
+      merged_root_region2->ensure_stack_region2 (merger->m_merged_model);
+      if (!stack_region2::can_merge_p (stack_a, stack_b, merger))
+	return false;
+    }
+  else if (stack_a || stack_b)
+    /* Don't attempt to merge if one model has a stack and the other
+       doesn't.  */
+    return false;
+
+  map_region2 *globals_a = root_region2_a->get_globals_region2 (merger->m_model_a);
+  map_region2 *globals_b = root_region2_b->get_globals_region2 (merger->m_model_b);
+  if (globals_a && globals_b)
+    {
+      /* If both models have globals region2s, attempt to merge them.  */
+      region2_id merged_globals_reg
+	= merged_root_region2->ensure_globals_region2 (merger->m_merged_model);
+      map_region2 *merged_globals
+	= merged_root_region2->get_globals_region2 (merger->m_merged_model);
+      if (!map_region2::can_merge_p (globals_a, globals_b,
+				    merged_globals, merged_globals_reg,
+				    merger))
+	return false;
+    }
+  /* otherwise, merge as "no globals".  */
+
+  map_region2 *code_a = root_region2_a->get_code_region2 (merger->m_model_a);
+  map_region2 *code_b = root_region2_b->get_code_region2 (merger->m_model_b);
+  if (code_a && code_b)
+    {
+      /* If both models have code region2s, attempt to merge them.  */
+      region2_id merged_code_reg
+	= merged_root_region2->ensure_code_region2 (merger->m_merged_model);
+      map_region2 *merged_code
+	= merged_root_region2->get_code_region2 (merger->m_merged_model);
+      if (!map_region2::can_merge_p (code_a, code_b,
+				    merged_code, merged_code_reg,
+				    merger))
+	return false;
+    }
+  /* otherwise, merge as "no code".  */
+
+  heap_region2 *heap_a = root_region2_a->get_heap_region2 (merger->m_model_a);
+  heap_region2 *heap_b = root_region2_b->get_heap_region2 (merger->m_model_b);
+  if (heap_a && heap_b)
+    {
+      /* If both have a heap, create a "merged" heap.
+	 Actually merging the heap contents happens via the region_svalue2
+	 instances, as needed, when seeing pairs of region_svalue2 instances.  */
+      merged_root_region2->ensure_heap_region2 (merger->m_merged_model);
+    }
+  /* otherwise, merge as "no heap".  */
+
+  return true;
+}
+#endif
+
+/* Implementation of region2::add_to_hash vfunc for root_region2.  */
+#if 0
+void
+root_region2::add_to_hash (inchash::hash &hstate) const
+{
+  region2::add_to_hash (hstate);
+  inchash::add (m_stack_reg, hstate);
+  inchash::add (m_globals_reg, hstate);
+  inchash::add (m_code_reg, hstate);
+  inchash::add (m_heap_reg, hstate);
+}
+#endif
+
+/* For debugging purposes: look for a descendant region2 for a local
+   or global decl named IDENTIFIER (or an SSA_NAME for such a decl),
+   returning its value, or svalue2_id::null if none are found.  */
+#if 0
+svalue2_id
+root_region2::get_value_by_name (tree identifier,
+				const region_model2 &model) const
+{
+  if (stack_region2 *stack = get_stack_region2 (&model))
+    {
+      svalue2 *sval = stack->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+  if (map_region2 *globals = get_globals_region2 (&model))
+    {
+      svalue2 *sval = globals->get_value_by_name (identifier, model);
+      if (!sid.null_p ())
+	return sid;
+    }
+  return svalue2_id::null ();
+}
+#endif
+
+/* class symbolic_region2 : public map_region2.  */
+
+#if 0
+/* symbolic_region2's copy ctor.  */
+
+symbolic_region2::symbolic_region2 (const symbolic_region2 &other)
+: region2 (other),
+  m_possibly_null (other.m_possibly_null)
+{
+}
+
+/* Compare the fields of this symbolic_region2 with OTHER, returning true
+   if they are equal.
+   For use by region2::operator==.  */
+
+bool
+symbolic_region2::compare_fields (const symbolic_region2 &other) const
+{
+  return m_possibly_null == other.m_possibly_null;
+}
+
+/* Implementation of region2::print_fields vfunc for symbolic_region2.  */
+
+void
+symbolic_region2::print_fields (const region_model2 &model,
+			       region2_id this_reg,
+			       pretty_printer *pp) const
+{
+  region2::print_fields (model, this_reg, pp);
+  pp_printf (pp, ", possibly_null: %s", m_possibly_null ? "true" : "false");
+}
+#endif
+
+/* class region_model2.  */
+
+/* region_model2's default ctor.  */
+
+#if 0
+region_model2::region_model2 ()
+{
+  m_root_reg = add_region2 (new root_region2 ());
+  m_constraints = new impl_constraint_manager (this);
+  // TODO
+}
+
+/* region_model2's copy ctor.  */
+
+region_model2::region_model2 (const region_model2 &other)
+: m_svalue2s (other.m_svalue2s.length ()),
+  m_region2s (other.m_region2s.length ()),
+  m_root_reg (other.m_root_reg)
+{
+  /* Clone the svalue2s and region2s.  */
+  int i;
+
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (other.m_svalue2s, i, svalue2)
+    m_svalue2s.quick_push (svalue2->clone ());
+
+  region2 *region2;
+  FOR_EACH_VEC_ELT (other.m_region2s, i, region2)
+    m_region2s.quick_push (region2->clone ());
+
+  m_constraints = other.m_constraints->clone (this);
+}
+#endif
+
+/* region_model2's dtor.  */
+
+#if 0
+region_model2::~region_model2 ()
+{
+  delete m_constraints;
+}
+#endif
+
+/* region_model2's assignment operator.  */
+#if 0
+region_model2 &
+region_model2::operator= (const region_model2 &other)
+{
+  unsigned i;
+  svalue2 *svalue2;
+  region2 *region2;
+
+  /* Delete existing content.  */
+  FOR_EACH_VEC_ELT (m_svalue2s, i, svalue2)
+    delete svalue2;
+  m_svalue2s.truncate (0);
+
+  FOR_EACH_VEC_ELT (m_region2s, i, region2)
+    delete region2;
+  m_region2s.truncate (0);
+
+  delete m_constraints;
+
+  /* Clone the svalue2s and region2s.  */
+  m_svalue2s.reserve (other.m_svalue2s.length (), true);
+  FOR_EACH_VEC_ELT (other.m_svalue2s, i, svalue2)
+    m_svalue2s.quick_push (svalue2->clone ());
+
+  m_region2s.reserve (other.m_region2s.length (), true);
+  FOR_EACH_VEC_ELT (other.m_region2s, i, region2)
+    m_region2s.quick_push (region2->clone ());
+
+  m_root_reg = other.m_root_reg;
+
+  m_constraints = other.m_constraints->clone (this);
+
+  return *this;
+}
+
+#endif
+
+/* Equality operator for region_model2.
+
+   Amongst other things this directly compares the svalue2 and region2
+   vectors and so for this to be meaningful both this and OTHER should
+   have been canonicalized.  */
+
+#if 0
+bool
+region_model2::operator== (const region_model2 &other) const
+{
+  if (m_root_reg != other.m_root_reg)
+    return false;
+
+  if (m_svalue2s.length () != other.m_svalue2s.length ())
+    return false;
+
+  if (m_region2s.length () != other.m_region2s.length ())
+    return false;
+
+  if (*m_constraints != *other.m_constraints)
+    return false;
+
+  unsigned i;
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (other.m_svalue2s, i, svalue2)
+    if (!(*m_svalue2s[i] == *other.m_svalue2s[i]))
+      return false;
+
+  region2 *region2;
+  FOR_EACH_VEC_ELT (other.m_region2s, i, region2)
+    if (!(*m_region2s[i] == *other.m_region2s[i]))
+      return false;
+
+  gcc_checking_assert (hash () == other.hash ());
+
+  return true;
+}
+#endif
+
+/* Generate a hash value for this region_model2.  */
+
+#if 0
+hashval_t
+region_model2::hash () const
+{
+  hashval_t result = 0;
+  int i;
+
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (m_svalue2s, i, svalue2)
+    result ^= svalue2->hash ();
+
+  region2 *region2;
+  FOR_EACH_VEC_ELT (m_region2s, i, region2)
+    result ^= region2->hash ();
+
+  result ^= m_constraints->hash ();
+
+  return result;
+}
+
+/* Print an all-on-one-line representation of this region_model2 to PP,
+   which must support %E for trees.  */
+
+void
+region_model2::print (pretty_printer *pp) const
+{
+  int i;
+
+  pp_string (pp, "svalue2s: [");
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (m_svalue2s, i, svalue2)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      print_svalue2 (svalue2_id::from_int (i), pp);
+    }
+
+  pp_string (pp, "], region2s: [");
+
+  region2 *region2;
+  FOR_EACH_VEC_ELT (m_region2s, i, region2)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      region2->print (*this, region2_id::from_int (i), pp);
+    }
+
+  pp_string (pp, "], constraints: ");
+
+  m_constraints->print (pp);
+}
+
+/* Print the svalue2 with id SID to PP.  */
+
+void
+region_model2::print_svalue2 (svalue2 *sval, pretty_printer *pp) const
+{
+  get_svalue2 (sid)->print (*this, sid, pp);
+}
+
+/* Dump a .dot representation of this region_model2 to PP, showing
+   the values and the hierarchy of region2s.  */
+
+void
+region_model2::dump_dot_to_pp (pretty_printer *pp) const
+{
+  graphviz_out gv (pp);
+
+  pp_string (pp, "digraph \"");
+  pp_write_text_to_stream (pp);
+  pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/false);
+  pp_string (pp, "\" {\n");
+
+  gv.indent ();
+
+  pp_string (pp, "overlap=false;\n");
+  pp_string (pp, "compound=true;\n");
+
+  int i;
+
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (m_svalue2s, i, svalue2)
+    svalue2->dump_dot_to_pp (*this, svalue2_id::from_int (i), pp);
+
+  region2 *region2;
+  FOR_EACH_VEC_ELT (m_region2s, i, region2)
+    region2->dump_dot_to_pp (*this, region2_id::from_int (i), pp);
+
+  /* TODO: constraints.  */
+
+  /* Terminate "digraph" */
+  gv.outdent ();
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* Dump a .dot representation of this region_model2 to FP.  */
+
+void
+region_model2::dump_dot_to_file (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp.buffer->stream = fp;
+  dump_dot_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a .dot representation of this region_model2 to PATH.  */
+
+void
+region_model2::dump_dot (const char *path) const
+{
+  FILE *fp = fopen (path, "w");
+  dump_dot_to_file (fp);
+  fclose (fp);
+}
+
+/* Dump a multiline representation of this model to PP, showing the
+   region2 hierarchy, the svalue2s, and any constraints.
+
+   If SUMMARIZE is true, show only the most pertinent information,
+   in a form that attempts to be less verbose.
+   Otherwise, show all information.  */
+
+void
+region_model2::dump_to_pp (pretty_printer *pp, bool summarize) const
+{
+  if (summarize)
+    {
+      auto_vec<path_var> rep_path_vars;
+
+      unsigned i;
+      region2 *reg;
+      FOR_EACH_VEC_ELT (m_region2s, i, reg)
+	{
+	  region2_id rid = region2_id::from_int (i);
+	  path_var pv = get_representative_path_var (rid);
+	  if (pv.m_tree)
+	    rep_path_vars.safe_push (pv);
+	}
+      bool is_first = true;
+
+      /* Work with a copy in case the get_lvalue calls change anything
+	 (they shouldn't).  */
+      region_model2 copy (*this);
+      copy.dump_summary_of_rep_path_vars (pp, &rep_path_vars, &is_first);
+
+      equiv_class *ec;
+      FOR_EACH_VEC_ELT (m_constraints->m_equiv_classes, i, ec)
+	{
+	  for (unsigned j = 0; j < ec->m_vars.length (); j++)
+	    {
+	      svalue2_id lhs_sval = ec->m_vars[j];
+	      tree lhs_tree = get_representative_tree (lhs_sval);
+	      if (lhs_tree == NULL_TREE)
+		continue;
+	      for (unsigned k = j + 1; k < ec->m_vars.length (); k++)
+		{
+		  svalue2_id rhs_sval = ec->m_vars[k];
+		  tree rhs_tree = get_representative_tree (rhs_sval);
+		  if (rhs_tree
+		      && !(CONSTANT_CLASS_P (lhs_tree)
+			   && CONSTANT_CLASS_P (rhs_tree)))
+		    {
+		      dump_separator (pp, &is_first);
+		      dump_tree (pp, lhs_tree);
+		      pp_string (pp, " == ");
+		      dump_tree (pp, rhs_tree);
+		    }
+		}
+	    }
+	}
+
+      constraint *c;
+      FOR_EACH_VEC_ELT (m_constraints->m_constraints, i, c)
+	{
+	  const equiv_class &lhs = c->m_lhs.get_obj (*m_constraints);
+	  const equiv_class &rhs = c->m_rhs.get_obj (*m_constraints);
+	  svalue2_id lhs_sval = lhs.get_representative ();
+	  svalue2_id rhs_sval = rhs.get_representative ();
+	  tree lhs_tree = get_representative_tree (lhs_sval);
+	  tree rhs_tree = get_representative_tree (rhs_sval);
+	  if (lhs_tree && rhs_tree
+	      && !(CONSTANT_CLASS_P (lhs_tree) && CONSTANT_CLASS_P (rhs_tree)))
+	    {
+	      dump_separator (pp, &is_first);
+	      dump_tree (pp, lhs_tree);
+	      pp_printf (pp, " %s ", constraint_op_code (c->m_op));
+	      dump_tree (pp, rhs_tree);
+	   }
+	}
+
+      return;
+    }
+
+  get_region2 (m_root_reg)->dump_to_pp (*this, m_root_reg, pp, "", true);
+
+  pp_string (pp, "svalue2s:");
+  pp_newline (pp);
+  int i;
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (m_svalue2s, i, svalue2)
+    {
+      pp_string (pp, "  ");
+      svalue2 *sval = svalue2_id::from_int (i);
+      print_svalue2 (sid, pp);
+      pp_newline (pp);
+    }
+
+  pp_string (pp, "constraint manager:");
+  pp_newline (pp);
+  m_constraints->dump_to_pp (pp);
+}
+
+/* Dump a multiline representation of this model to FILE.  */
+
+void
+region_model2::dump (FILE *fp, bool summarize) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp, summarize);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this model to stderr.  */
+
+DEBUG_FUNCTION void
+region_model2::dump (bool summarize) const
+{
+  dump (stderr, summarize);
+}
+
+/* Dump RMODEL fully to stderr (i.e. without summarization).  */
+
+DEBUG_FUNCTION void
+region_model2::debug () const
+{
+  dump (false);
+}
+
+/* Dump VEC to PP, in the form "{VEC elements}: LABEL".  */
+
+static void
+dump_vec_of_tree (pretty_printer *pp,
+		  bool *is_first,
+		  const auto_vec<tree> &vec,
+		  const char *label)
+{
+  if (vec.length () == 0)
+    return;
+
+  dump_separator (pp, is_first);
+  pp_printf (pp, "{");
+  unsigned i;
+  tree key;
+  FOR_EACH_VEC_ELT (vec, i, key)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      dump_tree (pp, key);
+    }
+  pp_printf (pp, "}: %s", label);
+}
+
+/* Dump all *REP_PATH_VARS to PP in compact form, updating *IS_FIRST.
+   Subroutine of region_model2::dump_to_pp.  */
+
+void
+region_model2::dump_summary_of_rep_path_vars (pretty_printer *pp,
+					     auto_vec<path_var> *rep_path_vars,
+					     bool *is_first)
+{
+  /* Print pointers, constants, and poisoned values that aren't "uninit";
+     gather keys for unknown and uninit values.  */
+  unsigned i;
+  path_var *pv;
+  auto_vec<tree> unknown_trees;
+  auto_vec<tree> uninit_trees;
+  FOR_EACH_VEC_ELT (*rep_path_vars, i, pv)
+    {
+      if (TREE_CODE (pv->m_tree) == STRING_CST)
+	continue;
+      tentative_region_model2_context ctxt;
+      region2_id child_reg = get_lvalue (*pv, &ctxt);
+      if (ctxt.had_errors_p ())
+	continue;
+      region2 *child_region2 = get_region2 (child_reg);
+      if (!child_region2)
+	continue;
+      svalue2 *sval = child_region2->get_value_direct ();
+      if (sid.null_p ())
+	continue;
+      svalue2 *sval = get_svalue2 (sid);
+      switch (sval->get_kind ())
+	{
+	default:
+	  gcc_unreachable ();
+	case SK_REGION2:
+	  {
+	    region_svalue2 *region2_sval = as_a <region_svalue2 *> (sval);
+	    region2_id pointee_reg = region2_sval->get_pointee ();
+	    gcc_assert (!pointee_reg.null_p ());
+	    tree pointee = get_representative_path_var (pointee_reg).m_tree;
+	    dump_separator (pp, is_first);
+	    dump_tree (pp, pv->m_tree);
+	    pp_string (pp, ": ");
+	    pp_character (pp, '&');
+	    if (pointee)
+	      dump_tree (pp, pointee);
+	    else
+	      pointee_reg.print (pp);
+	  }
+	  break;
+	case SK_CONSTANT:
+	  dump_separator (pp, is_first);
+	  dump_tree (pp, pv->m_tree);
+	  pp_string (pp, ": ");
+	  dump_tree (pp, sval->dyn_cast_constant_svalue2 ()->get_constant ());
+	  break;
+	case SK_UNKNOWN:
+	  unknown_trees.safe_push (pv->m_tree);
+	  break;
+	case SK_POISONED:
+	  {
+	    poisoned_svalue2 *poisoned_sval = as_a <poisoned_svalue2 *> (sval);
+	    enum poison_kind pkind = poisoned_sval->get_poison_kind ();
+	    if (pkind == POISON_KIND_UNINIT)
+	      uninit_trees.safe_push (pv->m_tree);
+	    else
+	      {
+		dump_separator (pp, is_first);
+		dump_tree (pp, pv->m_tree);
+		pp_printf (pp, ": %s", poison_kind_to_str (pkind));
+	      }
+	  }
+	  break;
+	case SK_SETJMP:
+	  dump_separator (pp, is_first);
+	  pp_printf (pp, "setjmp: EN: %i",
+		     sval->dyn_cast_setjmp_svalue2 ()->get_enode_index ());
+	  break;
+	}
+    }
+
+  /* Print unknown and uninitialized values in consolidated form.  */
+  dump_vec_of_tree (pp, is_first, unknown_trees, "unknown");
+  dump_vec_of_tree (pp, is_first, uninit_trees, "uninit");
+}
+#endif
+
+#if 0
+
+/* Assert that this object is valid.  */
+
+void
+region_model2::validate () const
+{
+  /* Skip this in a release build.  */
+#if !CHECKING_P
+  return;
+#endif
+
+  m_constraints->validate ();
+
+  unsigned i;
+  region2 *r;
+  FOR_EACH_VEC_ELT (m_region2s, i, r)
+    r->validate (*this);
+
+  // TODO: anything else?
+
+  /* Verify that the stack region2 (if any) has an "uninitialized" value.  */
+  region2 *stack_region2 = get_root_region2 ()->get_stack_region2 (this);
+  if (stack_region2)
+    {
+      svalue2_id stack_value_sval = stack_region2->get_value_direct ();
+      svalue2 *stack_value = get_svalue2 (stack_value_sval);
+      gcc_assert (stack_value->get_kind () == SK_POISONED);
+      poisoned_svalue2 *subclass = stack_value->dyn_cast_poisoned_svalue2 ();
+      gcc_assert (subclass);
+      gcc_assert (subclass->get_poison_kind () == POISON_KIND_UNINIT);
+    }
+}
+
+#endif
+
+/* A subclass of pending_diagnostic for complaining about uses of
+   poisoned values.  */
+
+class poisoned_value_diagnostic
+: public pending_diagnostic_subclass<poisoned_value_diagnostic>
+{
+public:
+  poisoned_value_diagnostic (tree expr, enum poison_kind pkind)
+  : m_expr (expr), m_pkind (pkind)
+  {}
+
+  const char *get_kind () const FINAL OVERRIDE { return "poisoned_value_diagnostic"; }
+
+  bool operator== (const poisoned_value_diagnostic &other) const
+  {
+    return m_expr == other.m_expr;
+  }
+
+  bool emit (rich_location *rich_loc) FINAL OVERRIDE
+  {
+    switch (m_pkind)
+      {
+      default:
+	gcc_unreachable ();
+      case POISON_KIND_UNINIT:
+	{
+	  diagnostic_metadata m;
+	  m.add_cwe (457); /* "CWE-457: Use of Uninitialized Variable".  */
+	  return warning_meta (rich_loc, m,
+			       OPT_Wanalyzer_use_of_uninitialized_value,
+			       "use of uninitialized value %qE",
+			       m_expr);
+	}
+	break;
+      case POISON_KIND_FREED:
+	{
+	  diagnostic_metadata m;
+	  m.add_cwe (416); /* "CWE-416: Use After Free".  */
+	  return warning_meta (rich_loc, m,
+			       OPT_Wanalyzer_use_after_free,
+			       "use after %<free%> of %qE",
+			       m_expr);
+	}
+	break;
+      case POISON_KIND_POPPED_STACK:
+	{
+	  /* TODO: which CWE?  */
+	  return warning_at (rich_loc,
+			     OPT_Wanalyzer_use_of_pointer_in_stale_stack_frame,
+			     "use of pointer %qE within stale stack frame",
+			     m_expr);
+	}
+	break;
+      }
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
+  {
+    switch (m_pkind)
+      {
+      default:
+	gcc_unreachable ();
+      case POISON_KIND_UNINIT:
+	return ev.formatted_print ("use of uninitialized value %qE here",
+				   m_expr);
+      case POISON_KIND_FREED:
+	return ev.formatted_print ("use after %<free%> of %qE here",
+				   m_expr);
+      case POISON_KIND_POPPED_STACK:
+	return ev.formatted_print
+	  ("use of pointer %qE within stale stack frame here",
+	   m_expr);
+      }
+  }
+
+private:
+  tree m_expr;
+  enum poison_kind m_pkind;
+};
+
+/* Determine if EXPR is poisoned, and if so, queue a diagnostic to CTXT.  */
+
+#if 0
+
+void
+region_model2::check_for_poison (tree expr, region_model2_context *ctxt)
+{
+  if (!ctxt)
+    return;
+
+  // TODO: this is disabled for now (too many false positives)
+  return;
+
+  svalue2_id expr_sval = get_rvalue (expr, ctxt);
+  gcc_assert (!expr_sval.null_p ());
+  svalue2 *expr_svalue2 = get_svalue2 (expr_sval);
+  gcc_assert (expr_svalue2);
+  if (const poisoned_svalue2 *poisoned_sval
+	= expr_svalue2->dyn_cast_poisoned_svalue2 ())
+    {
+      enum poison_kind pkind = poisoned_sval->get_poison_kind ();
+      ctxt->warn (new poisoned_value_diagnostic (expr, pkind));
+    }
+}
+
+#endif
+
+/* Update this model for the ASSIGN stmt, using CTXT to report any
+   diagnostics.  */
+#if 0
+void
+region_model2::on_assignment (const gassign *assign, region_model2_context *ctxt)
+{
+  tree lhs = gimple_assign_lhs (assign);
+  tree rhs1 = gimple_assign_rhs1 (assign);
+
+  region2 *lhs_reg = get_lvalue (lhs, ctxt);
+
+  /* Check for uses of poisoned values.  */
+  switch (get_gimple_rhs_class (gimple_expr_code (assign)))
+    {
+    case GIMPLE_INVALID_RHS:
+      gcc_unreachable ();
+      break;
+    case GIMPLE_TERNARY_RHS:
+      check_for_poison (gimple_assign_rhs3 (assign), ctxt);
+      /* Fallthru */
+    case GIMPLE_BINARY_RHS:
+      check_for_poison (gimple_assign_rhs2 (assign), ctxt);
+      /* Fallthru */
+    case GIMPLE_UNARY_RHS:
+    case GIMPLE_SINGLE_RHS:
+      check_for_poison (gimple_assign_rhs1 (assign), ctxt);
+    }
+
+  if (!lhs_reg)
+    return;
+  // TODO: issue a warning for this case
+
+  enum tree_code op = gimple_assign_rhs_code (assign);
+  switch (op)
+    {
+    default:
+      {
+	if (0)
+	  sorry_at (assign->location, "unhandled assignment op: %qs",
+		    get_tree_code_name (op));
+	set_to_new_unknown_value (lhs_reg, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case BIT_FIELD_REF:
+      {
+	// TODO
+      }
+      break;
+
+    case CONSTRUCTOR:
+      {
+	/* e.g. "x ={v} {CLOBBER};"  */
+	// TODO
+      }
+      break;
+
+    case POINTER_PLUS_EXPR:
+      {
+	/* e.g. "_1 = a_10(D) + 12;" */
+	tree ptr = rhs1;
+	tree offset = gimple_assign_rhs2 (assign);
+
+	svalue2 *ptr_sval = get_rvalue (ptr, ctxt);
+	svalue2 *offset_sval = get_rvalue (offset, ctxt);
+	region2 *element_reg
+	  = get_or_create_pointer_plus_expr (TREE_TYPE (TREE_TYPE (ptr)),
+					     ptr_sval, offset_sval,
+					     ctxt);
+	svalue2 *element_ptr_sval
+	  = get_or_create_ptr_svalue2 (TREE_TYPE (ptr), element_reg);
+	set_value (lhs_reg, element_ptr_sval, ctxt);
+      }
+      break;
+
+    case POINTER_DIFF_EXPR:
+      {
+	/* e.g. "_1 = p_2(D) - q_3(D);".  */
+
+	/* TODO.  */
+
+	set_to_new_unknown_value (lhs_reg, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case ADDR_EXPR:
+      {
+	/* LHS = &RHS;  */
+	svalue2 *ptr_sval = get_rvalue (rhs1, ctxt);
+	set_value (lhs_reg, ptr_sval, ctxt);
+      }
+      break;
+
+    case MEM_REF:
+      {
+	region2 *rhs_reg = get_lvalue (rhs1, ctxt);
+	svalue2 *rhs_sval
+	  = get_region2 (rhs_reg)->get_value (*this, true, ctxt);
+	set_value (lhs_reg, rhs_sval, ctxt);
+      }
+      break;
+
+    case REAL_CST:
+    case INTEGER_CST:
+    case ARRAY_REF:
+      {
+	/* LHS = RHS;  */
+	svalue2 *cst_sval = get_rvalue (rhs1, ctxt);
+	set_value (lhs_reg, cst_sval, ctxt);
+      }
+      break;
+
+    case FIX_TRUNC_EXPR:
+    case FLOAT_EXPR:
+    case NOP_EXPR:
+      // cast: TODO
+      // fall though for now
+    case SSA_NAME:
+    case VAR_DECL:
+    case PARM_DECL:
+      {
+	/* LHS = VAR;  */
+	region2 *rhs_reg = get_lvalue (rhs1, ctxt);
+	copy_region2 (lhs_reg, rhs_reg, ctxt);
+      }
+      break;
+
+    case EQ_EXPR:
+    case GE_EXPR:
+    case LE_EXPR:
+    case NE_EXPR:
+    case GT_EXPR:
+    case LT_EXPR:
+      {
+	tree rhs2 = gimple_assign_rhs2 (assign);
+
+	// TODO: constraints between svalue2s
+	svalue2 *rhs1_sval = get_rvalue (rhs1, ctxt);
+	svalue2 *rhs2_sval = get_rvalue (rhs2, ctxt);
+
+	tristate t = eval_condition (rhs1_sval, op, rhs2_sval);
+	if (t.is_known ())
+	  set_value (lhs_reg,
+		     get_rvalue (t.is_true ()
+				 ? boolean_true_node
+				 : boolean_false_node,
+				 ctxt),
+		     ctxt);
+	else
+	  set_to_new_unknown_value (lhs_reg, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case NEGATE_EXPR:
+    case BIT_NOT_EXPR:
+      {
+	// TODO: unary ops
+
+	// TODO: constant?
+
+	set_to_new_unknown_value (lhs_reg, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case PLUS_EXPR:
+    case MINUS_EXPR:
+    case MULT_EXPR:
+    case TRUNC_DIV_EXPR:
+    case TRUNC_MOD_EXPR:
+    case LSHIFT_EXPR:
+    case RSHIFT_EXPR:
+    case BIT_IOR_EXPR:
+    case BIT_XOR_EXPR:
+    case BIT_AND_EXPR:
+    case MIN_EXPR:
+    case MAX_EXPR:
+      {
+	/* Binary ops.  */
+	tree rhs2 = gimple_assign_rhs2 (assign);
+
+	svalue2 *rhs1_sval = get_rvalue (rhs1, ctxt);
+	svalue2 *rhs2_sval = get_rvalue (rhs2, ctxt);
+
+	if (tree rhs1_cst = maybe_get_constant (rhs1_sval))
+	  if (tree rhs2_cst = maybe_get_constant (rhs2_sval))
+	    {
+	      tree result = fold_binary (op, TREE_TYPE (lhs),
+					 rhs1_cst, rhs2_cst);
+	      if (result && CONSTANT_CLASS_P (result))
+		{
+		  svalue2 *result_sval
+		    = get_or_create_constant_svalue2 (result);
+		  set_value (lhs_reg, result_sval, ctxt);
+		  return;
+		}
+	    }
+	set_to_new_unknown_value (lhs_reg, TREE_TYPE (lhs), ctxt);
+      }
+      break;
+
+    case COMPONENT_REF:
+      {
+	/* LHS = op0.op1;  */
+	region2 *child_reg = get_lvalue (rhs1, ctxt);
+	svalue2 *child_sval
+	  = get_region2 (child_reg)->get_value (*this, true, ctxt);
+	set_value (lhs_reg, child_sval, ctxt);
+      }
+      break;
+    }
+}
+#endif
+
+/* Update this model for the CALL stmt, using CTXT to report any
+   diagnostics - the first half.
+
+   Updates to the region_model2 that should be made *before* sm-states
+   are updated are done here; other updates to the region_model2 are done
+   in region_model2::on_call_post.
+
+   Return true if the function call has unknown side effects (it wasn't
+   recognized and we don't have a body for it, or are unable to tell which
+   fndecl it is).  */
+#if 0
+bool
+region_model2::on_call_pre (const gcall *call, region_model2_context *ctxt)
+{
+  region2 *lhs_reg;
+  tree lhs_type = NULL_TREE;
+  if (tree lhs = gimple_call_lhs (call))
+    {
+      lhs_reg = get_lvalue (lhs, ctxt);
+      lhs_type = TREE_TYPE (lhs);
+    }
+
+  /* Check for uses of poisoned values.
+     For now, special-case "free", to avoid warning about "use-after-free"
+     when "double free" would be more precise.  */
+  if (!is_special_named_call_p (call, "free", 1))
+    for (unsigned i = 0; i < gimple_call_num_args (call); i++)
+      check_for_poison (gimple_call_arg (call, i), ctxt);
+
+  bool unknown_svale_effects = false;
+
+  if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
+    {
+      if (is_named_call_p (callee_fndecl, "malloc", call, 1))
+	{
+	  // TODO: capture size as a svalue2?
+	  region2 *new_reg = add_new_malloc_region2 ();
+	  if (lhs_reg)
+	    {
+	      svalue2 *ptr_sval
+		= get_or_create_ptr_svalue2 (lhs_type, new_reg);
+	      set_value (lhs_reg, ptr_sval, ctxt);
+	    }
+	  return false;
+	}
+      else if (is_named_call_p (callee_fndecl, "__builtin_alloca", call, 1))
+	{
+	  region2 *frame_reg = get_current_frame *();
+	  region2 *new_reg
+	    = add_region2 (new symbolic_region2 (frame_reg, NULL_TREE, false));
+	  if (lhs_reg)
+	    {
+	      svalue2 *ptr_sval
+		= get_or_create_ptr_svalue2 (lhs_type, new_reg);
+	      set_value (lhs_reg, ptr_sval, ctxt);
+	    }
+	  return false;
+	}
+      else if (gimple_call_builtin_p (call, BUILT_IN_EXPECT)
+	       || gimple_call_builtin_p (call, BUILT_IN_EXPECT_WITH_PROBABILITY)
+	       || gimple_call_internal_p (call, IFN_BUILTIN_EXPECT))
+	{
+	  /* __builtin_expect's return value is its initial argument.  */
+	  if (lhs_reg)
+	    {
+	      tree initial_arg = gimple_call_arg (call, 0);
+	      svalue2 *sval = get_rvalue (initial_arg, ctxt);
+	      set_value (lhs_reg, sid, ctxt);
+	    }
+	  return false;
+	}
+      else if (is_named_call_p (callee_fndecl, "strlen", call, 1))
+	{
+	  region2 *buf_reg = deref_rvalue (gimple_call_arg (call, 0), ctxt);
+	  svalue2 *buf_sval
+	    = get_region2 (buf_reg)->get_value (*this, true, ctxt);
+	  if (tree cst_expr = maybe_get_constant (buf_sval))
+	    {
+	      if (TREE_CODE (cst_expr) == STRING_CST
+		  && lhs_reg)
+		{
+		  /* TREE_STRING_LENGTH is sizeof, not strlen.  */
+		  int sizeof_cst = TREE_STRING_LENGTH (cst_expr);
+		  int strlen_cst = sizeof_cst - 1;
+		  tree t_cst = build_int_cst (lhs_type, strlen_cst);
+		  svalue2 *result_sval
+		    = get_or_create_constant_svalue2 (t_cst);
+		  set_value (lhs_reg, result_sval, ctxt);
+		  return false;
+		}
+	    }
+	  /* Otherwise an unknown value.  */
+	}
+      else if (is_named_call_p (callee_fndecl,
+				"__analyzer_dump_num_heap_region2s", call, 0))
+	{
+	  /* Handle the builtin "__analyzer_dump_num_heap_region2s" by emitting
+	     a warning (for use in DejaGnu tests).  */
+	  int num_heap_region2s = 0;
+	  region2 *heap_reg = get_root_region2 ()->ensure_heap_region2 (this);
+	  unsigned i;
+	  region2 *region2;
+	  FOR_EACH_VEC_ELT (m_region2s, i, region2)
+	    if (region2->get_parent () == heap_reg)
+	      num_heap_region2s++;
+	  /* Use quotes to ensure the output isn't truncated.  */
+	  warning_at (call->location, 0,
+		      "num heap region2s: %qi", num_heap_region2s);
+	  return false;
+	}
+      else if (!fndecl_has_gimple_body_p (callee_fndecl)
+	       && !DECL_PURE_P (callee_fndecl))
+	unknown_svale_effects = true;
+    }
+  else
+    unknown_svale_effects = true;
+
+  /* Unknown return value.  */
+  if (lhs_reg)
+    set_to_new_unknown_value (lhs_reg, lhs_type, ctxt);
+
+  return unknown_svale_effects;
+}
+#endif
+
+/* Update this model for the CALL stmt, using CTXT to report any
+   diagnostics - the second half.
+
+   Updates to the region_model2 that should be made *after* sm-states
+   are updated are done here; other updates to the region_model2 are done
+   in region_model2::on_call_pre.
+
+   If UNKNOWN_SVALE_EFFECTS is true, also call handle_unrecognized_call
+   to purge state.  */
+#if 0
+void
+region_model2::on_call_post (const gcall *call,
+			    bool unknown_svale_effects,
+			    region_model2_context *ctxt)
+{
+  /* Update for "free" here, after sm-handling.
+
+     If the ptr points to an underlying heap region2, delete the region2,
+     poisoning pointers to it and region2s within it.
+
+     We delay this until after sm-state has been updated so that the
+     sm-handling can transition all of the various casts of the pointer
+     to a "freed" state *before* we delete the related region2 here.
+
+     This has to be done here so that the sm-handling can use the fact
+     that they point to the same region2 to establish that they are equal
+     (in region_model2::eval_condition_without_cm), and thus transition
+     all pointers to the region2 to the "freed" state together, regardless
+     of casts.  */
+  if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
+    if (is_named_call_p (callee_fndecl, "free", call, 1))
+      {
+	tree ptr = gimple_call_arg (call, 0);
+	svalue2 *ptr_sval = get_rvalue (ptr, ctxt);
+	svalue2 *ptr_sval = get_svalue2 (ptr_sval);
+	if (region_svalue2 *ptr_to_region2_sval
+	    = ptr_sval->dyn_cast_region_svalue2 ())
+	  {
+	    /* If the ptr points to an underlying heap region2, delete it,
+	       poisoning pointers.  */
+	    region2 *pointee_reg = ptr_to_region2_sval->get_pointee ();
+	    region2 *heap_reg = get_root_region2 ()->ensure_heap_region2 (this);
+	    if (!pointee_reg.null_p ()
+		&& get_region2 (pointee_reg)->get_parent () == heap_reg)
+	      {
+		purge_stats stats;
+		delete_region2_and_descendents (pointee_reg,
+					       POISON_KIND_FREED,
+					       &stats, ctxt->get_logger ());
+		purge_unused_svalue2s (&stats, ctxt);
+		validate ();
+		// TODO: do anything with stats?
+	      }
+	  }
+	return;
+      }
+
+  if (unknown_svale_effects)
+    handle_unrecognized_call (call, ctxt);
+}
+#endif
+
+/* Helper class for region_model2::handle_unrecognized_call, for keeping
+   track of all region2s that are reachable, and, of those, which are
+   mutable.  */
+
+#if 0
+class reachable_region2s
+{
+public:
+  reachable_region2s (region_model2 *model)
+  : m_model (model), m_reachable_regs (), m_mutable_regs ()
+  {}
+
+  /* Lazily mark RID as being reachable, recursively adding region2s
+     reachable from RID.  */
+  void add (region2 *reg, bool is_mutable)
+  {
+    gcc_assert (reg);
+
+    unsigned idx = rid.as_int ();
+    /* Bail out if this region2 is already in the sets at the IS_MUTABLE
+       level of mutability.  */
+    if (!is_mutable && bitmap_bit_p (m_reachable_regs, idx))
+      return;
+    bitmap_set_bit (m_reachable_regs, idx);
+
+    if (is_mutable)
+      {
+	if (bitmap_bit_p (m_mutable_regs, idx))
+	  return;
+	else
+	  bitmap_set_bit (m_mutable_regs, idx);
+      }
+
+    /* If this region2's value is a pointer, add the pointee.  */
+    region2 *reg = m_model->get_region2 (rid);
+    svalue2 *sval = reg->get_value_direct ();
+    svalue2 *sval = m_model->get_svalue2 (sid);
+    if (sval)
+      if (region_svalue2 *ptr = sval->dyn_cast_region_svalue2 ())
+	{
+	  region2 *pointee_reg = ptr->get_pointee ();
+	  /* Use const-ness of pointer type to affect mutability.  */
+	  bool ptr_is_mutable = true;
+	  if (ptr->get_type ()
+	      && TREE_CODE (ptr->get_type ()) == POINTER_TYPE
+	      && TYPE_READONLY (TREE_TYPE (ptr->get_type ())))
+	    ptr_is_mutable = false;
+	  add (pointee_reg, ptr_is_mutable);
+	}
+
+    /* Add descendents of this region2.  */
+    region2_id_set descendents (m_model);
+    m_model->get_descendents (rid, &descendents, region2_id::null ());
+    for (unsigned i = 0; i < m_model->get_num_region2s (); i++)
+      {
+	region2 *iter_reg = region2_id::from_int (i);
+	if (descendents.region2_p (iter_reg))
+	  add (iter_reg, is_mutable);
+      }
+  }
+
+  bool mutable_p (region2 *reg)
+  {
+    gcc_assert (reg);
+    return bitmap_bit_p (m_mutable_regs, rid.as_int ());
+  }
+
+private:
+  region_model2 *m_model;
+
+  /* The region2 ids already seen.  This has to be an auto_bitmap rather than
+     an auto_sbitmap as new region2s can be created within the model during
+     the traversal.  */
+  auto_bitmap m_reachable_regs;
+
+  /* The region2_ids that can be changed (accessed via non-const pointers).  */
+  auto_bitmap m_mutable_regs;
+};
+#endif
+
+/* Handle a call CALL to a function with unknown behavior.
+
+   Traverse the region2s in this model, determining what region2s are
+   reachable from pointer arguments to CALL and from global variables,
+   recursively.
+
+   Set all reachable region2s to new unknown values and purge sm-state
+   from their values, and from values that point to them.  */
+#if 0
+void
+region_model2::handle_unrecognized_call (const gcall *call,
+					region_model2_context *ctxt)
+{
+  tree fndecl = get_fndecl_for_call (call, ctxt);
+
+  reachable_region2s reachable_region2s (this);
+
+  /* Determine the reachable region2s and their mutability.  */
+  {
+    /* Globals.  */
+    region2 *globals_reg = get_globals_region2 *();
+    if (!globals_reg.null_p ())
+      reachable_region2s.add (globals_reg, true);
+
+    /* Params that are pointers.  */
+    tree iter_param_types = NULL_TREE;
+    if (fndecl)
+      iter_param_types = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
+    for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
+      {
+	/* Track expected param type, where available.  */
+	tree param_type = NULL_TREE;
+	if (iter_param_types)
+	  {
+	    param_type = TREE_VALUE (iter_param_types);
+	    gcc_assert (param_type);
+	    iter_param_types = TREE_CHAIN (iter_param_types);
+	  }
+
+	tree parm = gimple_call_arg (call, arg_idx);
+	svalue2 *parm_sval = get_rvalue (parm, ctxt);
+	svalue2 *parm_sval = get_svalue2 (parm_sval);
+	if (parm_sval)
+	  if (region_svalue2 *parm_ptr = parm_sval->dyn_cast_region_svalue2 ())
+	    {
+	      region2 *pointee_reg = parm_ptr->get_pointee ();
+	      bool is_mutable = true;
+	      if (param_type
+		  && TREE_CODE (param_type) == POINTER_TYPE
+		  &&  TYPE_READONLY (TREE_TYPE (param_type)))
+		is_mutable = false;
+	      reachable_region2s.add (pointee_reg, is_mutable);
+	    }
+	// FIXME: what about compound parms that contain ptrs?
+      }
+  }
+
+  /* OK: we now have all reachable region2s.
+     Set them all to new unknown values.  */
+  for (unsigned i = 0; i < get_num_region2s (); i++)
+    {
+      region2 *iter_reg = region2_id::from_int (i);
+      if (reachable_region2s.mutable_p (iter_reg))
+	{
+	  region2 *reg = get_region2 (iter_reg);
+
+	  /* Purge any sm-state for any underlying svalue2.  */
+	  svalue2 *curr_sval = reg->get_value_direct ();
+	  if (!curr_sval.null_p ())
+	    ctxt->on_unknown_change (curr_sval);
+
+	  set_to_new_unknown_value (iter_reg,
+				    reg->get_type (),
+				    ctxt);
+	}
+    }
+
+  /* Purge sm-state for any remaining svalue2s that point to region2s that
+     were reachable.  This helps suppress leak false-positives.
+
+     For example, if we had a malloc call that was cast to a "foo *" type,
+     we could have a temporary void * for the result of malloc which has its
+     own svalue2, not reachable from the function call, but for which the
+     "foo *" svalue2 was reachable.  If we don't purge it, the temporary will
+     be reported as a leak.  */
+  int i;
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (m_svalue2s, i, svalue2)
+    if (region_svalue2 *ptr = svalue2->dyn_cast_region_svalue2 ())
+      {
+	region2 *pointee_reg = ptr->get_pointee ();
+	if (reachable_region2s.mutable_p (pointee_reg))
+	  ctxt->on_unknown_change (svalue2_id::from_int (i));
+      }
+
+  validate ();
+}
+#endif
+
+/* Update this model for the RETURN_STMT, using CTXT to report any
+   diagnostics.  */
+
+void
+region_model2::on_return (const greturn *return_stmt, region_model2_context *ctxt)
+{
+  tree callee = get_current_function ()->decl;
+  tree lhs = DECL_RESULT (callee);
+  tree rhs = gimple_return_retval (return_stmt);
+
+  if (lhs && rhs)
+    copy_region (get_lvalue (lhs, ctxt), get_lvalue (rhs, ctxt), ctxt);
+}
+
+/* Update this model for a call and return of setjmp/sigsetjmp at CALL within
+   ENODE, using CTXT to report any diagnostics.
+
+   This is for the initial direct invocation of setjmp/sigsetjmp (which returns
+   0), as opposed to any second return due to longjmp/sigsetjmp.  */
+#if 0
+void
+region_model2::on_setjmp (const gcall *call, const exploded_node *enode,
+			 region_model2_context *ctxt)
+{
+  region2 *buf_reg = deref_rvalue (gimple_call_arg (call, 0), ctxt);
+  region2 *buf = get_region2 (buf_reg);
+
+  /* Create a setjmp_svalue2 for this call and store it in BUF_REG's region2.  */
+  if (buf)
+    {
+      setjmp_record r (enode, call);
+      svalue2 *sval = new setjmp_svalue2 (r, buf->get_type ());
+      svalue2 *new_sval = add_svalue2 (sval);
+      set_value (buf_reg, new_sval, ctxt);
+    }
+
+  /* Direct calls to setjmp return 0.  */
+  if (tree lhs = gimple_call_lhs (call))
+    {
+      tree zero = build_int_cst (TREE_TYPE (lhs), 0);
+      svalue2 *new_sval = get_or_create_constant_svalue2 (zero);
+      region2 *lhs_reg = get_lvalue (lhs, ctxt);
+      set_value (lhs_reg, new_sval, ctxt);
+    }
+}
+#endif
+/* Update this region_model2 for rewinding from a "longjmp" at LONGJMP_CALL
+   to a "setjmp" at SETJMP_CALL where the final stack depth should be
+   SETJMP_STACK_DEPTH.  Purge any stack frames, potentially reporting on
+   leaks to CTXT.  */
+#if 0
+void
+region_model2::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
+			  int setjmp_stack_depth,
+			  region_model2_context *ctxt)
+{
+  /* Evaluate the val, using the frame of the "longjmp".  */
+  tree fake_retval = gimple_call_arg (longjmp_call, 1);
+  svalue2 *fake_retval_sval = get_rvalue (fake_retval, ctxt);
+
+  /* Pop any frames until we reach the stack depth of the function where
+     setjmp was called.  */
+  gcc_assert (get_stack_depth () >= setjmp_stack_depth);
+  while (get_stack_depth () > setjmp_stack_depth)
+    {
+      /* Don't purge unused svalue2s yet, as we're using fake_retval_sval.  */
+      pop_frame (region2_id::null (), false, NULL, ctxt);
+    }
+
+  gcc_assert (get_stack_depth () == setjmp_stack_depth);
+
+  /* Assign to LHS of "setjmp" in new_state.  */
+  if (tree lhs = gimple_call_lhs (setjmp_call))
+    {
+      /* Passing 0 as the val to longjmp leads to setjmp returning 1.  */
+      tree t_zero = build_int_cst (TREE_TYPE (fake_retval), 0);
+      svalue2 *zero_sval = get_or_create_constant_svalue2 (t_zero);
+      tristate eq_zero = eval_condition (fake_retval_sval, EQ_EXPR, zero_sval);
+      /* If we have 0, use 1.  */
+      if (eq_zero.is_true ())
+	{
+	  tree t_one = build_int_cst (TREE_TYPE (fake_retval), 1);
+	  svalue2 *one_sval = get_or_create_constant_svalue2 (t_one);
+	  fake_retval_sval = one_sval;
+	}
+      else
+	{
+	  /* Otherwise note that the value is nonzero.  */
+	  m_constraints->add_constraint (fake_retval_sval, NE_EXPR, zero_sval);
+	}
+
+      region2 *lhs_reg = get_lvalue (lhs, ctxt);
+      set_value (lhs_reg, fake_retval_sval, ctxt);
+    }
+
+  /* Now that we've assigned the fake_retval, we can purge the unused
+     svalue2s, which could detect leaks.  */
+  purge_unused_svalue2s (NULL, ctxt, NULL);
+  validate ();
+}
+#endif
+/* Update this region_model2 for a phi stmt of the form
+     LHS = PHI <...RHS...>.
+   where RHS is for the appropriate edge.  */
+
+void
+region_model2::handle_phi (const gphi *phi,
+			  tree lhs, tree rhs, bool is_back_edge,
+			  region_model2_context *ctxt)
+{
+  /* For now, don't bother tracking the .MEM SSA names.  */
+  if (tree var = SSA_NAME_VAR (lhs))
+    if (TREE_CODE (var) == VAR_DECL)
+      if (VAR_DECL_IS_VIRTUAL_OPERAND (var))
+	return;
+
+  svalue2 *rhs_sval = get_rvalue (rhs, ctxt);
+
+  if (is_back_edge && rhs_sval->get_kind () != svalue2::SK_UNKNOWN)
+    {
+      /* If we have a back edge, we probably have a loop.
+	 Use an unknown value, to avoid effectively unrolling the
+	 loop.
+	 To terminate, we need to avoid generating a series of
+	 models with an unbounded monotonically increasing number of
+	 redundant unknown values; hence we need to purge svalue2s
+	 before inserting the state into the exploded graph, to
+	 collect unused svalue2s.  */
+      set_to_new_unknown_value (get_lvalue (lhs, ctxt), TREE_TYPE (lhs), ctxt);
+    }
+  else
+    set_value (get_lvalue (lhs, ctxt), rhs_sval, ctxt);
+
+  if (ctxt)
+    ctxt->on_phi (phi, rhs);
+}
+
+/* Implementation of region_model2::get_lvalue; the latter adds type-checking.
+
+   Get the id of the region2 for PV within this region_model2,
+   emitting any diagnostics to CTXT.  */
+#if 0
+region2_id
+region_model2::get_lvalue_1 (path_var pv, region_model2_context *ctxt)
+{
+  tree expr = pv.m_tree;
+
+  gcc_assert (expr);
+
+  switch (TREE_CODE (expr))
+    {
+    default:
+      return make_region2_for_unexpected_tree_code (ctxt, expr,
+						   dump_location_t ());
+
+    case ARRAY_REF:
+      {
+	tree array = TREE_OPERAND (expr, 0);
+	tree index = TREE_OPERAND (expr, 1);
+#if 0
+	// TODO: operands 2 and 3, if present:
+	gcc_assert (TREE_OPERAND (expr, 2) == NULL_TREE);
+	gcc_assert (TREE_OPERAND (expr, 3) == NULL_TREE);
+#endif
+
+	region2 *array_reg = get_lvalue (array, ctxt);
+	svalue2 *index_sval = get_rvalue (index, ctxt);
+	region2 *base_array_reg = get_region2 (array_reg);
+	array_region2 *array_reg  = base_array_reg->dyn_cast_array_region2 ();
+	if (!array_reg)
+	  {
+	    /* Normally, array_reg ought to refer to an array_region2, since
+	       array's type will be ARRAY_TYPE.  However, if we have an
+	       unexpected tree code for array, we could have a
+	       symbolic_region2 here.  If so, we're in error-handling. */
+	    gcc_assert (base_array_reg->get_type () == NULL_TREE);
+	    return make_region2_for_unexpected_tree_code (ctxt, expr,
+							 dump_location_t ());
+	  }
+	return array_reg->get_element (this, array_reg, index_sval, ctxt);
+      }
+      break;
+
+    case BIT_FIELD_REF:
+      {
+	/* For now, create a view, as if a cast, ignoring the bit positions.  */
+	tree obj = TREE_OPERAND (expr, 0);
+	return get_or_create_view (get_lvalue (obj, ctxt), TREE_TYPE (expr),
+				   ctxt);
+      };
+      break;
+
+    case MEM_REF:
+      {
+	tree ptr = TREE_OPERAND (expr, 0);
+	tree offset = TREE_OPERAND (expr, 1);
+	svalue2 *ptr_sval = get_rvalue (ptr, ctxt);
+	svalue2 *offset_sval = get_rvalue (offset, ctxt);
+	return get_or_create_mem_ref (TREE_TYPE (expr), ptr_sval,
+				      offset_sval, ctxt);
+      }
+      break;
+
+    case VAR_DECL:
+      /* Handle globals.  */
+      if (is_global_var (expr))
+	{
+	  region2 *globals_reg
+	    = get_root_region2 ()->ensure_globals_region2 (this);
+	  map_region2 *globals = get_region2<map_region2> (globals_reg);
+	  region2 *var_reg = globals->get_or_create (this, globals_reg, expr,
+						      TREE_TYPE (expr), ctxt);
+	  return var_reg;
+	}
+
+      /* Fall through.  */
+
+    case SSA_NAME:
+    case PARM_DECL:
+    case RESULT_DECL:
+      {
+	gcc_assert (TREE_CODE (expr) == SSA_NAME
+		    || TREE_CODE (expr) == PARM_DECL
+		    || TREE_CODE (expr) == VAR_DECL
+		    || TREE_CODE (expr) == RESULT_DECL);
+
+	int stack_depth = pv.m_stack_depth;
+	stack_region2 *stack = get_root_region2 ()->get_stack_region2 (this);
+	gcc_assert (stack);
+	region2 *frame_reg = stack->get_frame_reg (stack_depth);
+	frame_region2 *frame = get_region2 <frame_region2> (frame_reg);
+	gcc_assert (frame);
+	region2 *child_reg = frame->get_or_create (this, frame_reg, expr,
+						    TREE_TYPE (expr), ctxt);
+	return child_reg;
+      }
+
+    case COMPONENT_REF:
+      {
+	/* obj.field  */
+	tree obj = TREE_OPERAND (expr, 0);
+	tree field = TREE_OPERAND (expr, 1);
+	tree obj_type = TREE_TYPE (obj);
+	if (TREE_CODE (obj_type) != RECORD_TYPE
+	    && TREE_CODE (obj_type) != UNION_TYPE)
+	  return make_region2_for_unexpected_tree_code (ctxt, obj_type,
+						       dump_location_t ());
+	region2 *obj_reg = get_lvalue (obj, ctxt);
+	region2 *struct_or_union_reg
+	  = get_or_create_view (obj_reg, TREE_TYPE (obj), ctxt);
+	return get_field_region2 (struct_or_union_reg, field, ctxt);
+      }
+      break;
+
+    case CONST_DECL:
+      {
+	tree cst_type = TREE_TYPE (expr);
+	region2 *cst_reg = add_region2_for_type (m_root_reg, cst_type, ctxt);
+	if (tree value = DECL_INITIAL (expr))
+	  {
+	    svalue2 *sval = get_rvalue (value, ctxt);
+	    get_region2 (cst_reg)->set_value (*this, cst_reg, sid, ctxt);
+	  }
+	return cst_reg;
+      }
+      break;
+
+    case STRING_CST:
+      {
+	tree cst_type = TREE_TYPE (expr);
+	array_region2 *cst_region2 = new array_region2 (m_root_reg, cst_type);
+	region2 *cst_reg = add_region2 (cst_region2);
+	svalue2 *cst_sval = get_or_create_constant_svalue2 (expr);
+	cst_region2->set_value (*this, cst_reg, cst_sval, ctxt);
+	return cst_reg;
+      }
+      break;
+
+    case NOP_EXPR:
+    case VIEW_CONVERT_EXPR:
+      {
+	tree obj = TREE_OPERAND (expr, 0);
+	return get_or_create_view (get_lvalue (obj, ctxt), TREE_TYPE (expr),
+				   ctxt);
+      };
+      break;
+    }
+}
+#endif
+
+/* If we see a tree code we don't know how to handle, rather than
+   ICE or generate bogus results, create a dummy region2, and notify
+   CTXT so that it can mark the new state as being not properly
+   modelled.  The exploded graph can then stop exploring that path,
+   since any diagnostics we might issue will have questionable
+   validity.  */
+#if 0
+region2 *
+region_model2::make_region2_for_unexpected_tree_code (region_model2_context *ctxt,
+						    tree t,
+						    const dump_location_t &loc)
+{
+  gcc_assert (ctxt);
+  region2 *new_reg
+    = add_region2 (new symbolic_region2 (m_root_reg, NULL_TREE, false));
+  ctxt->on_unexpected_tree_code (t, loc);
+  return new_reg;
+}
+#endif
+
+/* Assert that SRC_TYPE can be converted to DST_TYPE as a no-op.  */
+
+static void
+assert_compat_types (tree src_type, tree dst_type)
+{
+  if (src_type && dst_type && !VOID_TYPE_P (dst_type))
+    gcc_checking_assert (useless_type_conversion_p (src_type, dst_type));
+}
+
+/* Get the id of the region2 for PV within this region_model2,
+   emitting any diagnostics to CTXT.  */
+#if 0
+region2_id
+region_model2::get_lvalue (path_var pv, region_model2_context *ctxt)
+{
+  if (pv.m_tree == NULL_TREE)
+    return region2_id::null ();
+
+  region2 *result_reg = get_lvalue_1 (pv, ctxt);
+  assert_compat_types (get_region2 (result_reg)->get_type (),
+		       TREE_TYPE (pv.m_tree));
+  return result_reg;
+}
+#endif
+
+/* Get the region2 *for EXPR within this region_model2 (assuming the most
+   recent stack frame if it's a local).  */
+
+region2 *
+region_model2::get_lvalue (tree expr, region_model2_context *ctxt)
+{
+  return get_lvalue (path_var (expr, get_stack_depth () - 1), ctxt);
+}
+
+/* Implementation of region_model2::get_rvalue; the latter adds type-checking.
+
+   Get the value of PV within this region_model2,
+   emitting any diagnostics to CTXT.  */
+#if 0
+svalue2_id
+region_model2::get_rvalue_1 (path_var pv, region_model2_context *ctxt)
+{
+  gcc_assert (pv.m_tree);
+
+  switch (TREE_CODE (pv.m_tree))
+    {
+    default:
+      {
+	svalue2 *unknown_sval = new unknown_svalue2 (TREE_TYPE (pv.m_tree));
+	return add_svalue2 (unknown_sval);
+      }
+      break;
+
+    case ADDR_EXPR:
+      {
+	/* "&EXPR".  */
+	tree expr = pv.m_tree;
+	tree op0 = TREE_OPERAND (expr, 0);
+	if (TREE_CODE (op0) == FUNCTION_DECL)
+	  return get_svalue2_for_fndecl (TREE_TYPE (expr), op0, ctxt);
+	else if (TREE_CODE (op0) == LABEL_DECL)
+	  return get_svalue2_for_label (TREE_TYPE (expr), op0, ctxt);
+	region2 *expr_reg = get_lvalue (op0, ctxt);
+	return get_or_create_ptr_svalue2 (TREE_TYPE (expr), expr_reg);
+      }
+      break;
+
+    case ARRAY_REF:
+      {
+	region2 *element_reg = get_lvalue (pv, ctxt);
+	return get_region2 (element_reg)->get_value (*this, true, ctxt);
+      }
+
+    case INTEGER_CST:
+    case REAL_CST:
+    case STRING_CST:
+      return get_or_create_constant_svalue2 (pv.m_tree);
+
+    case COMPONENT_REF:
+    case MEM_REF:
+    case SSA_NAME:
+    case VAR_DECL:
+    case PARM_DECL:
+    case RESULT_DECL:
+      {
+	region2 *var_reg = get_lvalue (pv, ctxt);
+	return get_region2 (var_reg)->get_value (*this, true, ctxt);
+      }
+    }
+}
+
+/* Get the value of PV within this region_model2,
+   emitting any diagnostics to CTXT.  */
+
+svalue2_id
+region_model2::get_rvalue (path_var pv, region_model2_context *ctxt)
+{
+  if (pv.m_tree == NULL_TREE)
+    return svalue2_id::null ();
+  svalue2 *result_sval = get_rvalue_1 (pv, ctxt);
+
+  assert_compat_types (get_svalue2 (result_sval)->get_type (),
+		       TREE_TYPE (pv.m_tree));
+
+  return result_sval;
+}
+#endif
+/* Get the value of EXPR within this region_model2 (assuming the most
+   recent stack frame if it's a local).  */
+
+svalue2 *
+region_model2::get_rvalue (tree expr, region_model2_context *ctxt)
+{
+  return get_rvalue (path_var (expr, get_stack_depth () - 1), ctxt);
+}
+
+/* Return an svalue2 *for a pointer to RID of type PTR_TYPE, reusing
+   existing pointer values if one is available.  */
+#if 0
+svalue2_id
+region_model2::get_or_create_ptr_svalue2 (tree ptr_type, region2 *reg)
+{
+  /* Reuse existing region_svalue2, if one of the right type is
+     available.  */
+  /* In theory we could stash a svalue2 *in "region2", but differing
+     pointer types muddles things.
+     For now, just do a linear search through all existing svalue2s.  */
+  int i;
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (m_svalue2s, i, svalue2)
+    if (region_svalue2 *ptr_svalue2 = svalue2->dyn_cast_region_svalue2 ())
+      if (ptr_svalue2->get_pointee () == rid
+	  && ptr_svalue2->get_type () == ptr_type)
+	return svalue2_id::from_int (i);
+
+  return add_svalue2 (new region_svalue2 (ptr_type, rid));
+}
+#endif
+/* Return an svalue2 *for a constant_svalue2 for CST_EXPR,
+   creating the constant_svalue2 if necessary.
+   The constant_svalue2 instances are reused, based on pointer equality
+   of trees  */
+#if 0
+svalue2_id
+region_model2::get_or_create_constant_svalue2 (tree cst_expr)
+{
+  gcc_assert (cst_expr);
+
+  /* Reuse one if it already exists.  */
+  // TODO: maybe store a map, rather than do linear search?
+  int i;
+  svalue2 *svalue2;
+  FOR_EACH_VEC_ELT (m_svalue2s, i, svalue2)
+    if (svalue2->maybe_get_constant () == cst_expr)
+      return svalue2_id::from_int (i);
+
+  svalue2 *cst_sval = add_svalue2 (new constant_svalue2 (cst_expr));
+  return cst_sval;
+}
+#endif
+/* Return an svalue2 *for a region_svalue2 for FNDECL,
+   creating the function_region2 if necessary.  */
+#if 0
+svalue2_id
+region_model2::get_svalue2_for_fndecl (tree ptr_type, tree fndecl,
+				     region_model2_context *ctxt)
+{
+  gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL);
+  region2 *function_reg = get_region2_for_fndecl (fndecl, ctxt);
+  return get_or_create_ptr_svalue2 (ptr_type, function_reg);
+}
+#endif
+
+/* Return a function_region2 * for FNDECL, creating it if necessary.  */
+#if 0
+function_region2 *
+region_model2_manager::get_region2_for_fndecl (tree fndecl,
+					       region_model2_context *ctxt)
+{
+  gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL);
+
+  region2 *code_reg = get_root_region2 ()->ensure_code_region2 (this);
+  code_region2 *code = get_root_region2 ()->get_code_region2 (this);
+
+  return code->get_or_create (this, code_reg, fndecl, TREE_TYPE (fndecl),
+			      ctxt);
+}
+#endif
+
+/* Return an svalue2 *for a region_svalue2 for LABEL,
+   creating the label_region2 if necessary.  */
+#if 0
+svalue2_id
+region_model2::get_svalue2_for_label (tree ptr_type, tree label,
+				    region_model2_context *ctxt)
+{
+  gcc_assert (TREE_CODE (label) == LABEL_DECL);
+  region2 *label_reg = get_region2_for_label (label, ctxt);
+  return get_or_create_ptr_svalue2 (ptr_type, label_reg);
+}
+#endif
+
+/* Return a region2 *for a label_region2 for LABEL,
+   creating it if necessary.  */
+#if 0
+region2 *
+region_model2_manager::get_region2_for_label (tree label,
+					      region_model2_context *ctxt)
+{
+  gcc_assert (TREE_CODE (label) == LABEL_DECL);
+
+  tree fndecl = DECL_CONTEXT (label);
+  gcc_assert (fndecl && TREE_CODE (fndecl) == FUNCTION_DECL);
+
+  function_region2 *func_reg = get_region2_for_fndecl (fndecl, ctxt);
+  return func_reg->get_or_create (this, func_reg, label, TREE_TYPE (label),
+				  ctxt);
+}
+#endif
+
+/* Build a cast of SRC_EXPR to DST_TYPE, or return NULL_TREE.
+
+   Adapted from gcc::jit::playback::context::build_cast, which in turn is
+   adapted from
+     - c/c-typeck.c:build_c_cast
+     - c/c-convert.c: convert
+     - convert.h
+   Only some kinds of cast are currently supported here.  */
+
+static tree
+build_cast (tree dst_type, tree src_expr)
+{
+  tree result = targetm.convert_to_type (dst_type, src_expr);
+  if (result)
+    return result;
+  enum tree_code dst_code = TREE_CODE (dst_type);
+  switch (dst_code)
+    {
+    case INTEGER_TYPE:
+    case ENUMERAL_TYPE:
+      result = convert_to_integer (dst_type, src_expr);
+      goto maybe_fold;
+
+    case BOOLEAN_TYPE:
+      /* Compare with c_objc_common_truthvalue_conversion and
+	 c_common_truthvalue_conversion. */
+      /* For now, convert to: (src_expr != 0)  */
+      result = build2 (NE_EXPR, dst_type,
+		       src_expr,
+		       build_int_cst (TREE_TYPE (src_expr), 0));
+      goto maybe_fold;
+
+    case REAL_TYPE:
+      result = convert_to_real (dst_type, src_expr);
+      goto maybe_fold;
+
+    case POINTER_TYPE:
+      result = build1 (NOP_EXPR, dst_type, src_expr);
+      goto maybe_fold;
+
+    default:
+      return NULL_TREE;
+
+    maybe_fold:
+      if (TREE_CODE (result) != C_MAYBE_CONST_EXPR)
+	result = fold (result);
+      return result;
+    }
+}
+
+/* If the type of SID's underlying value is DST_TYPE, return SID.
+   Otherwise, attempt to create (or reuse) an svalue2 representing an access
+   of SID as a DST_TYPE and return that value's svalue2_id.  */
+#if 0
+svalue2_id
+region_model2::maybe_cast_1 (tree dst_type, svalue2 *sval)
+{
+  svalue2 *sval = get_svalue2 (sid);
+  tree src_type = sval->get_type ();
+  if (src_type == dst_type)
+    return sid;
+
+  if (POINTER_TYPE_P (dst_type)
+      || POINTER_TYPE_P (src_type))
+    {
+      /* Pointer to region2.  */
+      if (region_svalue2 *ptr_sval = sval->dyn_cast_region_svalue2 ())
+	return get_or_create_ptr_svalue2 (dst_type, ptr_sval->get_pointee ());
+
+      /* Unknown pointer?   Get or create a new unknown pointer of the
+	 correct type, preserving the equality between the pointers.  */
+      if (sval->dyn_cast_unknown_svalue2 ())
+	{
+	  equiv_class &ec = m_constraints->get_equiv_class (sid);
+
+	  /* Look for an existing pointer of the correct type within the EC.  */
+	  int i;
+	  svalue2 **equiv_sval;
+	  FOR_EACH_VEC_ELT (ec.m_vars, i, equiv_sval)
+	    {
+	      svalue2 *equiv_val = get_svalue2 (*equiv_sval);
+	      if (equiv_val->get_type () == dst_type)
+		return *equiv_sval;
+	    }
+
+	  /* Otherwise, create a new unknown pointer of the correct type.  */
+	  svalue2 *unknown_sval = new unknown_svalue2 (dst_type);
+	  svalue2 *new_ptr_sval = add_svalue2 (unknown_sval);
+	  m_constraints->add_constraint (sid, EQ_EXPR, new_ptr_sval);
+	  return new_ptr_sval;
+	}
+    }
+
+  /* Attempt to cast constants.  */
+  if (tree src_cst = sval->maybe_get_constant ())
+    {
+      if (tree dst = build_cast (dst_type, src_cst))
+	if (CONSTANT_CLASS_P (dst))
+	  return get_or_create_constant_svalue2 (dst);
+    }
+
+  /* Otherwise, return a new unknown value.  */
+  svalue2 *unknown_sval = new unknown_svalue2 (dst_type);
+  return add_svalue2 (unknown_sval);
+}
+
+/* If the type of SID's underlying value is DST_TYPE, return SID.
+   Otherwise, attempt to create (or reuse) an svalue2 representing an access
+   of SID as a DST_TYPE and return that value's svalue2_id.
+
+   If the result != SID, then call CTXT's on_cast vfunc (if CTXT is non-NULL),
+   so that sm-state can be propagated from SID to the result.  */
+
+svalue2_id
+region_model2::maybe_cast (tree dst_type, svalue2 *sval,
+			  region_model2_context *ctxt)
+{
+  svalue2 *result = maybe_cast_1 (dst_type, sid);
+  if (result != sid)
+    if (ctxt)
+      {
+	/* Notify ctxt about a cast, so any sm-state can be copied.  */
+	ctxt->on_cast (sid, result);
+      }
+  return result;
+}
+#endif
+
+/* Ensure that the region2 for OBJ_REG has a child region2 for FIELD;
+   return the child region2's region2_id.  */
+#if 0
+region2_id
+region_model2::get_field_region2 (region2 *struct_or_union_reg, tree field,
+				region_model2_context *ctxt)
+{
+  struct_or_union_region2 *sou_reg
+    = get_region2<struct_or_union_region2> (struct_or_union_reg);
+
+  /* Inherit constness from parent type.  */
+  const int qual_mask = TYPE_QUAL_CONST;
+  int sou_quals = TYPE_QUALS (sou_reg->get_type ()) & qual_mask;
+  tree field_type = TREE_TYPE (field);
+  tree field_type_with_quals = build_qualified_type (field_type, sou_quals);
+
+  // TODO: maybe convert to a vfunc?
+  if (sou_reg->get_kind () == RK_UNION)
+    {
+      /* Union.
+	 Get a view of the union as a whole, with the type of the field.  */
+      region2 *view_reg
+	= get_or_create_view (struct_or_union_reg, field_type_with_quals, ctxt);
+      return view_reg;
+    }
+  else
+    {
+      /* Struct.  */
+      region2 *child_reg
+	= sou_reg->get_or_create (this, struct_or_union_reg, field,
+				  field_type_with_quals, ctxt);
+      return child_reg;
+    }
+}
+#endif
+
+/* Get a region2 *for referencing PTR_SVAL, creating a region2 if need be, and
+   potentially generating warnings via CTXT.  */
+#if 0
+region2_id
+region_model2::deref_rvalue (svalue2 *ptr_sval, region_model2_context *ctxt)
+{
+  gcc_assert (!ptr_sval.null_p ());
+  svalue2 *ptr_svalue2 = get_svalue2 (ptr_sval);
+  gcc_assert (ptr_svalue2);
+
+  switch (ptr_svalue2->get_kind ())
+    {
+    case SK_REGION2:
+      {
+	region_svalue2 *region2_sval = as_a <region_svalue2 *> (ptr_svalue2);
+	return region2_sval->get_pointee ();
+      }
+
+    case SK_CONSTANT:
+      goto create_symbolic_region2;
+
+    case SK_POISONED:
+      {
+	if (ctxt)
+	  if (tree ptr = get_representative_tree (ptr_sval))
+	    {
+	      poisoned_svalue2 *poisoned_sval
+		= as_a <poisoned_svalue2 *> (ptr_svalue2);
+	      enum poison_kind pkind = poisoned_sval->get_poison_kind ();
+	      ctxt->warn (new poisoned_value_diagnostic (ptr, pkind));
+	    }
+	goto create_symbolic_region2;
+      }
+
+    case SK_UNKNOWN:
+      {
+      create_symbolic_region2:
+	/* We need a symbolic_region2 to represent this unknown region2.
+	   We don't know if it on the heap, stack, or a global,
+	   so use the root region2 as parent.  */
+	region2 *new_reg
+	  = add_region2 (new symbolic_region2 (m_root_reg, NULL_TREE, false));
+
+	/* We need to write the region2 back into the pointer,
+	   or we'll get a new, different region2 each time.
+	   We do this by changing the meaning of ptr_sval, replacing
+	   the unknown value with the ptr to the new region2.
+	   We replace the meaning of the ID rather than simply writing
+	   to PTR's lvalue since there could be several places sharing
+	   the same unknown ptr value.  */
+	svalue2 *ptr_val
+	  = new region_svalue2 (ptr_svalue2->get_type (), new_reg);
+	replace_svalue2 (ptr_sval, ptr_val);
+
+	return new_reg;
+      }
+
+    case SK_SETJMP:
+      goto create_symbolic_region2;
+    }
+
+  gcc_unreachable ();
+}
+
+/* Get a region2 *for referencing PTR, creating a region2 if need be, and
+   potentially generating warnings via CTXT.  */
+
+region2_id
+region_model2::deref_rvalue (tree ptr, region_model2_context *ctxt)
+{
+  svalue2 *ptr_sval = get_rvalue (ptr, ctxt);
+  return deref_rvalue (ptr_sval, ctxt);
+}
+#endif
+
+/* Set the value of the region2 given by LHS_REG to the value given
+   by RHS_SVAL.  */
+#if 0
+void
+region_model2::set_value (region2 *lhs_reg, svalue2 *rhs_sval,
+			 region_model2_context *ctxt)
+{
+  gcc_assert (lhs_reg);
+  gcc_assert (!rhs_sval.null_p ());
+  get_region2 (lhs_reg)->set_value (*this, lhs_reg, rhs_sval, ctxt);
+}
+
+/* Set the value of the region2 given by LHS to the value given
+   by RHS.  */
+
+void
+region_model2::set_value (tree lhs, tree rhs, region_model2_context *ctxt)
+{
+  region2 *lhs_reg = get_lvalue (lhs, ctxt);
+  svalue2 *rhs_sval = get_rvalue (rhs, ctxt);
+  gcc_assert (lhs_reg);
+  gcc_assert (!rhs_sval.null_p ());
+  set_value (lhs_reg, rhs_sval, ctxt);
+}
+#endif
+
+/* Determine what is known about the condition "LHS_SVAL OP RHS_SVAL" within
+   this model.  */
+#if 0
+tristate
+region_model2::eval_condition (svalue2 *lhs_sval,
+			      enum tree_code op,
+			      svalue2 *rhs_sval) const
+{
+  svalue2 *lhs = get_svalue2 (lhs_sval);
+  svalue2 *rhs = get_svalue2 (rhs_sval);
+
+  /* For now, make no attempt to capture constraints on floating-point
+     values.  */
+  if ((lhs->get_type () && FLOAT_TYPE_P (lhs->get_type ()))
+      || (rhs->get_type () && FLOAT_TYPE_P (rhs->get_type ())))
+    return tristate::unknown ();
+
+  tristate ts = eval_condition_without_cm (lhs_sval, op, rhs_sval);
+
+  if (ts.is_known ())
+    return ts;
+
+  /* Otherwise, try constraints.  */
+  return m_constraints->eval_condition (lhs_sval, op, rhs_sval);
+}
+
+/* Determine what is known about the condition "LHS_SVAL OP RHS_SVAL" within
+   this model, without resorting to the constraint_manager.
+
+   This is exposed so that impl_region_model2_context::on_state_leak can
+   check for equality part-way through region_model2::purge_unused_svalue2s
+   without risking creating new ECs.  */
+
+tristate
+region_model2::eval_condition_without_cm (svalue2 *lhs_sval,
+					 enum tree_code op,
+					 svalue2 *rhs_sval) const
+{
+  svalue2 *lhs = get_svalue2 (lhs_sval);
+  svalue2 *rhs = get_svalue2 (rhs_sval);
+  gcc_assert (lhs);
+  gcc_assert (rhs);
+
+  /* See what we know based on the values.  */
+  if (lhs && rhs)
+    {
+      /* For now, make no attempt to capture constraints on floating-point
+	 values.  */
+      if ((lhs->get_type () && FLOAT_TYPE_P (lhs->get_type ()))
+	  || (rhs->get_type () && FLOAT_TYPE_P (rhs->get_type ())))
+	return tristate::unknown ();
+
+      if (lhs == rhs)
+	{
+	  /* If we have the same svalue2, then we have equality
+	     (apart from NaN-handling).
+	     TODO: should this definitely be the case for poisoned values?  */
+	  switch (op)
+	    {
+	    case EQ_EXPR:
+	    case GE_EXPR:
+	    case LE_EXPR:
+	      return tristate::TS_TRUE;
+
+	    case NE_EXPR:
+	    case GT_EXPR:
+	    case LT_EXPR:
+	      return tristate::TS_FALSE;
+
+	    default:
+	      /* For other ops, use the logic below.  */
+	      break;
+	    }
+	}
+
+      /* If we have a pair of region_svalue2s, compare them.  */
+      if (region_svalue2 *lhs_ptr = lhs->dyn_cast_region_svalue2 ())
+	if (region_svalue2 *rhs_ptr = rhs->dyn_cast_region_svalue2 ())
+	  {
+	    tristate res = region_svalue2::eval_condition (lhs_ptr, op, rhs_ptr);
+	    if (res.is_known ())
+	      return res;
+	    /* Otherwise, only known through constraints.  */
+	  }
+
+      /* If we have a pair of constants, compare them.  */
+      if (constant_svalue2 *cst_lhs = lhs->dyn_cast_constant_svalue2 ())
+	if (constant_svalue2 *cst_rhs = rhs->dyn_cast_constant_svalue2 ())
+	  return constant_svalue2::eval_condition (cst_lhs, op, cst_rhs);
+
+      /* Handle comparison of a region_svalue2 against zero.  */
+      if (region_svalue2 *ptr = lhs->dyn_cast_region_svalue2 ())
+	if (constant_svalue2 *cst_rhs = rhs->dyn_cast_constant_svalue2 ())
+	  if (zerop (cst_rhs->get_constant ()))
+	    {
+	      /* A region_svalue2 is a non-NULL pointer, except in certain
+		 special cases (see the comment for region2::non_null_p.  */
+	      region2 *pointee = get_region2 (ptr->get_pointee ());
+	      if (pointee->non_null_p (*this))
+		{
+		  switch (op)
+		    {
+		    default:
+		      gcc_unreachable ();
+
+		    case EQ_EXPR:
+		    case GE_EXPR:
+		    case LE_EXPR:
+		      return tristate::TS_FALSE;
+
+		    case NE_EXPR:
+		    case GT_EXPR:
+		    case LT_EXPR:
+		      return tristate::TS_TRUE;
+		    }
+		}
+	    }
+    }
+
+  return tristate::TS_UNKNOWN;
+}
+
+/* Attempt to add the constraint "LHS OP RHS" to this region_model2.
+   If it is consistent with existing constraints, add it, and return true.
+   Return false if it contradicts existing constraints.
+   Use CTXT for reporting any diagnostics associated with the accesses.  */
+
+bool
+region_model2::add_constraint (tree lhs, enum tree_code op, tree rhs,
+			      region_model2_context *ctxt)
+{
+  /* For now, make no attempt to capture constraints on floating-point
+     values.  */
+  if (FLOAT_TYPE_P (TREE_TYPE (lhs)) || FLOAT_TYPE_P (TREE_TYPE (rhs)))
+    return true;
+
+  svalue2 *lhs_sval = get_rvalue (lhs, ctxt);
+  svalue2 *rhs_sval = get_rvalue (rhs, ctxt);
+
+  tristate t_cond = eval_condition (lhs_sval, op, rhs_sval);
+
+  /* If we already have the condition, do nothing.  */
+  if (t_cond.is_true ())
+    return true;
+
+  /* Reject a constraint that would contradict existing knowledge, as
+     unsatisfiable.  */
+  if (t_cond.is_false ())
+    return false;
+
+  /* Store the constraint.  */
+  m_constraints->add_constraint (lhs_sval, op, rhs_sval);
+
+  add_any_constraints_from_ssa_def_stmt (lhs, op, rhs, ctxt);
+
+  /* If we now know a symbolic_region2 is non-NULL, clear its
+     m_possibly_null.  */
+  if (zerop (rhs) && op == NE_EXPR)
+    if (region_svalue2 *ptr = get_svalue2 (lhs_sval)->dyn_cast_region_svalue2 ())
+      {
+	region2 *pointee = get_region2 (ptr->get_pointee ());
+	if (symbolic_region2 *sym_reg = pointee->dyn_cast_symbolic_region2 ())
+	  sym_reg->m_possibly_null = false;
+      }
+
+  /* Notify the context, if any.  This exists so that the state machines
+     in a program_state can be notified about the condition, and so can
+     set sm-state for e.g. unchecked->checked, both for cfg-edges, and
+     when synthesizing constraints as above.  */
+  if (ctxt)
+    ctxt->on_condition (lhs, op, rhs);
+
+  return true;
+}
+
+/* Subroutine of region_model2::add_constraint for handling optimized
+   && and || conditionals.
+
+   If we have an SSA_NAME for a boolean compared against 0,
+   look at anything implied by the def stmt and call add_constraint
+   for it (which could recurse).
+
+   For example, if we have
+      _1 = p_6 == 0B;
+      _2 = p_8 == 0B
+      _3 = _1 | _2
+    and add the constraint
+      (_3 == 0),
+    then the def stmt for _3 implies that _1 and _2 are both false,
+    and hence we can add the constraints:
+      p_6 != 0B
+      p_8 != 0B.  */
+
+void
+region_model2::add_any_constraints_from_ssa_def_stmt (tree lhs,
+						     enum tree_code op,
+						     tree rhs,
+						     region_model2_context *ctxt)
+{
+  if (TREE_CODE (lhs) != SSA_NAME)
+    return;
+
+  if (!zerop (rhs))
+    return;
+
+  if (op != NE_EXPR && op != EQ_EXPR)
+    return;
+
+  gimple *def_stmt = SSA_NAME_DEF_STMT (lhs);
+  if (const gassign *assign = dyn_cast<gassign *> (def_stmt))
+    add_any_constraints_from_gassign (op, rhs, assign, ctxt);
+  else if (gcall *call = dyn_cast<gcall *> (def_stmt))
+    add_any_constraints_from_gcall (op, rhs, call, ctxt);
+}
+
+/* Add any constraints for an SSA_NAME defined by ASSIGN
+   where the result OP RHS.  */
+
+void
+region_model2::add_any_constraints_from_gassign (enum tree_code op,
+						tree rhs,
+						const gassign *assign,
+						region_model2_context *ctxt)
+{
+  /* We have either
+     - "LHS != false" (i.e. LHS is true), or
+     - "LHS == false" (i.e. LHS is false).  */
+  bool is_true = op == NE_EXPR;
+
+  enum tree_code rhs_code = gimple_assign_rhs_code (assign);
+
+  switch (rhs_code)
+    {
+    default:
+      break;
+
+    case NOP_EXPR:
+      {
+	add_constraint (gimple_assign_rhs1 (assign), op, rhs, ctxt);
+      }
+      break;
+
+    case BIT_AND_EXPR:
+      {
+	if (is_true)
+	  {
+	    /* ...and "LHS == (rhs1 & rhs2) i.e. "(rhs1 & rhs2)" is true
+	       then both rhs1 and rhs2 must be true.  */
+	    tree rhs1 = gimple_assign_rhs1 (assign);
+	    tree rhs2 = gimple_assign_rhs2 (assign);
+	    add_constraint (rhs1, NE_EXPR, boolean_false_node, ctxt);
+	    add_constraint (rhs2, NE_EXPR, boolean_false_node, ctxt);
+	  }
+      }
+      break;
+
+    case BIT_IOR_EXPR:
+      {
+	if (!is_true)
+	  {
+	    /* ...and "LHS == (rhs1 | rhs2)
+	       i.e. "(rhs1 | rhs2)" is false
+	       then both rhs1 and rhs2 must be false.  */
+	    tree rhs1 = gimple_assign_rhs1 (assign);
+	    tree rhs2 = gimple_assign_rhs2 (assign);
+	    add_constraint (rhs1, EQ_EXPR, boolean_false_node, ctxt);
+	    add_constraint (rhs2, EQ_EXPR, boolean_false_node, ctxt);
+	  }
+      }
+      break;
+
+    case EQ_EXPR:
+    case NE_EXPR:
+      {
+	/* ...and "LHS == (rhs1 OP rhs2)"
+	   then rhs1 OP rhs2 must have the same logical value as LHS.  */
+	tree rhs1 = gimple_assign_rhs1 (assign);
+	tree rhs2 = gimple_assign_rhs2 (assign);
+	if (!is_true)
+	  rhs_code
+	    = invert_tree_comparison (rhs_code, false /* honor_nans */);
+	add_constraint (rhs1, rhs_code, rhs2, ctxt);
+      }
+      break;
+    }
+}
+
+/* Add any constraints for an SSA_NAME defined by CALL
+   where the result OP RHS.  */
+
+void
+region_model2::add_any_constraints_from_gcall (enum tree_code op,
+					       tree rhs,
+					       const gcall *call,
+					       region_model2_context *ctxt)
+{
+  if (gimple_call_builtin_p (call, BUILT_IN_EXPECT)
+      || gimple_call_builtin_p (call, BUILT_IN_EXPECT_WITH_PROBABILITY)
+      || gimple_call_internal_p (call, IFN_BUILTIN_EXPECT))
+    {
+      /* __builtin_expect's return value is its initial argument.  */
+      add_constraint (gimple_call_arg (call, 0), op, rhs, ctxt);
+    }
+}
+#endif
+
+/* Determine what is known about the condition "LHS OP RHS" within
+   this model.
+   Use CTXT for reporting any diagnostics associated with the accesses.  */
+
+tristate
+region_model2::eval_condition (tree lhs,
+			      enum tree_code op,
+			      tree rhs,
+			      region_model2_context *ctxt)
+{
+  /* For now, make no attempt to model constraints on floating-point
+     values.  */
+  if (FLOAT_TYPE_P (TREE_TYPE (lhs)) || FLOAT_TYPE_P (TREE_TYPE (rhs)))
+    return tristate::unknown ();
+
+  return eval_condition (get_rvalue (lhs, ctxt), op, get_rvalue (rhs, ctxt));
+}
+
+/* If SID is a constant value, return the underlying tree constant.
+   Otherwise, return NULL_TREE.  */
+#if 0
+tree
+region_model2::maybe_get_constant (svalue2 *sval) const
+{
+  gcc_assert (!sid.null_p ());
+  svalue2 *sval = get_svalue2 (sid);
+  return sval->maybe_get_constant ();
+}
+#endif
+
+/* Create a new child region2 of the heap (creating the heap region2 if
+   necessary).
+   Return the region2 *of the new child region2.  */
+#if 0
+region2_id
+region_model2::add_new_malloc_region2 ()
+{
+  region2 *heap_reg
+    = get_root_region2 ()->ensure_heap_region2 (this);
+  return add_region2 (new symbolic_region2 (heap_reg, NULL_TREE, true));
+}
+#endif
+
+/* Attempt to return a tree that represents SID, or return NULL_TREE.  */
+#if 0
+tree
+region_model2::get_representative_tree (svalue2 *sval) const
+{
+  if (sid.null_p ())
+    return NULL_TREE;
+
+  /* Find the first region2 that stores the value (e.g. a local) and
+     generate a representative tree for it.  */
+  unsigned i;
+  region2 *region2;
+  FOR_EACH_VEC_ELT (m_region2s, i, region2)
+    if (sid == region2->get_value_direct ())
+      {
+	path_var pv = get_representative_path_var (region2_id::from_int (i));
+	if (pv.m_tree)
+	  return pv.m_tree;
+      }
+
+  /* Handle string literals and various other pointers.  */
+  svalue2 *sval = get_svalue2 (sid);
+  if (region_svalue2 *ptr_sval = sval->dyn_cast_region_svalue2 ())
+    {
+      region2 *reg = ptr_sval->get_pointee ();
+      path_var pv = get_representative_path_var (rid);
+      if (pv.m_tree)
+	return build1 (ADDR_EXPR,
+		       TREE_TYPE (sval->get_type ()),
+		       pv.m_tree);
+    }
+
+  return maybe_get_constant (sid);
+}
+#endif
+/* Attempt to return a path_var that represents the region2, or return
+   the NULL path_var.
+   For example, a region2 for a field of a local would be a path_var
+   wrapping a COMPONENT_REF.  */
+#if 0
+path_var
+region_model2::get_representative_path_var (region2 *reg) const
+{
+  region2 *reg = get_region2 (rid);
+  region2 *parent_reg = get_region2 (reg->get_parent ());
+  region2 *stack_reg = get_stack_region2 *();
+  if (!stack_reg.null_p ())
+    if (parent_reg && parent_reg->get_parent () == stack_reg)
+      {
+	frame_region2 *parent_frame = (frame_region2 *)parent_reg;
+	tree t = parent_frame->get_tree_for_child_region2 (rid);
+	return path_var (t, parent_frame->get_depth ());
+    }
+  if (reg->get_parent () == get_globals_region2 *())
+    {
+      map_region2 *globals = get_root_region2 ()->get_globals_region2 (this);
+      if (globals)
+	return path_var (globals->get_tree_for_child_region2 (rid), -1);
+    }
+
+  /* Handle e.g. fields of a local by recursing.  */
+  region2 *parent_reg = reg->get_parent ();
+  if (parent_reg)
+    {
+      if (reg->is_view_p ())
+	{
+	  path_var parent_pv = get_representative_path_var (parent_reg);
+	  if (parent_pv.m_tree && reg->get_type ())
+	    return path_var (build1 (NOP_EXPR,
+				     reg->get_type (),
+				     parent_pv.m_tree),
+			     parent_pv.m_stack_depth);
+	}
+
+      if (parent_reg->get_kind () == RK_STRUCT)
+	{
+	  map_region2 *parent_map_region2 = (map_region2 *)parent_reg;
+	  /* This can fail if we have a view, rather than a field.  */
+	  if (tree child_key
+		= parent_map_region2->get_tree_for_child_region2 (rid))
+	    {
+	      path_var parent_pv = get_representative_path_var (parent_reg);
+	      if (parent_pv.m_tree && TREE_CODE (child_key) == FIELD_DECL)
+		return path_var (build3 (COMPONENT_REF,
+					 TREE_TYPE (child_key),
+					 parent_pv.m_tree, child_key,
+					 NULL_TREE),
+				 parent_pv.m_stack_depth);
+	    }
+	}
+
+      /* Handle elements within an array.  */
+      if (array_region2 *array_reg = parent_reg->dyn_cast_array_region2 ())
+      {
+	array_region2::key_t key;
+	if (array_reg->get_key_for_child_region2 (rid, &key))
+	  {
+	    path_var parent_pv = get_representative_path_var (parent_reg);
+	    if (parent_pv.m_tree && reg->get_type ())
+	      {
+		tree index = array_reg->constant_from_key (key);
+		return path_var (build4 (ARRAY_REF,
+					 reg->get_type (),
+					 parent_pv.m_tree, index,
+					 NULL_TREE, NULL_TREE),
+				 parent_pv.m_stack_depth);
+	      }
+	  }
+      }
+    }
+
+  /* Handle string literals.  */
+  svalue2 *sval = reg->get_value_direct ();
+  if (svalue2 *sval = get_svalue2 (sid))
+    if (tree cst = sval->maybe_get_constant ())
+      if (TREE_CODE (cst) == STRING_CST)
+	return path_var (cst, 0);
+
+  return path_var (NULL_TREE, 0);
+}
+#endif
+/* Locate all region2s that directly have value SID and append representative
+   path_var instances for them into *OUT.  */
+#if 0
+void
+region_model2::get_path_vars_for_svalue2 (svalue2 *sval, vec<path_var> *out) const
+{
+  unsigned i;
+  region2 *region2;
+  FOR_EACH_VEC_ELT (m_region2s, i, region2)
+    if (sid == region2->get_value_direct ())
+      {
+	path_var pv = get_representative_path_var (region2_id::from_int (i));
+	if (pv.m_tree)
+	  out->safe_push (pv);
+      }
+}
+#endif
+/* Set DST_REG value to be a new unknown value of type TYPE.  */
+#if 0
+svalue2_id
+region_model2::set_to_new_unknown_value (region2 *dst_reg, tree type,
+					region_model2_context *ctxt)
+{
+  gcc_assert (!dst_reg.null_p ());
+  svalue2 *new_sval = add_svalue2 (new unknown_svalue2 (type));
+  set_value (dst_reg, new_sval, ctxt);
+
+  // TODO: presumably purge all child region2s too (but do this in set_value?)
+
+  return new_sval;
+}
+#endif
+
+/* Update this model for any phis in SNODE, assuming we came from
+   LAST_CFG_SUPEREDGE.  */
+#if 0
+void
+region_model2::update_for_phis (const supernode *snode,
+			       const cfg_superedge *last_cfg_superedge,
+			       region_model2_context *ctxt)
+{
+  gcc_assert (last_cfg_superedge);
+
+  for (gphi_iterator gpi = const_cast<supernode *>(snode)->start_phis ();
+       !gsi_end_p (gpi); gsi_next (&gpi))
+    {
+      gphi *phi = gpi.phi ();
+
+      tree src = last_cfg_superedge->get_phi_arg (phi);
+      tree lhs = gimple_phi_result (phi);
+
+      /* Update next_state based on phi.  */
+      bool is_back_edge = last_cfg_superedge->back_edge_p ();
+      handle_phi (phi, lhs, src, is_back_edge, ctxt);
+    }
+}
+#endif
+/* Attempt to update this model for taking EDGE (where the last statement
+   was LAST_STMT), returning true if the edge can be taken, false
+   otherwise.
+
+   For CFG superedges where LAST_STMT is a conditional or a switch
+   statement, attempt to add the relevant conditions for EDGE to this
+   model, returning true if they are feasible, or false if they are
+   impossible.
+
+   For call superedges, push frame information and store arguments
+   into parameters.
+
+   For return superedges, pop frame information and store return
+   values into any lhs.
+
+   Rejection of call/return superedges happens elsewhere, in
+   program_point::on_edge (i.e. based on program point, rather
+   than program state).  */
+
+bool
+region_model2::maybe_update_for_edge (const superedge &edge,
+				      const gimple *last_stmt,
+				      region_model2_context *ctxt)
+{
+  /* Handle frame updates for interprocedural edges.  */
+  switch (edge.m_kind)
+    {
+    default:
+      break;
+
+    case SUPEREDGE_CALL:
+      {
+	const call_superedge *call_edge = as_a <const call_superedge *> (&edge);
+	update_for_call_superedge (*call_edge, ctxt);
+      }
+      break;
+
+    case SUPEREDGE_RETURN:
+      {
+	const return_superedge *return_edge
+	  = as_a <const return_superedge *> (&edge);
+	update_for_return_superedge (*return_edge, ctxt);
+      }
+      break;
+
+    case SUPEREDGE_INTRAPROCEDURAL_CALL:
+      {
+	const callgraph_superedge *cg_sedge
+	  = as_a <const callgraph_superedge *> (&edge);
+	update_for_call_summary (*cg_sedge, ctxt);
+      }
+      break;
+    }
+
+  if (last_stmt == NULL)
+    return true;
+
+  /* Apply any constraints for conditionals/switch statements.  */
+
+  if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
+    {
+      const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
+      return apply_constraints_for_gcond (*cfg_sedge, cond_stmt, ctxt);
+    }
+
+  if (const gswitch *switch_stmt = dyn_cast <const gswitch *> (last_stmt))
+    {
+      const switch_cfg_superedge *switch_sedge
+	= as_a <const switch_cfg_superedge *> (&edge);
+      return apply_constraints_for_gswitch (*switch_sedge, switch_stmt, ctxt);
+    }
+
+  return true;
+}
+
+/* Push a new frame_region2 on to the stack region2.
+   Populate the frame_region2 with child region2s for the function call's
+   parameters, using values from the arguments at the callsite in the
+   caller's frame.  */
+#if 1
+void
+region_model2::update_for_call_superedge (const call_superedge &call_edge,
+					  region_model2_context *ctxt)
+{
+  /* Build a vec of argument svalue2_id, using the current top
+     frame for resolving tree expressions.  */
+  const gcall *call_stmt = call_edge.get_call_stmt ();
+  auto_vec<svalue2 *> arg_svals (gimple_call_num_args (call_stmt));
+
+  for (unsigned i = 0; i < gimple_call_num_args (call_stmt); i++)
+    {
+      tree arg = gimple_call_arg (call_stmt, i);
+      arg_svals.quick_push (get_rvalue (arg, ctxt));
+    }
+
+  push_frame (call_edge.get_callee_function (), &arg_svals, ctxt);
+}
+#endif
+/* Pop the top-most frame_region2 from the stack, and copy the return
+   region2's values (if any) into the region2 for the lvalue of the LHS of
+   the call (if any).  */
+#if 0
+void
+region_model2::update_for_return_superedge (const return_superedge &return_edge,
+					   region_model2_context *ctxt)
+{
+  region2 *stack_reg = get_stack_region2 *();
+  stack_region2 *stack = get_region2 <stack_region2> (stack_reg);
+
+  /* Get the region2 for the result of the call, within the caller frame.  */
+  region2 *result_dst_reg;
+  const gcall *call_stmt = return_edge.get_call_stmt ();
+  tree lhs = gimple_call_lhs (call_stmt);
+  if (lhs)
+    {
+      /* Normally we access the top-level frame, which is:
+	   path_var (expr, stack->get_num_frames () - 1)
+	 whereas here we need the caller frame, hence "- 2" here.  */
+      gcc_assert (stack->get_num_frames () >= 2);
+      result_dst_reg = get_lvalue (path_var (lhs, stack->get_num_frames () - 2),
+				   ctxt);
+    }
+
+  purge_stats stats;
+  stack->pop_frame (this, result_dst_reg, true, &stats, ctxt);
+  // TODO: do something with the stats?
+
+  if (!lhs)
+    {
+      /* This could be a leak; try purging again, but this time,
+	 don't special-case the result sids (as was done in pop_frame).  */
+      purge_unused_svalue2s (&stats, ctxt);
+    }
+}
+#endif
+/* Update this region_model2 with a summary of the effect of calling
+   and returning from CG_SEDGE.
+
+   TODO: Currently this is extremely simplistic: we merely set the
+   return value to "unknown".  A proper implementation would e.g. update
+   sm-state, and presumably be reworked to support multiple outcomes.  */
+#if 0
+void
+region_model2::update_for_call_summary (const callgraph_superedge &cg_sedge,
+				       region_model2_context *ctxt)
+{
+  /* For now, set any return value to "unknown".  */
+  const gcall *call_stmt = cg_sedge.get_call_stmt ();
+  tree lhs = gimple_call_lhs (call_stmt);
+  if (lhs)
+    set_to_new_unknown_value (get_lvalue (lhs, ctxt), TREE_TYPE (lhs), ctxt);
+
+  // TODO: actually implement some kind of summary here
+}
+#endif
+/* Given a true or false edge guarded by conditional statement COND_STMT,
+   determine appropriate constraints for the edge to be taken.
+
+   If they are feasible, add the constraints and return true.
+
+   Return false if the constraints contradict existing knowledge
+   (and so the edge should not be taken).  */
+
+bool
+region_model2::apply_constraints_for_gcond (const cfg_superedge &sedge,
+					   const gcond *cond_stmt,
+					   region_model2_context *ctxt)
+{
+  ::edge cfg_edge = sedge.get_cfg_edge ();
+  gcc_assert (cfg_edge != NULL);
+  gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE));
+
+  enum tree_code op = gimple_cond_code (cond_stmt);
+  tree lhs = gimple_cond_lhs (cond_stmt);
+  tree rhs = gimple_cond_rhs (cond_stmt);
+  if (cfg_edge->flags & EDGE_FALSE_VALUE)
+    op = invert_tree_comparison (op, false /* honor_nans */);
+  return add_constraint (lhs, op, rhs, ctxt);
+}
+
+/* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
+   for the edge to be taken.
+
+   If they are feasible, add the constraints and return true.
+
+   Return false if the constraints contradict existing knowledge
+   (and so the edge should not be taken).  */
+
+bool
+region_model2::apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
+					     const gswitch *switch_stmt,
+					     region_model2_context *ctxt)
+{
+  tree index  = gimple_switch_index (switch_stmt);
+  tree case_label = edge.get_case_label ();
+  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+  tree lower_bound = CASE_LOW (case_label);
+  tree upper_bound = CASE_HIGH (case_label);
+  if (lower_bound)
+    {
+      if (upper_bound)
+	{
+	  /* Range.  */
+	  if (!add_constraint (index, GE_EXPR, lower_bound, ctxt))
+	    return false;
+	  return add_constraint (index, LE_EXPR, upper_bound, ctxt);
+	}
+      else
+	/* Single-value.  */
+	return add_constraint (index, EQ_EXPR, lower_bound, ctxt);
+    }
+  else
+    {
+      /* The default case.
+	 Add exclusions based on the other cases.  */
+      for (unsigned other_idx = 1;
+	   other_idx < gimple_switch_num_labels (switch_stmt);
+	   other_idx++)
+	{
+	  tree other_label = gimple_switch_label (switch_stmt,
+						  other_idx);
+	  tree other_lower_bound = CASE_LOW (other_label);
+	  tree other_upper_bound = CASE_HIGH (other_label);
+	  gcc_assert (other_lower_bound);
+	  if (other_upper_bound)
+	    {
+	      /* Exclude this range-valued case.
+		 For now, we just exclude the boundary values.
+		 TODO: exclude the values within the region2.  */
+	      if (!add_constraint (index, NE_EXPR, other_lower_bound, ctxt))
+		return false;
+	      if (!add_constraint (index, NE_EXPR, other_upper_bound, ctxt))
+		return false;
+	    }
+	  else
+	    /* Exclude this single-valued case.  */
+	    if (!add_constraint (index, NE_EXPR, other_lower_bound, ctxt))
+	      return false;
+	}
+      return true;
+    }
+}
+
+/* Get the root_region2 within this model (guaranteed to be non-null).  */
+#if 0
+root_region2 *
+region_model2::get_root_region2 () const
+{
+  return get_region2<root_region2> (m_root_reg);
+}
+
+/* Get the region2 *of this model's stack region2 (if any).  */
+
+region2_id
+region_model2::get_stack_region2 *() const
+{
+  return get_root_region2 ()->get_stack_region2 *();
+}
+#endif
+
+/* Create a new frame_region2 for a call to FUN and push it onto
+   the stack.
+
+   If ARG_SVALS is non-NULL, use it to populate the parameters
+   in the new frame.
+   Otherwise, populate them with unknown values.
+
+   Return the region2 *of the new frame_region2.  */
+#if 0
+region2 *
+region_model2::push_frame (function *fun, vec<svalue2_id> *arg_svals,
+			  region_model2_context *ctxt)
+{
+  return get_root_region2 ()->push_frame (this, fun, arg_svals, ctxt);
+}
+
+/* Get the region2 *of the top-most frame in this region_model2's stack,
+   if any.  */
+
+region2_id
+region_model2::get_current_frame *() const
+{
+  return get_root_region2 ()->get_current_frame *(*this);
+}
+#endif
+/* Get the function of the top-most frame in this region_model2's stack.
+   There must be such a frame.  */
+
+function *
+region_model2::get_current_function () const
+{
+  frame_region2 *frame = get_current_frame ();
+  return frame->get_function ();
+}
+
+/* Pop the topmost frame_region2 from this region_model2's stack;
+   see the comment for stack_region2::pop_frame.  */
+#if 0
+void
+region_model2::pop_frame (region2 *result_dst_reg,
+			 bool purge, purge_stats *out,
+			 region_model2_context *ctxt)
+{
+  get_root_region2 ()->pop_frame (this, result_dst_reg, purge, out, ctxt);
+}
+#endif
+
+/* Get the number of frames in this region_model2's stack.  */
+#if 0
+int
+region_model2::get_stack_depth () const
+{
+  stack_region2 *stack = get_root_region2 ()->get_stack_region2 (this);
+  if (stack)
+    return stack->get_num_frames ();
+  else
+    return 0;
+}
+#endif
+
+/* Get the function * at DEPTH within the call stack.  */
+#if 0
+function *
+region_model2::get_function_at_depth (unsigned depth) const
+{
+  stack_region2 *stack = get_root_region2 ()->get_stack_region2 (this);
+  gcc_assert (stack);
+  region2 *frame_reg = stack->get_frame_reg (depth);
+  frame_region2 *frame = get_region2 <frame_region2> (frame_reg);
+  return frame->get_function ();
+}
+#endif
+
+/* Get the region2 *of this model's globals region2 (if any).  */
+#if 0
+region2_id
+region_model2::get_globals_region2 *() const
+{
+  return get_root_region2 ()->get_globals_region2 *();
+}
+#endif
+
+/* Change the meaning of SID to be NEW_SVAL
+   (e.g. when deferencing an unknown pointer, the pointer
+   becomes a pointer to a symbolic region2, so that all users
+   of the former unknown pointer are now effectively pointing
+   at the same region2).  */
+#if 0
+void
+region_model2::replace_svalue2 (svalue2 *sval, svalue2 *new_sval)
+{
+  gcc_assert (!sid.null_p ());
+  int idx = sid.as_int ();
+
+  gcc_assert (m_svalue2s[idx]);
+  gcc_assert (m_svalue2s[idx]->get_type () == new_sval->get_type ());
+  delete m_svalue2s[idx];
+
+  m_svalue2s[idx] = new_sval;
+}
+#endif
+
+/* Make a region2 of an appropriate subclass for TYPE,
+   with parent PARENT_REG, or return NULL for types we don't yet know
+   how to handle.  */
+#if 0
+static region2 *
+make_region2_for_type (region2 *parent_reg, tree type)
+{
+  gcc_assert (TYPE_P (type));
+
+  if (INTEGRAL_TYPE_P (type)
+      || SCALAR_FLOAT_TYPE_P (type)
+      || POINTER_TYPE_P (type)
+      || TREE_CODE (type) == COMPLEX_TYPE
+      || TREE_CODE (type) == VECTOR_TYPE)
+    return new primitive_region2 (parent_reg, type);
+
+  if (TREE_CODE (type) == RECORD_TYPE)
+    return new struct_region2 (parent_reg, type);
+
+  if (TREE_CODE (type) == ARRAY_TYPE)
+    return new array_region2 (parent_reg, type);
+
+  if (TREE_CODE (type) == UNION_TYPE)
+    return new union_region2 (parent_reg, type);
+
+  if (FUNC_OR_METHOD_TYPE_P (type))
+    return new function_region2 (parent_reg, type);
+
+  /* If we have a void *, make a new symbolic region2.  */
+  if (VOID_TYPE_P (type))
+    return new symbolic_region2 (parent_reg, type, false);
+
+  return NULL;
+}
+#endif
+/* Add a region2 with type TYPE and parent PARENT_REG.  */
+#if 0
+region2_id
+region_model2::add_region2_for_type (region2 *parent_reg, tree type,
+				   region_model2_context *ctxt)
+{
+  gcc_assert (TYPE_P (type));
+
+  if (region2 *new_region2 = make_region2_for_type (parent_reg, type))
+    return add_region2 (new_region2);
+
+  /* If we can't handle TYPE, return a placeholder region2, and stop
+     exploring this path.  */
+  return make_region2_for_unexpected_tree_code (ctxt, type,
+					       dump_location_t ());
+}
+#endif
+
+/* Helper class for region_model2::purge_unused_svalue2s.  */
+
+#if 0
+class restrict_to_used_svalue2s : public purge_criteria
+{
+public:
+  restrict_to_used_svalue2s (const auto_sbitmap &used) : m_used (used) {}
+
+  bool should_purge_p (svalue2 *sval) const FINAL OVERRIDE
+  {
+    gcc_assert (!sid.null_p ());
+    return !bitmap_bit_p (m_used, sid.as_int ());
+  }
+
+private:
+  const auto_sbitmap &m_used;
+};
+
+/* Remove unused svalue2s from this model, accumulating stats into STATS.
+   Unused svalue2s are deleted.  Doing so could reorder the svalue2s, and
+   thus change the meaning of svalue2_ids.
+
+   If CTXT is non-NULL, then it is notified about svalue2 *remappings,
+   and about svalue2_ids that are about to be deleted.  This allows e.g.
+   for warning about resource leaks, for the case where the svalue2
+   represents a resource handle in the user code (e.g. a FILE * or a malloc
+   buffer).
+
+   Amongst other things, removing unused svalue2s is important for ensuring
+   that the analysis of loops terminates.  Otherwise, we could generate a
+   succession of models with unreferenced "unknown" values, where the
+   number of redundant unknown values could grow without bounds, and each
+   such model would be treated as distinct.
+
+   If KNOWN_USED_SVALS is non-NULL, treat *KNOWN_USED_SVALS as used (this is for
+   handling values being returned from functions as their frame is popped,
+   since otherwise we'd have to simultaneously determine both the rvalue
+   of the return expr in the callee frame and the lvalue for the gcall's
+   assignment in the caller frame, and it seems cleaner to express all
+   lvalue and rvalue lookups implicitly relative to a "current" frame).
+   The svalue2_ids in *KNOWN_USED_SVALS are not remapped and hence this
+   call makes it invalid.  */
+
+void
+region_model2::purge_unused_svalue2s (purge_stats *stats,
+				    region_model2_context *ctxt,
+				    svalue2_id_set *known_used_svals)
+{
+  // TODO: might want to avoid a vfunc call just to do logging here:
+  logger *logger = ctxt ? ctxt->get_logger () : NULL;
+
+  LOG_SCOPE (logger);
+
+  auto_sbitmap used (m_svalue2s.length ());
+  bitmap_clear (used);
+
+  if (known_used_svals)
+    {
+      /* We can't use an sbitmap for known_used_svals as the number of
+	 svalue2s could have grown since it was created.  */
+      for (unsigned i = 0; i < get_num_svalue2s (); i++)
+	if (known_used_svals->svalue2_p (svalue2_id::from_int (i)))
+	  bitmap_set_bit (used, i);
+    }
+
+  /* Walk the region2s, marking sids that are used.  */
+  unsigned i;
+  region2 *r;
+  FOR_EACH_VEC_ELT (m_region2s, i, r)
+    {
+      svalue2 *sval = r->get_value_direct ();
+      if (!sid.null_p ())
+	bitmap_set_bit (used, sid.as_int ());
+    }
+
+  /* Now purge any constraints involving svalue2s we don't care about.  */
+  restrict_to_used_svalue2s criterion (used);
+  m_constraints->purge (criterion, stats);
+
+  /* Mark any sids that are in constraints that survived.  */
+  {
+    equiv_class *ec;
+    FOR_EACH_VEC_ELT (m_constraints->m_equiv_classes, i, ec)
+      {
+	int j;
+	svalue2 **sid;
+	FOR_EACH_VEC_ELT (ec->m_vars, j, sid)
+	  {
+	    gcc_assert (!sid->null_p ());
+	    bitmap_set_bit (used, sid->as_int ());
+	  }
+      }
+  }
+
+  /* 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 svalue2 IDs, adding used ones to the front of
+     the new list, and unused ones to the back.  */
+  svalue2_id_map map (m_svalue2s.length ());
+  int next_used_new_sval = 0;
+  int after_next_unused_new_sval = m_svalue2s.length ();
+  for (unsigned i = 0; i < m_svalue2s.length (); i++)
+    {
+      svalue2 *src (svalue2_id::from_int (i));
+      if (bitmap_bit_p (used, i))
+	{
+	  if (logger)
+	    logger->log ("sv%i is used", i);
+	  map.put (src, svalue2_id::from_int (next_used_new_sval++));
+	}
+      else
+	{
+	  if (logger)
+	    logger->log ("sv%i is unused", i);
+	  map.put (src, svalue2_id::from_int (--after_next_unused_new_sval));
+	}
+    }
+  /* The two insertion points should have met.  */
+  gcc_assert (next_used_new_sval == after_next_unused_new_sval);
+
+  /* Now walk the region2s and the constraints, remapping sids,
+     so that all the redundant svalue2s are at the end.  */
+  remap_svalue2_ids (map);
+
+  if (logger)
+    {
+      logger->start_log_line ();
+      logger->log_partial ("map: ");
+      map.dump_to_pp (logger->get_printer ());
+      logger->end_log_line ();
+    }
+
+  /* Notify any client about the remapping and pending deletion.
+     Potentially this could trigger leak warnings.  */
+  if (ctxt)
+    {
+      ctxt->remap_svalue2_ids (map);
+      int num_client_items_purged
+	= ctxt->on_svalue2_purge (svalue2_id::from_int (next_used_new_sval), map);
+      if (stats)
+	stats->m_num_client_items += num_client_items_purged;
+    }
+
+  /* Drop the redundant svalue2s from the end of the vector.  */
+  while ((signed)m_svalue2s.length () > next_used_new_sval)
+    {
+      if (logger)
+	{
+	  svalue2 *victim = svalue2_id::from_int (m_svalue2s.length () - 1);
+	  logger->log ("deleting sv%i (was sv%i)",
+		       victim.as_int (),
+		       map.get_src_for_dst (victim).as_int ());
+	}
+      delete m_svalue2s.pop ();
+      if (stats)
+	stats->m_num_svalue2s++;
+    }
+
+  validate ();
+}
+#endif
+
+/* Delete all region2s within SET_TO_PURGE, remapping region2 IDs for
+   other region2s.  It's required that there are no uses of the
+   region2s within the set (or the region2 IDs will become invalid).
+
+   Accumulate stats to STATS.  */
+#if 0
+void
+region_model2::purge_region2s (const region2_id_set &set_to_purge,
+			     purge_stats *stats,
+			     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.
+     Iterate though region2 IDs, adding used ones to the front of
+     the new list, and unused ones to the back.  */
+  region2_id_map map (m_region2s.length ());
+  int next_used_new_reg = 0;
+  int after_next_unused_new_reg = m_region2s.length ();
+  for (unsigned i = 0; i < m_region2s.length (); i++)
+    {
+      region2 *src (region2_id::from_int (i));
+      if (set_to_purge.region2_p (src))
+	map.put (src, region2_id::from_int (--after_next_unused_new_reg));
+      else
+	map.put (src, region2_id::from_int (next_used_new_reg++));
+    }
+  /* The two insertion points should have met.  */
+  gcc_assert (next_used_new_reg == after_next_unused_new_reg);
+
+  /* Now walk the region2s and svalue2s, remapping rids,
+     so that all the redundant region2s are at the end.  */
+  remap_region2_ids (map);
+
+  /* Drop the redundant region2s from the end of the vector.  */
+  while ((signed)m_region2s.length () > next_used_new_reg)
+    {
+      delete m_region2s.pop ();
+      if (stats)
+	stats->m_num_region2s++;
+    }
+}
+#endif
+/* Populate *OUT with RID and all of its descendents.
+   If EXCLUDE_REG is non-null, then don't add it or its descendents.  */
+#if 0
+void
+region_model2::get_descendents (region2 *reg, region2_id_set *out,
+			       region2 *exclude_reg) const
+{
+  out->add_region2 (rid);
+
+  bool changed = true;
+  while (changed)
+    {
+      changed = false;
+      unsigned i;
+      region2 *r;
+      FOR_EACH_VEC_ELT (m_region2s, i, r)
+	{
+	  region2 *iter_reg = region2_id::from_int (i);
+	  if (iter_reg == exclude_reg)
+	    continue;
+	  if (!out->region2_p (iter_reg))
+	    {
+	      region2 *parent_reg = r->get_parent ();
+	      if (!parent_reg.null_p ())
+		if (out->region2_p (parent_reg))
+		  {
+		    out->add_region2 (iter_reg);
+		    changed = true;
+		  }
+	    }
+	}
+    }
+}
+#endif
+/* Delete RID and all descendent region2s.
+   Find any pointers to such region2s; convert them to
+   poisoned values of kind PKIND.
+   Accumulate stats on purged entities into STATS.  */
+#if 0
+void
+region_model2::delete_region2_and_descendents (region2 *reg,
+					     enum poison_kind pkind,
+					     purge_stats *stats,
+					     logger *logger)
+{
+  /* Find all child and descendent region2s.  */
+  region2_id_set descendents (this);
+  get_descendents (rid, &descendents, region2_id::null ());
+
+  /* Find any pointers to such region2s; convert to poisoned.  */
+  poison_any_pointers_to_bad_region2s (descendents, pkind);
+
+  /* Delete all such region2s.  */
+  purge_region2s (descendents, stats, logger);
+}
+#endif
+/* Find any pointers to region2s within BAD_REGION2S; convert them to
+   poisoned values of kind PKIND.  */
+#if 0
+void
+region_model2::poison_any_pointers_to_bad_region2s (const region2_id_set &
+						    bad_region2s,
+						  enum poison_kind pkind)
+{
+  int i;
+  svalue2 *sval;
+  FOR_EACH_VEC_ELT (m_svalue2s, i, sval)
+    if (region_svalue2 *ptr_sval = sval->dyn_cast_region_svalue2 ())
+      {
+	region2 *ptr_dst = ptr_sval->get_pointee ();
+	if (!ptr_dst.null_p ())
+	  if (bad_region2s.region2_p (ptr_dst))
+	    replace_svalue2
+	      (svalue2_id::from_int (i),
+	       new poisoned_svalue2 (pkind, sval->get_type ()));
+      }
+}
+#endif
+/* Attempt to merge THIS with OTHER_MODEL, writing the result
+   to OUT_MODEL, and populating SID_MAPPING.  */
+#if 0
+bool
+region_model2::can_merge_with_p (const region_model2 &other_model,
+				region_model2 *out_model,
+				svalue2_id_merger_mapping *sid_mapping) const
+{
+  gcc_assert (m_root_reg == other_model.m_root_reg);
+  gcc_assert (m_root_reg.as_int () == 0);
+  gcc_assert (sid_mapping);
+  gcc_assert (out_model);
+
+  model_merger merger (this, &other_model, out_model, sid_mapping);
+
+  if (!root_region2::can_merge_p (get_root_region2 (),
+				 other_model.get_root_region2 (),
+				 out_model->get_root_region2 (),
+				 &merger))
+    return false;
+
+  /* Merge constraints.  */
+  constraint_manager::merge (*m_constraints,
+			     *other_model.m_constraints,
+			     out_model->m_constraints,
+			     merger);
+
+  out_model->validate ();
+
+  /* The merged model should be simpler (or as simple) as the inputs.  */
+#if 0
+  gcc_assert (out_model->m_svalue2s.length () <= m_svalue2s.length ());
+  gcc_assert (out_model->m_svalue2s.length ()
+	      <= other_model.m_svalue2s.length ());
+#endif
+  gcc_assert (out_model->m_region2s.length () <= m_region2s.length ());
+  gcc_assert (out_model->m_region2s.length ()
+	      <= other_model.m_region2s.length ());
+  // TODO: same, for constraints
+
+  return true;
+}
+
+/* As above, but supply a placeholder svalue2_id_merger_mapping
+   instance to be used and receive output.  For use in selftests.  */
+
+bool
+region_model2::can_merge_with_p (const region_model2 &other_model,
+				region_model2 *out_model) const
+{
+  svalue2_id_merger_mapping sid_mapping (*this, other_model);
+  return can_merge_with_p (other_model, out_model, &sid_mapping);
+}
+#endif
+/* For debugging purposes: look for a region2 within this region_model2
+   for a decl named NAME (or an SSA_NAME for such a decl),
+   returning its value, or svalue2_id::null if none are found.  */
+#if 0
+svalue2_id
+region_model2::get_value_by_name (const char *name) const
+{
+  gcc_assert (name);
+  tree identifier = get_identifier (name);
+  return get_root_region2 ()->get_value_by_name (identifier, *this);
+}
+#endif
+/* Generate or reuse an svalue2 *within this model for an index
+   into an array of type PTR_TYPE, based on OFFSET_SVAL.  */
+#if 0
+svalue2_id
+region_model2::convert_byte_offset_to_array_index (tree ptr_type,
+						  svalue2 *offset_sval)
+{
+  gcc_assert (POINTER_TYPE_P (ptr_type));
+
+  if (tree offset_cst = maybe_get_constant (offset_sval))
+    {
+      tree elem_type = TREE_TYPE (ptr_type);
+
+      /* Arithmetic on void-pointers is a GNU C extension, treating the size
+	 of a void as 1.
+	 https://gcc.gnu.org/onlinedocs/gcc/Pointer-Arith.html  */
+      if (TREE_CODE (elem_type) == VOID_TYPE)
+	return offset_sval;
+
+      /* First, use int_size_in_bytes, to reject the case where we have an
+	 incomplete type, or a non-constant value.  */
+      HOST_WIDE_INT hwi_byte_size = int_size_in_bytes (elem_type);
+      if (hwi_byte_size > 0)
+	{
+	  /* Now call size_in_bytes to get the answer in tree form.  */
+	  tree byte_size = size_in_bytes (elem_type);
+	  gcc_assert (byte_size);
+	  /* Try to get a constant by dividing, ensuring that we're in a
+	     signed representation first.  */
+	  tree index
+	    = fold_binary (TRUNC_DIV_EXPR, ssizetype,
+			   fold_convert (ssizetype, offset_cst),
+			   fold_convert (ssizetype, byte_size));
+	  if (index && TREE_CODE (index) == INTEGER_CST)
+	    return get_or_create_constant_svalue2 (index);
+	}
+    }
+
+  /* Otherwise, we don't know the array index; generate a new unknown value.
+     TODO: do we need to capture the relationship between two unknown
+     values (the offset and the index)?  */
+  return add_svalue2 (new unknown_svalue2 (integer_type_node));
+}
+
+/* Get a region2 of type TYPE for PTR_SVAL[OFFSET_SVAL/sizeof (*PTR_SVAL)].
+
+   If OFFSET_SVAL is known to be zero, then dereference PTR_SVAL.
+   Otherwise, impose a view of "typeof(*PTR_SVAL)[]" on *PTR_SVAL,
+   and then get a view of type TYPE on the relevant array element.  */
+
+region2_id
+region_model2::get_or_create_mem_ref (tree type,
+				     svalue2 *ptr_sval,
+				     svalue2 *offset_sval,
+				     region_model2_context *ctxt)
+{
+  svalue2 *ptr_sval = get_svalue2 (ptr_sval);
+  tree ptr_type = ptr_sval->get_type ();
+  gcc_assert (ptr_type);
+
+  region2 *raw_reg = deref_rvalue (ptr_sval, ctxt);
+
+  svalue2 *offset_sval = get_svalue2 (offset_sval);
+  tree offset_type = offset_sval->get_type ();
+  gcc_assert (offset_type);
+
+  if (constant_svalue2 *cst_sval = offset_sval->dyn_cast_constant_svalue2 ())
+    {
+      if (zerop (cst_sval->get_constant ()))
+	{
+	  /* Handle the zero offset case.  */
+	  return get_or_create_view (raw_reg, type, ctxt);
+	}
+
+      /* If we're already within an array of the correct type,
+	 then we want to reuse that array, rather than starting
+	 a new view.
+	 If so, figure out our raw_reg's offset from its parent,
+	 if we can, and use that to offset OFFSET_SVAL, and create
+	 the element within the parent region2.  */
+      region2 *raw_reg = get_region2 (raw_reg);
+      region2 *parent_reg = raw_reg->get_parent ();
+      tree parent_type = get_region2 (parent_reg)->get_type ();
+      if (parent_type
+	  && TREE_CODE (parent_type) == ARRAY_TYPE)
+	{
+	  // TODO: check we have the correct parent type
+	  array_region2 *parent_array = get_region2 <array_region2> (parent_reg);
+	  array_region2::key_t key_for_raw_reg;
+	  if (parent_array->get_key_for_child_region2 (raw_reg,
+						      &key_for_raw_reg))
+	    {
+	      /* Convert from offset to index.  */
+	      svalue2 *index_sval
+		= convert_byte_offset_to_array_index (ptr_type, offset_sval);
+	      if (tree index_cst
+		    = get_svalue2 (index_sval)->maybe_get_constant ())
+		{
+		  array_region2::key_t index_offset
+		    = array_region2::key_from_constant (index_cst);
+		  array_region2::key_t index_rel_to_parent
+		    = key_for_raw_reg + index_offset;
+		  tree index_rel_to_parent_cst
+		    = wide_int_to_tree (integer_type_node,
+					index_rel_to_parent);
+		  svalue2 *index_sval
+		    = get_or_create_constant_svalue2 (index_rel_to_parent_cst);
+
+		  /* Carry on, using the parent region2 and adjusted index.  */
+		  region2 *element_reg
+		    = parent_array->get_element (this, raw_reg, index_sval,
+						 ctxt);
+		  return get_or_create_view (element_reg, type, ctxt);
+		}
+	    }
+	}
+    }
+
+  tree array_type = build_array_type (TREE_TYPE (ptr_type),
+				      integer_type_node);
+  region2 *array_view_reg = get_or_create_view (raw_reg, array_type, ctxt);
+  array_region2 *array_reg = get_region2 <array_region2> (array_view_reg);
+
+  svalue2 *index_sval
+    = convert_byte_offset_to_array_index (ptr_type, offset_sval);
+
+  region2 *element_reg
+    = array_reg->get_element (this, array_view_reg, index_sval, ctxt);
+
+  return get_or_create_view (element_reg, type, ctxt);
+}
+
+/* Get a region2 of type TYPE for PTR_SVAL + OFFSET_SVAL.
+
+   If OFFSET_SVAL is known to be zero, then dereference PTR_SVAL.
+   Otherwise, impose a view of "typeof(*PTR_SVAL)[]" on *PTR_SVAL,
+   and then get a view of type TYPE on the relevant array element.  */
+
+region2_id
+region_model2::get_or_create_pointer_plus_expr (tree type,
+					       svalue2 *ptr_sval,
+					       svalue2 *offset_in_bytes_sval,
+					       region_model2_context *ctxt)
+{
+  return get_or_create_mem_ref (type,
+				ptr_sval,
+				offset_in_bytes_sval,
+				ctxt);
+}
+
+/* Get or create a view of type TYPE of the region2 with id RAW_ID.
+   Return the id of the view (or RAW *if it of the same type).  */
+
+region2_id
+region_model2::get_or_create_view (region2 *raw_reg, tree type,
+				  region_model2_context *ctxt)
+{
+  region2 *raw_region2 = get_region2 (raw_reg);
+
+  gcc_assert (TYPE_P (type));
+  if (type != raw_region2->get_type ())
+    {
+      /* If the region2 already has a view of the requested type,
+	 reuse it.  */
+      region2 *existing_view_reg = raw_region2->get_view (type, this);
+      if (!existing_view_reg.null_p ())
+	return existing_view_reg;
+
+      /* Otherwise, make one (adding it to the region_model2 and
+	 to the viewed region2).  */
+      region2 *view_reg = add_region2_for_type (raw_reg, type, ctxt);
+      raw_region2->add_view (view_reg, this);
+      // TODO: something to signify that this is a "view"
+      return view_reg;
+    }
+
+  return raw_reg;
+}
+#endif
+
+/* Attempt to get the fndecl used at CALL, if known, or NULL_TREE
+   otherwise.  */
+#if 0
+tree
+region_model2::get_fndecl_for_call (const gcall *call,
+				    region_model2_context *ctxt)
+{
+  tree fn_ptr = gimple_call_fn (call);
+  if (fn_ptr == NULL_TREE)
+    return NULL_TREE;
+  svalue2 *fn_ptr_sval = get_rvalue (fn_ptr, ctxt);
+  svalue2 *fn_ptr_sval = get_svalue2 (fn_ptr_sval);
+  if (region_svalue2 *fn_ptr_ptr = fn_ptr_sval->dyn_cast_region_svalue2 ())
+    {
+      region2 *fn_reg = fn_ptr_ptr->get_pointee ();
+      code_region2 *code = get_root_region2 ()->get_code_region2 (this);
+      if (code)
+	{
+	  tree fn_decl = code->get_tree_for_child_region2 (fn_reg);
+	  if (!fn_decl)
+	    return NULL_TREE;
+	  cgraph_node *node = cgraph_node::get (fn_decl);
+	  if (!node)
+	    return NULL_TREE;
+	  const cgraph_node *ultimate_node = node->ultimate_alias_target ();
+	  if (ultimate_node)
+	    return ultimate_node->decl;
+	}
+    }
+
+  return NULL_TREE;
+}
+#endif
+
+/* struct model_merger.  */
+
+/* Dump a multiline representation of this merger to PP.  */
+#if 0
+void
+model_merger::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "model A:");
+  pp_newline (pp);
+  m_model_a->dump_to_pp (pp, false);
+  pp_newline (pp);
+
+  pp_string (pp, "model B:");
+  pp_newline (pp);
+  m_model_b->dump_to_pp (pp, false);
+  pp_newline (pp);
+
+  pp_string (pp, "merged model:");
+  pp_newline (pp);
+  m_merged_model->dump_to_pp (pp, false);
+  pp_newline (pp);
+
+  pp_string (pp, "region2 map: model A to merged model:");
+  pp_newline (pp);
+  m_map_region2s_from_a_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "region2 map: model B to merged model:");
+  pp_newline (pp);
+  m_map_region2s_from_b_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  m_sval_mapping->dump_to_pp (pp);
+}
+
+/* Dump a multiline representation of this merger to FILE.  */
+
+void
+model_merger::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this merger to stderr.  */
+
+DEBUG_FUNCTION void
+model_merger::dump () const
+{
+  dump (stderr);
+}
+
+/* Attempt to merge the svalue2s of SID_A and SID_B (from their
+   respective models), writing the id of the resulting svalue2
+   into *MERGED_SVAL.
+   Return true if the merger is possible, false otherwise.  */
+
+bool
+model_merger::can_merge_values_p (svalue2 *sval_a,
+				  svalue2 *sval_b,
+				  svalue2 **merged_sval)
+{
+  gcc_assert (merged_sval);
+  svalue2 *sval_a = m_model_a->get_svalue2 (sid_a);
+  svalue2 *sval_b = m_model_b->get_svalue2 (sid_b);
+
+  /* If both are NULL, then the "values" are trivially mergeable.  */
+  if (!sval_a && !sval_b)
+    return true;
+
+  /* If one is NULL and the other non-NULL, then the "values"
+     are not mergeable.  */
+  if (!(sval_a && sval_b))
+    return false;
+
+  /* Have they both already been mapped to the same new svalue2_id?
+     If so, use it.  */
+  svalue2 *sval_a_in_m
+    = m_sval_mapping->m_map_from_a_to_m.get_dst_for_src (sid_a);
+  svalue2 *sval_b_in_m
+    = m_sval_mapping->m_map_from_b_to_m.get_dst_for_src (sid_b);
+  if (!sid_a_in_m.null_p ()
+      && !sid_b_in_m.null_p ()
+      && sid_a_in_m == sid_b_in_m)
+    {
+      *merged_sval = sid_a_in_m;
+      return true;
+    }
+
+  tree type = sval_a->get_type ();
+  if (type == NULL_TREE)
+    type = sval_b->get_type ();
+
+  /* If the values have different kinds, or are both unknown,
+     then merge as "unknown".  */
+  if (sval_a->get_kind () != sval_b->get_kind ()
+      || sval_a->get_kind () == SK_UNKNOWN)
+    {
+      svalue2 *merged_sval = new unknown_svalue2 (type);
+      *merged_sval = m_merged_model->add_svalue2 (merged_sval);
+      record_svalue2s (sid_a, sid_b, *merged_sval);
+      return true;
+    }
+
+  gcc_assert (sval_a->get_kind () == sval_b->get_kind ());
+
+  switch (sval_a->get_kind ())
+    {
+    default:
+    case SK_UNKNOWN: /* SK_UNKNOWN handled above.  */
+       gcc_unreachable ();
+
+    case SK_REGION2:
+      {
+	/* If we have two region2 pointers, then we can merge (possibly to
+	   "unknown").  */
+	const region_svalue2 &region2_sval_a = *as_a <region_svalue2 *> (sval_a);
+	const region_svalue2 &region2_sval_b = *as_a <region_svalue2 *> (sval_b);
+	region_svalue2::merge_values (region2_sval_a, region2_sval_b,
+				     merged_sval, type,
+				     this);
+	record_svalue2s (sid_a, sid_b, *merged_sval);
+	return true;
+      }
+      break;
+    case SK_CONSTANT:
+      {
+	/* If we have two constants, then we can merge.  */
+	const constant_svalue2 &cst_sval_a = *as_a <constant_svalue2 *> (sval_a);
+	const constant_svalue2 &cst_sval_b = *as_a <constant_svalue2 *> (sval_b);
+	constant_svalue2::merge_values (cst_sval_a, cst_sval_b,
+				       merged_sval, this);
+	record_svalue2s (sid_a, sid_b, *merged_sval);
+	return true;
+      }
+      break;
+
+    case SK_POISONED:
+    case SK_SETJMP:
+      return false;
+    }
+}
+
+/* Record that A_REG in model A and B_REG in model B
+   correspond to MERGED_REG in the merged model, so
+   that pointers can be accurately merged.  */
+
+void
+model_merger::record_region2s (region2 *a_reg,
+			      region2 *b_reg,
+			      region2 *merged_reg)
+{
+  m_map_region2s_from_a_to_m.put (a_reg, merged_reg);
+  m_map_region2s_from_b_to_m.put (b_reg, merged_reg);
+}
+
+/* Record that A_SVAL in model A and B_SVAL in model B
+   correspond to MERGED_SVAL in the merged model.  */
+
+void
+model_merger::record_svalue2s (svalue2 *a_sval,
+			      svalue2 *b_sval,
+			      svalue2 *merged_sval)
+{
+  gcc_assert (m_sval_mapping);
+  m_sval_mapping->m_map_from_a_to_m.put (a_sval, merged_sval);
+  m_sval_mapping->m_map_from_b_to_m.put (b_sval, merged_sval);
+}
+#endif
+
+/* struct svalue2_id_merger_mapping.  */
+
+#if 0
+/* svalue2_id_merger_mapping's ctor.  */
+
+svalue2_id_merger_mapping::svalue2_id_merger_mapping (const region_model2 &a,
+						    const region_model2 &b)
+: m_map_from_a_to_m (a.get_num_svalue2s ()),
+  m_map_from_b_to_m (b.get_num_svalue2s ())
+{
+}
+
+/* Dump a multiline representation of this to PP.  */
+
+void
+svalue2_id_merger_mapping::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "svalue2 *map: model A to merged model:");
+  pp_newline (pp);
+  m_map_from_a_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+
+  pp_string (pp, "svalue2 *map: model B to merged model:");
+  pp_newline (pp);
+  m_map_from_b_to_m.dump_to_pp (pp);
+  pp_newline (pp);
+}
+
+/* Dump a multiline representation of this to FILE.  */
+
+void
+svalue2_id_merger_mapping::dump (FILE *fp) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = fp;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* Dump a multiline representation of this to stderr.  */
+
+DEBUG_FUNCTION void
+svalue2_id_merger_mapping::dump () const
+{
+  dump (stderr);
+}
+#endif
+
+} // namespace ana
+
+/* Dump RMODEL fully to stderr (i.e. without summarization).  */
+
+DEBUG_FUNCTION void
+debug (const region_model2 &rmodel)
+{
+  rmodel.dump (false);
+}
+
+namespace ana {
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Build a constant tree of the given type from STR.  */
+
+static tree
+build_real_cst_from_string (tree type, const char *str)
+{
+  REAL_VALUE_TYPE real;
+  real_from_string (&real, str);
+  return build_real (type, real);
+}
+
+/* Append various "interesting" constants to OUT (e.g. NaN).  */
+
+static void
+append_interesting_constants (auto_vec<tree> *out)
+{
+  out->safe_push (build_int_cst (integer_type_node, 0));
+  out->safe_push (build_int_cst (integer_type_node, 42));
+  out->safe_push (build_int_cst (unsigned_type_node, 0));
+  out->safe_push (build_int_cst (unsigned_type_node, 42));
+  out->safe_push (build_real_cst_from_string (float_type_node, "QNaN"));
+  out->safe_push (build_real_cst_from_string (float_type_node, "-QNaN"));
+  out->safe_push (build_real_cst_from_string (float_type_node, "SNaN"));
+  out->safe_push (build_real_cst_from_string (float_type_node, "-SNaN"));
+  out->safe_push (build_real_cst_from_string (float_type_node, "0.0"));
+  out->safe_push (build_real_cst_from_string (float_type_node, "-0.0"));
+  out->safe_push (build_real_cst_from_string (float_type_node, "Inf"));
+  out->safe_push (build_real_cst_from_string (float_type_node, "-Inf"));
+}
+
+/* Verify that tree_cmp is a well-behaved comparator for qsort, even
+   if the underlying constants aren't comparable.  */
+
+static void
+test_tree_cmp_on_constants ()
+{
+  auto_vec<tree> csts;
+  append_interesting_constants (&csts);
+
+  /* Try sorting every triple. */
+  const unsigned num = csts.length ();
+  for (unsigned i = 0; i < num; i++)
+    for (unsigned j = 0; j < num; j++)
+      for (unsigned k = 0; k < num; k++)
+	{
+	  auto_vec<tree> v (3);
+	  v.quick_push (csts[i]);
+	  v.quick_push (csts[j]);
+	  v.quick_push (csts[k]);
+	  v.qsort (tree_cmp);
+	}
+}
+
+/* Implementation detail of the ASSERT_CONDITION_* macros.  */
+
+void
+assert_condition (const location &loc,
+		  region_model2 &model,
+		  tree lhs, tree_code op, tree rhs,
+		  tristate expected)
+{
+  tristate actual = model.eval_condition (lhs, op, rhs, NULL);
+  ASSERT_EQ_AT (loc, actual, expected);
+}
+
+/* Implementation detail of ASSERT_DUMP_TREE_EQ.  */
+
+static void
+assert_dump_tree_eq (const location &loc, tree t, const char *expected)
+{
+  auto_fix_quotes sentinel;
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  dump_tree (&pp, t);
+  ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected);
+}
+
+/* Assert that dump_tree (T) is EXPECTED.  */
+
+#define ASSERT_DUMP_TREE_EQ(T, EXPECTED) \
+  SELFTEST_BEGIN_STMT							\
+  assert_dump_tree_eq ((SELFTEST_LOCATION), (T), (EXPECTED)); \
+  SELFTEST_END_STMT
+
+/* Implementation detail of ASSERT_DUMP_EQ.  */
+
+static void
+assert_dump_eq (const location &loc,
+		const region_model2 &model,
+		bool summarize,
+		const char *expected)
+{
+  auto_fix_quotes sentinel;
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  model.dump_to_pp (&pp, summarize);
+  ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected);
+}
+
+/* Assert that MODEL.dump_to_pp (SUMMARIZE) is EXPECTED.  */
+
+#define ASSERT_DUMP_EQ(MODEL, SUMMARIZE, EXPECTED) \
+  SELFTEST_BEGIN_STMT							\
+  assert_dump_eq ((SELFTEST_LOCATION), (MODEL), (SUMMARIZE), (EXPECTED)); \
+  SELFTEST_END_STMT
+
+/* Smoketest for region_model2::dump_to_pp.  */
+#if 0
+static void
+test_dump ()
+{
+  region_model2 model;
+  model.get_root_region2 ()->ensure_stack_region2 (&model);
+  model.get_root_region2 ()->ensure_globals_region2 (&model);
+  model.get_root_region2 ()->ensure_heap_region2 (&model);
+
+  ASSERT_DUMP_EQ (model, false,
+		  "r0: {kind: `root', parent: null, sval: null}\n"
+		  "|-stack: r1: {kind: `stack', parent: r0, sval: sv0}\n"
+		  "|  |: sval: sv0: {poisoned: uninit}\n"
+		  "|-globals: r2: {kind: `globals', parent: r0, sval: null, map: {}}\n"
+		  "`-heap: r3: {kind: `heap', parent: r0, sval: sv1}\n"
+		  "  |: sval: sv1: {poisoned: uninit}\n"
+		  "svalue2s:\n"
+		  "  sv0: {poisoned: uninit}\n"
+		  "  sv1: {poisoned: uninit}\n"
+		  "constraint manager:\n"
+		  "  equiv classes:\n"
+		  "  constraints:\n");
+  ASSERT_DUMP_EQ (model, true, "");
+}
+
+/* Helper function for selftests.  Create a struct or union type named NAME,
+   with the fields given by the FIELD_DECLS in FIELDS.
+   If IS_STRUCT is true create a RECORD_TYPE (aka a struct), otherwise
+   create a UNION_TYPE.  */
+
+static tree
+make_test_compound_type (const char *name, bool is_struct,
+			 const auto_vec<tree> *fields)
+{
+  tree t = make_node (is_struct ? RECORD_TYPE : UNION_TYPE);
+  TYPE_NAME (t) = get_identifier (name);
+  TYPE_SIZE (t) = 0;
+
+  tree fieldlist = NULL;
+  int i;
+  tree field;
+  FOR_EACH_VEC_ELT (*fields, i, field)
+    {
+      gcc_assert (TREE_CODE (field) == FIELD_DECL);
+      DECL_CONTEXT (field) = t;
+      fieldlist = chainon (field, fieldlist);
+    }
+  fieldlist = nreverse (fieldlist);
+  TYPE_FIELDS (t) = fieldlist;
+
+  layout_type (t);
+  return t;
+}
+
+/* Selftest fixture for creating the type "struct coord {int x; int y; };".  */
+
+struct coord_test
+{
+  coord_test ()
+  {
+    auto_vec<tree> fields;
+    m_x_field = build_decl (UNKNOWN_LOCATION, FIELD_DECL,
+			       get_identifier ("x"), integer_type_node);
+    fields.safe_push (m_x_field);
+    m_y_field = build_decl (UNKNOWN_LOCATION, FIELD_DECL,
+			       get_identifier ("y"), integer_type_node);
+    fields.safe_push (m_y_field);
+    m_coord_type = make_test_compound_type ("coord", true, &fields);
+  }
+
+  tree m_x_field;
+  tree m_y_field;
+  tree m_coord_type;
+};
+
+/* Verify that dumps can show struct fields.  */
+
+static void
+test_dump_2 ()
+{
+  coord_test ct;
+
+  tree c = build_global_decl ("c", ct.m_coord_type);
+  tree c_x = build3 (COMPONENT_REF, TREE_TYPE (ct.m_x_field),
+		     c, ct.m_x_field, NULL_TREE);
+  tree c_y = build3 (COMPONENT_REF, TREE_TYPE (ct.m_y_field),
+		     c, ct.m_y_field, NULL_TREE);
+
+  tree int_17 = build_int_cst (integer_type_node, 17);
+  tree int_m3 = build_int_cst (integer_type_node, -3);
+
+  region_model2 model;
+  model.set_value (c_x, int_17, NULL);
+  model.set_value (c_y, int_m3, NULL);
+
+  /* Simplified dump.  */
+  ASSERT_DUMP_EQ (model, true, "c.x: 17, c.y: -3");
+
+  /* Full dump.  */
+  ASSERT_DUMP_EQ
+    (model, false,
+     "r0: {kind: `root', parent: null, sval: null}\n"
+     "`-globals: r1: {kind: `globals', parent: r0, sval: null, map: {`c': r2}}\n"
+     "  `-`c': r2: {kind: `struct', parent: r1, sval: null, type: `struct coord', map: {`x': r3, `y': r4}}\n"
+     "    |: type: `struct coord'\n"
+     "    |-`x': r3: {kind: `primitive', parent: r2, sval: sv0, type: `int'}\n"
+     "    |  |: sval: sv0: {type: `int', `17'}\n"
+     "    |  |: type: `int'\n"
+     "    `-`y': r4: {kind: `primitive', parent: r2, sval: sv1, type: `int'}\n"
+     "      |: sval: sv1: {type: `int', `-3'}\n"
+     "      |: type: `int'\n"
+     "svalue2s:\n"
+     "  sv0: {type: `int', `17'}\n"
+     "  sv1: {type: `int', `-3'}\n"
+     "constraint manager:\n"
+     "  equiv classes:\n"
+     "  constraints:\n");
+}
+
+/* Verify that dumps can show array elements.  */
+
+static void
+test_dump_3 ()
+{
+  tree tlen = size_int (10);
+  tree arr_type = build_array_type (char_type_node, build_index_type (tlen));
+
+  tree a = build_global_decl ("a", arr_type);
+
+  region_model2 model;
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree a_0 = build4 (ARRAY_REF, char_type_node,
+		     a, int_0, NULL_TREE, NULL_TREE);
+  tree char_A = build_int_cst (char_type_node, 'A');
+  model.set_value (a_0, char_A, NULL);
+
+  /* Simplified dump.  */
+  ASSERT_DUMP_EQ (model, true, "a[0]: 65");
+
+  /* Full dump.  */
+  ASSERT_DUMP_EQ
+    (model, false,
+     "r0: {kind: `root', parent: null, sval: null}\n"
+     "`-globals: r1: {kind: `globals', parent: r0, sval: null, map: {`a': r2}}\n"
+     "  `-`a': r2: {kind: `array', parent: r1, sval: null, type: `char[11]', array: {[0]: r3}}\n"
+     "    |: type: `char[11]'\n"
+     "    `-[0]: r3: {kind: `primitive', parent: r2, sval: sv1, type: `char'}\n"
+     "      |: sval: sv1: {type: `char', `65'}\n"
+     "      |: type: `char'\n"
+     "svalue2s:\n"
+     "  sv0: {type: `int', `0'}\n"
+     "  sv1: {type: `char', `65'}\n"
+     "constraint manager:\n"
+     "  equiv classes:\n"
+     "  constraints:\n");
+}
+
+/* Verify that region_model2::get_representative_tree works as expected.  */
+
+static void
+test_get_representative_tree ()
+{
+  /* STRING_CST.  */
+  {
+    tree string_cst = build_string (4, "foo");
+    region_model2 m;
+    svalue2 *str_sval = m.get_rvalue (string_cst, NULL);
+    tree rep = m.get_representative_tree (str_sval);
+    ASSERT_EQ (rep, string_cst);
+  }
+
+  /* String literal.  */
+  {
+    tree string_cst_ptr = build_string_literal (4, "foo");
+    region_model2 m;
+    svalue2 *str_sval = m.get_rvalue (string_cst_ptr, NULL);
+    tree rep = m.get_representative_tree (str_sval);
+    ASSERT_DUMP_TREE_EQ (rep, "&\"foo\"[0]");
+  }
+}
+
+/* Verify that calling region_model2::get_rvalue repeatedly on the same
+   tree constant retrieves the same svalue2_id.  */
+
+static void
+test_unique_constants ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree int_42 = build_int_cst (integer_type_node, 42);
+
+  test_region_model2_context ctxt;
+  region_model2 model;
+  ASSERT_EQ (model.get_rvalue (int_0, &ctxt), model.get_rvalue (int_0, &ctxt));
+  ASSERT_EQ (model.get_rvalue (int_42, &ctxt),
+	     model.get_rvalue (int_42, &ctxt));
+  ASSERT_NE (model.get_rvalue (int_0, &ctxt), model.get_rvalue (int_42, &ctxt));
+  ASSERT_EQ (ctxt.get_num_diagnostics (), 0);
+}
+
+/* Check that operator== and hashing works as expected for the
+   various svalue2 subclasses.  */
+
+static void
+test_svalue2_equality ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  /* Create pairs instances of the various subclasses of svalue2,
+     testing for hash and equality between (this, this) and
+     (this, other of same subclass). */
+  svalue2 *ptr_to_r0
+    = new region_svalue2 (ptr_type_node, region2_id::from_int (0));
+  svalue2 *ptr_to_r1
+    = new region_svalue2 (ptr_type_node, region2_id::from_int (1));
+
+  ASSERT_EQ (ptr_to_r0->hash (), ptr_to_r0->hash ());
+  ASSERT_EQ (*ptr_to_r0, *ptr_to_r0);
+
+  ASSERT_NE (ptr_to_r0->hash (), ptr_to_r1->hash ());
+  ASSERT_NE (*ptr_to_r0, *ptr_to_r1);
+
+  svalue2 *cst_int_42 = new constant_svalue2 (int_42);
+  svalue2 *cst_int_0 = new constant_svalue2 (int_0);
+
+  ASSERT_EQ (cst_int_42->hash (), cst_int_42->hash ());
+  ASSERT_EQ (*cst_int_42, *cst_int_42);
+
+  ASSERT_NE (cst_int_42->hash (), cst_int_0->hash ());
+  ASSERT_NE (*cst_int_42, *cst_int_0);
+
+  svalue2 *uninit = new poisoned_svalue2 (POISON_KIND_UNINIT, NULL_TREE);
+  svalue2 *freed = new poisoned_svalue2 (POISON_KIND_FREED, NULL_TREE);
+
+  ASSERT_EQ (uninit->hash (), uninit->hash ());
+  ASSERT_EQ (*uninit, *uninit);
+
+  ASSERT_NE (uninit->hash (), freed->hash ());
+  ASSERT_NE (*uninit, *freed);
+
+  svalue2 *unknown_0 = new unknown_svalue2 (ptr_type_node);
+  svalue2 *unknown_1 = new unknown_svalue2 (ptr_type_node);
+  ASSERT_EQ (unknown_0->hash (), unknown_0->hash ());
+  ASSERT_EQ (*unknown_0, *unknown_0);
+  ASSERT_EQ (*unknown_1, *unknown_1);
+
+  /* Comparisons between different kinds of svalue2.  */
+  ASSERT_NE (*ptr_to_r0, *cst_int_42);
+  ASSERT_NE (*ptr_to_r0, *uninit);
+  ASSERT_NE (*ptr_to_r0, *unknown_0);
+  ASSERT_NE (*cst_int_42, *ptr_to_r0);
+  ASSERT_NE (*cst_int_42, *uninit);
+  ASSERT_NE (*cst_int_42, *unknown_0);
+  ASSERT_NE (*uninit, *ptr_to_r0);
+  ASSERT_NE (*uninit, *cst_int_42);
+  ASSERT_NE (*uninit, *unknown_0);
+  ASSERT_NE (*unknown_0, *ptr_to_r0);
+  ASSERT_NE (*unknown_0, *cst_int_42);
+  ASSERT_NE (*unknown_0, *uninit);
+
+  delete ptr_to_r0;
+  delete ptr_to_r1;
+  delete cst_int_42;
+  delete cst_int_0;
+  delete uninit;
+  delete freed;
+  delete unknown_0;
+  delete unknown_1;
+}
+
+/* Check that operator== and hashing works as expected for the
+   various region2 subclasses.  */
+
+static void
+test_region2_equality ()
+{
+  region2 *r0
+    = new primitive_region2 (region2_id::from_int (3), integer_type_node);
+  region2 *r1
+    = new primitive_region2 (region2_id::from_int (4), integer_type_node);
+
+  ASSERT_EQ (*r0, *r0);
+  ASSERT_EQ (r0->hash (), r0->hash ());
+  ASSERT_NE (*r0, *r1);
+  ASSERT_NE (r0->hash (), r1->hash ());
+
+  delete r0;
+  delete r1;
+
+  // TODO: test coverage for the map within a map_region2
+}
+
+/* A subclass of purge_criteria for selftests: purge all svalue2 *instances.  */
+
+class purge_all_svalue2_ids : public purge_criteria
+{
+public:
+  bool should_purge_p (svalue2_id) const FINAL OVERRIDE
+  {
+    return true;
+  }
+};
+
+/* A subclass of purge_criteria: purge a specific svalue2_id.  */
+
+class purge_one_svalue2 *: public purge_criteria
+{
+public:
+  purge_one_svalue2 *(svalue2 *victim) : m_victim (victim) {}
+
+  purge_one_svalue2 *(region_model2 model, tree expr)
+  : m_victim (model.get_rvalue (expr, NULL)) {}
+
+  bool should_purge_p (svalue2 *sval) const FINAL OVERRIDE
+  {
+    return sid == m_victim;
+  }
+
+private:
+  svalue2 *m_victim;
+};
+
+/* Check that constraint_manager::purge works for individual svalue2_ids.  */
+
+static void
+test_purging_by_criteria ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  {
+    region_model2 model0;
+    region_model2 model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, y);
+    ASSERT_NE (model0, model1);
+
+    purge_stats stats_for_px;
+    purge_one_svalue2 *px (model1, x);
+    model1.get_constraints ()->purge (px, &stats_for_px);
+    ASSERT_EQ (stats_for_px.m_num_equiv_classes, 0);
+
+    purge_stats stats_for_py;
+    purge_one_svalue2 *py (model1.get_rvalue (y, NULL));
+    model1.get_constraints ()->purge (py, &stats_for_py);
+    ASSERT_EQ (stats_for_py.m_num_equiv_classes, 1);
+
+    ASSERT_EQ (*model0.get_constraints (), *model1.get_constraints ());
+  }
+
+  {
+    region_model2 model0;
+    region_model2 model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, EQ_EXPR, int_42);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, EQ_EXPR, int_42);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue2 *(model1, x), &stats);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, EQ_EXPR, int_42);
+  }
+
+  {
+    region_model2 model0;
+    region_model2 model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, GE_EXPR, int_0);
+    ADD_SAT_CONSTRAINT (model1, x, LE_EXPR, int_42);
+    ASSERT_NE (model0, model1);
+
+    ASSERT_CONDITION_TRUE (model1, x, GE_EXPR, int_0);
+    ASSERT_CONDITION_TRUE (model1, x, LE_EXPR, int_42);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue2 *(model1, x), &stats);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, GE_EXPR, int_0);
+    ASSERT_CONDITION_UNKNOWN (model1, x, LE_EXPR, int_42);
+  }
+
+  {
+    region_model2 model0;
+    region_model2 model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (model1, y, NE_EXPR, int_0);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_one_svalue2 *(model1, x), &stats);
+    ASSERT_NE (model0, model1);
+
+    ASSERT_CONDITION_UNKNOWN (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+  }
+
+  {
+    region_model2 model0;
+    region_model2 model1;
+
+    ADD_SAT_CONSTRAINT (model1, x, NE_EXPR, int_42);
+    ADD_SAT_CONSTRAINT (model1, y, NE_EXPR, int_0);
+    ASSERT_NE (model0, model1);
+    ASSERT_CONDITION_TRUE (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_TRUE (model1, y, NE_EXPR, int_0);
+
+    purge_stats stats;
+    model1.get_constraints ()->purge (purge_all_svalue2_ids (), &stats);
+    ASSERT_CONDITION_UNKNOWN (model1, x, NE_EXPR, int_42);
+    ASSERT_CONDITION_UNKNOWN (model1, y, NE_EXPR, int_0);
+  }
+
+}
+
+/* Test that region_model2::purge_unused_svalue2s works as expected.  */
+
+static void
+test_purge_unused_svalue2s ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  test_region_model2_context ctxt;
+  region_model2 model;
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x),
+				  &ctxt);
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x),
+				  &ctxt);
+  model.set_to_new_unknown_value (model.get_lvalue (x, &ctxt), TREE_TYPE (x),
+				  &ctxt);
+  model.add_constraint (x, NE_EXPR, int_42, &ctxt);
+
+  model.set_value (model.get_lvalue (x, &ctxt),
+		   model.get_rvalue (int_42, &ctxt),
+		   &ctxt);
+  model.add_constraint (y, GT_EXPR, int_0, &ctxt);
+
+  /* The redundant unknown values should have been purged.  */
+  purge_stats purged;
+  model.purge_unused_svalue2s (&purged, NULL);
+  ASSERT_EQ (purged.m_num_svalue2s, 3);
+
+  /* and the redundant constraint on an old, unknown value for x should
+     have been purged.  */
+  ASSERT_EQ (purged.m_num_equiv_classes, 1);
+  ASSERT_EQ (purged.m_num_constraints, 1);
+  ASSERT_EQ (model.get_constraints ()->m_constraints.length (), 2);
+
+  /* ...but we should still have x == 42.  */
+  ASSERT_EQ (model.eval_condition (x, EQ_EXPR, int_42, &ctxt),
+	     tristate::TS_TRUE);
+
+  /* ...and we should still have the constraint on y.  */
+  ASSERT_EQ (model.eval_condition (y, GT_EXPR, int_0, &ctxt),
+	     tristate::TS_TRUE);
+
+  ASSERT_EQ (ctxt.get_num_diagnostics (), 0);
+}
+
+/* Verify that simple assignments work as expected.  */
+
+static void
+test_assignment ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  /* "x == 0", then use of y, then "y = 0;".  */
+  region_model2 model;
+  ADD_SAT_CONSTRAINT (model, x, EQ_EXPR, int_0);
+  ASSERT_CONDITION_UNKNOWN (model, y, EQ_EXPR, int_0);
+  model.set_value (model.get_lvalue (y, NULL),
+		   model.get_rvalue (int_0, NULL),
+		   NULL);
+  ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, int_0);
+  ASSERT_CONDITION_TRUE (model, y, EQ_EXPR, x);
+
+  ASSERT_DUMP_EQ (model, true, "y: 0, {x}: unknown, x == y");
+}
+
+/* Verify that compound assignments work as expected.  */
+
+static void
+test_compound_assignment ()
+{
+  coord_test ct;
+
+  tree c = build_global_decl ("c", ct.m_coord_type);
+  tree c_x = build3 (COMPONENT_REF, TREE_TYPE (ct.m_x_field),
+		     c, ct.m_x_field, NULL_TREE);
+  tree c_y = build3 (COMPONENT_REF, TREE_TYPE (ct.m_y_field),
+		     c, ct.m_y_field, NULL_TREE);
+  tree d = build_global_decl ("d", ct.m_coord_type);
+  tree d_x = build3 (COMPONENT_REF, TREE_TYPE (ct.m_x_field),
+		     d, ct.m_x_field, NULL_TREE);
+  tree d_y = build3 (COMPONENT_REF, TREE_TYPE (ct.m_y_field),
+		     d, ct.m_y_field, NULL_TREE);
+
+  tree int_17 = build_int_cst (integer_type_node, 17);
+  tree int_m3 = build_int_cst (integer_type_node, -3);
+
+  region_model2 model;
+  model.set_value (c_x, int_17, NULL);
+  model.set_value (c_y, int_m3, NULL);
+
+  ASSERT_DUMP_EQ (model, true, "c.x: 17, c.y: -3");
+
+  /* Copy c to d.  */
+  model.copy_region2 (model.get_lvalue (d, NULL), model.get_lvalue (c, NULL),
+		     NULL);
+  /* Check that the fields have the same svalue2s.  */
+  ASSERT_EQ (model.get_rvalue (c_x, NULL), model.get_rvalue (d_x, NULL));
+  ASSERT_EQ (model.get_rvalue (c_y, NULL), model.get_rvalue (d_y, NULL));
+}
+
+/* Verify the details of pushing and popping stack frames.  */
+
+static void
+test_stack_frames ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_10 = build_int_cst (integer_type_node, 10);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+  tree int_0 = build_int_cst (integer_type_node, 0);
+
+  auto_vec <tree> param_types;
+  tree parent_fndecl = make_fndecl (integer_type_node,
+				    "parent_fn",
+				    param_types);
+  allocate_struct_function (parent_fndecl, true);
+
+  tree child_fndecl = make_fndecl (integer_type_node,
+				   "child_fn",
+				   param_types);
+  allocate_struct_function (child_fndecl, true);
+
+  /* "a" and "b" in the parent frame.  */
+  tree a = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("a"),
+		       integer_type_node);
+  tree b = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("b"),
+		       integer_type_node);
+  /* "x" and "y" in a child frame.  */
+  tree x = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("x"),
+		       integer_type_node);
+  tree y = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("y"),
+		       integer_type_node);
+
+  /* "p" global.  */
+  tree p = build_global_decl ("p", ptr_type_node);
+
+  /* "q" global.  */
+  tree q = build_global_decl ("q", ptr_type_node);
+
+  test_region_model2_context ctxt;
+  region_model2 model;
+
+  /* Push stack frame for "parent_fn".  */
+  region2 *parent_frame_reg
+    = model.push_frame (DECL_STRUCT_FUNCTION (parent_fndecl), NULL, &ctxt);
+  ASSERT_EQ (model.get_current_frame *(), parent_frame_reg);
+  region2 *a_in_parent_reg = model.get_lvalue (a, &ctxt);
+  model.set_value (a_in_parent_reg, model.get_rvalue (int_42, &ctxt), &ctxt);
+  model.set_to_new_unknown_value (model.get_lvalue (b, &ctxt),
+				  integer_type_node, &ctxt);
+  model.add_constraint (b, LT_EXPR, int_10, &ctxt);
+  ASSERT_EQ (model.eval_condition (b, LT_EXPR, int_10, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  /* Push stack frame for "child_fn".  */
+  region2 *child_frame_reg
+    = model.push_frame (DECL_STRUCT_FUNCTION (child_fndecl), NULL, &ctxt);
+  ASSERT_EQ (model.get_current_frame *(), child_frame_reg);
+  region2 *x_in_child_reg = model.get_lvalue (x, &ctxt);
+  model.set_value (x_in_child_reg, model.get_rvalue (int_0, &ctxt), &ctxt);
+  model.set_to_new_unknown_value (model.get_lvalue (y, &ctxt),
+				  integer_type_node, &ctxt);
+  model.add_constraint (y, NE_EXPR, int_5, &ctxt);
+  ASSERT_EQ (model.eval_condition (y, NE_EXPR, int_5, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  /* Point a global pointer at a local in the child frame:  p = &x.  */
+  region2 *p_in_globals_reg = model.get_lvalue (p, &ctxt);
+  model.set_value (p_in_globals_reg,
+		   model.get_or_create_ptr_svalue2 (ptr_type_node,
+						   x_in_child_reg),
+		   &ctxt);
+
+  /* Point another global pointer at p: q = &p.  */
+  region2 *q_in_globals_reg = model.get_lvalue (q, &ctxt);
+  model.set_value (q_in_globals_reg,
+		   model.get_or_create_ptr_svalue2 (ptr_type_node,
+						   p_in_globals_reg),
+		   &ctxt);
+
+  /* Test get_descendents.  */
+  region2_id_set descendents (&model);
+  model.get_descendents (child_frame_reg, &descendents, region2_id::null ());
+  ASSERT_TRUE (descendents.region2_p (child_frame_reg));
+  ASSERT_TRUE (descendents.region2_p (x_in_child_reg));
+  ASSERT_FALSE (descendents.region2_p (a_in_parent_reg));
+  ASSERT_EQ (descendents.num_region2s (), 3);
+#if 0
+  auto_vec<region2_id> test_vec;
+  for (region2_id_set::iterator_t iter = descendents.begin ();
+       iter != descendents.end ();
+       ++iter)
+    test_vec.safe_push (*iter);
+  gcc_unreachable (); // TODO
+  //ASSERT_EQ ();
+#endif
+
+  ASSERT_DUMP_EQ (model, true,
+		  "a: 42, x: 0, p: &x, q: &p, {b, y}: unknown, b < 10, y != 5");
+
+  /* Pop the "child_fn" frame from the stack.  */
+  purge_stats purged;
+  model.pop_frame (region2_id::null (), true, &purged, &ctxt);
+
+  /* We should have purged the unknown values for x and y. */
+  ASSERT_EQ (purged.m_num_svalue2s, 2);
+
+  /* We should have purged the frame region2 and the region2s for x and y. */
+  ASSERT_EQ (purged.m_num_region2s, 3);
+
+  /* We should have purged the constraint on y.  */
+  ASSERT_EQ (purged.m_num_equiv_classes, 1);
+  ASSERT_EQ (purged.m_num_constraints, 1);
+
+  /* Verify that p (which was pointing at the local "x" in the popped
+     frame) has been poisoned.  */
+  svalue2 *new_p_sval = model.get_svalue2 (model.get_rvalue (p, &ctxt));
+  ASSERT_EQ (new_p_sval->get_kind (), SK_POISONED);
+  ASSERT_EQ (new_p_sval->dyn_cast_poisoned_svalue2 ()->get_poison_kind (),
+	     POISON_KIND_POPPED_STACK);
+
+  /* Verify that q still points to p, in spite of the region2
+     renumbering.  */
+  svalue2 *new_q_sval = model.get_svalue2 (model.get_rvalue (q, &ctxt));
+  ASSERT_EQ (new_q_sval->get_kind (), SK_REGION2);
+  ASSERT_EQ (new_q_sval->dyn_cast_region_svalue2 ()->get_pointee (),
+	     model.get_lvalue (p, &ctxt));
+
+  /* Verify that top of stack has been updated.  */
+  ASSERT_EQ (model.get_current_frame *(), parent_frame_reg);
+
+  /* Verify locals in parent frame.  */
+  /* Verify "a" still has its value.  */
+  svalue2 *new_a_sval = model.get_svalue2 (model.get_rvalue (a, &ctxt));
+  ASSERT_EQ (new_a_sval->get_kind (), SK_CONSTANT);
+  ASSERT_EQ (new_a_sval->dyn_cast_constant_svalue2 ()->get_constant (),
+	     int_42);
+  /* Verify "b" still has its constraint.  */
+  ASSERT_EQ (model.eval_condition (b, LT_EXPR, int_10, &ctxt),
+	     tristate (tristate::TS_TRUE));
+}
+
+/* Verify that get_representative_path_var works as expected, that
+   we can map from region2 ids to parms and back within a recursive call
+   stack.  */
+
+static void
+test_get_representative_path_var ()
+{
+  auto_vec <tree> param_types;
+  tree fndecl = make_fndecl (integer_type_node,
+			     "factorial",
+			     param_types);
+  allocate_struct_function (fndecl, true);
+
+  /* Parm "n".  */
+  tree n = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("n"),
+		       integer_type_node);
+
+  region_model2 model;
+
+  /* Push 5 stack frames for "factorial", each with a param  */
+  auto_vec<region2_id> parm_regs;
+  auto_vec<svalue2_id> parm_svals;
+  for (int depth = 0; depth < 5; depth++)
+    {
+      region2 *frame_reg
+	= model.push_frame (DECL_STRUCT_FUNCTION (fndecl), NULL, NULL);
+      region2 *reg_n = model.get_lvalue (path_var (n, depth), NULL);
+      parm_regs.safe_push (rid_n);
+
+      ASSERT_EQ (model.get_region2 (rid_n)->get_parent (), frame_reg);
+
+      svalue2 *sval_n
+	= model.set_to_new_unknown_value (rid_n, integer_type_node, NULL);
+      parm_svals.safe_push (sid_n);
+    }
+
+  /* Verify that we can recognize that the region2s are the parms,
+     at every depth.  */
+  for (int depth = 0; depth < 5; depth++)
+    {
+      ASSERT_EQ (model.get_representative_path_var (parm_regs[depth]),
+		 path_var (n, depth));
+      /* ...and that we can lookup lvalues for locals for all frames,
+	 not just the top.  */
+      ASSERT_EQ (model.get_lvalue (path_var (n, depth), NULL),
+		 parm_regs[depth]);
+      /* ...and that we can locate the svalue2s.  */
+      auto_vec<path_var> pvs;
+      model.get_path_vars_for_svalue2 (parm_svals[depth], &pvs);
+      ASSERT_EQ (pvs.length (), 1);
+      ASSERT_EQ (pvs[0], path_var (n, depth));
+    }
+}
+
+/* Verify that the core region2s within a region_model2 are in a consistent
+   order after canonicalization.  */
+
+static void
+test_canonicalization_1 ()
+{
+  region_model2 model0;
+  model0.get_root_region2 ()->ensure_stack_region2 (&model0);
+  model0.get_root_region2 ()->ensure_globals_region2 (&model0);
+
+  region_model2 model1;
+  model1.get_root_region2 ()->ensure_globals_region2 (&model1);
+  model1.get_root_region2 ()->ensure_stack_region2 (&model1);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+/* Verify that region2 models for
+      x = 42; y = 113;
+   and
+      y = 113; x = 42;
+   are equal after canonicalization.  */
+
+static void
+test_canonicalization_2 ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_113 = build_int_cst (integer_type_node, 113);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  region_model2 model0;
+  model0.set_value (model0.get_lvalue (x, NULL),
+		    model0.get_rvalue (int_42, NULL),
+		    NULL);
+  model0.set_value (model0.get_lvalue (y, NULL),
+		    model0.get_rvalue (int_113, NULL),
+		    NULL);
+
+  region_model2 model1;
+  model1.set_value (model1.get_lvalue (y, NULL),
+		    model1.get_rvalue (int_113, NULL),
+		    NULL);
+  model1.set_value (model1.get_lvalue (x, NULL),
+		    model1.get_rvalue (int_42, NULL),
+		    NULL);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+/* Verify that constraints for
+     x > 3 && y > 42
+   and
+     y > 42 && x > 3
+   are equal after canonicalization.  */
+
+static void
+test_canonicalization_3 ()
+{
+  tree int_3 = build_int_cst (integer_type_node, 3);
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+
+  region_model2 model0;
+  model0.add_constraint (x, GT_EXPR, int_3, NULL);
+  model0.add_constraint (y, GT_EXPR, int_42, NULL);
+
+  region_model2 model1;
+  model1.add_constraint (y, GT_EXPR, int_42, NULL);
+  model1.add_constraint (x, GT_EXPR, int_3, NULL);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+/* Verify that we can canonicalize a model containing NaN and other real
+   constants.  */
+
+static void
+test_canonicalization_4 ()
+{
+  auto_vec<tree> csts;
+  append_interesting_constants (&csts);
+
+  region_model2 model;
+
+  unsigned i;
+  tree cst;
+  FOR_EACH_VEC_ELT (csts, i, cst)
+    model.get_rvalue (cst, NULL);
+
+  model.canonicalize (NULL);
+}
+
+/* Assert that if we have two region_model2 instances
+   with values VAL_A and VAL_B for EXPR that they are
+   mergable.  Write the merged model to *OUT_MERGED_MODEL,
+   and the merged svalue2 ptr to *OUT_MERGED_SVALUE2.
+   If VAL_A or VAL_B are NULL_TREE, don't populate EXPR
+   for that region_model2.  */
+
+static void
+assert_region_model2s_merge (tree expr, tree val_a, tree val_b,
+			    region_model2 *out_merged_model,
+			    svalue2 **out_merged_svalue2)
+{
+  test_region_model2_context ctxt;
+  region_model2 model0;
+  region_model2 model1;
+  if (val_a)
+    model0.set_value (model0.get_lvalue (expr, &ctxt),
+		      model0.get_rvalue (val_a, &ctxt),
+		      &ctxt);
+  if (val_b)
+    model1.set_value (model1.get_lvalue (expr, &ctxt),
+		      model1.get_rvalue (val_b, &ctxt),
+		      &ctxt);
+
+  /* They should be mergeable.  */
+  ASSERT_TRUE (model0.can_merge_with_p (model1, out_merged_model));
+
+  svalue2 *merged_svalue2_sval = out_merged_model->get_rvalue (expr, &ctxt);
+  *out_merged_svalue2 = out_merged_model->get_svalue2 (merged_svalue2_sval);
+}
+
+/* Verify that we can merge region_model2 instances.  */
+
+static void
+test_state_merging ()
+{
+  tree int_42 = build_int_cst (integer_type_node, 42);
+  tree int_113 = build_int_cst (integer_type_node, 113);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+  tree p = build_global_decl ("p", ptr_type_node);
+
+  tree addr_of_y = build1 (ADDR_EXPR, ptr_type_node, y);
+  tree addr_of_z = build1 (ADDR_EXPR, ptr_type_node, z);
+
+  auto_vec <tree> param_types;
+  tree test_fndecl = make_fndecl (integer_type_node, "test_fn", param_types);
+  allocate_struct_function (test_fndecl, true);
+
+  /* Param "a".  */
+  tree a = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("a"),
+		       integer_type_node);
+  tree addr_of_a = build1 (ADDR_EXPR, ptr_type_node, a);
+
+  /* Param "q", a pointer.  */
+  tree q = build_decl (UNKNOWN_LOCATION, PARM_DECL,
+		       get_identifier ("q"),
+		       ptr_type_node);
+
+  {
+    region_model2 model0;
+    region_model2 model1;
+    region_model2 merged;
+    /* Verify empty models can be merged.  */
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Verify that we can merge two contradictory constraints on the
+     value for a global.  */
+  /* TODO: verify that the merged model doesn't have a value for
+     the global  */
+  {
+    region_model2 model0;
+    region_model2 model1;
+    region_model2 merged;
+    test_region_model2_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, EQ_EXPR, int_113, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_NE (model0, merged);
+    ASSERT_NE (model1, merged);
+  }
+
+  /* Verify handling of a PARM_DECL.  */
+  {
+    test_region_model2_context ctxt;
+    region_model2 model0;
+    region_model2 model1;
+    ASSERT_EQ (model0.get_stack_depth (), 0);
+    model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, &ctxt);
+    ASSERT_EQ (model0.get_stack_depth (), 1);
+    ASSERT_EQ (model0.get_function_at_depth (0),
+	       DECL_STRUCT_FUNCTION (test_fndecl));
+    model1.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, &ctxt);
+
+    svalue2 *sval_a
+      = model0.set_to_new_unknown_value (model0.get_lvalue (a, &ctxt),
+					 integer_type_node, &ctxt);
+    model1.set_to_new_unknown_value (model1.get_lvalue (a, &ctxt),
+				     integer_type_node, &ctxt);
+    ASSERT_EQ (model0, model1);
+
+    /* Check that get_value_by_name works for locals.  */
+    ASSERT_EQ (model0.get_value_by_name ("a"), sid_a);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model2 merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+    /* In particular, there should be an unknown value for "a".  */
+    svalue2 *merged_a_sval = merged.get_svalue2 (merged.get_rvalue (a, &ctxt));
+    ASSERT_EQ (merged_a_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Verify handling of a global.  */
+  {
+    test_region_model2_context ctxt;
+    region_model2 model0;
+    region_model2 model1;
+    svalue2 *sval_x
+      = model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+					 integer_type_node, &ctxt);
+    model1.set_to_new_unknown_value (model1.get_lvalue (x, &ctxt),
+				     integer_type_node, &ctxt);
+    ASSERT_EQ (model0, model1);
+
+    /* Check that get_value_by_name works for globals.  */
+    ASSERT_EQ (model0.get_value_by_name ("x"), sid_x);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model2 merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+    /* In particular, there should be an unknown value for "x".  */
+    svalue2 *merged_x_sval = merged.get_svalue2 (merged.get_rvalue (x, &ctxt));
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Use global-handling to verify various combinations of values.  */
+
+  /* Two equal constant values.  */
+  {
+    region_model2 merged;
+    svalue2 *merged_x_sval;
+    assert_region_model2s_merge (x, int_42, int_42, &merged, &merged_x_sval);
+
+    /* In particular, there should be a constant value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_CONSTANT);
+    ASSERT_EQ (merged_x_sval->dyn_cast_constant_svalue2 ()->get_constant (),
+	       int_42);
+  }
+
+  /* Two non-equal constant values.  */
+  {
+    region_model2 merged;
+    svalue2 *merged_x_sval;
+    assert_region_model2s_merge (x, int_42, int_113, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Uninit and constant.  */
+  {
+    region_model2 merged;
+    svalue2 *merged_x_sval;
+    assert_region_model2s_merge (x, NULL_TREE, int_113, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Constant and uninit.  */
+  {
+    region_model2 merged;
+    svalue2 *merged_x_sval;
+    assert_region_model2s_merge (x, int_42, NULL_TREE, &merged, &merged_x_sval);
+
+    /* In particular, there should be an unknown value for "x".  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Unknown and constant.  */
+  // TODO
+
+  /* Pointers: NULL and NULL.  */
+  // TODO
+
+  /* Pointers: NULL and non-NULL.  */
+  // TODO
+
+  /* Pointers: non-NULL and non-NULL: ptr to a local.  */
+  {
+    region_model2 model0;
+    model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+    model0.set_to_new_unknown_value (model0.get_lvalue (a, NULL),
+				     integer_type_node, NULL);
+    model0.set_value (model0.get_lvalue (p, NULL),
+		      model0.get_rvalue (addr_of_a, NULL), NULL);
+
+    region_model2 model1 (model0);
+    ASSERT_EQ (model0, model1);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model2 merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Pointers: non-NULL and non-NULL: ptr to a global.  */
+  {
+    region_model2 merged;
+    /* p == &y in both input models.  */
+    svalue2 *merged_p_sval;
+    assert_region_model2s_merge (p, addr_of_y, addr_of_y, &merged,
+				&merged_p_sval);
+
+    /* We should get p == &y in the merged model.  */
+    ASSERT_EQ (merged_p_sval->get_kind (), SK_REGION2);
+    region_svalue2 *merged_p_ptr = merged_p_sval->dyn_cast_region_svalue2 ();
+    region2 *merged_p_star_reg = merged_p_ptr->get_pointee ();
+    ASSERT_EQ (merged_p_star_reg, merged.get_lvalue (y, NULL));
+  }
+
+  /* Pointers: non-NULL ptrs to different globals: should be unknown.  */
+  {
+    region_model2 merged;
+    /* x == &y vs x == &z in the input models.  */
+    svalue2 *merged_x_sval;
+    assert_region_model2s_merge (x, addr_of_y, addr_of_z, &merged,
+				&merged_x_sval);
+
+    /* We should get x == unknown in the merged model.  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_UNKNOWN);
+  }
+
+  /* Pointers: non-NULL and non-NULL: ptr to a heap region2.  */
+  {
+    test_region_model2_context ctxt;
+    region_model2 model0;
+    region2 *new_reg = model0.add_new_malloc_region2 ();
+    svalue2 *ptr_sval
+      = model0.get_or_create_ptr_svalue2 (ptr_type_node, new_reg);
+    model0.set_value (model0.get_lvalue (p, &ctxt),
+		      ptr_sval, &ctxt);
+    model0.canonicalize (&ctxt);
+
+    region_model2 model1 (model0);
+
+    ASSERT_EQ (model0, model1);
+
+    region_model2 merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+
+    merged.canonicalize (&ctxt);
+
+    /* The merged model ought to be identical (after canonicalization,
+       at least).  */
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Two region2s sharing the same unknown svalue2 should continue sharing
+     an unknown svalue2 after self-merger.  */
+  {
+    test_region_model2_context ctxt;
+    region_model2 model0;
+    svalue2 *sval
+      = model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+					 integer_type_node, &ctxt);
+    model0.set_value (model0.get_lvalue (y, &ctxt), sid, &ctxt);
+    region_model2 model1 (model0);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model2 merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    ASSERT_EQ (model0, merged);
+
+    /* In particular, we should have x == y.  */
+    ASSERT_EQ (merged.eval_condition (x, EQ_EXPR, y, &ctxt),
+	       tristate (tristate::TS_TRUE));
+  }
+
+#if 0
+  {
+    region_model2 model0;
+    region_model2 model1;
+    test_region_model2_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, NE_EXPR, int_42, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1));
+  }
+
+  {
+    region_model2 model0;
+    region_model2 model1;
+    test_region_model2_context ctxt;
+    model0.add_constraint (x, EQ_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, NE_EXPR, int_42, &ctxt);
+    model1.add_constraint (x, EQ_EXPR, int_113, &ctxt);
+    ASSERT_TRUE (model0.can_merge_with_p (model1));
+  }
+#endif
+
+  // TODO: what can't we merge? need at least one such test
+
+  /* TODO: various things
+     - heap region2s
+     - value merging:
+       - every combination, but in particular
+          - pairs of region2s
+   */
+
+  /* Views.  */
+  {
+    test_region_model2_context ctxt;
+    region_model2 model0;
+
+    region2 *x_reg = model0.get_lvalue (x, &ctxt);
+    region2 *x_as_ptr = model0.get_or_create_view (x_reg, ptr_type_node,
+						    &ctxt);
+    model0.set_value (x_as_ptr, model0.get_rvalue (addr_of_y, &ctxt), &ctxt);
+
+    region_model2 model1 (model0);
+    ASSERT_EQ (model1, model0);
+
+    /* They should be mergeable, and the result should be the same.  */
+    region_model2 merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+  }
+
+  /* Verify that we can merge a model in which a local in an older stack
+     frame points to a local in a more recent stack frame.  */
+  {
+    region_model2 model0;
+    model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+    region2 *q_in_first_frame = model0.get_lvalue (q, NULL);
+
+    /* Push a second frame.  */
+    region2 *reg_2nd_frame
+      = model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+
+    /* Have a pointer in the older frame point to a local in the
+       more recent frame.  */
+    svalue2 *sval_ptr = model0.get_rvalue (addr_of_a, NULL);
+    model0.set_value (q_in_first_frame, sid_ptr, NULL);
+
+    /* Verify that it's pointing at the newer frame.  */
+    region2 *reg_pointee
+      = model0.get_svalue2 (sid_ptr)->dyn_cast_region_svalue2 ()->get_pointee ();
+    ASSERT_EQ (model0.get_region2 (rid_pointee)->get_parent (), rid_2nd_frame);
+
+    model0.canonicalize (NULL);
+
+    region_model2 model1 (model0);
+    ASSERT_EQ (model0, model1);
+
+    /* They should be mergeable, and the result should be the same
+       (after canonicalization, at least).  */
+    region_model2 merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    merged.canonicalize (NULL);
+    ASSERT_EQ (model0, merged);
+  }
+
+  /* Verify that we can merge a model in which a local points to a global.  */
+  {
+    region_model2 model0;
+    model0.push_frame (DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+    model0.set_value (model0.get_lvalue (q, NULL),
+		      model0.get_rvalue (addr_of_y, NULL), NULL);
+
+    model0.canonicalize (NULL);
+
+    region_model2 model1 (model0);
+    ASSERT_EQ (model0, model1);
+
+    /* They should be mergeable, and the result should be the same
+       (after canonicalization, at least).  */
+    region_model2 merged;
+    ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+    merged.canonicalize (NULL);
+    ASSERT_EQ (model0, merged);
+  }
+}
+
+/* Verify that constraints are correctly merged when merging region_model2
+   instances.  */
+
+static void
+test_constraint_merging ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree int_5 = build_int_cst (integer_type_node, 5);
+  tree x = build_global_decl ("x", integer_type_node);
+  tree y = build_global_decl ("y", integer_type_node);
+  tree z = build_global_decl ("z", integer_type_node);
+  tree n = build_global_decl ("n", integer_type_node);
+
+  test_region_model2_context ctxt;
+
+  /* model0: 0 <= (x == y) < n.  */
+  region_model2 model0;
+  model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+				   integer_type_node, &ctxt);
+  model0.add_constraint (x, EQ_EXPR, y, &ctxt);
+  model0.add_constraint (x, GE_EXPR, int_0, NULL);
+  model0.add_constraint (x, LT_EXPR, n, NULL);
+
+  /* model1: z != 5 && (0 <= x < n).  */
+  region_model2 model1;
+  model1.set_to_new_unknown_value (model1.get_lvalue (x, &ctxt),
+				   integer_type_node, &ctxt);
+  model1.add_constraint (z, NE_EXPR, int_5, NULL);
+  model1.add_constraint (x, GE_EXPR, int_0, NULL);
+  model1.add_constraint (x, LT_EXPR, n, NULL);
+
+  /* They should be mergeable; the merged constraints should
+     be: (0 <= x < n).  */
+  region_model2 merged;
+  ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+
+  ASSERT_EQ (merged.eval_condition (x, GE_EXPR, int_0, &ctxt),
+	     tristate (tristate::TS_TRUE));
+  ASSERT_EQ (merged.eval_condition (x, LT_EXPR, n, &ctxt),
+	     tristate (tristate::TS_TRUE));
+
+  ASSERT_EQ (merged.eval_condition (z, NE_EXPR, int_5, &ctxt),
+	     tristate (tristate::TS_UNKNOWN));
+  ASSERT_EQ (merged.eval_condition (x, LT_EXPR, y, &ctxt),
+	     tristate (tristate::TS_UNKNOWN));
+}
+
+/* Verify that if we mark a pointer to a malloc-ed region2 as non-NULL,
+   all cast pointers to that region2 are also known to be non-NULL.  */
+
+static void
+test_malloc_constraints ()
+{
+  region_model2 model;
+  tree p = build_global_decl ("p", ptr_type_node);
+  tree char_star = build_pointer_type (char_type_node);
+  tree q = build_global_decl ("q", char_star);
+  tree null_ptr = build_int_cst (ptr_type_node, 0);
+
+  region2 *reg = model.add_new_malloc_region2 ();
+  svalue2 *sval = model.get_or_create_ptr_svalue2 (ptr_type_node, rid);
+  model.set_value (model.get_lvalue (p, NULL), sid, NULL);
+  model.set_value (q, p, NULL);
+
+  /* We should have a symbolic_region2 with m_possibly_null: true.  */
+  region2 *pointee = model.get_region2 (rid);
+  symbolic_region2 *sym_reg = pointee->dyn_cast_symbolic_region2 ();
+  ASSERT_NE (sym_reg, NULL);
+  ASSERT_TRUE (sym_reg->m_possibly_null);
+
+  ASSERT_CONDITION_UNKNOWN (model, p, NE_EXPR, null_ptr);
+  ASSERT_CONDITION_UNKNOWN (model, p, EQ_EXPR, null_ptr);
+  ASSERT_CONDITION_UNKNOWN (model, q, NE_EXPR, null_ptr);
+  ASSERT_CONDITION_UNKNOWN (model, q, EQ_EXPR, null_ptr);
+
+  model.add_constraint (p, NE_EXPR, null_ptr, NULL);
+
+  /* Adding the constraint should have cleared m_possibly_null.  */
+  ASSERT_FALSE (sym_reg->m_possibly_null);
+
+  ASSERT_CONDITION_TRUE (model, p, NE_EXPR, null_ptr);
+  ASSERT_CONDITION_FALSE (model, p, EQ_EXPR, null_ptr);
+  ASSERT_CONDITION_TRUE (model, q, NE_EXPR, null_ptr);
+  ASSERT_CONDITION_FALSE (model, q, EQ_EXPR, null_ptr);
+}
+#endif
+
+/* Run all of the selftests within this file.  */
+
+void
+analyzer_region_model2_cc_tests ()
+{
+#if 0
+  test_tree_cmp_on_constants ();
+  test_dump ();
+  test_dump_2 ();
+  test_dump_3 ();
+  test_get_representative_tree ();
+  test_unique_constants ();
+  test_svalue2_equality ();
+  test_region2_equality ();
+  test_purging_by_criteria ();
+  test_purge_unused_svalue2s ();
+  test_assignment ();
+  test_compound_assignment ();
+  test_stack_frames ();
+  test_get_representative_path_var ();
+  test_canonicalization_1 ();
+  test_canonicalization_2 ();
+  test_canonicalization_3 ();
+  test_canonicalization_4 ();
+  test_state_merging ();
+  test_constraint_merging ();
+  test_malloc_constraints ();
+#endif
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/region-model2.h b/gcc/analyzer/region-model2.h
new file mode 100644
index 00000000000..f5117e2b207
--- /dev/null
+++ b/gcc/analyzer/region-model2.h
@@ -0,0 +1,1847 @@
+/* Classes for modeling the state of memory.
+   Copyright (C) 2019-2020 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_REGION_MODEL_2_H
+#define GCC_ANALYZER_REGION_MODEL_2_H
+
+/* Implementation of the region-based ternary model described in:
+     "A Memory Model for Static Analysis of C Programs"
+      (Zhongxing Xu, Ted Kremenek, and Jian Zhang)
+     http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf  */
+
+
+/* FIXME:
+   This prototype has an alternate implementation of region_model.
+   Idea:
+   - no region_id or svalue_id, just pointers.
+   - regions are shared throughout the analysis, via a region_manager.
+   - should svalues also be shared?  Probably
+   - regions and values don't have state; region_model2 instances do
+     (though potentially we want to use the regions to store information
+     about the shared region structure e.g. subregions of map_region)
+   - no canonicalization or ID remapping should be needed
+   - uniqueness of regions: comparison by pointer rather than by fields
+     (but maybe we need to keep the compare_fields for implementing the
+     uniquifying?)
+   - not yet sure if we need validation
+   - the concept of what the "current stack" is needs to move from the
+     regions to the region_model2 (maybe a region *m_current_frame)
+   - do we still need region cloning?
+   - should there still be a root_region?  or should those things go in the
+     ext_state (or something owned by ext_state: region_model2_manager)
+   Hopefully this is a huge simplification, and (I hope) avoids lots of heap
+   churn as states are copied.  */
+
+/* A tree, extended with stack frame information for locals, so that
+   we can distinguish between different values of locals within a potentially
+   recursive callstack.  */
+// TODO: would this be better as a new tree code?
+
+namespace ana {
+
+class path_var
+{
+public:
+  path_var (tree t, int stack_depth)
+  : m_tree (t), m_stack_depth (stack_depth)
+  {
+    // TODO: ignore stack depth for globals and constants
+  }
+
+  bool operator== (const path_var &other) const
+  {
+    return (m_tree == other.m_tree
+	    && m_stack_depth == other.m_stack_depth);
+  }
+
+  void dump (pretty_printer *pp) const;
+
+  tree m_tree;
+  int m_stack_depth; // or -1 for globals?
+};
+
+} // namespace ana
+
+using namespace ana;
+
+namespace inchash
+{
+  extern void add_path_var (path_var pv, hash &hstate);
+} // namespace inchash
+
+
+namespace ana {
+
+/* A region_model2 is effectively a graph of regions and symbolic values.
+   FIXME: we're eliminating IDs in this prototype.
+   We store per-model IDs rather than pointers to make it easier to clone
+   and to compare graphs.  */
+
+/* Various operations delete information from a region_model2.
+
+   This struct tracks how many of each kind of entity were purged (e.g.
+   for selftests, and for debugging).  */
+
+struct purge_stats
+{
+  purge_stats ()
+  : m_num_svalues (0),
+    m_num_regions (0),
+    m_num_equiv_classes (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;
+};
+
+/* svalue and its subclasses.
+
+   The class hierarchy looks like this (using indentation to show
+   inheritance, and with kinds shown for the concrete subclasses):
+
+   svalue2
+     region_svalue2 (SK_REGION)
+     constant_svalue2 (SK_CONSTANT)
+     unknown_svalue2 (SK_UNKNOWN)
+     poisoned_svalue2 (SK_POISONED)
+     setjmp_svalue2 (SK_SETJMP).  */
+
+/* An abstract base class representing a value held by a region of memory.  */
+
+class svalue2
+{
+public:
+  /* An enum for discriminating between the different concrete subclasses
+   of svalue.  */
+  enum kind
+  {
+   SK_REGION,
+   SK_CONSTANT,
+   SK_UNKNOWN,
+   SK_POISONED,
+   SK_SETJMP
+  };
+
+  virtual ~svalue2 () {}
+
+  bool operator== (const svalue2 &other) const;
+  bool operator!= (const svalue2 &other) const { return !(*this == other); }
+
+#if 0
+  virtual svalue2 *clone () const = 0;
+#endif
+
+  tree get_type () const { return m_type; }
+
+  virtual enum kind get_kind () const = 0;
+
+  hashval_t hash () const;
+
+  void print (const region_model2 &model,
+	      pretty_printer *pp) const;
+
+  virtual void dump_dot_to_pp (const region_model2 &model,
+			       pretty_printer *pp) const;
+
+  virtual region_svalue2 *dyn_cast_region_svalue2 () { return NULL; }
+  virtual constant_svalue2 *dyn_cast_constant_svalue2 () { return NULL; }
+  virtual const constant_svalue2 *dyn_cast_constant_svalue2 () const
+  { return NULL; }
+  virtual poisoned_svalue2 *dyn_cast_poisoned_svalue2 () { return NULL; }
+  virtual unknown_svalue2 *dyn_cast_unknown_svalue2 () { return NULL; }
+  virtual setjmp_svalue2 *dyn_cast_setjmp_svalue2 () { return NULL; }
+
+  tree maybe_get_constant () const;
+
+ protected:
+  svalue2 (tree type) : m_type (type) {}
+
+  virtual void add_to_hash (inchash::hash &hstate) const = 0;
+
+ private:
+  virtual void print_details (const region_model2 &model,
+			      pretty_printer *pp) const = 0;
+  tree m_type;
+};
+
+/* Concrete subclass of svalue2 representing a pointer value that points to
+   a known region  */
+
+class region_svalue2 : public svalue2
+{
+public:
+  region_svalue2 (tree type, region2 *reg) : svalue2 (type), m_reg (reg)
+  {
+    gcc_assert (m_reg != NULL);
+  }
+
+#if 0
+  svalue2 *clone () const FINAL OVERRIDE
+  { return new region_svalue2 (get_type (), m_reg); }
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return SK_REGION; }
+
+  void dump_dot_to_pp (const region_model2 &model,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region_svalue2 *dyn_cast_region_svalue2 () FINAL OVERRIDE { return this; }
+
+  region2 * get_pointee () const { return m_reg; }
+
+#if 0
+  static void merge_values (const region_svalue2 &region_sval_a,
+			    const region_svalue2 &region_sval_b,
+			    svalue2_id *merged_sid,
+			    tree type,
+			    model_merger *merger);
+#endif
+
+  static tristate eval_condition (region_svalue2 *lhs_ptr,
+				  enum tree_code op,
+				  region_svalue2 *rhs_ptr);
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+ private:
+  void print_details (const region_model2 &model, pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  region2 *m_reg;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <region_svalue2 *>::test (svalue2 *sval)
+{
+  return sval->get_kind () == svalue2::SK_REGION;
+}
+
+namespace ana {
+
+/* Concrete subclass of svalue2 representing a specific constant value.  */
+
+class constant_svalue2 : public svalue2
+{
+public:
+  constant_svalue2 (tree cst_expr)
+  : svalue2 (TREE_TYPE (cst_expr)), m_cst_expr (cst_expr)
+  {
+    gcc_assert (cst_expr);
+    gcc_assert (CONSTANT_CLASS_P (cst_expr));
+  }
+
+#if 0
+  svalue2 *clone () const FINAL OVERRIDE
+  { return new constant_svalue2 (m_cst_expr); }
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return SK_CONSTANT; }
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  constant_svalue2 *dyn_cast_constant_svalue2 () FINAL OVERRIDE { return this; }
+  const constant_svalue2 *dyn_cast_constant_svalue2 () const FINAL OVERRIDE
+  { return this; }
+
+  tree get_constant () const { return m_cst_expr; }
+
+#if 0
+  static void merge_values (const constant_svalue2 &cst_sval_a,
+			    const constant_svalue2 &cst_sval_b,
+			    svalue2_id *merged_sid,
+			    model_merger *merger);
+#endif
+
+  static tristate eval_condition (constant_svalue2 *lhs,
+				  enum tree_code op,
+				  constant_svalue2 *rhs);
+
+ private:
+  void print_details (const region_model2 &model,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  tree m_cst_expr;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <constant_svalue2 *>::test (svalue2 *sval)
+{
+  return sval->get_kind () == svalue2::SK_CONSTANT;
+}
+
+namespace ana {
+
+/* Concrete subclass of svalue2 representing a unique but unknown value.
+   Comparisons of variables that share the same unknown value are known
+   to be equal, even if we don't know what the value is.  */
+
+class unknown_svalue2 : public svalue2
+{
+public:
+  unknown_svalue2 (tree type)
+  : svalue2 (type)
+  {}
+
+#if 0
+  svalue2 *clone () const FINAL OVERRIDE
+  { return new unknown_svalue2 (get_type ()); }
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return SK_UNKNOWN; }
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  unknown_svalue2 *dyn_cast_unknown_svalue2 () FINAL OVERRIDE { return this; }
+
+ private:
+  void print_details (const region_model2 &model,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+};
+
+/* 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.  */
+  POISON_KIND_POPPED_STACK
+};
+
+extern const char *poison_kind_to_str (enum poison_kind);
+
+/* Concrete subclass of svalue2 representing a value that should not
+   be used (e.g. uninitialized memory, freed memory).  */
+
+class poisoned_svalue2 : public svalue2
+{
+public:
+  poisoned_svalue2 (enum poison_kind kind, tree type)
+  : svalue2 (type), m_kind (kind) {}
+
+#if 0
+  svalue2 *clone () const FINAL OVERRIDE
+  { return new poisoned_svalue2 (m_kind, get_type ()); }
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return SK_POISONED; }
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  poisoned_svalue2 *dyn_cast_poisoned_svalue2 () FINAL OVERRIDE { return this; }
+
+  enum poison_kind get_poison_kind () const { return m_kind; }
+
+ private:
+  void print_details (const region_model2 &model,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  enum poison_kind m_kind;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <poisoned_svalue2 *>::test (svalue2 *sval)
+{
+  return sval->get_kind () == svalue2::SK_POISONED;
+}
+
+namespace ana {
+
+/* A bundle of information recording a setjmp/sigsetjmp call, corresponding
+   roughly to a jmp_buf.  */
+
+struct setjmp_record
+{
+  setjmp_record (const exploded_node *enode,
+		 const gcall *setjmp_call)
+  : m_enode (enode), m_setjmp_call (setjmp_call)
+  {
+  }
+
+  bool operator== (const setjmp_record &other) const
+  {
+    return (m_enode == other.m_enode
+	    && m_setjmp_call == other.m_setjmp_call);
+  }
+
+  const exploded_node *m_enode;
+  const gcall *m_setjmp_call;
+};
+
+/* Concrete subclass of svalue2 representing buffers for setjmp/sigsetjmp,
+   so that longjmp/siglongjmp can potentially "return" to an entirely
+   different function.  */
+
+class setjmp_svalue2 : public svalue2
+{
+public:
+  setjmp_svalue2 (const setjmp_record &setjmp_record,
+		 tree type)
+  : svalue2 (type), m_setjmp_record (setjmp_record)
+  {}
+
+#if 0
+  svalue2 *clone () const FINAL OVERRIDE
+  { return new setjmp_svalue2 (m_setjmp_record, get_type ()); }
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return SK_SETJMP; }
+
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  setjmp_svalue2 *dyn_cast_setjmp_svalue2 () FINAL OVERRIDE { return this; }
+
+  int get_enode_index () const;
+
+  const setjmp_record &get_setjmp_record () const { return m_setjmp_record; }
+
+ private:
+  void print_details (const region_model2 &model,
+		      pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  setjmp_record m_setjmp_record;
+};
+
+/* Region and its subclasses.
+
+   The class hierarchy looks like this (using indentation to show
+   inheritance, and with region_kinds shown for the concrete subclasses):
+
+   region
+     primitive_region (RK_PRIMITIVE)
+     map_region
+       struct_or_union_region
+         struct_region (RK_STRUCT)
+         union_region (RK_UNION)
+       scope_region
+         frame_region (RK_FRAME)
+         globals_region (RK_GLOBALS)
+       code_region (RK_CODE)
+       function_region (RK_FUNCTION)
+     array_region (RK_ARRAY)
+     stack_region (RK_STACK)
+     heap_region (RK_HEAP)
+     root_region (RK_ROOT)
+     label_region (RK_FUNCTION)
+     symbolic_region (RK_SYMBOLIC).  */
+
+/* Abstract base class representing a chunk of memory.
+
+   Regions form a tree-like hierarchy, with a root region at the base,
+   with memory space regions within it, representing the stack and
+   globals, with frames within the stack, and regions for variables
+   within the frames and the "globals" region.  Regions for structs
+   can have subregions for fields.
+
+   A region can optionally have a value, or inherit its value from
+   the first ancestor with a value.  For example, the stack region
+   has a "uninitialized" poison value which is inherited by all
+   descendent regions that don't themselves have a value.  */
+
+class region2
+{
+public:
+  /* An enum for discriminating between the different concrete subclasses
+     of region2.  */
+  enum kind
+  {
+   RK_PRIMITIVE,
+   RK_STRUCT,
+   RK_UNION,
+   RK_FRAME,
+   RK_GLOBALS,
+   RK_CODE,
+   RK_FUNCTION,
+   RK_ARRAY,
+   RK_STACK,
+   RK_HEAP,
+   RK_ROOT,
+   RK_SYMBOLIC
+  };
+  static const char *kind_to_str (enum kind);
+
+  virtual ~region2 () {}
+
+  bool operator== (const region &other) const;
+  bool operator!= (const region &other) const { return !(*this == other); }
+
+#if 0
+  virtual region2 *clone () const = 0;
+#endif
+
+  virtual enum kind get_kind () const = 0;
+  virtual map_region2 *dyn_cast_map_region2 () { return NULL; }
+  virtual array_region2 *dyn_cast_array_region2 () { return NULL; }
+  virtual symbolic_region2 *dyn_cast_symbolic_region2 () { return NULL; }
+  virtual const symbolic_region2 *dyn_cast_symbolic_region2 () const { return NULL; }
+
+  region2 *get_parent_region () const { return m_parent; }
+
+#if 0
+  void set_value (region_model2 &model, region_id this_rid, svalue2_id rhs_sid,
+		  region_model2_context *ctxt);
+  svalue2_id get_value (region_model2 &model, bool non_null,
+		       region_model2_context *ctxt);
+  svalue2_id get_value_direct () const { return m_sval_id; }
+
+  svalue2_id get_inherited_child_sid (region *child,
+				     region_model2 &model,
+				     region_model2_context *ctxt);
+#endif
+
+  tree get_type () const { return m_type; }
+
+  hashval_t hash () const;
+
+  void print (const region_model2 &model,
+	      pretty_printer *pp) const;
+
+  virtual void dump_dot_to_pp (const region_model2 &model,
+			       pretty_printer *pp) const;
+
+  void dump_to_pp (const region_model2 &model,
+		   pretty_printer *pp,
+		   const char *prefix,
+		   bool is_last_child) const;
+  virtual void dump_child_label (const region_model2 &model,
+				 region2 *child,
+				 pretty_printer *pp) const;
+
+#if 0
+  void add_view (region_id view_rid, region_model2 *model);
+  region_id get_view (tree type, region_model2 *model) const;
+  region_id get_active_view () const { return m_active_view_rid; }
+  bool is_view_p () const { return m_is_view; }
+#endif
+  virtual void validate (const region_model2 &model) const;
+
+  bool non_null_p (const region_model2 &model) const;
+
+ protected:
+  region2 (region2 *parent, tree type);
+#if 0
+  region2 (const region2 &other);
+#endif
+
+  virtual void add_to_hash (inchash::hash &hstate) const;
+  virtual void print_fields (const region_model2 &model,
+			     pretty_printer *pp) const;
+
+ private:
+#if 0  
+  void become_active_view (region_model2 &model, region_id this_rid);
+  void deactivate_any_active_view (region_model2 &model);
+  void deactivate_view (region_model2 &model, region_id this_view_rid);
+#endif
+
+  region2 *m_parent;
+  tree m_type;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <region *>::test (region *)
+{
+  return true;
+}
+
+namespace ana {
+
+/* Concrete region subclass for storing "primitive" types (integral types,
+   pointers, etc).  */
+
+class primitive_region2 : public region2
+{
+public:
+  primitive_region2 (region2 *parent, tree type)
+  : region2 (parent, type)
+  {}
+
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+
+  enum region2::kind get_kind () const FINAL OVERRIDE { return RK_PRIMITIVE; }
+};
+
+/* A region2 that has children identified by tree keys.
+   For example a stack frame has subregion2s per local, and a region2
+   for a struct has subregion2s per field.  */
+
+class map_region2 : public region2
+{
+public:
+  typedef ordered_hash_map<tree, region2 *> map_t;
+  typedef map_t::iterator iterator_t;
+
+  map_region2 (region2 *parent, tree type)
+  : region2 (parent, type)
+  {}
+  map_region2 (const map_region2 &other);
+
+  map_region2 *dyn_cast_map_region2 () FINAL OVERRIDE { return this; }
+
+  void dump_dot_to_pp (const region_model2 &model,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  void dump_child_label (const region_model2 &model,
+			 region2 *child,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+#if 0
+  region2_id get_or_create (region_model2 *model,
+			   tree expr, tree type,
+			   region_model2_context *ctxt);
+  void unbind (tree expr);
+  region2_id *get (tree expr);
+#endif
+
+#if 0
+  tree get_tree_for_child_region2 (region2_id child_rid) const;
+
+  tree get_tree_for_child_region2 (region2 *child,
+				  const region_model2 &model) const;
+
+  static bool can_merge_p (const map_region2 *map_region2_a,
+			   const map_region2 *map_region2_b,
+			   map_region2 *merged_map_region2,
+			   region2_id merged_rid,
+			   model_merger *merger);
+#endif
+  virtual bool valid_key_p (tree key) const = 0;
+
+#if 0
+  svalue2_id get_value_by_name (tree identifier,
+			       const region_model2 &model) const;
+#endif
+
+  iterator_t begin () { return m_map.begin (); }
+  iterator_t end () { return m_map.end (); }
+  size_t elements () const { return m_map.elements (); }
+
+ protected:
+  void add_to_hash (inchash::hash &hstate) const OVERRIDE;
+  void print_fields (const region_model2 &model,
+		     pretty_printer *pp) const
+    OVERRIDE;
+  void validate (const region_model2 &model) const FINAL OVERRIDE;
+
+ private:
+  /* Mapping from tree to child region2.  */
+  map_t m_map;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <map_region2 *>::test (region2 *reg)
+{
+  return (reg->dyn_cast_map_region2 () != NULL);
+}
+
+namespace ana {
+
+/* Abstract subclass representing a region2 with fields
+   (either a struct or a union).  */
+
+class struct_or_union_region2 : public map_region2
+{
+public:
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+ protected:
+  struct_or_union_region2 (region2 *parent, tree type)
+  : map_region2 (parent, type)
+  {}
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <struct_or_union_region2 *>::test (region2 *reg)
+{
+  return (reg->get_kind () == region2::RK_STRUCT
+	  || reg->get_kind () == region2::RK_UNION);
+}
+
+namespace ana {
+
+/* Concrete region2 subclass.  A map_region2 representing a struct, using
+   FIELD_DECLs for its keys.  */
+
+class struct_region2 : public struct_or_union_region2
+{
+public:
+  struct_region2 (region2 *parent, tree type)
+  : struct_or_union_region2 (parent, type)
+  {
+    gcc_assert (TREE_CODE (type) == RECORD_TYPE);
+  }
+
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE
+  { return region2::RK_STRUCT; }
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <struct_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_STRUCT;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass.  A map_region2 representing a union, using
+   FIELD_DECLs for its keys.  */
+
+class union_region2 : public struct_or_union_region2
+{
+public:
+  union_region2 (region2 *parent, tree type)
+  : struct_or_union_region2 (parent, type)
+  {
+    gcc_assert (TREE_CODE (type) == UNION_TYPE);
+  }
+
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return RK_UNION; }
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <union_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_UNION;
+}
+
+namespace ana {
+
+/* Abstract map_region2 subclass for accessing decls, used as a base class
+   for function frames and for the globals region2.  */
+
+class scope_region2 : public map_region2
+{
+ public:
+
+ protected:
+  scope_region2 (region2 *parent)
+  : map_region2 (parent, NULL_TREE)
+  {}
+
+  scope_region2 (const scope_region2 &other)
+  : map_region2 (other)
+  {
+  }
+
+  bool compare_fields (const scope_region2 &other) const;
+};
+
+/* Concrete region2 subclass, representing a function frame on the stack,
+   to contain the locals.  */
+
+class frame_region2 : public scope_region2
+{
+public:
+  frame_region2 (region2 *parent, function *fun, int depth)
+  : scope_region2 (parent), m_fun (fun), m_depth (depth)
+  {}
+
+  frame_region2 (const frame_region2 &other)
+  : scope_region2 (other), m_fun (other.m_fun), m_depth (other.m_depth)
+  {
+  }
+
+  /* region2 vfuncs.  */
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+  enum kind get_kind () const FINAL OVERRIDE { return RK_FRAME; }
+  void print_fields (const region_model2 &model,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+
+  /* map_region2 vfuncs.  */
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+  /* Accessors.  */
+  function *get_function () const { return m_fun; }
+  int get_depth () const { return m_depth; }
+
+  bool compare_fields (const frame_region2 &other) const;
+
+ private:
+  function *m_fun;
+  int m_depth;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <frame_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_FRAME;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass, to hold global variables (data and bss).  */
+
+class globals_region2 : public scope_region2
+{
+ public:
+  globals_region2 (region2 *parent)
+  : scope_region2 (parent)
+  {}
+
+  globals_region2 (const globals_region2 &other)
+  : scope_region2 (other)
+  {
+  }
+
+  /* region2 vfuncs.  */
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+  enum kind get_kind () const FINAL OVERRIDE { return RK_GLOBALS; }
+
+  /* map_region2 vfuncs.  */
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+  bool compare_fields (const globals_region2 &other) const;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <globals_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_GLOBALS;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass.  A map_region2 representing the code, using
+   FUNCTION_DECLs for its keys.  */
+
+class code_region2 : public map_region2
+{
+public:
+  code_region2 (region2 *parent)
+  : map_region2 (parent, NULL_TREE)
+  {}
+  code_region2 (const code_region2 &other)
+  : map_region2 (other)
+  {}
+
+  /* region2 vfuncs.  */
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+  enum kind get_kind () const FINAL OVERRIDE { return RK_CODE; }
+
+  /* map_region2 vfunc.  */
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+  region2 *get_element (region_model2 *model,
+			svalue2 *index,
+			region_model2_context *ctxt);
+
+  bool compare_fields (const code_region2 &other) const;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <code_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_CODE;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass.  A map_region2 representing the code for
+   a particular function, using LABEL_DECLs for its keys.  */
+
+class function_region2 : public map_region2
+{
+public:
+  function_region2 (region2 *parent, tree type)
+  : map_region2 (parent, type)
+  {
+    gcc_assert (FUNC_OR_METHOD_TYPE_P (type));
+  }
+  function_region2 (const function_region2 &other)
+  : map_region2 (other)
+  {}
+
+  /* region2 vfuncs.  */
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+  enum kind get_kind () const FINAL OVERRIDE { return RK_FUNCTION; }
+
+  /* map_region2 vfunc.  */
+  bool valid_key_p (tree key) const FINAL OVERRIDE;
+
+  region2 *get_element (region_model2 *model,
+			svalue2 *index_sid,
+			region_model2_context *ctxt);
+
+  bool compare_fields (const function_region2 &other) const;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <function_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_FUNCTION;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass representing an array (or an array-like view
+   of a parent region2 of memory.
+   This can't be a map_region2 as we can't use trees as the keys: there's
+   no guarantee about the uniqueness of an INTEGER_CST.  */
+
+class array_region2 : public region2
+{
+public:
+#if 0
+  wide_int m_test;
+
+  typedef wide_int key_t;
+  typedef int_hash <wide_int, -1, -2> hash_t;
+  typedef ordered_hash_map<hash_t, region2_id> map_t;
+#else
+  typedef int key_t;
+  typedef int_hash <int, -1, -2> int_hash_t;
+  typedef ordered_hash_map<int_hash_t, region2 *> map_t;
+#endif
+  typedef map_t::iterator iterator_t;
+
+  array_region2 (region2 *parent, tree type)
+  : region2 (parent, type)
+  {
+    gcc_assert (TREE_CODE (type) == ARRAY_TYPE);
+  }
+  array_region2 (const array_region2 &other);
+
+  void dump_dot_to_pp (const region_model2 &model,
+		       pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  void dump_child_label (const region_model2 &model,
+			 region2 *child,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+  /* region2 vfuncs.  */
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+  enum kind get_kind () const FINAL OVERRIDE { return RK_ARRAY; }
+  array_region2 *dyn_cast_array_region2 () { return this; }
+
+  region2 *get_element (region_model2 *model,
+			svalue2 *index,
+			region_model2_context *ctxt);
+
+  bool compare_fields (const array_region2 &other) const;
+#if 0
+  static bool can_merge_p (const array_region2 *array_region2_a,
+			   const array_region2 *array_region2_b,
+			   array_region2 *merged_array_region2,
+			   region2_id merged_rid,
+			   model_merger *merger);
+#endif
+
+  iterator_t begin () { return m_map.begin (); }
+  iterator_t end () { return m_map.end (); }
+  size_t elements () const { return m_map.elements (); }
+
+  region2 *get_or_create (region_model2 *model,
+			  key_t key, tree type,
+			  region_model2_context *ctxt);
+//  void unbind (int expr);
+  region2 *get (key_t key);
+
+  bool get_key_for_child_region2 (region2 *child,
+				 key_t *out) const;
+
+#if 0
+  bool get_key_for_child_region2 (region2 *child,
+				 const region_model2 &model,
+				 key_t *out) const;
+#endif
+
+  void add_to_hash (inchash::hash &hstate) const OVERRIDE;
+  void print_fields (const region_model2 &model,
+		     pretty_printer *pp) const
+    OVERRIDE;
+  void validate (const region_model2 &model) const FINAL OVERRIDE;
+
+  static key_t key_from_constant (tree cst);
+  tree constant_from_key (key_t key);
+
+ private:
+  static int key_cmp (const void *, const void *);
+
+  /* Mapping from tree to child region2.  */
+  map_t m_map;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <array_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_ARRAY;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass representing a stack, containing all stack
+   frames, and implicitly providing a POISON_KIND_UNINIT value to all
+   child region2s by default.  */
+
+class stack_region2 : public region2
+{
+public:
+  stack_region2 (region2 *parent)
+  : region2 (parent, NULL_TREE)
+  {}
+
+  stack_region2 (const stack_region2 &other);
+
+  bool compare_fields (const stack_region2 &other) const;
+
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return RK_STACK; }
+
+  void dump_child_label (const region_model2 &model,
+			 region2 *child,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+#if 0
+  void push_frame (region2 *frame_rid);
+  region2 *get_current_frame_id () const;
+  void pop_frame (region_model2 *model, region2_id result_dst_rid,
+		  bool purge, purge_stats *stats,
+		  region_model2_context *ctxt);
+  unsigned get_num_frames () const { return m_frame_rids.length (); }
+  region2_id get_frame_rid (unsigned i) const { return m_frame_rids[i]; }
+
+  static bool can_merge_p (const stack_region2 *stack_region2_a,
+			   const stack_region2 *stack_region2_b,
+			   model_merger *merger);
+  svalue2 *get_value_by_name (tree identifier,
+			      const region_model2 &model) const;
+#endif
+
+  void validate (const region_model2 &model) const FINAL OVERRIDE;
+
+ private:
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+  void print_fields (const region_model2 &model,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+#if 0
+  auto_vec<region2 *> m_frame_rids;
+#endif
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <stack_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_STACK;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass: a region2 within which region2s can be
+   dynamically allocated.  */
+
+class heap_region2 : public region2
+{
+public:
+  heap_region2 (region2 *parent)
+  : region2 (parent, NULL_TREE)
+  {}
+  heap_region2 (const heap_region2 &other);
+
+  bool compare_fields (const heap_region2 &other) const;
+
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return RK_HEAP; }
+#if 0
+  static bool can_merge_p (const heap_region2 *heap_a, region2_id heap_a_rid,
+			   const heap_region2 *heap_b, region2_id heap_b_rid,
+			   heap_region2 *merged_heap, region2_id merged_heap_rid,
+			   model_merger *merger);
+#endif
+
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <heap_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_HEAP;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass.  The root region2, containing all region2s
+   (either directly, or as descendents).
+   Unique within a region_model2.  */
+
+class root_region2 : public region2
+{
+public:
+  root_region2 ();
+  root_region2 (const root_region2 &other);
+
+  bool compare_fields (const root_region2 &other) const;
+
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return RK_ROOT; }
+
+  void dump_child_label (const region_model2 &model,
+			 region2 *child,
+			 pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+#if 0
+  region2_id push_frame (region_model2 *model, function *fun,
+			vec<svalue2_id> *arg_sids,
+			region_model2_context *ctxt);
+  region2_id get_current_frame_id (const region_model2 &model) const;
+  void pop_frame (region_model2 *model, region2_id result_dst_rid,
+		  bool purge, purge_stats *stats,
+		  region_model2_context *ctxt);
+  region2_id ensure_stack_region2 (region_model2 *model);
+  region2_id get_stack_region2_id () const { return m_stack_rid; }
+  stack_region2 *get_stack_region2 (const region_model2 *model) const;
+
+  region2_id ensure_globals_region2 (region_model2 *model);
+  region2_id get_globals_region2_id () const { return m_globals_rid; }
+  globals_region2 *get_globals_region2 (const region_model2 *model) const;
+
+  region2_id ensure_code_region2 (region_model2 *model);
+  code_region2 *get_code_region2 (const region_model2 *model) const;
+
+  region2_id ensure_heap_region2 (region_model2 *model);
+  heap_region2 *get_heap_region2 (const region_model2 *model) const;
+
+  static bool can_merge_p (const root_region2 *root_region2_a,
+			   const root_region2 *root_region2_b,
+			   root_region2 *merged_root_region2,
+			   model_merger *merger);
+#endif
+
+  svalue2 *get_value_by_name (tree identifier,
+			      const region_model2 &model) const;
+
+  void validate (const region_model2 &model) const FINAL OVERRIDE;
+
+private:
+  void add_to_hash (inchash::hash &hstate) const FINAL OVERRIDE;
+  void print_fields (const region_model2 &model,
+		     pretty_printer *pp) const
+    FINAL OVERRIDE;
+
+#if 0
+  region2_id m_stack_rid;
+  region2_id m_globals_rid;
+  region2_id m_code_rid;
+  region2_id m_heap_rid;
+#endif
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <root_region2 *>::test (region2 *reg)
+{
+  return reg->get_kind () == region2::RK_ROOT;
+}
+
+namespace ana {
+
+/* Concrete region2 subclass: a region2 to use when dereferencing an unknown
+   pointer.  */
+
+class symbolic_region2 : public region2
+{
+public:
+  symbolic_region2 (region2 *parent, tree type, bool possibly_null)
+  : region2 (parent, type),
+    m_possibly_null (possibly_null)
+  {}
+  symbolic_region2 (const symbolic_region2 &other);
+
+  const symbolic_region2 *dyn_cast_symbolic_region2 () const FINAL OVERRIDE
+  { return this; }
+  symbolic_region2 *dyn_cast_symbolic_region2 () FINAL OVERRIDE
+  { return this; }
+
+  bool compare_fields (const symbolic_region2 &other) const;
+
+#if 0
+  region2 *clone () const FINAL OVERRIDE;
+#endif
+
+  enum kind get_kind () const FINAL OVERRIDE { return RK_SYMBOLIC; }
+
+  void print_fields (const region_model2 &model,
+		     pretty_printer *pp) const FINAL OVERRIDE;
+
+  bool m_possibly_null;
+};
+
+/* FIXME.  */
+  
+class region_model2_manager
+{
+public:
+  function_region2 *get_region2_for_fndecl (tree fndecl, region_model2_context *ctxt);
+  region2 *get_region2_for_label (tree label, region_model2_context *ctxt);
+};
+
+/* A region_model2 encapsulates a representation of the state of memory, with
+   a tree of region2s, along with their associated values.
+   The representation is graph-like because values can be pointers to
+   region2s.
+   It also stores a constraint_manager, capturing relationships between
+   the values.  */
+
+class region_model2
+{
+ public:
+  region_model2 ();
+  region_model2 (const region_model2 &other);
+  ~region_model2 ();
+
+#if 0//__cplusplus >= 201103
+  region_model2 (region_model2 &&other);
+#endif
+
+#if 0
+  region_model2 &operator= (const region_model2 &other);
+#endif
+
+  bool operator== (const region_model2 &other) const;
+  bool operator!= (const region_model2 &other) const
+  {
+    return !(*this == other);
+  }
+
+  hashval_t hash () const;
+
+  void print (pretty_printer *pp) const;
+
+  void dump_dot_to_pp (pretty_printer *pp) const;
+  void dump_dot_to_file (FILE *fp) const;
+  void dump_dot (const char *path) const;
+
+  void dump_to_pp (pretty_printer *pp, bool summarize) const;
+  void dump (FILE *fp, bool summarize) const;
+  void dump (bool summarize) const;
+
+  void debug () const;
+
+  void validate () const;
+
+  void check_for_poison (tree expr, region_model2_context *ctxt);
+  void on_assignment (const gassign *stmt, region_model2_context *ctxt);
+  bool on_call_pre (const gcall *stmt, region_model2_context *ctxt);
+  void on_call_post (const gcall *stmt,
+		     bool unknown_side_effects,
+		     region_model2_context *ctxt);
+  void handle_unrecognized_call (const gcall *call,
+				 region_model2_context *ctxt);
+  void on_return (const greturn *stmt, region_model2_context *ctxt);
+  void on_setjmp (const gcall *stmt, const exploded_node *enode,
+		  region_model2_context *ctxt);
+  void on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call,
+		   int setjmp_stack_depth, region_model2_context *ctxt);
+
+  void update_for_phis (const supernode *snode,
+			const cfg_superedge *last_cfg_superedge,
+			region_model2_context *ctxt);
+
+  void handle_phi (const gphi *phi,
+		   tree lhs, tree rhs, bool is_back_edge,
+		   region_model2_context *ctxt);
+
+  bool maybe_update_for_edge (const superedge &edge,
+			      const gimple *last_stmt,
+			      region_model2_context *ctxt);
+
+#if 0
+  egion2_id get_root_rid () const { return m_root_rid; }
+  root_region2 *get_root_region2 () const;
+
+  region2_id get_stack_region2_id () const;
+#endif
+  region2 *push_frame (function *fun, vec<svalue2 *> *arg_sids,
+		       region_model2_context *ctxt);
+  frame_region2 *get_current_frame () const;
+  function * get_current_function () const;
+  void pop_frame (region2 *result_dst,
+		  bool purge, purge_stats *stats,
+		  region_model2_context *ctxt);
+  int get_stack_depth () const;
+  function *get_function_at_depth (unsigned depth) const;
+
+#if 0
+  region2_id get_globals_region2_id () const;
+  void replace_svalue2 (svalue2_id sid, svalue2 *new_sval);
+#endif
+
+  region2 *get_lvalue (path_var pv, region_model2_context *ctxt);
+  region2 *get_lvalue (tree expr, region_model2_context *ctxt);
+  svalue2 *get_rvalue (path_var pv, region_model2_context *ctxt);
+  svalue2 *get_rvalue (tree expr, region_model2_context *ctxt);
+
+#if 0
+  svalue2_id get_or_create_ptr_svalue2 (tree ptr_type, region2_id id);
+  svalue2_id get_or_create_constant_svalue2 (tree cst_expr);
+  svalue2_id get_svalue2_for_fndecl (tree ptr_type, tree fndecl,
+				   region_model2_context *ctxt);
+  svalue2_id get_svalue2_for_label (tree ptr_type, tree label,
+				  region_model2_context *ctxt);
+
+  svalue2_id maybe_cast (tree type, svalue2_id sid, region_model2_context *ctxt);
+  svalue2_id maybe_cast_1 (tree type, svalue2_id sid);
+
+  region2_id get_field_region2 (region2_id rid, tree field,
+			      region_model2_context *ctxt);
+
+  region2_id deref_rvalue (svalue2_id ptr_sid, region_model2_context *ctxt);
+  region2_id deref_rvalue (tree ptr, region_model2_context *ctxt);
+
+#endif
+  void set_value (region2 *lhs_reg, svalue2 *rhs_sval,
+		  region_model2_context *ctxt);
+  void set_value (tree lhs, tree rhs, region_model2_context *ctxt);
+  svalue2 *set_to_new_unknown_value (region2 *dst_reg, tree type,
+				     region_model2_context *ctxt);
+  void copy_region (region2 *dst_reg, region2 *src_reg,
+		    region_model2_context *ctxt);
+  tristate eval_condition (svalue2 *lhs,
+			   enum tree_code op,
+			   svalue2 *rhs) const;
+#if 0
+  tristate eval_condition_without_cm (svalue2_id lhs,
+				      enum tree_code op,
+				      svalue2_id rhs) const;
+#endif
+  tristate eval_condition (tree lhs,
+			   enum tree_code op,
+			   tree rhs,
+			   region_model2_context *ctxt);
+  bool add_constraint (tree lhs, enum tree_code op, tree rhs,
+		       region_model2_context *ctxt);
+#if 0
+  tree maybe_get_constant (svalue2_id sid) const;
+
+  region2_id add_new_malloc_region2 ();
+
+  tree get_representative_tree (svalue2_id sid) const;
+  path_var get_representative_path_var (region2_id rid) const;
+  void get_path_vars_for_svalue2 (svalue2_id sid, vec<path_var> *out) const;
+
+  void purge_unused_svalue2s (purge_stats *out,
+			     region_model2_context *ctxt,
+			     svalue2_id_set *known_used_sids = NULL);
+  void purge_region2s (const region2_id_set &set,
+		      purge_stats *stats,
+		      logger *logger);
+
+  unsigned get_num_svalue2s () const { return m_svalue2s.length (); }
+  unsigned get_num_region2s () const { return m_region2s.length (); }
+
+  /* For selftests.  */
+  constraint_manager *get_constraints ()
+  {
+    return m_constraints;
+  }
+
+  void get_descendents (region2_id rid, region2_id_set *out,
+			region2_id exclude_rid) const;
+
+  void delete_region2_and_descendents (region2_id rid,
+				      enum poison_kind pkind,
+				      purge_stats *stats,
+				      logger *logger);
+
+  bool can_merge_with_p (const region_model2 &other_model,
+			 region_model2 *out_model,
+			 svalue2_id_merger_mapping *out) const;
+  bool can_merge_with_p (const region_model2 &other_model,
+			 region_model2 *out_model) const;
+
+  svalue2_id get_value_by_name (const char *name) const;
+
+  svalue2_id convert_byte_offset_to_array_index (tree ptr_type,
+						svalue2_id offset_sid);
+
+  region2_id get_or_create_mem_ref (tree type,
+				   svalue2_id ptr_sid,
+				   svalue2_id offset_sid,
+				   region_model2_context *ctxt);
+  region2_id get_or_create_pointer_plus_expr (tree type,
+					     svalue2_id ptr_sid,
+					     svalue2_id offset_sid,
+					     region_model2_context *ctxt);
+  region2_id get_or_create_view (region2_id raw_rid, tree type,
+				region_model2_context *ctxt);
+
+  tree get_fndecl_for_call (const gcall *call,
+			    region_model2_context *ctxt);
+#endif
+
+ private:
+#if 0
+  region2_id get_lvalue_1 (path_var pv, region_model2_context *ctxt);
+  svalue2_id get_rvalue_1 (path_var pv, region_model2_context *ctxt);
+
+  void copy_struct_region2 (region2_id dst_rid, struct_region2 *dst_reg,
+			   struct_region2 *src_reg, region_model2_context *ctxt);
+  void copy_union_region2 (region2_id dst_rid, union_region2 *src_reg,
+			  region_model2_context *ctxt);
+  void copy_array_region2 (region2_id dst_rid, array_region2 *dst_reg,
+			  array_region2 *src_reg, region_model2_context *ctxt);
+
+  region2_id make_region2_for_unexpected_tree_code (region_model2_context *ctxt,
+						  tree t,
+						  const dump_location_t &loc);
+
+  void add_any_constraints_from_ssa_def_stmt (tree lhs,
+					      enum tree_code op,
+					      tree rhs,
+					      region_model2_context *ctxt);
+  void add_any_constraints_from_gassign (enum tree_code op,
+					 tree rhs,
+					 const gassign *assign,
+  void add_any_constraints_from_gcall (enum tree_code op,
+				       tree rhs,
+				       const gcall *call,
+				       region_model2_context *ctxt);
+#endif					 
+  void update_for_call_superedge (const call_superedge &call_edge,
+				  region_model2_context *ctxt);
+  void update_for_return_superedge (const return_superedge &return_edge,
+				    region_model2_context *ctxt);
+  void update_for_call_summary (const callgraph_superedge &cg_sedge,
+				region_model2_context *ctxt);
+  bool apply_constraints_for_gcond (const cfg_superedge &edge,
+				    const gcond *cond_stmt,
+				    region_model2_context *ctxt);
+  bool apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
+				      const gswitch *switch_stmt,
+				      region_model2_context *ctxt);
+#if 0
+  void poison_any_pointers_to_bad_region2s (const region2_id_set &bad_region2s,
+					    enum poison_kind pkind);
+
+  void dump_summary_of_rep_path_vars (pretty_printer *pp,
+				      auto_vec<path_var> *rep_path_vars,
+				      bool *is_first);
+#endif
+
+  /* TODO: map from region2 to svalue2.  */
+  
+#if 0
+  auto_delete_vec<svalue2> m_svalue2s;
+  auto_delete_vec<region2> m_region2s;
+  region2_id m_root_rid;
+  constraint_manager *m_constraints; // TODO: embed, rather than dynalloc?
+#endif
+};
+
+/* Some region_model2 activity could lead to warnings (e.g. attempts to use an
+   uninitialized value).  This abstract base class encapsulates an interface
+   for the region2 model to use when emitting such warnings.
+
+   It also provides an interface for being notified about svalue2_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_model2,
+   whilst keeping them somewhat modularized.  */
+
+class region_model2_context
+{
+ public:
+  virtual void warn (pending_diagnostic *d) = 0;
+
+#if 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 (svalue2_id sid) = 0;
+#endif
+
+  /* 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).
+     MAP has already been applied to the IDs, but is provided in case
+     the client needs to figure out the old IDs.  */
+  virtual int on_svalue2_purge (svalue2 *purged_value) = 0;
+
+  virtual logger *get_logger () = 0;
+
+#if 0
+  /* Hook for clients to be notified when CHILD_SID is created
+     from PARENT_SID, when "inheriting" a value for a region2 from a
+     parent region2.
+     This exists so that state machines that inherit state can
+     propagate the state from parent to child.  */
+  virtual void on_inherited_svalue2 (svalue2_id parent_sid,
+				    svalue2_id child_sid) = 0;
+
+  /* Hook for clients to be notified when DST_SID is created
+     (or reused) as a cast from SRC_SID.
+     This exists so that state machines can propagate the state
+     from SRC_SID to DST_SID.  */
+  virtual void on_cast (svalue2_id src_sid,
+			svalue2_id dst_sid) = 0;
+#endif
+  /* Hook for clients to be notified when the condition
+     "LHS OP RHS" is added to the region2 model.
+     This exists so that state machines can detect tests on edges,
+     and use them to trigger sm-state transitions (e.g. transitions due
+     to ptrs becoming known to be NULL or non-NULL, rather than just
+     "unchecked") */
+  virtual void on_condition (tree lhs, enum tree_code op, tree rhs) = 0;
+
+#if 0
+  /* Hooks for clients to be notified when an unknown change happens
+     to SID (in response to a call to an unknown function).  */
+  virtual void on_unknown_change (svalue2_id sid) = 0;
+#endif
+  /* Hooks for clients to be notified when a phi node is handled,
+     where RHS is the pertinent argument.  */
+  virtual void on_phi (const gphi *phi, tree rhs) = 0;
+
+  /* Hooks for clients to be notified when the region2 model doesn't
+     know how to handle the tree code of T at LOC.  */
+  virtual void on_unexpected_tree_code (tree t,
+					const dump_location_t &loc) = 0;
+};
+
+/* A "do nothing" subclass of region_model2_context.  */
+
+class noop_region_model2_context : public region_model2_context
+{
+public:
+  void warn (pending_diagnostic *) OVERRIDE {}
+#if 0
+  int on_svalue2_purge (svalue2_id, const svalue2_id_map &) OVERRIDE
+  {
+    return 0;
+  }
+  logger *get_logger () OVERRIDE { return NULL; }
+  void on_inherited_svalue2 (svalue2_id parent_sid ATTRIBUTE_UNUSED,
+			    svalue2_id child_sid  ATTRIBUTE_UNUSED)
+    OVERRIDE
+  {
+  }
+  void on_cast (svalue2_id src_sid ATTRIBUTE_UNUSED,
+		svalue2_id dst_sid ATTRIBUTE_UNUSED) OVERRIDE
+  {
+  }
+  void on_condition (tree lhs ATTRIBUTE_UNUSED,
+		     enum tree_code op ATTRIBUTE_UNUSED,
+		     tree rhs ATTRIBUTE_UNUSED) OVERRIDE
+  {
+  }
+  void on_unknown_change (svalue2_id sid ATTRIBUTE_UNUSED) OVERRIDE
+  {
+  }
+  void on_phi (const gphi *phi ATTRIBUTE_UNUSED,
+	       tree rhs ATTRIBUTE_UNUSED) OVERRIDE
+  {
+  }
+  void on_unexpected_tree_code (tree, const dump_location_t &) OVERRIDE {}
+#endif
+};
+
+/* A subclass of region_model2_context for determining if operations fail
+   e.g. "can we generate a region2 for the lvalue of EXPR?".  */
+
+class tentative_region_model2_context : public noop_region_model2_context
+{
+public:
+  tentative_region_model2_context () : m_num_unexpected_codes (0) {}
+
+  void on_unexpected_tree_code (tree, const dump_location_t &)
+    FINAL OVERRIDE
+  {
+    m_num_unexpected_codes++;
+  }
+
+  bool had_errors_p () const { return m_num_unexpected_codes > 0; }
+
+private:
+  int m_num_unexpected_codes;
+};
+
+#if 0
+/* A bundle of data for use when attempting to merge two region_model2
+   instances to make a third.  */
+
+struct model_merger
+{
+  model_merger (const region_model2 *model_a,
+		const region_model2 *model_b,
+		region_model2 *merged_model,
+		svalue2_id_merger_mapping *sid_mapping)
+  : m_model_a (model_a), m_model_b (model_b),
+    m_merged_model (merged_model),
+    m_map_region2s_from_a_to_m (model_a->get_num_region2s ()),
+    m_map_region2s_from_b_to_m (model_b->get_num_region2s ()),
+    m_sid_mapping (sid_mapping)
+  {
+    gcc_assert (sid_mapping);
+  }
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump (FILE *fp) const;
+  void dump () const;
+
+  template <typename Subclass>
+  Subclass *get_region2_a (region2_id rid_a) const
+  {
+    return m_model_a->get_region2 <Subclass> (rid_a);
+  }
+
+  template <typename Subclass>
+  Subclass *get_region2_b (region2_id rid_b) const
+  {
+    return m_model_b->get_region2 <Subclass> (rid_b);
+  }
+
+  bool can_merge_values_p (svalue2_id sid_a,
+			   svalue2_id sid_b,
+			   svalue2_id *merged_sid);
+
+  void record_region2s (region2_id a_rid,
+		       region2_id b_rid,
+		       region2_id merged_rid);
+
+  void record_svalue2s (svalue2_id a_sid,
+		       svalue2_id b_sid,
+		       svalue2_id merged_sid);
+
+  const region_model2 *m_model_a;
+  const region_model2 *m_model_b;
+  region_model2 *m_merged_model;
+#if 0
+  one_way_region2_id_map m_map_region2s_from_a_to_m;
+  one_way_region2_id_map m_map_region2s_from_b_to_m;
+  svalue2_id_merger_mapping *m_sid_mapping;
+#endif
+};
+#endif
+
+} // namespace ana
+
+extern void debug (const region_model2 &rmodel);
+
+namespace ana {
+
+#if CHECKING_P
+
+namespace selftest {
+
+using namespace ::selftest;
+
+/* An implementation of region_model2_context for use in selftests, which
+   stores any pending_diagnostic instances passed to it.  */
+
+class test_region_model2_context : public noop_region_model2_context
+{
+public:
+  void warn (pending_diagnostic *d) FINAL OVERRIDE
+  {
+    m_diagnostics.safe_push (d);
+  }
+
+  unsigned get_num_diagnostics () const { return m_diagnostics.length (); }
+
+  void on_unexpected_tree_code (tree t, const dump_location_t &)
+    FINAL OVERRIDE
+  {
+    internal_error ("unhandled tree code: %qs",
+		    get_tree_code_name (TREE_CODE (t)));
+  }
+
+private:
+  /* Implicitly delete any diagnostics in the dtor.  */
+  auto_delete_vec<pending_diagnostic> m_diagnostics;
+};
+
+/* Attempt to add the constraint (LHS OP RHS) to MODEL.
+   Verify that MODEL remains satisfiable.  */
+
+#define ADD_SAT_CONSTRAINT(MODEL, LHS, OP, RHS)	\
+  SELFTEST_BEGIN_STMT					\
+    bool sat = (MODEL).add_constraint (LHS, OP, RHS, NULL);	\
+    ASSERT_TRUE (sat);					\
+  SELFTEST_END_STMT
+
+/* Attempt to add the constraint (LHS OP RHS) to MODEL.
+   Verify that the result is not satisfiable.  */
+
+#define ADD_UNSAT_CONSTRAINT(MODEL, LHS, OP, RHS)	\
+  SELFTEST_BEGIN_STMT					\
+    bool sat = (MODEL).add_constraint (LHS, OP, RHS, NULL);	\
+    ASSERT_FALSE (sat);				\
+  SELFTEST_END_STMT
+
+/* Implementation detail of the ASSERT_CONDITION_* macros.  */
+
+void assert_condition (const location &loc,
+		       region_model2 &model,
+		       tree lhs, tree_code op, tree rhs,
+		       tristate expected);
+
+/* Assert that REGION_MODEL2 evaluates the condition "LHS OP RHS"
+   as "true".  */
+
+#define ASSERT_CONDITION_TRUE(REGION_MODEL2, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL2, LHS, OP, RHS,	\
+		    tristate (tristate::TS_TRUE));		\
+  SELFTEST_END_STMT
+
+/* Assert that REGION_MODEL2 evaluates the condition "LHS OP RHS"
+   as "false".  */
+
+#define ASSERT_CONDITION_FALSE(REGION_MODEL2, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL2, LHS, OP, RHS,	\
+		    tristate (tristate::TS_FALSE));		\
+  SELFTEST_END_STMT
+
+/* Assert that REGION_MODEL2 evaluates the condition "LHS OP RHS"
+   as "unknown".  */
+
+#define ASSERT_CONDITION_UNKNOWN(REGION_MODEL2, LHS, OP, RHS) \
+  SELFTEST_BEGIN_STMT							\
+  assert_condition (SELFTEST_LOCATION, REGION_MODEL2, LHS, OP, RHS,	\
+		    tristate (tristate::TS_UNKNOWN));		\
+  SELFTEST_END_STMT
+
+} /* end of namespace selftest.  */
+
+#endif /* #if CHECKING_P */
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_REGION_MODEL_2_H */
diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi
index 4bd1c59d8b6..7040f775d82 100644
--- a/gcc/doc/analyzer.texi
+++ b/gcc/doc/analyzer.texi
@@ -257,6 +257,8 @@ ways a graph of graphs.
 Here's an example of printing a @code{region_model}, showing the ASCII-art
 used to visualize the region hierarchy (colorized when printing to stderr):
 
+FIXME: this will need updating
+
 @smallexample
 (gdb) call debug (*this)
 r0: @{kind: 'root', parent: null, sval: null@}
@@ -393,13 +395,6 @@ The constraint-handling code assumes reflexivity in some places
 As a simple workaround, constraints on floating-point values are
 currently ignored.
 @item
-The region model code creates lots of little mutable objects at each
-@code{region_model} (and thus per @code{exploded_node}) rather than
-sharing immutable objects and having the mutable state in the
-@code{program_state} or @code{region_model}.  The latter approach might be
-more efficient, and might avoid dealing with IDs rather than pointers
-(which requires us to impose an ordering to get meaningful equality).
-@item
 The region model code doesn't yet support @code{memcpy}.  At the
 gimple-ssa level these have been optimized to statements like this:
 @smallexample
@@ -515,6 +510,8 @@ breaks with an assertion failure when a variable called @code{ptr}
 acquires a value that's unknown, using
 @code{region_model::get_value_by_name} to locate the variable
 
+FIXME: this will need updating:
+
 @smallexample
     /* Find a variable matching "ptr".  */
     svalue_id sid = get_value_by_name ("ptr");
-- 
2.21.0

