diff mbox series

c++, v3: Add support for __real__/__imag__ modifications in constant expressions [PR88174]

Message ID YsMMKeZReGabAcaY@tucnak
State New
Headers show
Series c++, v3: Add support for __real__/__imag__ modifications in constant expressions [PR88174] | expand

Commit Message

Jakub Jelinek July 4, 2022, 3:50 p.m. UTC
On Mon, Jun 27, 2022 at 06:31:18PM +0200, Jakub Jelinek via Gcc-patches wrote:
> Because the late evaluation of the initializer could have touched
> the destination, so we need to reevaluate it.
> Same reason why we call get_or_insert_ctor_field again in the second
> loop as we call it in the first loop.
> If it would help, I could move that repeated part into:

> > This seems like it needs to come before the ctors loop, so that these flags
> > can be propagated out to enclosing constructors.
> 
> I could indeed move this in between
>   bool c = TREE_CONSTANT (init);
>   bool s = TREE_SIDE_EFFECTS (init);
> and
>   if (!c || s || activated_union_member_p)
> and update c and s from *cexpr flags.

Here is it in patch form, so far lightly tested, ok for trunk if it passes
full bootstrap/regtest?

2022-07-04  Jakub Jelinek  <jakub@redhat.com>

	PR c++/88174
	* constexpr.cc (canonicalize_complex_to_complex_expr): New function.
	(cxx_eval_store_expression): Handle REALPART_EXPR and IMAGPART_EXPR.
	Change ctors from releasing_vec to auto_vec<tree *>, adjust all uses.

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



	Jakub

Comments

Jason Merrill July 5, 2022, 8:44 p.m. UTC | #1
On 7/4/22 11:50, Jakub Jelinek wrote:
> On Mon, Jun 27, 2022 at 06:31:18PM +0200, Jakub Jelinek via Gcc-patches wrote:

>>> Hmm, why do we need to handle complex in the !preeval case?  I'd think we
>>> want to preevaluate all complex values or components thereof.

>> Because the late evaluation of the initializer could have touched
>> the destination, so we need to reevaluate it.
>> Same reason why we call get_or_insert_ctor_field again in the second
>> loop as we call it in the first loop.

But preeval should always be true, so we'd never reach the new handling 
in the if (!preeval) block.  Certainly the new testcase doesn't exercise 
this code.

