Hi!

As the following testcases shows, cxx_eval_store_expression mishandles the
case when constexpr evaluation of the rhs (init) modifies part of the ctor
that the store stores into.
Except for unions (see below) I believe it is fine the way the outer refs
are handled, because we advance into another CONSTRUCTOR and if the pointer
to that is reallocated or memmoved somewhere else, it shouldn't matter.
For the last CONSTRUCTOR, we set valp to
&CONSTRUCTOR_ELT (*valp, something)->value
but CONSTRUCTOR_ELTS is not a linked list, but vector, which can be
reallocated if we need more elements, or vec_safe_insert some earlier
element memmoves further elts later in the same vector.

The likely case is still that nothing has changed in between, so this patch
just quickly verifies if that is the case (by comparing
CONSTRUCTOR_ELT (ctor, 0) with the previously saved value of that and by
checking if at the spot in the vector is the expected index).  If that is
the case, it doesn't do anything else, otherwise it updates the valp
pointer.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

Note, at least by my reading of the standard, the union case seems to be
mishandled (and the patch doesn't change anything on that).  The union
member being stored should IMHO become active after evaluating both the
lhs and rhs, but before the actual store, while the current code
invalidates the previously active member already before evaluating the rhs
(if it is different from the upcoming one).  I think
constexpr int foo () {
  union U { int a; long b; };
  union V { union U u; short v; };
  V w {};
  w.u.a = w.v = w.u.b = 5L;
  return w.u.a;
}
static_assert (foo () == 5, "");
should be valid (though clang++ rejects it as well).  If it is indeed valid,
it is not going to be very easy to implement properly, I guess we shouldn't
          if (last_code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp)
              && CONSTRUCTOR_ELT (*valp, 0)->index != index)
            /* Changing active member.  */
            vec_safe_truncate (CONSTRUCTOR_ELTS (*valp), 0);
early, on the other side, at least with the way it currently works we want
to allocate a new CONSTRUCTOR if we are going to store to a further
component of the union member and we likely need to store it somewhere
for the benefit of the rhs constexpr evaluation, because I think even
when some member is not active before the outer store_expression, it can
become active during the rhs evaluation.  Say:
  union U { int a[5]; long b; };
  union V { union U u; short v; };
  V w {};
  w.v = 5L; // Make v the active member
  w.u.a[3] = w.u.a[1] = w.v;
