From b6b46e9b209453ab5c435e9dfc20905def5bf702 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 9 Jul 2019 16:13:45 -0400
Subject: [PATCH 070/108] FIXME: WIP on region-merging

---
 gcc/analyzer/impl-4.cc       |  99 +++++++----
 gcc/analyzer/region-model.cc | 392 +++++++++++++++++++++++++++++++++----------
 gcc/analyzer/region-model.h  |  27 ++-
 3 files changed, 398 insertions(+), 120 deletions(-)

diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index c3cdffe..122dc09 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -1051,14 +1051,21 @@ struct eg_hash_map_traits
   }
 };
 
+/* FIXME.  */
+
+struct per_program_point_data
+{
+  auto_vec<exploded_node *> m_enodes;
+};
+
 /* Traits class for storing per-program_point data within
    an exploded_graph.  */
 
 struct eg_point_hash_map_traits
 {
   typedef const program_point *key_type;
-  typedef exploded_node *value_type;
-  typedef exploded_node *compare_type;
+  typedef per_program_point_data *value_type;
+  typedef per_program_point_data *compare_type;
 
   static inline hashval_t hash (const key_type &k)
   {
@@ -1352,10 +1359,11 @@ private:
 
   /* Map from program_point to the first exploded_node at
      that point.
+     FIXME: generalize
      FIXME: why?*/
-  typedef hash_map <const program_point *, exploded_node *,
-		    eg_point_hash_map_traits> point_to_enode_map_t;
-  point_to_enode_map_t m_point_to_enode;
+  typedef hash_map <const program_point *, per_program_point_data *,
+		    eg_point_hash_map_traits> point_map_t;
+  point_map_t m_per_point_data;
 
   worklist m_worklist;
 
@@ -3599,6 +3607,8 @@ program_state::prune_for_point (exploded_graph &eg,
   eg.log ("num constraints purged: %i", stats.m_num_constraints);
   eg.log ("num sm map items purged: %i", stats.m_num_client_items);
 
+  new_state.m_region_model->canonicalize (&ctxt);
+
   return new_state;
 }
 
@@ -3642,6 +3652,10 @@ program_state::can_merge_with_p (const program_state &other,
 					 result.m_region_model))
     return false;
 
+  /* Copy m_checker_states to result.  */
+  FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+    out->m_checker_states.safe_push (new sm_state_map (*smap));
+
   if (out)
     *out = result;
 
@@ -5474,41 +5488,48 @@ exploded_graph::get_or_create_node (const program_point &point,
 
   /* Consider merging state with another enode at this program_point.  */
   if (flag_analyzer_state_merge)
-    if (exploded_node **slot = m_point_to_enode.get (&point))
+    if (per_program_point_data **slot = m_per_point_data.get (&point))
       {
-	exploded_node *existing_enode = (*slot);
-	log ("considering merging with existing EN: %i for point",
-	     existing_enode->m_index);
-	gcc_assert (existing_enode->get_point () == point);
-	const program_state &existing_state = existing_enode->get_state ();
-
-	/* They shouldn't be equal, or we would have found and returned
-	   it above.  */
-	gcc_assert (pruned_state != existing_state);
-
-	program_state merged_state (m_ext_state);
-	if (pruned_state.can_merge_with_p (existing_state, m_ext_state,
-					   &merged_state))
+	per_program_point_data *per_point_data = (*slot);
+	exploded_node *existing_enode;
+	unsigned i;
+	FOR_EACH_VEC_ELT (per_point_data->m_enodes, i, existing_enode)
 	  {
-	    log ("merging new state with that of EN: %i",
+	    log ("considering merging with existing EN: %i for point",
 		 existing_enode->m_index);
+	    gcc_assert (existing_enode->get_point () == point);
+	    const program_state &existing_state = existing_enode->get_state ();
+
+	    /* They shouldn't be equal, or we would have found and returned
+	       it above.  */
+	    gcc_assert (pruned_state != existing_state);
+
+	    // FIXME: this is successively merging within the loop
 
-	    /* Try again for a cache hit.  */
-	    ps.set_state (merged_state);
-	    if (exploded_node **slot = m_point_and_state_to_node.get (&ps))
+	    program_state merged_state (m_ext_state);
+	    if (pruned_state.can_merge_with_p (existing_state, m_ext_state,
+					       &merged_state))
 	      {
-		/* An exploded_node for PS already exists.  */
-		log ("reused EN: %i", (*slot)->m_index);
-		m_global_stats.m_node_reuse_after_merge_count++;
-		per_fn_stats->m_node_reuse_after_merge_count++;
-		return *slot;
-	      }
+		log ("merging new state with that of EN: %i",
+		     existing_enode->m_index);
+
+		/* Try again for a cache hit.  */
+		ps.set_state (merged_state);
+		if (exploded_node **slot = m_point_and_state_to_node.get (&ps))
+		  {
+		    /* An exploded_node for PS already exists.  */
+		    log ("reused EN: %i", (*slot)->m_index);
+		    m_global_stats.m_node_reuse_after_merge_count++;
+		    per_fn_stats->m_node_reuse_after_merge_count++;
+		    return *slot;
+		  }
 
-	    /* Otherwise, continue, using the merged state in "ps".  */
+		/* Otherwise, continue, using the merged state in "ps".  */
+	      }
+	    else
+	      log ("not merging new state with that of EN: %i",
+		   existing_enode->m_index);
 	  }
-	else
-	  log ("not merging new state with that of EN: %i",
-	       existing_enode->m_index);
       }
 
   /* An exploded_node for "ps" doesn't already exist; create one.  */
@@ -5517,10 +5538,20 @@ exploded_graph::get_or_create_node (const program_point &point,
   m_point_and_state_to_node.put (node->get_ps_key (), node);
 
   /* Update per-program_point data.  */
+  // FIXME: update this comment:
   /* FIXME: doing this stores the last enode with the given program_point.
      Should we store more than one enode (perhaps a vec, and bail
      out if we have too many states per program_point?  */
-  m_point_to_enode.put (node->get_point_key (), node);
+  if (per_program_point_data **slot = m_per_point_data.get (&point))
+    {
+      (*slot)->m_enodes.safe_push (node);
+    }
+  else
+    {
+      per_program_point_data *per_point_data = new per_program_point_data;
+      m_per_point_data.put (node->get_point_key (), per_point_data);
+      per_point_data->m_enodes.safe_push (node);
+    }
 
   m_global_stats.m_num_nodes[node->get_point ().m_kind]++;
   per_fn_stats->m_num_nodes[node->get_point ().m_kind]++;
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index fbe97bd..9f537dd 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -485,6 +485,98 @@ region_svalue::remap_region_ids (const region_id_map &map)
   map.update (&m_rid);
 }
 
+/* FIXME.  */
+
+bool
+region_svalue::can_merge_values_p (const region_svalue &region_sval_a,
+				   const region_svalue &region_sval_b,
+				   svalue_id *merged_sid,
+				   tree type,
+				   const model_merger &merger)
+{
+  /* FIXME: they must be non-NULL; how could we test the kind otherwise?
+     We should have been testing the pointee value.  */
+  region_id a_rid = region_sval_a.get_pointee ();
+  region_id b_rid = region_sval_b.get_pointee ();
+
+  if (a_rid.null_p () && b_rid.null_p ())
+    {
+      /* Both are NULL.  */
+      svalue *merged_sval = new region_svalue (type, region_id::null ());
+      *merged_sid = merger.m_merged_model->add_svalue (merged_sval);
+      return true;
+    }
+
+  if (a_rid.null_p () || b_rid.null_p ())
+    {
+      /* If we have NULL vs non-NULL, then merge to unknown.  */
+      svalue *merged_sval = new unknown_svalue (type);
+      *merged_sid = merger.m_merged_model->add_svalue (merged_sval);
+      return true;
+    }
+
+  /* Both are non-NULL. */
+  gcc_assert (!a_rid.null_p () && !b_rid.null_p ());
+
+  /* If we have two regions that haven't been referenced yet,
+     and they're both in the heap, create a new heap region, and record
+     that.
+     Various other cases.
+     Probably move this to a region_svalue function.  */
+
+  /* FIXME: maybe use get_representative_tree?
+     or introduce a new thing on regions to test for equivalence?
+     but presumably we'd need the representative tree to do the lookup in
+     the merged model - assuming the region exists - aha: but get_lvalue
+     would presumably add it lazily.  */
+  tree tree_a = merger.m_model_a->get_representative_tree (a_rid);
+  tree tree_b = merger.m_model_b->get_representative_tree (b_rid);
+
+  /* FIXME: we probably need a deeper test here e.g. for fields of structs.  */
+  if (tree_a
+      && tree_a == tree_b)
+    {
+      region_id merged_pointee_rid
+	= merger.m_merged_model->get_lvalue (tree_a, NULL);
+      svalue *merged_sval
+	= new region_svalue (type, merged_pointee_rid);
+      *merged_sid = merger.m_merged_model->add_svalue (merged_sval);
+      return true;
+    }
+
+  /* FIXME: what about heap regions?
+     (and descendents of heap regions)
+     Perhaps have get_representative_tree return "*expr" for some expr.
+     Presumably we'd still have to create a heap region in the merged model,
+     though.
+     Or have something like get_repr_tree that returns a richer object that
+     "knows" about heap regions.
+  */
+
+#if 0
+    /* FIXME: is it OK to just check that the region_id is the same?  */
+    if (rid_a == rid_b)
+	{
+	  region_id merged_pointee_rid; // FIXME: what should this be???
+	  svalue *merged_sval
+	    = new region_svalue (type, merged_pointee_rid);
+	  *merged_sid = merger.m_merged_model->add_svalue (merged_sval);
+	  return true;
+	}
+      else
+#endif
+	/* FIXME: for now, always just merge to unknown for two non-NULL
+	   pointers, even if they might point to the same thing in
+	   A and B.  */
+	{
+	  /* Two different non-NULL pointers?  Merge to unknown.  */
+	  // TODO: add a constraint?
+	  svalue *merged_sval = new unknown_svalue (type);
+	  *merged_sid = merger.m_merged_model->add_svalue (merged_sval);
+	  return true;
+	}
+}
+
 /* class constant_svalue : public svalue.  */
 
 /* FIXME.  */
@@ -509,6 +601,36 @@ constant_svalue::hash () const
 
 /* FIXME.  */
 
+bool
+constant_svalue::can_merge_values_p (const constant_svalue &cst_sval_a,
+				     svalue_id sid_a ATTRIBUTE_UNUSED,
+				     const constant_svalue &cst_sval_b,
+				     svalue_id sid_b ATTRIBUTE_UNUSED,
+				     svalue_id *merged_sid,
+				     const model_merger &merger)
+{
+  tree cst_a = cst_sval_a.get_constant ();
+  tree cst_b = cst_sval_b.get_constant ();
+  svalue *merged_sval;
+  if (cst_a == cst_b) // FIXME: something other than pointer equality?
+    {
+      /* If they are the same constant, merge as that constant value.  */
+      merged_sval = new constant_svalue (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_svalue (TREE_TYPE (cst_a));
+    }
+  *merged_sid = merger.m_merged_model->add_svalue (merged_sval);
+  return true;
+}
+
+/* FIXME.  */
+
 void
 constant_svalue::print_details (const region_model &model ATTRIBUTE_UNUSED,
 				svalue_id this_sid ATTRIBUTE_UNUSED,
@@ -1159,6 +1281,49 @@ map_region::get_tree_for_child_region (region_id child_rid) const
   return NULL_TREE;
 }
 
+/* FIXME.  */
+
+static int
+tree_cmp (const void *p1, const void *p2)
+{
+  const_tree t1 = *(const_tree const *)p1;
+  const_tree t2 = *(const_tree const *)p2;
+
+  gcc_assert (t1);
+  gcc_assert (t2);
+
+#if 0
+  if (TREE_CODE (t1) != TREE_CODE (t2))
+    return TREE_CODE (t1) - TREE_CODE (t2);
+
+  if (DECL_P (t1))
+    {
+      if (DECL_NAME (t1) && DECL_NAME (t2))
+    }
+
+#if 0
+  switch (TREE_CODE (t1))
+    {
+    case PARM_DECL:
+
+    default:
+	gcc_unreachable ();
+    }
+#endif
+
+  gcc_unreachable ();
+
+#endif
+
+  if (t1 > t2)
+    return 1;
+  if (t1 < t2)
+    return -1;
+  return 0;
+
+  //return t1 - t2;
+}
+
 /* Attempt to merge MAP_REGION_A and MAP_REGION_B into MERGED_MAP_REGION,
    which has region_id MERGED_RID, using MERGER.
    Return true if the merger is possible, false otherwise.  */
@@ -1171,12 +1336,27 @@ map_region::can_merge_p (const map_region *map_region_a,
 			 const model_merger &merger)
 {
   // TODO:
+  /* Sort keys into a consistent order, to avoid cache misses
+     due to svalues/regions being in different orders between
+     two region_model instances.  */
+  auto_vec<tree> keys;
   for (map_t::iterator iter = map_region_a->m_map.begin ();
        iter != map_region_a->m_map.end ();
        ++iter)
     {
       tree key_a = (*iter).first;
-      region_id rid_a = (*iter).second;
+      keys.safe_push (key_a);
+    }
+#if 0
+  keys.qsort (tree_cmp);
+#endif
+
+  unsigned i;
+  tree key_a;
+  FOR_EACH_VEC_ELT (keys, i, key_a)
+    {
+      region_id rid_a
+	= *const_cast<map_region *>(map_region_a)->m_map.get (key_a);
 
       if (const region_id *slot_b
 	  = const_cast<map_region *>(map_region_b)->m_map.get (key_a))
@@ -1492,6 +1672,30 @@ heap_region::clone () const
   return new heap_region (*this);
 }
 
+/* FIXME.  */
+
+bool
+heap_region::can_merge_p (const heap_region *heap_a ATTRIBUTE_UNUSED,
+			  region_id heap_a_rid ATTRIBUTE_UNUSED,
+			  const heap_region *heap_b ATTRIBUTE_UNUSED,
+			  region_id heap_b_rid ATTRIBUTE_UNUSED,
+			  heap_region *merged_heap ATTRIBUTE_UNUSED,
+			  region_id merged_heap_rid ATTRIBUTE_UNUSED,
+			  const model_merger &merger ATTRIBUTE_UNUSED)
+{
+  /* TODO:
+     iterate through descendents of each, cloning
+     But how to match them up between the two?
+     Assume they're in the same order?
+     Or do it based on what's pointing to them?
+     Maybe do it from the region_svalue instances, as needed, when
+     seeing pairs of region_svalue instances.  */
+
+  //gcc_unreachable (); // TODO
+  //return false;
+  return true;
+}
+
 /* class root_region : public region.  */
 
 /* FIXME.  */
@@ -1782,7 +1986,15 @@ root_region::can_merge_p (const root_region *root_region_a,
   heap_region *heap_b = root_region_b->get_heap_region (merger.m_model_b);
   if (heap_a && heap_b)
     {
-      // FIXME: attempt to merge, or fail
+      region_id merged_heap_rid
+	= merged_root_region->ensure_heap_region (merger.m_merged_model);
+      heap_region *merged_heap
+	= merged_root_region->get_heap_region (merger.m_merged_model);
+      if (!heap_region::can_merge_p (heap_a, root_region_a->m_heap_rid,
+				     heap_b, root_region_b->m_heap_rid,
+				     merged_heap, merged_heap_rid,
+				     merger))
+	return false;
     }
   /* otherwise, merge as "no heap".  */
 
@@ -2145,6 +2357,14 @@ region_model::validate () const
 /* FIXME.  */
 
 void
+region_model::canonicalize (region_model_context *ctxt ATTRIBUTE_UNUSED)
+{
+  // TODO
+}
+
+/* FIXME.  */
+
+void
 region_model::on_assignment (const gassign *assign, region_model_context *ctxt)
 {
   tree lhs = gimple_assign_lhs (assign);
@@ -2384,7 +2604,8 @@ region_model::on_call (const gcall *call, region_model_context *ctxt)
 	     poisoning pointers.  */
 	  region_id pointee_rid = ptr_to_region_sval->get_pointee ();
 	  region_id heap_rid = get_root_region ()->ensure_heap_region (this);
-	  if (get_region (pointee_rid)->get_parent () == heap_rid)
+	  if (!pointee_rid.null_p ()
+	      && get_region (pointee_rid)->get_parent () == heap_rid)
 	    {
 	      purge_stats stats;
 	      delete_region_and_descendents (pointee_rid,
@@ -3052,16 +3273,12 @@ region_model::get_representative_tree (region_id rid) const
       frame_region *frame = get_region <frame_region> (get_current_frame_id ());
       return frame->get_tree_for_child_region (rid);
     }
-  /* FIXME: the following leads to a regression
-     TODO: where?  why?  */
-#if 0
   if (reg->get_parent () == get_globals_region_id ())
     {
       map_region *globals = get_root_region ()->get_globals_region (this);
       if (globals)
 	return globals->get_tree_for_child_region (rid);
     }
-#endif
 
   /* Handle e.g. fields of a local by recursing.  */
   region_id parent_rid = reg->get_parent ();
@@ -3946,10 +4163,10 @@ model_merger::can_merge_values_p (svalue_id sid_a,
   if (type == NULL_TREE)
     type = sval_b->get_type ();
 
-  /* If one of the values is unknown, then we can merge,
-     using an unknown value.  */
-  if (sval_a->get_kind () == SK_UNKNOWN
-      || sval_b->get_kind () == SK_UNKNOWN)
+  /* 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)
     {
       /* FIXME: this loses information about unknown values
 	 that are shared between multiple regions.  */
@@ -3958,85 +4175,46 @@ model_merger::can_merge_values_p (svalue_id sid_a,
       return true;
     }
 
-  /* If we have two constants, then we can merge.  */
-  if (sval_a->get_kind () == SK_CONSTANT
-      && sval_b->get_kind () == SK_CONSTANT)
-    {
-      const constant_svalue *cst_sval_a = (const constant_svalue *)sval_a;
-      const constant_svalue *cst_sval_b = (const constant_svalue *)sval_b;
-      tree cst_a = cst_sval_a->get_constant ();
-      tree cst_b = cst_sval_b->get_constant ();
-      svalue *merged_sval;
-      if (cst_a == cst_b) // FIXME: something other than pointer equality?
-	{
-	  /* If they are the same constant, merge as that constant value.  */
-	  merged_sval = new constant_svalue (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_svalue (TREE_TYPE (cst_a));
-	}
-      *merged_sid = m_merged_model->add_svalue (merged_sval);
-      return true;
-    }
-#endif
+  gcc_assert (sval_a->get_kind () == sval_b->get_kind ());
 
-  /* If we have two regions... .  */
-  if (sval_a->get_kind () == SK_REGION
-      && sval_b->get_kind () == SK_REGION)
+  switch (sval_a->get_kind ())
     {
-      const region_svalue *region_sval_a = (const region_svalue *)sval_a;
-      const region_svalue *region_sval_b = (const region_svalue *)sval_b;
+    default:
+      gcc_unreachable ();
 
-      if (region_sval_a && region_sval_b)
-	{
-#if 0
-	  /* FIXME: is it OK to just check that the region_id is the same?  */
-	  if (region_sval_a->get_pointee () == region_sval_b->get_pointee ())
-	    {
-	      region_id merged_pointee_rid; // FIXME: what should this be???
-	      svalue *merged_sval
-		= new region_svalue (type, merged_pointee_rid);
-	      *merged_sid = m_merged_model->add_svalue (merged_sval);
-	      return true;
-	    }
-	  else
-#endif
-	    /* FIXME: for now, always just merge to unknown for two non-NULL
-	       pointers, even if they might point to the same thing in
-	       A and B.  */
-	    {
-	      /* Two different non-NULL pointers?  Merge to unknown.  */
-	      // TODO: add a constraint?
-	      svalue *merged_sval = new unknown_svalue (type);
-	      *merged_sid = m_merged_model->add_svalue (merged_sval);
-	      return true;
-	    }
-	}
-      else if (region_sval_a == NULL && region_sval_b == NULL)
-	{
-	  /* Both are NULL.  */
-	  svalue *merged_sval = new region_svalue (type, region_id::null ());
-	  *merged_sid = m_merged_model->add_svalue (merged_sval);
-	  return true;
-	}
-      else
-	{
-	  /* If we have NULL vs non-NULL, then merge to unknown.  */
-	  svalue *merged_sval = new unknown_svalue (type);
-	  *merged_sid = m_merged_model->add_svalue (merged_sval);
-	  return true;
-	}
-    }
+    case SK_REGION:
+      {
+	/* If we have two regions... .  */
+	const region_svalue &region_sval_a = *(const region_svalue *)sval_a;
+	const region_svalue &region_sval_b = *(const region_svalue *)sval_b;
+	return region_svalue::can_merge_values_p (region_sval_a, region_sval_b,
+						  merged_sid, type,
+						  *this);
+      }
+      break;
+    case SK_CONSTANT:
+      {
+	/* If we have two constants, then we can merge.  */
+	const constant_svalue &cst_sval_a = *(const constant_svalue *)sval_a;
+	const constant_svalue &cst_sval_b = *(const constant_svalue *)sval_b;
+	return constant_svalue::can_merge_values_p (cst_sval_a, sid_a,
+						    cst_sval_b, sid_b,
+						    merged_sid,
+						    *this);
+      }
+      break;
 
-  // TODO: what about other kinds of value?
+    case SK_POISONED:
+      {
+      }
+      break;
+    case SK_UNKNOWN:
+      {
+      }
+      break;
+    }
 
-  /* See what kinds of pairs we're getting here.  */
-  //gcc_unreachable ();
+  // TODO: move these to svalue subclasses
 
   return false;
 }
@@ -4560,6 +4738,11 @@ 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 addr_of_y = build1 (ADDR_EXPR, ptr_type_node, y);
+  tree addr_of_z = build1 (ADDR_EXPR, ptr_type_node, z);
 
   {
     region_model model0;
@@ -4701,6 +4884,45 @@ test_state_merging ()
 #endif
   }
 
+  /* Pointers: NULL and NULL.  */
+  // TODO
+
+  /* Pointers: NULL and non-NULL.  */
+  // TODO
+
+  /* Pointers: non-NULL and non-NULL: ptr to a local.  */
+  // TODO
+
+  /* Pointers: non-NULL and non-NULL: ptr to a global.  */
+  {
+    region_model merged;
+    /* x == &y in both input models.  */
+    svalue *merged_x_sval;
+    assert_region_models_merge (x, addr_of_y, addr_of_y, &merged,
+				&merged_x_sval);
+
+    /* We should get x == &y in the merged model.  */
+    ASSERT_EQ (merged_x_sval->get_kind (), SK_REGION);
+    region_svalue *merged_x_ptr = merged_x_sval->dyn_cast_region_svalue ();
+    region_id merged_x_star_rid = merged_x_ptr->get_pointee ();
+    ASSERT_EQ (merged_x_star_rid, merged.get_lvalue (y, NULL));
+  }
+
+  /* Pointers: non-NULL ptrs to different globals: should be unknown.  */
+  {
+    region_model merged;
+    /* x == &y vs x == &z in the input models.  */
+    svalue *merged_x_sval;
+    assert_region_models_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 region.  */
+  // TODO
+
 #if 0
   {
     region_model model0;
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index f9d8a57..deebead 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -266,7 +266,11 @@ public:
 class region_svalue : public svalue
 {
 public:
-  region_svalue (tree type, region_id rid) : svalue (type), m_rid (rid) {}
+  region_svalue (tree type, region_id rid) : svalue (type), m_rid (rid)
+  {
+    /* Should we support NULL ptrs here?  */
+    gcc_assert (!rid.null_p ());
+  }
 
   bool compare_fields (const region_svalue &other) const;
 
@@ -288,6 +292,12 @@ public:
 
   void remap_region_ids (const region_id_map &map) FINAL OVERRIDE;
 
+  static bool can_merge_values_p (const region_svalue &region_sval_a,
+				  const region_svalue &region_sval_b,
+				  svalue_id *merged_sid,
+				  tree type,
+				  const model_merger &merger);
+
  private:
   void print_details (const region_model &model,
 		      svalue_id this_sid,
@@ -320,6 +330,13 @@ public:
 
   tree get_constant () const { return m_cst_expr; }
 
+  static bool can_merge_values_p (const constant_svalue &cst_sval_a,
+				  svalue_id sid_a,
+				  const constant_svalue &cst_sval_b,
+				  svalue_id sid_b,
+				  svalue_id *merged_sid,
+				  const model_merger &merger);
+
  private:
   void print_details (const region_model &model,
 		      svalue_id this_sid,
@@ -658,6 +675,12 @@ public:
   region *clone () const OVERRIDE;
 
   enum region_kind get_kind () const OVERRIDE { return RK_HEAP; }
+
+  static bool can_merge_p (const heap_region *heap_a, region_id heap_a_rid,
+			   const heap_region *heap_b, region_id heap_b_rid,
+			   heap_region *merged_heap, region_id merged_heap_rid,
+			   const model_merger &merger);
+
 };
 
 /* The root region, containing all regions (either directly, or as
@@ -779,6 +802,8 @@ class region_model
 
   void validate () const;
 
+  void canonicalize (region_model_context *ctxt);
+
   void on_assignment (const gassign *stmt, region_model_context *ctxt);
   void on_call (const gcall *stmt, region_model_context *ctxt);
   void on_return (const greturn *stmt, region_model_context *ctxt);
-- 
1.8.5.3

