From f3099840c64188448ec15d014cb8c439ab232770 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 20 Feb 2020 06:24:32 -0500
Subject: [PATCH 25/31] FIXME: canon&purge: fix loop tests

---
 gcc/analyzer/constraint-manager.cc | 95 +++++++++++++++++++++++++-----
 gcc/analyzer/region-model.cc       | 36 +++++++++++
 gcc/analyzer/region-model.h        |  3 +
 3 files changed, 118 insertions(+), 16 deletions(-)

diff --git a/gcc/analyzer/constraint-manager.cc b/gcc/analyzer/constraint-manager.cc
index b4b9e7106c7..03ebd0d794a 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -48,6 +48,22 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
+/* FIXME.  */
+
+static tristate
+compare_constants (tree lhs, enum tree_code op, tree rhs)
+{
+  if (!types_compatible_p (TREE_TYPE (lhs), TREE_TYPE (rhs)))
+    return tristate::unknown ();
+  tree comparison
+    = fold_binary (op, boolean_type_node, lhs, rhs);
+  if (comparison == boolean_true_node)
+    return tristate (tristate::TS_TRUE);
+  if (comparison == boolean_false_node)
+    return tristate (tristate::TS_FALSE);
+  return tristate::unknown ();
+}
+
 /* One of the end-points of a range.  */
 
 struct bound
@@ -1017,12 +1033,9 @@ constraint_manager::eval_condition (equiv_class_id lhs_ec,
   tree rhs_const = rhs_ec.get_obj (*this).get_any_constant ();
   if (lhs_const && 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);
+      tristate t = compare_constants (lhs_const, op, rhs_const);
+      if (t.is_known ())
+	return t;
     }
 
   enum tree_code swapped_op = swap_tree_comparison (op);
