c++: Support &typeid(x) == &typeid(y) and typeid(x) == typeid(y) in constant evaluation [PR103600]

Message ID 20211208103528.GE2646553@tucnak
State Superseded
Headers
Series c++: Support &typeid(x) == &typeid(y) and typeid(x) == typeid(y) in constant evaluation [PR103600] |

Commit Message

Jakub Jelinek Dec. 8, 2021, 10:35 a.m. UTC
  Hi!

If the tinfo vars are emitted in the current TU, they are emitted at the end
of the compilation, and for some types they are exported from
libstdc++/libsupc++ and not emitted in the current TU at all.

The following patch allows constant folding of comparisons of typeid
addresses and makes it possible to implement P1328R1 - making type_info
operator== constexpr (Jonathan has a patch for that).

As mentioned in the PR, the varpool/middle-end code is trying to be
conservative with address comparisons of different vars if those vars
don't bind locally, because of possible aliases in other TUs etc.
and so while match.pd folds &typeid(int) == &typeid(int) because
it is equality comparison with the same operands, for different typeids
it doesn't fold it.  The first constexpr.c hunk takes care of that
during constant evaluation.  As the std::type_info class has a protected
member __name only, I think people shouldn't be comparing anything but
the starts of those vars, so we don't need to deal with offsets into
those vars etc.

The other constexpr.c hunk and rtti.c change is to allow constexpr
operator== that will in the manifestly constant evaluation just
compare the __name members for equality.  Again, the _ZTS* variables
aren't constructed yet and for some types will not be at all.
So, what this patch does is when evaluating typeid(whatever).__name
fold the INDIRECT_REF on (const std::type_info *) &_ZTIwhatever
into a CONSTRUCTOR_NO_CLEARING ctor with just __name field initialized
to &"1S" etc.

Testcase coverage includes just the &typeid comparison, for typeid
comparison it will be in the libstdc++ patch.

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

2021-12-08  Jakub Jelinek  <jakub@redhat.com>

	PR c++/103600
	* cp-tree.h (tinfo_for_constant_evaluation): Declare.
	* constexpr.c (cxx_eval_binary_expression): Evaluate
	&typeid(x) == &typeid(y) to true or false depending on whether
	the typeid is the same or not.
	(cxx_eval_indirect_ref): Fold typeid(S) to {.__name="1S"}.
	* rtti.c (tinfo_for_constant_evaluation): New function.

	* g++.dg/cpp0x/constexpr-typeid2.C: New test.


	Jakub
  

Comments

Jason Merrill Dec. 8, 2021, 1:53 p.m. UTC | #1
On 12/8/21 05:35, Jakub Jelinek wrote:
> Hi!
> 
> If the tinfo vars are emitted in the current TU, they are emitted at the end
> of the compilation, and for some types they are exported from
> libstdc++/libsupc++ and not emitted in the current TU at all.
> 
> The following patch allows constant folding of comparisons of typeid
> addresses and makes it possible to implement P1328R1 - making type_info
> operator== constexpr (Jonathan has a patch for that).
> 
> As mentioned in the PR, the varpool/middle-end code is trying to be
> conservative with address comparisons of different vars if those vars
> don't bind locally, because of possible aliases in other TUs etc.

Would it make sense to assume that DECL_ARTIFICIAL variables can't be 
aliases?  If not, could we have some way of marking a variable as 
non-aliasing, perhaps an attribute?

> and so while match.pd folds &typeid(int) == &typeid(int) because
> it is equality comparison with the same operands, for different typeids
> it doesn't fold it.  The first constexpr.c hunk takes care of that
> during constant evaluation.  As the std::type_info class has a protected
> member __name only, I think people shouldn't be comparing anything but
> the starts of those vars, so we don't need to deal with offsets into
> those vars etc.
> 
> The other constexpr.c hunk and rtti.c change is to allow constexpr
> operator== that will in the manifestly constant evaluation just
> compare the __name members for equality.  Again, the _ZTS* variables
> aren't constructed yet and for some types will not be at all.
> So, what this patch does is when evaluating typeid(whatever).__name
> fold the INDIRECT_REF on (const std::type_info *) &_ZTIwhatever
> into a CONSTRUCTOR_NO_CLEARING ctor with just __name field initialized
> to &"1S" etc.

During constant evaluation, the operator== could compare the type_info 
address instead of the __name address, reducing this to the previous 
problem.