We can't invalidate w.v before handling the rhs, but if we create a new
CONSTRUCTOR for w.u for the w.u.a[3] = assignment and we'd create a different
one for the w.u.a[1] = assignment, then w.u.a[1] wouldn't show in w.
I wonder if we shouldn't temporarily allow the UNION_TYPE CONSTRUCTOR_ELTS
to contain more than one element, where e.g. the first one would be always
the currently active one (that is what we'd use for any reads) and after
that would be other (preferrably only empty CONSTRUCTORs containing at most
other CONSTRUCTORs but no real fields), which we'd use when we'd want to
activate something union member.  Plus remember in store_expression if
a particular union CONSTRUCTOR had at most one element before and kill all
the other ones at the end at that point.  Though, I admit it is not very
well thought out.

2019-02-13  Jakub Jelinek  <ja...@redhat.com>

        PR c++/89336
        * constexpr.c (cxx_eval_store_expression): Recompute valp after
        constexpr evaluation of init if CONSTRUCTOR_ELTS have been
        reallocated or earlier elements inserted.

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

--- gcc/cp/constexpr.c.jj       2019-02-12 21:48:51.000000000 +0100
+++ gcc/cp/constexpr.c  2019-02-13 20:01:25.101100373 +0100
@@ -3733,6 +3733,10 @@ cxx_eval_store_expression (const constex
   bool no_zero_init = true;
 
   vec<tree,va_gc> *ctors = make_tree_vector ();
+  unsigned HOST_WIDE_INT last_idx = 0;
+  tree last_index = NULL_TREE;
+  enum tree_code last_code = ERROR_MARK;
+  constructor_elt *last_celt = NULL;
   while (!refs->is_empty())
     {
       if (*valp == NULL_TREE)
@@ -3775,12 +3779,12 @@ cxx_eval_store_expression (const constex
 
       vec_safe_push (ctors, *valp);
 
-      enum tree_code code = TREE_CODE (type);
+      last_code = TREE_CODE (type);
       type = refs->pop();
       tree index = refs->pop();
 
       constructor_elt *cep = NULL;
-      if (code == ARRAY_TYPE)
+      if (last_code == ARRAY_TYPE)
        {
          HOST_WIDE_INT i
            = find_array_ctor_elt (*valp, index, /*insert*/true);
@@ -3799,7 +3803,7 @@ cxx_eval_store_expression (const constex
          tree fields = TYPE_FIELDS (DECL_CONTEXT (index));
          unsigned HOST_WIDE_INT idx;
 
-         if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp)
+         if (last_code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp)
              && CONSTRUCTOR_ELT (*valp, 0)->index != index)
            /* Changing active member.  */
            vec_safe_truncate (CONSTRUCTOR_ELTS (*valp), 0);
@@ -3830,6 +3834,9 @@ cxx_eval_store_expression (const constex
          }
        found:;
        }
+      last_celt = CONSTRUCTOR_ELT (*valp, 0);
+      last_idx = cep - last_celt;
+      last_index = cep->index;
       valp = &cep->value;
     }
   release_tree_vector (refs);
@@ -3856,6 +3863,40 @@ cxx_eval_store_expression (const constex
   if (target == object)
     /* The hash table might have moved since the get earlier.  */
     valp = ctx->values->get (object);
+  /* The above cxx_eval_constant_expression call might have added further
+     ctor elements before the *valp one, or might have added some elements
+     after it and reallocate the vector.  In that case, need to recompute
+     valp.  In all the cases, the element we've added above should still
+     be there.  */
+  else if (!vec_safe_is_empty (ctors)
+          && (CONSTRUCTOR_ELT (ctors->last (), 0) != last_celt
+              || last_celt[last_idx].index != last_index))
+    {
+      tree ctor = ctors->last ();
+      constructor_elt *cep = NULL;
+      if (last_code == ARRAY_TYPE)
+       {
+         HOST_WIDE_INT i
+           = find_array_ctor_elt (ctor, last_index, /*insert*/false);
+         gcc_assert (i >= 0);
+         cep = CONSTRUCTOR_ELT (ctor, i);
+         gcc_assert (TREE_CODE (cep->index) != RANGE_EXPR);
+       }
+      else if (last_code == UNION_TYPE)
+       {
+         cep = CONSTRUCTOR_ELT (ctor, 0);
+         cep->index = last_index;
+       }
+      else
+       {
+         for (unsigned HOST_WIDE_INT idx = last_idx;
+              vec_safe_iterate (CONSTRUCTOR_ELTS (ctor), idx, &cep); idx++)
+           if (last_index == cep->index)
+             break;
+         gcc_assert (cep);
+       }
+      valp = &cep->value;
+    }
 
   if (TREE_CODE (init) == CONSTRUCTOR)
     {
--- gcc/testsuite/g++.dg/cpp1y/constexpr-89336-1.C.jj   2019-02-13 
20:12:54.836796658 +0100
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-89336-1.C      2019-02-13 
20:07:27.645154780 +0100
@@ -0,0 +1,35 @@
+// PR c++/89336
+// { dg-do compile { target c++14 } }
+
+template <typename T, int N> struct A {
+  T a[N];
+  constexpr T &operator[] (int x) { return a[x]; }
+  constexpr const T &operator[] (int x) const { return a[x]; }
+};
+
+constexpr A<int, 16>
+foo ()
+{
+  A<int, 16> r{};
+  for (int i = 0; i < 6; ++i)
+    r[i + 8] = r[i] = i + 1;
+  return r;
+}
+
+constexpr auto x = foo ();
+static_assert (x[0] == 1, "");
+static_assert (x[1] == 2, "");
+static_assert (x[2] == 3, "");
+static_assert (x[3] == 4, "");
+static_assert (x[4] == 5, "");
+static_assert (x[5] == 6, "");
+static_assert (x[6] == 0, "");
+static_assert (x[7] == 0, "");
+static_assert (x[8] == 1, "");
+static_assert (x[9] == 2, "");
+static_assert (x[10] == 3, "");
+static_assert (x[11] == 4, "");
+static_assert (x[12] == 5, "");
+static_assert (x[13] == 6, "");
+static_assert (x[14] == 0, "");
+static_assert (x[15] == 0, "");
--- gcc/testsuite/g++.dg/cpp1y/constexpr-89336-2.C.jj   2019-02-13 
20:22:44.059172675 +0100
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-89336-2.C      2019-02-13 
20:22:03.683835843 +0100
@@ -0,0 +1,56 @@
+// PR c++/89336
+// { dg-do compile { target c++14 } }
+
+constexpr int
+foo ()
+{
+  int a[16] = {};
+  int r = 0;
+  a[15] = a[14] = a[13] = a[12] = a[11] = a[10] = a[9] = a[8]
+    = a[7] = a[6] = a[5] = a[4] = a[3] = a[2] = a[1] = a[0] = 5;
+  for (int i = 0; i < 16; ++i)
+    r += a[i];
+  return r;
+}
+
+static_assert (foo () == 16 * 5, "");
+
+struct A { int a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p; };
+
+constexpr int
+bar ()
+{
+  A a {};
+  a.p = a.o = a.n = a.m = a.l = a.k = a.j = a.i
+    = a.h = a.g = a.f = a.e = a.d = a.c = a.b = a.a = 8;
+  return a.a + a.b + a.c + a.d + a.e + a.f + a.g + a.h
+        + a.i + a.j + a.k + a.l + a.m + a.n + a.o + a.p;
+}
+
+static_assert (bar () == 16 * 8, "");
+
+constexpr int
+baz ()
+{
+  int a[16] = {};
+  int r = 0;
+  a[0] = a[1] = a[2] = a[3] = a[4] = a[5] = a[6] = a[7]
+    = a[8] = a[9] = a[10] = a[11] = a[12] = a[13] = a[14] = a[15] = 7;
+  for (int i = 0; i < 16; ++i)
+    r += a[i];
+  return r;
+}
+
+static_assert (baz () == 16 * 7, "");
+
+constexpr int
+qux ()
+{
+  A a {};
+  a.a = a.b = a.c = a.d = a.e = a.f = a.g = a.h
+    = a.i = a.j = a.k = a.l = a.m = a.n = a.o = a.p = 6;
+  return a.a + a.b + a.c + a.d + a.e + a.f + a.g + a.h
+        + a.i + a.j + a.k + a.l + a.m + a.n + a.o + a.p;
+}
+
+static_assert (qux () == 16 * 6, "");

        Jakub

Reply via email to