>> If it would help, I could move that repeated part into:
> 
>>> This seems like it needs to come before the ctors loop, so that these flags
>>> can be propagated out to enclosing constructors.
>>
>> I could indeed move this in between
>>    bool c = TREE_CONSTANT (init);
>>    bool s = TREE_SIDE_EFFECTS (init);
>> and
>>    if (!c || s || activated_union_member_p)
>> and update c and s from *cexpr flags.
> 
> Here is it in patch form, so far lightly tested, ok for trunk if it passes
> full bootstrap/regtest?
> 
> 2022-07-04  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/88174
> 	* constexpr.cc (canonicalize_complex_to_complex_expr): New function.
> 	(cxx_eval_store_expression): Handle REALPART_EXPR and IMAGPART_EXPR.
> 	Change ctors from releasing_vec to auto_vec<tree *>, adjust all uses.
> 
> 	* g++.dg/cpp1y/constexpr-complex1.C: New test.
> 
> --- gcc/cp/constexpr.cc.jj	2022-07-04 12:26:18.147053851 +0200
> +++ gcc/cp/constexpr.cc	2022-07-04 17:35:53.100556949 +0200
> @@ -5640,6 +5640,26 @@ modifying_const_object_p (tree_code code
>     return false;
>   }
>   
> +/* Helper of cxx_eval_store_expression, turn a COMPLEX_CST or
> +   empty no clearing CONSTRUCTOR into a COMPLEX_EXPR.  */
> +
> +static tree
> +canonicalize_complex_to_complex_expr (tree t)
> +{
> +  if (TREE_CODE (t) == COMPLEX_CST)
> +    t = build2 (COMPLEX_EXPR, TREE_TYPE (t),
> +		TREE_REALPART (t), TREE_IMAGPART (t));
> +  else if (TREE_CODE (t) == CONSTRUCTOR
> +	   && CONSTRUCTOR_NELTS (t) == 0
> +	   && CONSTRUCTOR_NO_CLEARING (t))
> +    {
> +      tree r = build_constructor (TREE_TYPE (TREE_TYPE (t)), NULL);
> +      CONSTRUCTOR_NO_CLEARING (r) = 1;
> +      t = build2 (COMPLEX_EXPR, TREE_TYPE (t), r, r);
> +    }
> +  return t;
> +}
> +
>   /* Evaluate an INIT_EXPR or MODIFY_EXPR.  */
>   
>   static tree
> @@ -5726,6 +5746,20 @@ cxx_eval_store_expression (const constex
>   	  }
>   	  break;
>   
> +	case REALPART_EXPR:
> +	  gcc_assert (probe == target);
> +	  vec_safe_push (refs, probe);
> +	  vec_safe_push (refs, TREE_TYPE (probe));
> +	  probe = TREE_OPERAND (probe, 0);
> +	  break;
> +
> +	case IMAGPART_EXPR:
> +	  gcc_assert (probe == target);
> +	  vec_safe_push (refs, probe);
> +	  vec_safe_push (refs, TREE_TYPE (probe));
> +	  probe = TREE_OPERAND (probe, 0);
> +	  break;
> +
>   	default:
>   	  if (evaluated)
>   	    object = probe;
> @@ -5764,7 +5798,8 @@ cxx_eval_store_expression (const constex
>     type = TREE_TYPE (object);
>     bool no_zero_init = true;
>   
> -  releasing_vec ctors, indexes;
> +  auto_vec<tree *> ctors;
> +  releasing_vec indexes;
>     auto_vec<int> index_pos_hints;
>     bool activated_union_member_p = false;
>     bool empty_base = false;
> @@ -5804,14 +5839,26 @@ cxx_eval_store_expression (const constex
>   	  *valp = ary_ctor;
>   	}
>   
> -      /* If the value of object is already zero-initialized, any new ctors for
> -	 subobjects will also be zero-initialized.  */
> -      no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp);
> -
>         enum tree_code code = TREE_CODE (type);
>         tree reftype = refs->pop();
>         tree index = refs->pop();
>   
> +      if (code == COMPLEX_TYPE)
> +	{
> +	  *valp = canonicalize_complex_to_complex_expr (*valp);
> +	  gcc_assert (TREE_CODE (*valp) == COMPLEX_EXPR);
> +	  ctors.safe_push (valp);
> +	  vec_safe_push (indexes, index);
> +	  valp = &TREE_OPERAND (*valp, TREE_CODE (index) == IMAGPART_EXPR);
> +	  gcc_checking_assert (refs->is_empty ());
> +	  type = reftype;
> +	  break;
> +	}
> +
> +      /* If the value of object is already zero-initialized, any new ctors for
> +	 subobjects will also be zero-initialized.  */
> +      no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp);
> +
>         if (code == RECORD_TYPE && is_empty_field (index))
>   	/* Don't build a sub-CONSTRUCTOR for an empty base or field, as they
>   	   have no data and might have an offset lower than previously declared
> @@ -5854,7 +5901,7 @@ cxx_eval_store_expression (const constex
>   	  no_zero_init = true;
>   	}
>   
> -      vec_safe_push (ctors, *valp);
> +      ctors.safe_push (valp);
>         vec_safe_push (indexes, index);
>   
>         constructor_elt *cep
> @@ -5916,11 +5963,11 @@ cxx_eval_store_expression (const constex
>   	     semantics are not applied on an object under construction.
>   	     They come into effect when the constructor for the most
>   	     derived object ends."  */
> -	  for (tree elt : *ctors)
> +	  for (tree *elt : ctors)
>   	    if (same_type_ignoring_top_level_qualifiers_p
> -		(TREE_TYPE (const_object_being_modified), TREE_TYPE (elt)))
> +		(TREE_TYPE (const_object_being_modified), TREE_TYPE (*elt)))
>   	      {
> -		fail = TREE_READONLY (elt);
> +		fail = TREE_READONLY (*elt);
>   		break;
>   	      }
>   	}
> @@ -5961,6 +6008,16 @@ cxx_eval_store_expression (const constex
>         valp = ctx->global->values.get (object);
>         for (unsigned i = 0; i < vec_safe_length (indexes); i++)
>   	{
> +	  ctors[i] = valp;
> +	  if (TREE_CODE (indexes[i]) == REALPART_EXPR
> +	      || TREE_CODE (indexes[i]) == IMAGPART_EXPR)
> +	    {
> +	      *valp = canonicalize_complex_to_complex_expr (*valp);
> +	      gcc_assert (TREE_CODE (*valp) == COMPLEX_EXPR);
> +	      valp = &TREE_OPERAND (*valp,
> +				    TREE_CODE (indexes[i]) == IMAGPART_EXPR);
> +	      break;
> +	    }
>   	  constructor_elt *cep
>   	    = get_or_insert_ctor_field (*valp, indexes[i], index_pos_hints[i]);
>   	  valp = &cep->value;
> @@ -6023,17 +6080,45 @@ cxx_eval_store_expression (const constex
>        CONSTRUCTORs, if any.  */
>     bool c = TREE_CONSTANT (init);
>     bool s = TREE_SIDE_EFFECTS (init);
> +  if (!indexes->is_empty ())
> +    {
> +      tree last = indexes->last ();
> +      if (TREE_CODE (last) == REALPART_EXPR
> +	  || TREE_CODE (last) == IMAGPART_EXPR)
> +	{
> +	  /* And canonicalize COMPLEX_EXPR into COMPLEX_CST if
> +	     possible.  */
> +	  tree *cexpr = ctors.last ();
> +	  if (tree c = const_binop (COMPLEX_EXPR, TREE_TYPE (*cexpr),
> +				    TREE_OPERAND (*cexpr, 0),
> +				    TREE_OPERAND (*cexpr, 1)))
> +	    *cexpr = c;
> +	  else
> +	    {
> +	      TREE_CONSTANT (*cexpr)
> +		= (TREE_CONSTANT (TREE_OPERAND (*cexpr, 0))
> +		   & TREE_CONSTANT (TREE_OPERAND (*cexpr, 1)));
> +	      TREE_SIDE_EFFECTS (*cexpr)
> +		= (TREE_SIDE_EFFECTS (TREE_OPERAND (*cexpr, 0))
> +		   | TREE_SIDE_EFFECTS (TREE_OPERAND (*cexpr, 1)));
> +	    }
> +	  c = TREE_CONSTANT (*cexpr);
> +	  s = TREE_SIDE_EFFECTS (*cexpr);
> +	}
> +    }
>     if (!c || s || activated_union_member_p)
> -    for (tree elt : *ctors)
> +    for (tree *elt : ctors)
>         {
> +	if (TREE_CODE (*elt) != CONSTRUCTOR)
> +	  continue;
>   	if (!c)
> -	  TREE_CONSTANT (elt) = false;
> +	  TREE_CONSTANT (*elt) = false;
>   	if (s)
> -	  TREE_SIDE_EFFECTS (elt) = true;
> +	  TREE_SIDE_EFFECTS (*elt) = true;
>   	/* Clear CONSTRUCTOR_NO_CLEARING since we've activated a member of
>   	   this union.  */
> -	if (TREE_CODE (TREE_TYPE (elt)) == UNION_TYPE)
> -	  CONSTRUCTOR_NO_CLEARING (elt) = false;
> +	if (TREE_CODE (TREE_TYPE (*elt)) == UNION_TYPE)
> +	  CONSTRUCTOR_NO_CLEARING (*elt) = false;
>         }
>   
>     if (lval)
> --- gcc/testsuite/g++.dg/cpp1y/constexpr-complex1.C.jj	2022-07-04 17:30:47.884580998 +0200
> +++ gcc/testsuite/g++.dg/cpp1y/constexpr-complex1.C	2022-07-04 17:30:47.884580998 +0200
> @@ -0,0 +1,24 @@
> +// PR c++/88174
> +// { dg-do compile { target c++14 } }
> +
> +constexpr bool
> +foo (double x, double y, double z, double w)
> +{
> +  __complex__ double a = 0;
> +  __real__ a = x;
> +  __imag__ a = y;
> +#if __cpp_constexpr >= 201907L
> +  __complex__ double b;
> +  __real__ b = z;
> +#else
> +  __complex__ double b = z;
> +#endif
> +  __imag__ b = w;
> +  a += b;
> +  a -= b;
> +  a *= b;
> +  a /= b;
> +  return __real__ a == x && __imag__ a == y;
> +}
> +
> +static_assert (foo (1.0, 2.0, 3.0, 4.0), "");
> 
> 
> 	Jakub
>
Jakub Jelinek July 5, 2022, 8:57 p.m. UTC | #2
On Tue, Jul 05, 2022 at 04:44:41PM -0400, Jason Merrill wrote:
> On 7/4/22 11:50, Jakub Jelinek wrote:
> > On Mon, Jun 27, 2022 at 06:31:18PM +0200, Jakub Jelinek via Gcc-patches wrote:
> 
> > > > Hmm, why do we need to handle complex in the !preeval case?  I'd think we
> > > > want to preevaluate all complex values or components thereof.
> 
> > > Because the late evaluation of the initializer could have touched
> > > the destination, so we need to reevaluate it.
> > > Same reason why we call get_or_insert_ctor_field again in the second
> > > loop as we call it in the first loop.
> 
> But preeval should always be true, so we'd never reach the new handling in
> the if (!preeval) block.  Certainly the new testcase doesn't exercise this
> code.

Ah, you're right, in the complex case SCALAR_TYPE_P (type) has to be true
because it is COMPLEX_TYPE and so preeval must be true.
I'll rework the patch then.

	Jakub
diff mbox series

Patch

--- gcc/cp/constexpr.cc.jj	2022-07-04 12:26:18.147053851 +0200
+++ gcc/cp/constexpr.cc	2022-07-04 17:35:53.100556949 +0200
@@ -5640,6 +5640,26 @@  modifying_const_object_p (tree_code code
   return false;
 }
 
+/* Helper of cxx_eval_store_expression, turn a COMPLEX_CST or
+   empty no clearing CONSTRUCTOR into a COMPLEX_EXPR.  */
+
+static tree
+canonicalize_complex_to_complex_expr (tree t)
+{
+  if (TREE_CODE (t) == COMPLEX_CST)
+    t = build2 (COMPLEX_EXPR, TREE_TYPE (t),
+		TREE_REALPART (t), TREE_IMAGPART (t));
+  else if (TREE_CODE (t) == CONSTRUCTOR
+	   && CONSTRUCTOR_NELTS (t) == 0
+	   && CONSTRUCTOR_NO_CLEARING (t))
+    {
+      tree r = build_constructor (TREE_TYPE (TREE_TYPE (t)), NULL);
+      CONSTRUCTOR_NO_CLEARING (r) = 1;
+      t = build2 (COMPLEX_EXPR, TREE_TYPE (t), r, r);
+    }
+  return t;
+}
+
 /* Evaluate an INIT_EXPR or MODIFY_EXPR.  */
 
 static tree
@@ -5726,6 +5746,20 @@  cxx_eval_store_expression (const constex
 	  }
 	  break;
 
+	case REALPART_EXPR:
+	  gcc_assert (probe == target);
+	  vec_safe_push (refs, probe);
+	  vec_safe_push (refs, TREE_TYPE (probe));
+	  probe = TREE_OPERAND (probe, 0);
+	  break;
+
+	case IMAGPART_EXPR:
+	  gcc_assert (probe == target);
+	  vec_safe_push (refs, probe);
+	  vec_safe_push (refs, TREE_TYPE (probe));
+	  probe = TREE_OPERAND (probe, 0);
+	  break;
+
 	default:
 	  if (evaluated)
 	    object = probe;
@@ -5764,7 +5798,8 @@  cxx_eval_store_expression (const constex
   type = TREE_TYPE (object);
   bool no_zero_init = true;
 
-  releasing_vec ctors, indexes;
+  auto_vec<tree *> ctors;
+  releasing_vec indexes;
   auto_vec<int> index_pos_hints;
   bool activated_union_member_p = false;
   bool empty_base = false;
@@ -5804,14 +5839,26 @@  cxx_eval_store_expression (const constex
 	  *valp = ary_ctor;
 	}
 
-      /* If the value of object is already zero-initialized, any new ctors for
-	 subobjects will also be zero-initialized.  */
-      no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp);
-
       enum tree_code code = TREE_CODE (type);
       tree reftype = refs->pop();
       tree index = refs->pop();
 
+      if (code == COMPLEX_TYPE)
+	{
+	  *valp = canonicalize_complex_to_complex_expr (*valp);
+	  gcc_assert (TREE_CODE (*valp) == COMPLEX_EXPR);
+	  ctors.safe_push (valp);
+	  vec_safe_push (indexes, index);
+	  valp = &TREE_OPERAND (*valp, TREE_CODE (index) == IMAGPART_EXPR);
+	  gcc_checking_assert (refs->is_empty ());
+	  type = reftype;
+	  break;
+	}
+
+      /* If the value of object is already zero-initialized, any new ctors for
+	 subobjects will also be zero-initialized.  */
+      no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp);
+
       if (code == RECORD_TYPE && is_empty_field (index))
 	/* Don't build a sub-CONSTRUCTOR for an empty base or field, as they
 	   have no data and might have an offset lower than previously declared
@@ -5854,7 +5901,7 @@  cxx_eval_store_expression (const constex
 	  no_zero_init = true;
 	}
 
-      vec_safe_push (ctors, *valp);
+      ctors.safe_push (valp);
       vec_safe_push (indexes, index);
 
       constructor_elt *cep
@@ -5916,11 +5963,11 @@  cxx_eval_store_expression (const constex
 	     semantics are not applied on an object under construction.
 	     They come into effect when the constructor for the most
 	     derived object ends."  */
-	  for (tree elt : *ctors)
+	  for (tree *elt : ctors)
 	    if (same_type_ignoring_top_level_qualifiers_p
-		(TREE_TYPE (const_object_being_modified), TREE_TYPE (elt)))
+		(TREE_TYPE (const_object_being_modified), TREE_TYPE (*elt)))
 	      {
-		fail = TREE_READONLY (elt);
+		fail = TREE_READONLY (*elt);
 		break;
 	      }
 	}