> Testcase coverage includes just the &typeid comparison, for typeid
> comparison it will be in the libstdc++ patch.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> 
> 2021-12-08  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/103600
> 	* cp-tree.h (tinfo_for_constant_evaluation): Declare.
> 	* constexpr.c (cxx_eval_binary_expression): Evaluate
> 	&typeid(x) == &typeid(y) to true or false depending on whether
> 	the typeid is the same or not.
> 	(cxx_eval_indirect_ref): Fold typeid(S) to {.__name="1S"}.
> 	* rtti.c (tinfo_for_constant_evaluation): New function.
> 
> 	* g++.dg/cpp0x/constexpr-typeid2.C: New test.
> 
> --- gcc/cp/cp-tree.h.jj	2021-12-04 11:02:03.925646583 +0100
> +++ gcc/cp/cp-tree.h	2021-12-07 16:58:06.698802940 +0100
> @@ -7376,6 +7376,7 @@ extern tree build_dynamic_cast			(locati
>   						 tsubst_flags_t);
>   extern void emit_support_tinfos			(void);
>   extern bool emit_tinfo_decl			(tree);
> +extern tree tinfo_for_constant_evaluation	(tree);
>   extern unsigned get_pseudo_tinfo_index		(tree);
>   extern tree get_pseudo_tinfo_type		(unsigned);
>   extern tree build_if_nonnull			(tree, tree, tsubst_flags_t);
> --- gcc/cp/constexpr.c.jj	2021-12-04 11:02:03.949646241 +0100
> +++ gcc/cp/constexpr.c	2021-12-07 17:32:07.324739082 +0100
> @@ -3346,6 +3346,25 @@ cxx_eval_binary_expression (const conste
>   	lhs = cplus_expand_constant (lhs);
>         else if (TREE_CODE (rhs) == PTRMEM_CST)
>   	rhs = cplus_expand_constant (rhs);
> +
> +      /* Fold &typeid(x) == &typeid(y) to
> +	 whether the DECL_TINFO_P vars are the same or not.  */
> +      if (flag_rtti)
> +	{
> +	  tree op0 = lhs;
> +	  tree op1 = rhs;
> +	  STRIP_NOPS (op0);
> +	  STRIP_NOPS (op1);
> +	  if (TREE_CODE (op0) == ADDR_EXPR
> +	      && VAR_P (TREE_OPERAND (op0, 0))
> +	      && DECL_TINFO_P (TREE_OPERAND (op0, 0))
> +	      && TREE_CODE (op1) == ADDR_EXPR
> +	      && VAR_P (TREE_OPERAND (op1, 0))
> +	      && DECL_TINFO_P (TREE_OPERAND (op1, 0)))
> +	    r = constant_boolean_node ((TREE_OPERAND (op0, 0)
> +					!= TREE_OPERAND (op1, 0))
> +				       ^ is_code_eq, type);
> +	}
>       }
>     if (code == POINTER_PLUS_EXPR && !*non_constant_p
>         && integer_zerop (lhs) && !integer_zerop (rhs))
> @@ -5261,6 +5280,18 @@ cxx_eval_indirect_ref (const constexpr_c
>   	  STRIP_NOPS (sub);
>   	  if (TREE_CODE (sub) == ADDR_EXPR)
>   	    {
> +	      /* For *(const std::type_info *)&_ZTIwhatever return
> +		 a constructor with no clearing and __name being the
> +		 tinfo name.  */
> +	      if (flag_rtti
> +		  && VAR_P (TREE_OPERAND (sub, 0))
> +		  && DECL_TINFO_P (TREE_OPERAND (sub, 0))
> +		  && !lval
> +		  && TREE_TYPE (t) == const_type_info_type_node)
> +		if (tree ret
> +		    = tinfo_for_constant_evaluation (TREE_OPERAND (sub, 0)))
> +		  return ret;
> +
>   	      gcc_assert (!similar_type_p
>   			  (TREE_TYPE (TREE_TYPE (sub)), TREE_TYPE (t)));
>   	      /* DR 1188 says we don't have to deal with this.  */
> --- gcc/cp/rtti.c.jj	2021-09-15 08:55:37.569497474 +0200
> +++ gcc/cp/rtti.c	2021-12-07 17:17:50.943944129 +0100
> @@ -1700,4 +1700,39 @@ emit_tinfo_decl (tree decl)
>       return false;
>   }
>   
> +/* For constant evaluation purposes, return for _ZTI* DECL a CONSTRUCTOR
> +   containing just __name initialized to the tinfo_name string.  */
> +
> +tree
> +tinfo_for_constant_evaluation (tree decl)
> +{
> +  gcc_assert (DECL_TINFO_P (decl));
> +
> +  tree type = TREE_TYPE (DECL_NAME (decl));
> +  if (typeinfo_in_lib_p (type))
> +    ;
> +  else if (involves_incomplete_p (type))
> +    return NULL_TREE;
> +
> +  tree field
> +    = next_initializable_field (TYPE_FIELDS (const_type_info_type_node));
> +  if (field == NULL_TREE)
> +    return NULL_TREE;
> +  field = next_initializable_field (DECL_CHAIN (field));
> +  if (field == NULL_TREE
> +      || TREE_CODE (TREE_TYPE (field)) != POINTER_TYPE
> +      || TREE_TYPE (TREE_TYPE (field)) == error_mark_node
> +      || TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (field))) != char_type_node
> +      || !TYPE_READONLY (TREE_TYPE (TREE_TYPE (field))))
> +    return NULL_TREE;
> +
> +  tree str = tinfo_name (type, !TREE_PUBLIC (decl));
> +  str = build_fold_addr_expr (str);
> +  str = fold_convert (TREE_TYPE (field), str);
> +
> +  tree init = build_constructor_single (const_type_info_type_node, field, str);
> +  CONSTRUCTOR_NO_CLEARING (init) = true;
> +  return init;
> +}
> +
>   #include "gt-cp-rtti.h"
> --- gcc/testsuite/g++.dg/cpp0x/constexpr-typeid2.C.jj	2021-12-07 19:12:02.233350194 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/constexpr-typeid2.C	2021-12-07 19:11:43.071623082 +0100
> @@ -0,0 +1,14 @@
> +// PR c++/103600
> +// { dg-do compile { target c++11 } }
> +
> +#include <typeinfo>
> +
> +struct S { int i; };
> +namespace {
> +  struct T { int i; };
> +};
> +constexpr bool a = &typeid (int) == &typeid (int);
> +constexpr bool b = &typeid (int) == &typeid (long);
> +constexpr bool c = &typeid (double) != &typeid (int);
> +constexpr bool d = &typeid (S) != &typeid (T);
> +static_assert (a && !b && c && d, "");
> 
> 	Jakub
>
  