@@ -1355,30 +1368,76 @@ private:
 };
 
 /* Concrete subclass of fact_visitor for use by constraint_manager::merge.
-   For every fact in CM_A, see if it is also true in *CM_B.  Add such
-   facts to *OUT.  */
+   For every fact in the visited CM, see if it is also true in *OTHER_CM.
+   Add such facts to *OUT.  */
 
 class merger_fact_visitor : public fact_visitor
 {
 public:
-  merger_fact_visitor (constraint_manager *cm_b,
+  merger_fact_visitor (constraint_manager *other_cm,
+		       const region_model *merged_model,
+		       const one_way_svalue_id_map &other_sid_map,
+		       const region_model *other_model,
 		       constraint_manager *out)
-  : m_cm_b (cm_b), m_out (out)
-  {}
+  : m_other_cm (other_cm), m_merged_model (merged_model),
+    m_other_sid_map (other_sid_map),
+    m_other_model (other_model), m_out (out),
+    m_two_way_sid_map (MAX (other_model->get_num_svalues (),
+			    merged_model->get_num_svalues ()))
+  {
+    for (unsigned i = 0; i < m_other_sid_map.length (); i++)
+      {
+	svalue_id sid = svalue_id::from_int (i);
+	svalue_id merged_sid = m_other_sid_map.get_dst_for_src (sid);
+	if (!merged_sid.null_p ())
+	  m_two_way_sid_map.put (sid, merged_sid);
+      }
+  }
 
   void on_fact (svalue_id lhs, enum tree_code code, svalue_id rhs)
     FINAL OVERRIDE
   {
-    if (m_cm_b->eval_condition (lhs, code, rhs).is_true ())
+    if (eval_condition (lhs, code, rhs).is_true ())
       {
 	bool sat = m_out->add_constraint (lhs, code, rhs);
 	gcc_assert (sat);
       }
   }
 
+  tristate eval_condition (svalue_id lhs,
+			   enum tree_code code,
+			   svalue_id rhs) const
+  {
+    tristate t = m_other_cm->eval_condition (lhs, code, rhs);
+    if (t.is_known ())
+      return t;
+    /* Handle the case where we know one of the svalues is a constant
+       in OTHER_CM due to it being a constant in the original model
+       (which won't otherwise show up in the constraint_manager during
+       merging).  */
+    if (tree t_lhs = maybe_get_constant (lhs))
+      if (tree t_rhs = maybe_get_constant (rhs))
+	return compare_constants (t_lhs, code, t_rhs);
+    return tristate::unknown ();
+  }
+
+  tree maybe_get_constant (svalue_id merged_sid) const
+  {
+    if (tree t = m_merged_model->maybe_get_constant (merged_sid))
+      return t;
+    svalue_id old_sid = m_two_way_sid_map.get_src_for_dst (merged_sid);
+    if (old_sid.null_p ())
+      return NULL_TREE;
+    return m_other_model->maybe_get_constant (old_sid);
+  }
+
 private:
-  constraint_manager *m_cm_b;
+  constraint_manager *m_other_cm;
+  const region_model *m_merged_model;
+  const one_way_svalue_id_map &m_other_sid_map;
+  const region_model *m_other_model;
   constraint_manager *m_out;
+  svalue_id_map m_two_way_sid_map;
 };
 
 /* Use MERGER to merge CM_A and CM_B into *OUT.
@@ -1414,10 +1473,14 @@ constraint_manager::merge (const constraint_manager &cm_a,
 
   /* Merge the equivalence classes and constraints.
      The easiest way to do this seems to be to enumerate all of the facts
-     in cleaned_cm_a, see which are also true in cleaned_cm_b,
-     and add those to *OUT.  */
-  merger_fact_visitor v (&cleaned_cm_b, out);
+     in one cleaned_cm, see which are also true in the other cleaned_cm_b,
+     and add those to *OUT, and vice-versa.  */
+  merger_fact_visitor v (&cleaned_cm_b, merger.m_merged_model,
+			 map_b_to_m, merger.m_model_b, out);
   cleaned_cm_a.for_each_fact (&v);
+  merger_fact_visitor v2 (&cleaned_cm_a, merger.m_merged_model,
+			  map_a_to_m, merger.m_model_a, out);
+  cleaned_cm_b.for_each_fact (&v2);
 }
 
 /* A subroutine of constraint_manager::merge.
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 511f230db72..e115ad62397 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -8494,6 +8494,41 @@ test_constraint_merging ()
 	     tristate (tristate::TS_UNKNOWN));
 }
 
+/* Verify constraint merging for loop iterator, when merging
+   initial iteration of loop body and subsequent iterations.  */
+
+static void
+test_constraint_merging_2 ()
+{
+  tree int_0 = build_int_cst (integer_type_node, 0);
+  tree int_256 = build_int_cst (integer_type_node, 256);
+  tree i = build_global_decl ("i", integer_type_node);
+
+  /* model0: i == 0.  */
+  region_model model0;
+  model0.set_value (model0.get_lvalue (i, NULL),
+		    model0.get_rvalue (int_0, NULL), NULL);
+
+  /* model1: i < 256.  */
+  region_model model1;
+  model1.add_constraint (i, LT_EXPR, int_256, NULL);
+  model1.canonicalize_and_purge (NULL);
+  ASSERT_EQ (model1.eval_condition (i, LT_EXPR, int_256, NULL),
+	     tristate (tristate::TS_TRUE));
+
+  /* They should be mergeable; the merged model should have the
+     constraint i < 256.  */
+  region_model merged;
+  ASSERT_TRUE (model0.can_merge_with_p (model1, &merged));
+
+  ASSERT_EQ (merged.eval_condition (i, LT_EXPR, int_256, NULL),
+	     tristate (tristate::TS_TRUE));
+
+  /* The merged model should equal model1 after canonicalization.  */
+  merged.canonicalize_and_purge (NULL);
+  ASSERT_EQ (model1, merged);
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -8519,6 +8554,7 @@ analyzer_region_model_cc_tests ()
   test_canonicalization_6 ();
   test_state_merging ();
   test_constraint_merging ();
+  test_constraint_merging_2 ();
 }
 
 } // namespace selftest
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 8542d75ccd3..b663d663cd8 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -231,6 +231,8 @@ template <typename T>
 inline void
 id_map<T>::put (T src, T dst)
 {
+  gcc_assert (!src.null_p ());
+  gcc_assert (!dst.null_p ());
   m_src_to_dst[src.as_int ()] = dst;
   m_dst_to_src[dst.as_int ()] = src;
 }
@@ -387,6 +389,7 @@ class one_way_id_map
   void dump_to_pp (pretty_printer *pp) const;
   void dump () const;
   void update (T *) const;
+  unsigned length () const { return m_src_to_dst.length (); }
 
  private:
   auto_vec<T> m_src_to_dst;
-- 
2.21.0

