From a2602ab7fca96e382d5a519150172a358004e9c9 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 17 Jun 2020 18:17:02 -0400
Subject: [PATCH 233/251] FIXME: make eval_constraint const

---
 gcc/analyzer/constraint-manager2.cc           | 322 +++++++++++++++---
 gcc/analyzer/constraint-manager2.h            |  47 ++-
 .../gcc.dg/analyzer/conditionals-notrans.c    |  21 +-
 gcc/testsuite/gcc.dg/analyzer/loop.c          |   7 +-
 4 files changed, 324 insertions(+), 73 deletions(-)

diff --git a/gcc/analyzer/constraint-manager2.cc b/gcc/analyzer/constraint-manager2.cc
index bdc4d9819b5..3ad56fc277c 100644
--- a/gcc/analyzer/constraint-manager2.cc
+++ b/gcc/analyzer/constraint-manager2.cc
@@ -54,38 +54,17 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
-/* One of the end-points of a range2.  */
-
-struct bound2
-{
-  bound2 () : m_constant (NULL_TREE), m_closed (false) {}
-  bound2 (tree constant, bool closed)
-  : m_constant (constant), m_closed (closed) {}
-
-  void ensure_closed (bool is_upper);
-
-  const char * get_relation_as_str () const;
-
-  tree m_constant;
-  bool m_closed;
-};
-
-/* A range of values, used for determining if a value has been
-   constrained to just one possible constant value.  */
-
-struct range2
+static tristate
+compare_constants (tree lhs_const, enum tree_code op, tree rhs_const)
 {
-  range2 () : m_lower_bound (), m_upper_bound () {}
-  range2 (const bound2 &lower, const bound2 &upper)
-  : m_lower_bound (lower), m_upper_bound (upper) {}
-
-  void dump (pretty_printer *pp) const;
-
-  bool constrained_to_single_element (tree *out);
-
-  bound2 m_lower_bound;
-  bound2 m_upper_bound;
-};
+  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 (tristate::TS_UNKNOWN);
+}
 
 /* struct bound2.  */
 
@@ -125,13 +104,44 @@ bound2::get_relation_as_str () const
 /* Dump this range2 to PP, which must support %E for tree.  */
 
 void