Jonathan Wakely Dec. 8, 2021, 1:56 p.m. UTC | #2
On Wed, 8 Dec 2021 at 13:53, Jason Merrill wrote:
> During constant evaluation, the operator== could compare the type_info
> address instead of the __name address, reducing this to the previous
> problem.

That makes sense to me. We might still want the libstdc++ changes in
case other compilers choose to do this differently. I'll get in touch
with some Clang and EDG people about that.
  

Patch

--- gcc/cp/cp-tree.h.jj	2021-12-04 11:02:03.925646583 +0100
+++ gcc/cp/cp-tree.h	2021-12-07 16:58:06.698802940 +0100
@@ -7376,6 +7376,7 @@  extern tree build_dynamic_cast			(locati
 						 tsubst_flags_t);
 extern void emit_support_tinfos			(void);
 extern bool emit_tinfo_decl			(tree);
+extern tree tinfo_for_constant_evaluation	(tree);
 extern unsigned get_pseudo_tinfo_index		(tree);
 extern tree get_pseudo_tinfo_type		(unsigned);
 extern tree build_if_nonnull			(tree, tree, tsubst_flags_t);
--- gcc/cp/constexpr.c.jj	2021-12-04 11:02:03.949646241 +0100
+++ gcc/cp/constexpr.c	2021-12-07 17:32:07.324739082 +0100
@@ -3346,6 +3346,25 @@  cxx_eval_binary_expression (const conste
 	lhs = cplus_expand_constant (lhs);
       else if (TREE_CODE (rhs) == PTRMEM_CST)
 	rhs = cplus_expand_constant (rhs);
+
+      /* Fold &typeid(x) == &typeid(y) to
+	 whether the DECL_TINFO_P vars are the same or not.  */
+      if (flag_rtti)
+	{
+	  tree op0 = lhs;
+	  tree op1 = rhs;
+	  STRIP_NOPS (op0);
+	  STRIP_NOPS (op1);
+	  if (TREE_CODE (op0) == ADDR_EXPR
+	      && VAR_P (TREE_OPERAND (op0, 0))
+	      && DECL_TINFO_P (TREE_OPERAND (op0, 0))
+	      && TREE_CODE (op1) == ADDR_EXPR
+	      && VAR_P (TREE_OPERAND (op1, 0))
+	      && DECL_TINFO_P (TREE_OPERAND (op1, 0)))
+	    r = constant_boolean_node ((TREE_OPERAND (op0, 0)
+					!= TREE_OPERAND (op1, 0))
+				       ^ is_code_eq, type);
+	}
     }
   if (code == POINTER_PLUS_EXPR && !*non_constant_p
       && integer_zerop (lhs) && !integer_zerop (rhs))
