From c5879087c7c0810eb0da1d7426de6a4dac4dac12 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 12 Jul 2019 16:22:35 -0400
Subject: [PATCH 085/108] FIXME: WIP on constraint merging

---
 gcc/analyzer/constraint-manager.cc | 134 ++++++++++++++++++++++-
 gcc/analyzer/constraint-manager.h  |  32 ++++--
 gcc/analyzer/region-model.cc       | 216 +++++++++----------------------------
 gcc/analyzer/region-model.h        |  77 +++++++++++++
 4 files changed, 282 insertions(+), 177 deletions(-)

diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index 97a4c60..5336f12 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -350,6 +350,23 @@ equiv_class_id::get_obj (constraint_manager &cm) const
   return cm.get_equiv_class_by_index (m_idx);
 }
 
+void
+equiv_class_id::print (pretty_printer *pp) const
+{
+  if (null_p ())
+    pp_printf (pp, "null");
+  else
+    pp_printf (pp, "ec%i", m_idx);
+}
+
+std::string
+equiv_class_id::to_str () const
+{
+  pretty_printer pp;
+  print (&pp);
+  return pp_formatted_text (&pp);
+}
+
 /* FIXME.  */
 
 constraint_manager::constraint_manager (const constraint_manager &other)
@@ -1173,13 +1190,14 @@ constraint_manager::purge (const purge_criteria &p, purge_stats *stats)
       equiv_class *ec = m_equiv_classes[ec_idx];
       if (ec->m_vars.length () == 0)
 	{
+	  equiv_class_id ec_id (ec_idx);
 	  bool has_constraint = false;
 	  for (unsigned con_idx = 0; con_idx < m_constraints.length ();
 	       con_idx++)
 	    {
 	      constraint *c = &m_constraints[con_idx];
-	      if (c->m_lhs.m_idx == ec_idx
-		  || c->m_rhs.m_idx == ec_idx)
+	      if (c->m_lhs == ec_id
+		  || c->m_rhs == ec_id)
 		{
 		  has_constraint = true;
 		  break;
@@ -1223,6 +1241,112 @@ constraint_manager::remap_svalue_ids (const svalue_id_map &map)
 /* FIXME.  */
 
 void
+constraint_manager::merge (const constraint_manager &cm_a,
+			   const constraint_manager &cm_b,
+			   constraint_manager *out,
+			   const model_merger &merger)
+{
+  /* We can only attempt to merge them if we have svalue_id mapping data.  */
+  if (!merger.m_svalue_id_mapping)
+    return;
+
+
+  // TODO
+  //auto_vec<equiv_class *> m_equiv_classes;
+  //auto_vec<constraint> m_constraints;
+  /* TODO:
+     Map svalue_ids in each equiv class from src to merged, dropping ids
+     that don't survive merger.
+     Try to find correspondences between such ECs, find the intersection
+     of svalues in the merged.
+     This gives us our ECs, then somehow add constraints between them.
+
+     Alternate approach:
+     Having mapped svalue IDs, try to add constraints to OUT
+     until you get a contradiction - but this doesn't work; we want
+     to find things that are true in *both* inputs.
+  */
+  //constraint_manager cleaned_cm_a;
+  // FIXME: move to subroutine, call it for a and for b:
+  const one_way_svalue_id_map &map_a_to_m
+    = merger.m_svalue_id_mapping->m_map_from_a_to_m;
+  auto_vec<equiv_class *> cleaned_ecs_a;
+  auto_vec<constraint> cleaned_constraints_a;
+  clean_merger_input (cm_a, out, map_a_to_m, &cleaned_ecs_a,
+		      &cleaned_constraints_a);
+
+  const one_way_svalue_id_map &map_b_to_m
+    = merger.m_svalue_id_mapping->m_map_from_b_to_m;
+  auto_vec<equiv_class *> cleaned_ecs_b;
+  auto_vec<constraint> cleaned_constraints_b;
+  clean_merger_input (cm_b, out, map_b_to_m, &cleaned_ecs_b,
+		      &cleaned_constraints_b);
+
+  // TODO: do something with the above
+}
+
+/* FIXME.  */
+
+void
+constraint_manager::
+clean_merger_input (const constraint_manager &cm_in,
+		    constraint_manager *out,
+		    const one_way_svalue_id_map &map_sid_to_m,
+		    auto_vec<equiv_class *> *out_cleaned_ecs,
+		    auto_vec<constraint> *out_cleaned_constraints)
+{
+  one_way_id_map<equiv_class_id> map_ec_to_m
+    (cm_in.m_equiv_classes.length ());
+  unsigned ec_idx;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (cm_in.m_equiv_classes, ec_idx, ec)
+    {
+      equiv_class cleaned_ec;
+      if (tree cst = ec->get_any_constant ())
+	{
+	  cleaned_ec.m_constant = cst;
+	  /* Lazily create the constant in the out region_model.  */
+	  // TODO: enabling this causes lots of churn in the DejaGnu testcases:
+#if 0
+	  cleaned_ec.m_cst_sid = out->get_sid_for_constant (cst);
+#endif
+	}
+      unsigned var_idx;
+      svalue_id *var_in_sid;
+      FOR_EACH_VEC_ELT (ec->m_vars, var_idx, var_in_sid)
+	{
+	  svalue_id var_m_sid = map_sid_to_m.get_dst_for_src (*var_in_sid);
+	  if (!var_m_sid.null_p ())
+	    cleaned_ec.m_vars.safe_push (var_m_sid);
+	}
+      if (cleaned_ec.get_any_constant () || !cleaned_ec.m_vars.is_empty ())
+	{
+	  map_ec_to_m.put (ec_idx, out_cleaned_ecs->length ());
+	  out_cleaned_ecs->safe_push (new equiv_class (cleaned_ec));
+	}
+    }
+
+  /* Write out to *OUT_CLEANED_CONSTRAINTS any constraints for which both
+     sides survived cleaning, using the new EC IDs.  */
+  unsigned con_idx;
+  constraint *c;
+  FOR_EACH_VEC_ELT (cm_in.m_constraints, con_idx, c)
+    {
+      equiv_class_id new_lhs = map_ec_to_m.get_dst_for_src (c->m_lhs);
+      if (new_lhs.null_p ())
+	continue;
+      equiv_class_id new_rhs = map_ec_to_m.get_dst_for_src (c->m_rhs);
+      if (new_rhs.null_p ())
+	continue;
+      out_cleaned_constraints->safe_push (constraint (new_lhs,
+						      c->m_op,
+						      new_rhs));
+    }
+}
+
+/* FIXME.  */
+
+void
 constraint_manager::validate () const
 {
   // TODO: skip this in a release build
@@ -1251,8 +1375,10 @@ constraint_manager::validate () const
   constraint *c;
   FOR_EACH_VEC_ELT (m_constraints, i, c)
     {
-      gcc_assert (c->m_lhs.m_idx <= m_equiv_classes.length ());
-      gcc_assert (c->m_rhs.m_idx <= m_equiv_classes.length ());
+      gcc_assert (!c->m_lhs.null_p ());
+      gcc_assert (c->m_lhs.as_int () <= m_equiv_classes.length ());
+      gcc_assert (!c->m_rhs.null_p ());
+      gcc_assert (c->m_rhs.as_int () <= m_equiv_classes.length ());
     }
 }
 
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
index 0d36a8f..deaf471 100644
--- a/gcc/analyzer/constraint-manager.h
+++ b/gcc/analyzer/constraint-manager.h
@@ -117,6 +117,8 @@ const char *constraint_op_code (enum constraint_op c_op);
 class equiv_class_id
 {
 public:
+  static equiv_class_id null () { return equiv_class_id (-1); }
+
   equiv_class_id (unsigned idx) : m_idx (idx) {}
   const equiv_class &get_obj (const constraint_manager &cm) const;
   equiv_class &get_obj (constraint_manager &cm) const;
@@ -130,10 +132,13 @@ public:
     return m_idx != other.m_idx;
   }
 
-  std::string to_str () const
-  {
-    return std::string ("ec") + std::to_string (m_idx);
-  }
+  bool null_p () const { return m_idx == -1; }
+
+  static equiv_class_id from_int (int idx) { return equiv_class_id (idx); }
+  int as_int () const { return m_idx; }
+
+  void print (pretty_printer *pp) const;
+  std::string to_str () const;
 
   void update_for_removal (equiv_class_id other)
   {
@@ -141,7 +146,7 @@ public:
       m_idx--;
   }
 
-  unsigned m_idx;
+  int m_idx;
 };
 
 /* FIXME.  */
@@ -149,7 +154,11 @@ public:
 class constraint {
  public:
   constraint (equiv_class_id lhs, enum constraint_op c_op, equiv_class_id rhs)
-    : m_lhs (lhs), m_op (c_op), m_rhs (rhs) {}
+  : m_lhs (lhs), m_op (c_op), m_rhs (rhs)
+  {
+    gcc_assert (!lhs.null_p ());
+    gcc_assert (!rhs.null_p ());
+  }
 
   std::string to_str (const constraint_manager &cm) const;
   hashval_t hash () const;
@@ -235,12 +244,23 @@ public:
 
   void remap_svalue_ids (const svalue_id_map &map);
 
+  static void merge (const constraint_manager &cm_a,
+		     const constraint_manager &cm_b,
+		     constraint_manager *out,
+		     const model_merger &merger);
+
   void validate () const;
 
   auto_vec<equiv_class *> m_equiv_classes;
   auto_vec<constraint> m_constraints;
 
  private:
+  static void clean_merger_input (const constraint_manager &cm_in,
+				  constraint_manager *out,
+				  const one_way_svalue_id_map &map_sid_to_m,
+				  auto_vec<equiv_class *> *out_cleaned_ecs,
+				  auto_vec<constraint> *out_cleaned_constraints);
+
   void add_constraint_internal (equiv_class_id lhs_id,
 				enum constraint_op c_op,
 				equiv_class_id rhs_id);
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 89100b8..9a41c11 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -292,83 +292,6 @@ id_map<T>::update (T *id) const
 
 ////////////////////////////////////////////////////////////////////////////
 
-/* class one_way_id_map.  */
-
-/* one_way_id_map's ctor, which populates the map with dummy null values.  */
-
-template <typename T>
-one_way_id_map<T>::one_way_id_map (int num_svalues)
-: m_src_to_dst (num_svalues)
-{
-  for (int i = 0; i < num_svalues; i++)
-    m_src_to_dst.quick_push (T::null ());
-}
-
-/* Record that SRC is to be mapped to DST.  */
-
-template <typename T>
-void
-one_way_id_map<T>::put (T src, T dst)
-{
-  m_src_to_dst[src.as_int ()] = dst;
-}
-
-/* Get the new value for SRC within the map.  */
-
-template <typename T>
-T
-one_way_id_map<T>::get_dst_for_src (T src) const
-{
-  if (src.null_p ())
-    return src;
-  return m_src_to_dst[src.as_int ()];
-}
-
-/* FIXME.  */
-
-template <typename T>
-void
-one_way_id_map<T>::dump_to_pp (pretty_printer *pp) const
-{
-  pp_string (pp, "src to dst: {");
-  unsigned i;
-  T *dst;
-  FOR_EACH_VEC_ELT (m_src_to_dst, i, dst)
-    {
-      if (i > 0)
-	pp_string (pp, ", ");
-      T src (T::from_int (i));
-      src.print (pp);
-      pp_string (pp, " -> ");
-      dst->print (pp);
-    }
-  pp_string (pp, "}");
-  pp_newline (pp);
-}
-
-/* FIXME.  */
-
-template <typename T>
-DEBUG_FUNCTION void
-one_way_id_map<T>::dump () const
-{
-  pretty_printer pp;
-  pp.buffer->stream = stderr;
-  dump_to_pp (&pp);
-  pp_flush (&pp);
-}
-
-/* FIXME.  */
-
-template <typename T>
-std::string
-one_way_id_map<T>::to_str () const
-{
-  pretty_printer pp;
-  dump_to_pp (&pp);
-  return pp_formatted_text (&pp);
-}
-
 /* Explicit specialization, so that this can be used from impl-4.cc.  */
 template class one_way_id_map<svalue_id>;
 
@@ -4319,10 +4242,6 @@ region_model::can_merge_with_p (const region_model &other_model,
   gcc_assert (m_root_rid == other_model.m_root_rid);
   gcc_assert (m_root_rid.as_int () == 0);
 
-  /* TODO: assert they have the same frames.  */
-
-#if 1
-#if 1
   region_model result;
   model_merger merger (this, &other_model, &result, out);
 
@@ -4331,93 +4250,12 @@ region_model::can_merge_with_p (const region_model &other_model,
 				 result.get_root_region (),
 				 &merger))
     return false;
-#else
-  if (m_regions.length () != other.m_regions.length ())
-    return false;
-
-  if (m_svalues.length () != other.m_svalues.length ())
-    return false;
-
-  /* Build up result, based on the things in common between the two
-     input models.  */
-  region_model result;
-  result.m_regions.truncate (0);
-  result.m_root_rid = region_id::null ();
-
-  /* FIXME: we may want to be less strict here, to allow for more
-     merging.  e.g. if one model has a value for a field, and
-     the other doesn't, then they'll have different m_regions.length ()
-     but they ought to be still mergable, given a sufficiently smart
-     implementation...
-
-     What would a "sufficiently smart implementation" look like?
-     Perhaps have each one do a mark and sweep over the others, to find
-     the regions that are common between the two
-     (with a pair of region_id_map, one for each original model),
-     based on the hierarchical "paths" within the model.
-     Build a pair of region_id_set describing the regions that are being
-     discarded.
-
-     Start at the root regions, finding common children based on identity.
-     Clone them into the result.
-
-     I think this always succeeds; it just might throw away more information
-     than the sm_state_map should keep (and hence the program_state can check
-     this, and then reject the merger for those cases)
-
-     class region_model_merger_peer
-     {
-       region_id_map m_region_map;
-       region_id_set m_discarded_regions;
-     };
-
-     class region_model_merger
-     {
-       region_model_merger_peer m_from_1;
-       region_model_merger_peer m_from_2;
-     };
-  */
 
-  unsigned i;
-  region *r;
-  FOR_EACH_VEC_ELT (m_regions, i, r)
-    {
-      if (*r != *other.m_regions[i])
-	return false;
-      result.add_region (r->clone ());
-      /* This won't be valid yet, as result doesn't have any svalues yet.  */
-
-      /* FIXME: update root_rid values, etc.  */
-      if (i == 
-    }
-
-  svalue *sval;
-  FOR_EACH_VEC_ELT (m_svalues, i, sval)
-    {
-      if (*sval != *other.m_svalues[i])
-	return false;
-      // FIXME: what about constants, though?
-      result.add_svalue (sval->clone ());
-    }
-
-  // TODO: constraint manager
-#endif
-
-#else
-  /* FIXME: for now, forget everything.  */
-  region_model result;
-
-  // FIXME: but we do need the same frames:
-  if (stack_region *stack = get_root_region ()->get_stack_region (this))
-    {
-      for (unsigned i = 0; i < stack->get_num_frames (); i++)
-	{
-	  region_id rid = stack->get_frame_rid (i);
-	  frame_region *frame = get_region <frame_region> (rid);
-	  result.push_frame (frame->get_function (), NULL);
-	}
-    }
-#endif
+  /* Merge constraints.  */
+  constraint_manager::merge (*m_constraints,
+			     *other_model.m_constraints,
+			     result.m_constraints,
+			     merger);
 
   result.validate ();
 
@@ -4460,6 +4298,9 @@ model_merger::dump_to_pp (pretty_printer *pp) const
   pp_newline (pp);
   m_map_regions_from_b_to_m.dump_to_pp (pp);
   pp_newline (pp);
+
+  if (m_svalue_id_mapping)
+    m_svalue_id_mapping->dump_to_pp (pp);
 }
 
 /* Dump a multiline representation of this merger to FILE.  */
@@ -5551,6 +5392,46 @@ test_state_merging ()
    */
 }
 
+/* FIXME.  */
+
+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_model_context ctxt;
+
+  /* model0: 0 <= (x == y) < n.  */
+  region_model model0;
+  model0.set_to_new_unknown_value (model0.get_lvalue (x, &ctxt),
+				   integer_type_node);
+  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_model model1;
+  model1.set_to_new_unknown_value (model1.get_lvalue (x, &ctxt),
+				   integer_type_node);
+  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_model merged;
+  svalue_id_merger_mapping sid_mapping (model0, model1);
+  ASSERT_TRUE (model0.can_merge_with_p (model1, &merged, &sid_mapping));
+
+  // TODO
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -5572,6 +5453,7 @@ analyzer_region_model_cc_tests ()
   test_canonicalization_1 ();
   test_canonicalization_2 ();
   test_state_merging ();
+  test_constraint_merging ();
 }
 
 } // namespace selftest
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index fdcf97d..7511d96 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -166,6 +166,83 @@ class one_way_id_map
 typedef one_way_id_map<svalue_id> one_way_svalue_id_map;
 typedef one_way_id_map<region_id> one_way_region_id_map;
 