-range2::dump (pretty_printer *pp) const
+range2::dump_to_pp (pretty_printer *pp) const
+{
+  if (m_lower_bound.m_constant)
+    {
+      if (m_upper_bound.m_constant)
+	pp_printf (pp, "%qE %s x %s %qE",
+		   m_lower_bound.m_constant,
+		   m_lower_bound.get_relation_as_str (),
+		   m_upper_bound.get_relation_as_str (),
+		   m_upper_bound.m_constant);
+      else
+	pp_printf (pp, "%qE %s x",
+		   m_lower_bound.m_constant,
+		   m_lower_bound.get_relation_as_str ());
+    }
+  else
+    {
+      if (m_upper_bound.m_constant)
+	pp_printf (pp, "x %s %qE",
+		   m_upper_bound.get_relation_as_str (),
+		   m_upper_bound.m_constant);
+      else
+	pp_string (pp, "x");
+    }
+}
+
+/* Dump this range2 to stderr.  */
+
+DEBUG_FUNCTION void
+range2::dump () const
 {
-  pp_printf (pp, "%qE %s x %s %qE",
-	     m_lower_bound.m_constant,
-	     m_lower_bound.get_relation_as_str (),
-	     m_upper_bound.get_relation_as_str (),
-	     m_upper_bound.m_constant);
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp);
+  pp_newline (&pp);
+  pp_flush (&pp);
 }
 
 /* Determine if there is only one possible value for this range2.
@@ -141,6 +151,10 @@ range2::dump (pretty_printer *pp) const
 bool
 range2::constrained_to_single_element (tree *out)
 {
+  if (m_lower_bound.m_constant == NULL_TREE
+      || m_upper_bound.m_constant == NULL_TREE)
+    return false;
+
   if (!INTEGRAL_TYPE_P (TREE_TYPE (m_lower_bound.m_constant)))
     return false;
   if (!INTEGRAL_TYPE_P (TREE_TYPE (m_upper_bound.m_constant)))
@@ -163,6 +177,95 @@ range2::constrained_to_single_element (tree *out)
     return false;
 }
 
+/* Eval the condition "X OP RHS_CONST" for X within the range.  */
+
+tristate
+range2::eval_condition (enum tree_code op,
+			tree rhs_const) const
+{
+#if 0
+  range2 copy (*this);
+  tree single_element;
+  if (copy.constrained_to_single_element (&single_element))
+    return compare_constants (single_element, op, rhs_const);
+#endif
+
+  switch (op)
+    {
+    case EQ_EXPR:
+      if (below_lower_bound (rhs_const))
+	return tristate (tristate::TS_FALSE);
+      if (above_upper_bound (rhs_const))
+	return tristate (tristate::TS_FALSE);
+      break;
+
+    case LT_EXPR:
+      // TODO: distinction between LT and GE
+    case LE_EXPR:
+      /* Qn: "X < RHS_CONST".  */
+      /* If RHS_CONST > upper bound, then it's true.
+	 If RHS_CONST < lower bound, then it's false.
+	 Otherwise unknown.  */
+      if (above_upper_bound (rhs_const))
+	return tristate (tristate::TS_TRUE);
+      if (below_lower_bound (rhs_const))
+	return tristate (tristate::TS_FALSE);
+      break;
+
+    case NE_EXPR:
+      /* Qn: "X != RHS_CONST".  */
+      /* If RHS_CONST < lower bound, then it's true.
+	 If RHS_CONST > upper bound, then it's false.
+	 Otherwise unknown.  */
+      if (below_lower_bound (rhs_const))
+	return tristate (tristate::TS_TRUE);
+      if (above_upper_bound (rhs_const))
+	return tristate (tristate::TS_TRUE);
+      break;
+
+    case GE_EXPR:
+      // TODO: distinction between GE and GT
+    case GT_EXPR:
+      /* Qn: "X > RHS_CONST".  */
+      if (above_upper_bound (rhs_const))
+	return tristate (tristate::TS_FALSE);
+      if (below_lower_bound (rhs_const))
+	return tristate (tristate::TS_TRUE);
+      break;
+
+    default:
+      gcc_unreachable (); // TODO
+      break;
+    }
+  return tristate (tristate::TS_UNKNOWN);
+}
+
+/* Return true if RHS_CONST is below the lower bound of this range.  */
+
+bool
+range2::below_lower_bound (tree rhs_const) const
+{
+  if (!m_lower_bound.m_constant)
+    return false;
+
+  return compare_constants (rhs_const,
+			    m_lower_bound.m_closed ? LT_EXPR : LE_EXPR,
+			    m_lower_bound.m_constant).is_true ();
+}
+
+/* Return true if RHS_CONST is above the upper bound of this range.  */
+
+bool
+range2::above_upper_bound (tree rhs_const) const
+{
+  if (!m_upper_bound.m_constant)
+    return false;
+
+  return compare_constants (rhs_const,
+			    m_upper_bound.m_closed ? GT_EXPR : GE_EXPR,
+			    m_upper_bound.m_constant).is_true ();
+}
+
 /* class equiv_class2.  */
 
 /* equiv_class2's default ctor.  */
