From 8c2d6ceb80411769d24e8bd59761dee4f86f7a7b Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 15 Jul 2019 06:28:41 -0400
Subject: [PATCH 087/108] FIXME: implement canonicalization of constraints

---
 gcc/analyzer/constraint-manager.cc | 105 +++++++++++++++++++++++++++++++++++++
 gcc/analyzer/constraint-manager.h  |   4 ++
 gcc/analyzer/region-model.cc       |  39 +++++++++++++-
 gcc/analyzer/region-model.h        |  10 ++++
 4 files changed, 157 insertions(+), 1 deletion(-)

diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index 236037b..27e8f14 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -347,6 +347,8 @@ equiv_class::get_representative () const
     }
 }
 
+/* FIXME.  */
+
 void
 equiv_class::remap_svalue_ids (const svalue_id_map &map)
 {
@@ -357,6 +359,24 @@ equiv_class::remap_svalue_ids (const svalue_id_map &map)
   map.update (&m_cst_sid);
 }
 
+/* Comparator for use by equiv_class::canonicalize.  */
+
+static int
+svalue_id_cmp (const void *p1, const void *p2)
+{
+  const svalue_id *sid1 = (const svalue_id *)p1;
+  const svalue_id *sid2 = (const svalue_id *)p2;
+  return sid1->as_int () - sid2->as_int ();
+}
+
+/* Sort the svalues_ids within this equiv_class.  */
+
+void
+equiv_class::canonicalize ()
+{
+  m_vars.qsort (svalue_id_cmp);
+}
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* FIXME.  */
@@ -1299,6 +1319,91 @@ constraint_manager::remap_svalue_ids (const svalue_id_map &map)
     ec->remap_svalue_ids (map);
 }
 
+/* Comparator for use by constraint_manager::canonicalize.
+   Sort a pair of equiv_class instances, using the representative
+   svalue_id as a sort key.  */
+
+static int
+equiv_class_cmp (const void *p1, const void *p2)
+{
+  const equiv_class *ec1 = *(const equiv_class * const *)p1;
+  const equiv_class *ec2 = *(const equiv_class * const *)p2;
+
+  svalue_id rep1 = ec1->get_representative ();
+  svalue_id rep2 = ec2->get_representative ();
+
+  return rep1.as_int () - rep2.as_int ();
+}
+
+/* Comparator for use by constraint_manager::canonicalize.
+   Sort a pair of constraint instances.  */
+
+static int
+constraint_cmp (const void *p1, const void *p2)
+{
+  const constraint *c1 = (const constraint *)p1;
+  const constraint *c2 = (const constraint *)p2;
+  int lhs_cmp = c1->m_lhs.as_int () - c2->m_lhs.as_int ();
+  if (lhs_cmp)
+    return lhs_cmp;
+  int rhs_cmp = c1->m_rhs.as_int () - c2->m_rhs.as_int ();
+  if (rhs_cmp)
+    return rhs_cmp;
+  return c1->m_op - c2->m_op;
+}
+
+/* Reorder the equivalence classes and constraints within this
+   constraint_manager into a canonical order, to increase the
+   chances of finding equality with another instance.  */
+
+void
+constraint_manager::canonicalize (unsigned num_svalue_ids)
+{
+  /* First, sort svalue_ids within the ECs.  */
+  unsigned i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    ec->canonicalize ();
+
+  /* Next, sort the ECs into a canonical order.  */
+
+  /* We will need to remap the equiv_class_ids in the constraints,
+     so we need to store the original index of each EC.
+     Build a lookup table, mapping from representative svalue_id
+     to the original equiv_class_id of that svalue_id.  */
+  auto_vec<equiv_class_id> original_ec_id (num_svalue_ids);
+  for (i = 0; i < num_svalue_ids; i++)
+    original_ec_id.quick_push (equiv_class_id::null ());
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      svalue_id rep = ec->get_representative ();
+      gcc_assert (!rep.null_p ());
+      original_ec_id[rep.as_int ()] = i;
+    }
+
+  /* Sort the equivalence classes.  */
+  m_equiv_classes.qsort (equiv_class_cmp);
+
+  /* Populate ec_id_map based on the old vs new EC ids.  */
+  one_way_id_map<equiv_class_id> ec_id_map (m_equiv_classes.length ());
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    {
+      svalue_id rep = ec->get_representative ();
+      ec_id_map.put (original_ec_id[rep.as_int ()], i);
+    }
+
+  /* Update the EC ids within the constraints.  */
+  constraint *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      ec_id_map.update (&c->m_lhs);
+      ec_id_map.update (&c->m_rhs);
+    }
+
+  /* Finally, sort the constraints. */
+  m_constraints.qsort (constraint_cmp);
+}
+
 /* FIXME.  */
 
 void