@@ -5961,6 +6008,16 @@  cxx_eval_store_expression (const constex
       valp = ctx->global->values.get (object);
       for (unsigned i = 0; i < vec_safe_length (indexes); i++)
 	{
+	  ctors[i] = valp;
+	  if (TREE_CODE (indexes[i]) == REALPART_EXPR
+	      || TREE_CODE (indexes[i]) == IMAGPART_EXPR)
+	    {
+	      *valp = canonicalize_complex_to_complex_expr (*valp);
+	      gcc_assert (TREE_CODE (*valp) == COMPLEX_EXPR);
+	      valp = &TREE_OPERAND (*valp,
+				    TREE_CODE (indexes[i]) == IMAGPART_EXPR);
+	      break;
+	    }
 	  constructor_elt *cep
 	    = get_or_insert_ctor_field (*valp, indexes[i], index_pos_hints[i]);
 	  valp = &cep->value;
@@ -6023,17 +6080,45 @@  cxx_eval_store_expression (const constex
      CONSTRUCTORs, if any.  */
   bool c = TREE_CONSTANT (init);
   bool s = TREE_SIDE_EFFECTS (init);
+  if (!indexes->is_empty ())
+    {
+      tree last = indexes->last ();
+      if (TREE_CODE (last) == REALPART_EXPR
+	  || TREE_CODE (last) == IMAGPART_EXPR)
+	{
+	  /* And canonicalize COMPLEX_EXPR into COMPLEX_CST if
+	     possible.  */
+	  tree *cexpr = ctors.last ();
+	  if (tree c = const_binop (COMPLEX_EXPR, TREE_TYPE (*cexpr),
+				    TREE_OPERAND (*cexpr, 0),
+				    TREE_OPERAND (*cexpr, 1)))
+	    *cexpr = c;
+	  else
+	    {
+	      TREE_CONSTANT (*cexpr)
+		= (TREE_CONSTANT (TREE_OPERAND (*cexpr, 0))
+		   & TREE_CONSTANT (TREE_OPERAND (*cexpr, 1)));
+	      TREE_SIDE_EFFECTS (*cexpr)
+		= (TREE_SIDE_EFFECTS (TREE_OPERAND (*cexpr, 0))
+		   | TREE_SIDE_EFFECTS (TREE_OPERAND (*cexpr, 1)));
+	    }
+	  c = TREE_CONSTANT (*cexpr);
+	  s = TREE_SIDE_EFFECTS (*cexpr);
+	}
+    }
   if (!c || s || activated_union_member_p)
-    for (tree elt : *ctors)
+    for (tree *elt : ctors)
       {
+	if (TREE_CODE (*elt) != CONSTRUCTOR)
+	  continue;
 	if (!c)
-	  TREE_CONSTANT (elt) = false;
+	  TREE_CONSTANT (*elt) = false;
 	if (s)
-	  TREE_SIDE_EFFECTS (elt) = true;
+	  TREE_SIDE_EFFECTS (*elt) = true;
 	/* Clear CONSTRUCTOR_NO_CLEARING since we've activated a member of
 	   this union.  */
-	if (TREE_CODE (TREE_TYPE (elt)) == UNION_TYPE)
-	  CONSTRUCTOR_NO_CLEARING (elt) = false;
+	if (TREE_CODE (TREE_TYPE (*elt)) == UNION_TYPE)
+	  CONSTRUCTOR_NO_CLEARING (*elt) = false;
       }
 
   if (lval)
--- gcc/testsuite/g++.dg/cpp1y/constexpr-complex1.C.jj	2022-07-04 17:30:47.884580998 +0200
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-complex1.C	2022-07-04 17:30:47.884580998 +0200
@@ -0,0 +1,24 @@ 
+// PR c++/88174
+// { dg-do compile { target c++14 } }
+
+constexpr bool
+foo (double x, double y, double z, double w)
+{
+  __complex__ double a = 0;
+  __real__ a = x;
+  __imag__ a = y;
+#if __cpp_constexpr >= 201907L
+  __complex__ double b;
+  __real__ b = z;
+#else
+  __complex__ double b = z;
+#endif
+  __imag__ b = w;
+  a += b;
+  a -= b;
+  a *= b;
+  a /= b;
+  return __real__ a == x && __imag__ a == y;
+}
+
+static_assert (foo (1.0, 2.0, 3.0, 4.0), "");