https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89074

--- Comment #10 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
So, for #c5,
  if (&a[1] == &b[0])
instead of if (a+1 == b+0) works right, that is handled by the match.pd
  (cmp (convert1?@2 addr@0) (convert2? addr@1))
address_compare simplification.  And it also works for GIMPLE, where we have
in gimple-fold.c the:
            /* Translate &x + CST into an invariant form suitable for
               further propagation.  */
            if (subcode == POINTER_PLUS_EXPR)
              {
                tree op0 = (*valueize) (gimple_assign_rhs1 (stmt));
                tree op1 = (*valueize) (gimple_assign_rhs2 (stmt));
                if (TREE_CODE (op0) == ADDR_EXPR
                    && TREE_CODE (op1) == INTEGER_CST)
                  {
                    tree off = fold_convert (ptr_type_node, op1);
                    return build1_loc
                        (loc, ADDR_EXPR, TREE_TYPE (op0),
                         fold_build2 (MEM_REF,
                                      TREE_TYPE (TREE_TYPE (op0)),
                                      unshare_expr (op0), off));
                  }
              }
I think we don't do this for GENERIC because that can seriously break the
constant evaluation, the details on which exact field is accessed can be lost
in there.
But, if we take e.g.
struct S { int s; };
struct T : public S {};
struct U : public T {};

constexpr bool
test()
{
  U a[] = { 1, 2, 3, 4 };
  U b[] = { 5, 6, 7, 8 };
  T *c = (T *) a + 1;
  S *d = (S *) c + 2;
  S *e = (S *) b + 1;

  if (a+0 == b+0)       // OK
    return false;

  if (d == e)       // ERROR
    return false;

  return true;
}

static_assert( test() );
then we can see the comparison is tried on
‘((((S*)((& a[0].U::<anonymous>) + 4)) + 8) == ((&
b[0].U::<anonymous>.T::<anonymous>) + 4))’ is not a constant expression
so the multiple pointer_plus aren't merged in there either.
Hasving a GENERIC guarded variant of the address_compare with one pointer_plus
in one or the other or both operands might be still doable, but the above shows
that it wouldn't be sufficient.
So, perhaps instead if simple comparison fails during constexpr evaluation,
we could call some helper function that looks through cast and optimizes the
POINTER_PLUS into MEM_REF that it folds and then we'd try to compare it again
(or call that helper function regardless on both comparison operands)?

Another thing is that the following testcase isn't rejected:
2022-01-05  Jakub Jelinek  <ja...@redhat.com>

        PR c++/89074
        * fold-const.c (address_compare): Punt on comparison of address of
        one object with address of end of another object if
        folding_initializer.

        * g++.dg/cpp1y/constexpr-89074-1.C: New test.

--- gcc/fold-const.c.jj 2022-01-05 20:30:08.731806756 +0100
+++ gcc/fold-const.c    2022-01-05 20:34:52.277822349 +0100
@@ -16627,7 +16627,7 @@ address_compare (tree_code code, tree ty
   /* If this is a pointer comparison, ignore for now even
      valid equalities where one pointer is the offset zero
      of one object and the other to one past end of another one.  */
-  else if (!INTEGRAL_TYPE_P (type))
+  else if (!folding_initializer && !INTEGRAL_TYPE_P (type))
     ;
   /* Assume that automatic variables can't be adjacent to global
      variables.  */
--- gcc/testsuite/g++.dg/cpp1y/constexpr-89074-1.C.jj   2022-01-05
20:43:03.696917484 +0100
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-89074-1.C      2022-01-05
20:42:12.676634044 +0100
@@ -0,0 +1,28 @@
+// PR c++/89074
+// { dg-do compile { target c++14 } }
+
+constexpr bool
+foo ()
+{
+  int a[] = { 1, 2 };
+  int b[] = { 3, 4 };
+
+  if (&a[0] == &b[0])
+    return false;
+
+  if (&a[1] == &b[0])
+    return false;
+
+  if (&a[1] == &b[1])
+    return false;
+
+  if (&a[2] == &b[1])
+    return false;
+
+  if (&a[2] == &b[0])          // { dg-error "is not a constant expression" }
+    return false;
+
+  return true;
+}
+
+constexpr bool a = foo ();

Reply via email to