diff --git a/gcc/analyzer/constraint-manager.h b/gcc/analyzer/constraint-manager.h
index e84161c..d685889 100644
--- a/gcc/analyzer/constraint-manager.h
+++ b/gcc/analyzer/constraint-manager.h
@@ -62,6 +62,8 @@ public:
 
   void remap_svalue_ids (const svalue_id_map &map);
 
+  void canonicalize ();
+
   /* An equivalence class can contain at most one constant.  */
   tree m_constant;
   svalue_id m_cst_sid;
@@ -218,6 +220,8 @@ public:
 
   void remap_svalue_ids (const svalue_id_map &map);
 
+  void canonicalize (unsigned num_svalue_ids);
+
   static void merge (const constraint_manager &cm_a,
 		     const constraint_manager &cm_b,
 		     constraint_manager *out,
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 9a41c11..bd24aaa 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -2595,6 +2595,10 @@ region_model::canonicalize (region_model_context *ctxt)
   if (ctxt)
     ctxt->remap_svalue_ids (c.m_sid_map);
 
+  /* Canonicalize the constraint_manager (it has already had its
+     svalue_ids remapped above).  */
+  m_constraints->canonicalize (get_num_svalues ());
+
   validate ();
 }
 
@@ -5111,7 +5115,11 @@ test_canonicalization_1 ()
   ASSERT_EQ (model0, model1);
 }
 
-/* FIXME.  */
+/* Verify that region models for
+      x = 42; y = 113;
+   and
+      y = 113; x = 42;
+   are equal after canonicalization.  */
 
 static void
 test_canonicalization_2 ()
@@ -5138,6 +5146,34 @@ test_canonicalization_2 ()
   ASSERT_EQ (model0, model1);
 }
 
+/* Verify that constraints for
+     x > 0 && y > 0
+   and
+     y > 0 && x > 0
+   are equal after canonicalization.  */
+
+static void
+test_canonicalization_3 ()
+{
+  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_model model0;
+  model0.add_constraint (x, GT_EXPR, int_0, NULL);
+  model0.add_constraint (y, GT_EXPR, int_0, NULL);
+
+  region_model model1;
+  model1.add_constraint (y, GT_EXPR, int_0, NULL);
+  model1.add_constraint (x, GT_EXPR, int_0, NULL);
+
+  model0.canonicalize (NULL);
+  model1.canonicalize (NULL);
+  ASSERT_EQ (model0, model1);
+}
+
+// FIXME: test for ordering within an EC
+
 struct region_model_merger_test
 {
   test_region_model_context ctxt;
@@ -5452,6 +5488,7 @@ analyzer_region_model_cc_tests ()
   // TODO: test coverage for get_representative_tree / get_trees_for_svalue
   test_canonicalization_1 ();
   test_canonicalization_2 ();
+  test_canonicalization_3 ();
   test_state_merging ();
   test_constraint_merging ();
 }
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 7511d96..86edfa1 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -158,6 +158,7 @@ class one_way_id_map
   void dump_to_pp (pretty_printer *pp) const;
   void dump () const;
   std::string to_str () const;
+  void update (T *) const;
 
  private:
   auto_vec<T> m_src_to_dst;
@@ -243,6 +244,15 @@ one_way_id_map<T>::to_str () const
   return pp_formatted_text (&pp);
 }
 
+/* Update *ID from the old value to its new value in this map.  */
+
+template <typename T>
+inline void
+one_way_id_map<T>::update (T *id) const
+{
+  *id = get_dst_for_src (*id);
+}
+
 ////////////////////////////////////////////////////////////////////////////
 
 /* A set of IDs within a region_model (either svalue_id or region_id).  */
-- 
1.8.5.3