@@ -1027,7 +1130,7 @@ constraint_manager2::get_or_add_equiv_class2 (const svalue2 *sval)
 tristate
 constraint_manager2::eval_condition (equiv_class2_id lhs_ec,
 				    enum tree_code op,
-				    equiv_class2_id rhs_ec)
+				    equiv_class2_id rhs_ec) const
 {
   if (lhs_ec == rhs_ec)
     {
@@ -1051,12 +1154,10 @@ constraint_manager2::eval_condition (equiv_class2_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 result_for_constants
+	= compare_constants (lhs_const, op, rhs_const);
+      if (result_for_constants.is_known ())
+	return result_for_constants;
     }
 
   enum tree_code swapped_op = swap_tree_comparison (op);
@@ -1087,13 +1188,92 @@ constraint_manager2::eval_condition (equiv_class2_id lhs_ec,
   return tristate (tristate::TS_UNKNOWN);
 }
 
-/* Evaluate the condition LHS OP RHS, creating equiv_class2 instances for
-   LHS and RHS if they aren't already in equiv_class2es.  */
+range2
+constraint_manager2::get_ec_bounds (equiv_class2_id ec_id) const
+{
+  range2 result;
+
+  int i;
+  constraint2 *c;
+  FOR_EACH_VEC_ELT (m_constraints, i, c)
+    {
+      if (c->m_lhs == ec_id)
+	{
+	  if (tree other_cst = c->m_rhs.get_obj (*this).get_any_constant ())
+	    switch (c->m_op)
+	      {
+	      default:
+		gcc_unreachable ();
+	      case CONSTRAINT2_NE:
+		continue;
+
+	      case CONSTRAINT2_LT:
+		/* We have "EC_ID < OTHER_CST".  */
+		result.m_upper_bound = bound2 (other_cst, false);
+		break;
+
+	      case CONSTRAINT2_LE:
+		/* We have "EC_ID <= OTHER_CST".  */
+		result.m_upper_bound = bound2 (other_cst, true);
+		break;
+	      }
+	}
+      if (c->m_rhs == ec_id)
+	{
+	  if (tree other_cst = c->m_lhs.get_obj (*this).get_any_constant ())
+	    switch (c->m_op)
+	      {
+	      default:
+		gcc_unreachable ();
+	      case CONSTRAINT2_NE:
+		continue;
+
+	      case CONSTRAINT2_LT:
+		/* We have "OTHER_CST < EC_ID"
+		   i.e. "EC_ID >= OTHER_CST".  */
+		result.m_lower_bound = bound2 (other_cst, true);
+		break;
+
+	      case CONSTRAINT2_LE:
+		/* We have "OTHER_CST <= EC_ID"
+		   i.e. "EC_ID > OTHER_CST".  */
+		result.m_lower_bound = bound2 (other_cst, false);
+		break;
+	      }
+	}
+    }
+
+  return result;
+}
+
+
+/* Evaluate the condition LHS_EC OP RHS_CONST, avoiding the creation
+   of equiv_class2 instances.  */
+
+tristate
+constraint_manager2::eval_condition (equiv_class2_id lhs_ec,
+				     enum tree_code op,
+				     tree rhs_const) const
+{
+  gcc_assert (!lhs_ec.null_p ());
+  gcc_assert (CONSTANT_CLASS_P (rhs_const));
+
+  if (tree lhs_const = lhs_ec.get_obj (*this).get_any_constant ())
+    return compare_constants (lhs_const, op, rhs_const);
+
+  /* Look at existing bounds on LHS_EC.  */
+  range2 lhs_bounds = get_ec_bounds (lhs_ec);
+  return lhs_bounds.eval_condition (op, rhs_const);
+}
+
+// FIXME:
+/* Evaluate the condition LHS OP RHS, avoiding the creation
+   of equiv_class2 instances.  */
 
 tristate
 constraint_manager2::eval_condition (const svalue2 *lhs,
 				     enum tree_code op,
-				     const svalue2 *rhs)
+				     const svalue2 *rhs) const
 {
   lhs = lhs->unwrap_any_unmergeable ();
   rhs = rhs->unwrap_any_unmergeable ();
@@ -1103,9 +1283,45 @@ constraint_manager2::eval_condition (const svalue2 *lhs,
       || rhs->get_kind () == svalue2::SK_UNKNOWN)
     return tristate (tristate::TS_UNKNOWN);
 
-  return eval_condition (get_or_add_equiv_class2 (lhs),
-			 op,
-			 get_or_add_equiv_class2 (rhs));
+  equiv_class2_id lhs_ec (-1);
+  equiv_class2_id rhs_ec (-1);
+  get_equiv_class2_by_svalue2 (lhs, &lhs_ec);
+  get_equiv_class2_by_svalue2 (rhs, &rhs_ec);
+  if (!lhs_ec.null_p () && !rhs_ec.null_p ())
+    return eval_condition (lhs_ec, op, rhs_ec);
+
+  /* If at least one is not in an EC, we have no constraints
+     comparing LHS and RHS yet.
+     They might still be comparable if one (or both) is a constant.  */
+  tree lhs_const = lhs->maybe_get_constant ();
+  tree rhs_const = rhs->maybe_get_constant ();
+#if 0
+  if (lhs_const && rhs_const)
+    {
+      tristate result_for_constants
+	= compare_constants (lhs_const, op, rhs_const);
+      if (result_for_constants.is_known ())
+	return result_for_constants;
+    }
+#endif
+
+  if (!lhs_ec.null_p ())
+    {
+      gcc_assert (rhs_ec.null_p ());
+      if (rhs_const)
+	return eval_condition (lhs_ec, op, rhs_const);
+    }
+  if (!rhs_ec.null_p ())
+    {
+      gcc_assert (lhs_ec.null_p ());
+      if (lhs_const)
+	{
+	  enum tree_code swapped_op = swap_tree_comparison (op);
+	  return eval_condition (rhs_ec, swapped_op, lhs_const);
+	}
+    }
+
+  return tristate (tristate::TS_UNKNOWN);
 }
 
 /* Delete any information about svalue2_id instances identified by P.
@@ -2236,13 +2452,15 @@ test_constraint_impl ()
 
     /* Assert various things about the insides of model.  */
     constraint_manager2 *cm = model.get_constraints2 ();
-    ASSERT_EQ (cm->m_constraints.length (), 1);
-    ASSERT_EQ (cm->m_equiv_class2es.length (), 2);
+    ASSERT_EQ (cm->m_constraints.length (), 0);
+    ASSERT_EQ (cm->m_equiv_class2es.length (), 1);
+#if 0
     ASSERT_EQ (cm->m_constraints[0].m_lhs,
 	       cm->get_or_add_equiv_class2 (model.get_rvalue (int_0, NULL)));
     ASSERT_EQ (cm->m_constraints[0].m_rhs,
 	       cm->get_or_add_equiv_class2 (model.get_rvalue (int_42, NULL)));
     ASSERT_EQ (cm->m_constraints[0].m_op, CONSTRAINT2_LT);
+#endif
   }
 
   // TODO: selftest for merging ecs "in the middle"