+/* class one_way_id_map.  */
+
+/* one_way_id_map's ctor, which populates the map with dummy null values.  */
+
+template <typename T>
+inline one_way_id_map<T>::one_way_id_map (int num_svalues)
+: m_src_to_dst (num_svalues)
+{
+  for (int i = 0; i < num_svalues; i++)
+    m_src_to_dst.quick_push (T::null ());
+}
+
+/* Record that SRC is to be mapped to DST.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::put (T src, T dst)
+{
+  m_src_to_dst[src.as_int ()] = dst;
+}
+
+/* Get the new value for SRC within the map.  */
+
+template <typename T>
+inline T
+one_way_id_map<T>::get_dst_for_src (T src) const
+{
+  if (src.null_p ())
+    return src;
+  return m_src_to_dst[src.as_int ()];
+}
+
+/* FIXME.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "src to dst: {");
+  unsigned i;
+  T *dst;
+  FOR_EACH_VEC_ELT (m_src_to_dst, i, dst)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      T src (T::from_int (i));
+      src.print (pp);
+      pp_string (pp, " -> ");
+      dst->print (pp);
+    }
+  pp_string (pp, "}");
+  pp_newline (pp);
+}
+
+/* FIXME.  */
+
+template <typename T>
+DEBUG_FUNCTION inline void
+one_way_id_map<T>::dump () const
+{
+  pretty_printer pp;
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp);
+  pp_flush (&pp);
+}
+
+/* FIXME.  */
+
+template <typename T>
+inline std::string
+one_way_id_map<T>::to_str () const
+{
+  pretty_printer pp;
+  dump_to_pp (&pp);
+  return pp_formatted_text (&pp);
+}
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* A set of IDs within a region_model (either svalue_id or region_id).  */
-- 
1.8.5.3