@@ -5261,6 +5280,18 @@  cxx_eval_indirect_ref (const constexpr_c
 	  STRIP_NOPS (sub);
 	  if (TREE_CODE (sub) == ADDR_EXPR)
 	    {
+	      /* For *(const std::type_info *)&_ZTIwhatever return
+		 a constructor with no clearing and __name being the
+		 tinfo name.  */
+	      if (flag_rtti
+		  && VAR_P (TREE_OPERAND (sub, 0))
+		  && DECL_TINFO_P (TREE_OPERAND (sub, 0))
+		  && !lval
+		  && TREE_TYPE (t) == const_type_info_type_node)
+		if (tree ret
+		    = tinfo_for_constant_evaluation (TREE_OPERAND (sub, 0)))
+		  return ret;
+
 	      gcc_assert (!similar_type_p
 			  (TREE_TYPE (TREE_TYPE (sub)), TREE_TYPE (t)));
 	      /* DR 1188 says we don't have to deal with this.  */
--- gcc/cp/rtti.c.jj	2021-09-15 08:55:37.569497474 +0200
+++ gcc/cp/rtti.c	2021-12-07 17:17:50.943944129 +0100
@@ -1700,4 +1700,39 @@  emit_tinfo_decl (tree decl)
     return false;
 }
 
+/* For constant evaluation purposes, return for _ZTI* DECL a CONSTRUCTOR
+   containing just __name initialized to the tinfo_name string.  */
+
+tree
+tinfo_for_constant_evaluation (tree decl)
+{
+  gcc_assert (DECL_TINFO_P (decl));
+
+  tree type = TREE_TYPE (DECL_NAME (decl));
+  if (typeinfo_in_lib_p (type))
+    ;
+  else if (involves_incomplete_p (type))
+    return NULL_TREE;
+
+  tree field
+    = next_initializable_field (TYPE_FIELDS (const_type_info_type_node));
+  if (field == NULL_TREE)
+    return NULL_TREE;
+  field = next_initializable_field (DECL_CHAIN (field));
+  if (field == NULL_TREE
+      || TREE_CODE (TREE_TYPE (field)) != POINTER_TYPE
+      || TREE_TYPE (TREE_TYPE (field)) == error_mark_node
+      || TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (field))) != char_type_node
+      || !TYPE_READONLY (TREE_TYPE (TREE_TYPE (field))))
+    return NULL_TREE;
+
+  tree str = tinfo_name (type, !TREE_PUBLIC (decl));
+  str = build_fold_addr_expr (str);
+  str = fold_convert (TREE_TYPE (field), str);
+
+  tree init = build_constructor_single (const_type_info_type_node, field, str);
+  CONSTRUCTOR_NO_CLEARING (init) = true;
+  return init;
+}
+
 #include "gt-cp-rtti.h"
--- gcc/testsuite/g++.dg/cpp0x/constexpr-typeid2.C.jj	2021-12-07 19:12:02.233350194 +0100
+++ gcc/testsuite/g++.dg/cpp0x/constexpr-typeid2.C	2021-12-07 19:11:43.071623082 +0100
@@ -0,0 +1,14 @@ 
+// PR c++/103600
+// { dg-do compile { target c++11 } }
+
+#include <typeinfo>
+
+struct S { int i; };
+namespace {
+  struct T { int i; };
+};
+constexpr bool a = &typeid (int) == &typeid (int);
+constexpr bool b = &typeid (int) == &typeid (long);
+constexpr bool c = &typeid (double) != &typeid (int);
+constexpr bool d = &typeid (S) != &typeid (T);
+static_assert (a && !b && c && d, "");