diff --git a/gcc/analyzer/constraint-manager2.h b/gcc/analyzer/constraint-manager2.h
index 5425de08ab9..13c2b5d3cc5 100644
--- a/gcc/analyzer/constraint-manager2.h
+++ b/gcc/analyzer/constraint-manager2.h
@@ -25,6 +25,45 @@ namespace ana {
 
 class constraint_manager2;
 
+/* One of the end-points of a range2.  */
+
+struct bound2
+{
+  bound2 () : m_constant (NULL_TREE), m_closed (false) {}
+  bound2 (tree constant, bool closed)
+  : m_constant (constant), m_closed (closed) {}
+
+  void ensure_closed (bool is_upper);
+
+  const char * get_relation_as_str () const;
+
+  tree m_constant;
+  bool m_closed;
+};
+
+/* A range of values, used for determining if a value has been
+   constrained to just one possible constant value.  */
+
+struct range2
+{
+  range2 () : m_lower_bound (), m_upper_bound () {}
+  range2 (const bound2 &lower, const bound2 &upper)
+  : m_lower_bound (lower), m_upper_bound (upper) {}
+
+  void dump_to_pp (pretty_printer *pp) const;
+  void dump () const;
+
+  bool constrained_to_single_element (tree *out);
+
+  tristate eval_condition (enum tree_code op,
+			   tree rhs_const) const;
+  bool below_lower_bound (tree rhs_const) const;
+  bool above_upper_bound (tree rhs_const) const;
+
+  bound2 m_lower_bound;
+  bound2 m_upper_bound;
+};
+
 /* Abstract base class for specifying how state should be purged.  */
 
 class purge_criteria2
@@ -213,10 +252,14 @@ public:
   equiv_class2_id get_or_add_equiv_class2 (const svalue2 *sval);
   tristate eval_condition (equiv_class2_id lhs,
 			   enum tree_code op,
-			   equiv_class2_id rhs);
+			   equiv_class2_id rhs) const;
+  tristate eval_condition (equiv_class2_id lhs_ec,
+			   enum tree_code op,
+			   tree rhs_const) const;
   tristate eval_condition (const svalue2 *lhs,
 			   enum tree_code op,
-			   const svalue2 *rhs);
+			   const svalue2 *rhs) const;
+  range2 get_ec_bounds (equiv_class2_id ec_id) const;
 
   void purge (const purge_criteria2 &p, purge_stats2 *stats);
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c b/gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
index a00127b1a7f..baee0b684b7 100644
--- a/gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
+++ b/gcc/testsuite/gcc.dg/analyzer/conditionals-notrans.c
@@ -7,15 +7,12 @@ void test (int i, int j)
     {
       __analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
       __analyzer_eval (i <= 4); /* { dg-warning "FALSE" } */
-      __analyzer_eval (i > 3); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
-      /* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i > 3); /* { dg-warning "TRUE" } */
 
       __analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
-      __analyzer_eval (i != 3); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
-      /* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i != 3); /* { dg-warning "TRUE" } */
 
-      __analyzer_eval (i == 3); /* { dg-warning "FALSE" "desired" { xfail *-*-* } } */
-      /* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i == 3); /* { dg-warning "FALSE" } */
 
       __analyzer_eval (i != 4); /* { dg-warning "TRUE" } */
       __analyzer_eval (i == 4); /* { dg-warning "FALSE" } */
@@ -43,21 +40,17 @@ void test (int i, int j)
       __analyzer_eval (i <= 4); /* { dg-warning "TRUE" } */
       __analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
 
-      __analyzer_eval (i > 5); /* { dg-warning "FALSE" "desired" { xfail *-*-* } } */
-	  /* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i > 5); /* { dg-warning "FALSE" } */
       __analyzer_eval (i != 3); /* { dg-warning "UNKNOWN" } */
 
       __analyzer_eval (i == 3); /* { dg-warning "UNKNOWN" } */
 
       __analyzer_eval (i != 4); /* { dg-warning "UNKNOWN" } */
       __analyzer_eval (i == 4); /* { dg-warning "UNKNOWN" } */
-      __analyzer_eval (i == 5); /* { dg-warning "FALSE" "desired" { xfail *-*-* } } */
-      /* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
-      __analyzer_eval (i != 5); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
-      /* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i == 5); /* { dg-warning "FALSE" } */
+      __analyzer_eval (i != 5); /* { dg-warning "TRUE" } */
       __analyzer_eval (i < 5); /* { dg-warning "TRUE" } */
-      __analyzer_eval (i <= 5); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
-      /* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
+      __analyzer_eval (i <= 5); /* { dg-warning "TRUE" } */
     }
 }
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/loop.c b/gcc/testsuite/gcc.dg/analyzer/loop.c
index e5f327e2847..c4cfd88c912 100644
--- a/gcc/testsuite/gcc.dg/analyzer/loop.c
+++ b/gcc/testsuite/gcc.dg/analyzer/loop.c
@@ -1,5 +1,3 @@
-/* { dg-additional-options "-fno-analyzer-state-purge" } */
-
 #include "analyzer-decls.h"
 
 void test(void)
@@ -12,13 +10,12 @@ void test(void)
       __analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
       /* (should report TRUE twice). */
 
-      __analyzer_eval (i == 0); /* { dg-warning "TRUE" "1st" } */
+      __analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
       /* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
       /* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
       /* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration.  */
 
-      __analyzer_eval (i >= 0); /* { dg-warning "TRUE" "1st" } */
-      /* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
+      __analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
 
       __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
   }
-- 
2.26.2

