c, c++, v2: Accept __builtin_classify_type (typename)

Message ID ZNUDrXSjHpgTuknm@tucnak
State Unresolved
Headers
Series c, c++, v2: Accept __builtin_classify_type (typename) |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

Jakub Jelinek Aug. 10, 2023, 3:35 p.m. UTC
  Hi!

I'd like to ping this patch.  Reposting it as I found a typo in the
documentation - s/builtin-in/built-in/.  Bootstrapped/regtested again
on x86_64-linux and i686-linux, ok for trunk?

On Mon, Jun 12, 2023 at 09:57:17PM +0200, Jakub Jelinek via Gcc-patches wrote:
> As mentioned in my stdckdint.h mail, __builtin_classify_type has
> a problem that argument promotion (the argument is passed to ...
> prototyped builtin function) means that certain type classes will
> simply never appear.
> I think it is too late to change how it behaves, lots of code in the
> wild might rely on the current behavior.
> 
> So, the following patch adds option to use a typename rather than
> expression as the operand to the builtin, making it behave similarly
> to sizeof, typeof or say the clang _Generic extension where the
> first argument can be there not just expression, but also typename.
> 
> I think we have other prior art here, e.g. __builtin_va_arg also
> expects typename.
> 
> I've added this to both C and C++, because it would be weird if it
> supported it only in C and not in C++.

2023-08-10  Jakub Jelinek  <jakub@redhat.com>

gcc/
	* builtins.h (type_to_class): Declare.
	* builtins.cc (type_to_class): No longer static.  Return
	int rather than enum.
	* doc/extend.texi (__builtin_classify_type): Document.
gcc/c/
	* c-parser.cc (c_parser_postfix_expression_after_primary): Parse
	__builtin_classify_type call with typename as argument.
gcc/cp/
	* parser.cc (cp_parser_postfix_expression): Parse
	__builtin_classify_type call with typename as argument.
	* pt.cc (tsubst_copy_and_build): Handle __builtin_classify_type
	with dependent typename as argument.
gcc/testsuite/
	* c-c++-common/builtin-classify-type-1.c: New test.
	* g++.dg/ext/builtin-classify-type-1.C: New test.
	* g++.dg/ext/builtin-classify-type-2.C: New test.
	* gcc.dg/builtin-classify-type-1.c: New test.



	Jakub
  

Comments

Jason Merrill Aug. 10, 2023, 9:44 p.m. UTC | #1
On 8/10/23 11:35, Jakub Jelinek wrote:
> Hi!
> 
> I'd like to ping this patch.  Reposting it as I found a typo in the
> documentation - s/builtin-in/built-in/.  Bootstrapped/regtested again
> on x86_64-linux and i686-linux, ok for trunk?
> 
> On Mon, Jun 12, 2023 at 09:57:17PM +0200, Jakub Jelinek via Gcc-patches wrote:
>> As mentioned in my stdckdint.h mail, __builtin_classify_type has
>> a problem that argument promotion (the argument is passed to ...
>> prototyped builtin function) means that certain type classes will
>> simply never appear.
>> I think it is too late to change how it behaves, lots of code in the
>> wild might rely on the current behavior.

Hmm, you really think there's any code at all in the wild relying on 
__builtin_classify_type + array/function decay?  It's a (previously) 
undocumented built-in, I wouldn't expect anyone outside the project to 
be using it.  So at first glance I'd be inclined to fix it whether or 
not we also allow it to accept a type.  But I don't actually know how 
it's used, so could well be wrong...

> --- gcc/cp/parser.cc.jj	2023-06-06 20:02:35.631211230 +0200
> +++ gcc/cp/parser.cc	2023-06-12 16:19:04.892202240 +0200
> @@ -48,6 +48,7 @@ along with GCC; see the file COPYING3.
>   #include "c-family/known-headers.h"
>   #include "contracts.h"
>   #include "bitmap.h"
> +#include "builtins.h"
>   
>   
>   /* The lexer.  */
> @@ -7850,6 +7851,50 @@ cp_parser_postfix_expression (cp_parser
>   		  = parser->non_integral_constant_expression_p;
>   		parser->integral_constant_expression_p = false;
>   	      }
> +	    else if (TREE_CODE (stripped_expression) == FUNCTION_DECL
> +		     && fndecl_built_in_p (stripped_expression,
> +					   BUILT_IN_CLASSIFY_TYPE))
> +	      {
> +		/* __builtin_classify_type (type)  */
> +		auto cl1 = make_temp_override
> +			     (parser->type_definition_forbidden_message,
> +			      G_("types may not be defined in "
> +				 "%<__builtin_classify_type%> calls"));
> +		auto cl2 = make_temp_override
> +			     (parser->type_definition_forbidden_message_arg,
> +			      NULL);
> +		auto cl3 = make_temp_override (parser->in_type_id_in_expr_p,
> +					       true);
> +		cp_evaluated ev;
> +		++cp_unevaluated_operand;
> +		++c_inhibit_evaluation_warnings;

These three lines seem unnecessary for parsing a type.

> +		tentative_firewall firewall (parser);

I think you only need a tentative_firewall if you're going to call 
cp_parser_commit_to_tentative_parse yourself, which you don't.

Jason
  
Joseph Myers Aug. 10, 2023, 10:27 p.m. UTC | #2
On Thu, 10 Aug 2023, Jason Merrill via Gcc-patches wrote:

> On 8/10/23 11:35, Jakub Jelinek wrote:
> > Hi!
> > 
> > I'd like to ping this patch.  Reposting it as I found a typo in the
> > documentation - s/builtin-in/built-in/.  Bootstrapped/regtested again
> > on x86_64-linux and i686-linux, ok for trunk?
> > 
> > On Mon, Jun 12, 2023 at 09:57:17PM +0200, Jakub Jelinek via Gcc-patches
> > wrote:
> > > As mentioned in my stdckdint.h mail, __builtin_classify_type has
> > > a problem that argument promotion (the argument is passed to ...
> > > prototyped builtin function) means that certain type classes will
> > > simply never appear.
> > > I think it is too late to change how it behaves, lots of code in the
> > > wild might rely on the current behavior.
> 
> Hmm, you really think there's any code at all in the wild relying on
> __builtin_classify_type + array/function decay?  It's a (previously)
> undocumented built-in, I wouldn't expect anyone outside the project to be
> using it.  So at first glance I'd be inclined to fix it whether or not we also
> allow it to accept a type.  But I don't actually know how it's used, so could
> well be wrong...

I don't know about array / function decay, but it seems quite plausible 
that code checking for pointers expects those to occur.  Certainly glibc's 
tgmath.h has code expecting booleans to be treated like other integers 
(i.e. subject to integer promotions because passed in ...) by 
__builtin_classify_type (though you'd need a fairly old glibc version to 
use that implementation of the tgmath.h macros with current GCC rather 
than one based on __builtin_tgmath).
  
Jakub Jelinek Aug. 10, 2023, 11:13 p.m. UTC | #3
On Thu, Aug 10, 2023 at 05:44:20PM -0400, Jason Merrill wrote:
> Hmm, you really think there's any code at all in the wild relying on
> __builtin_classify_type + array/function decay?  It's a (previously)

Looking at the first uses of the builtin back in 90s in va*.h, it certainly
relied on array/function decay there (the macros would abort e.g. on
array_type_class, function_type_class and various other return values).
Looking at older versions of tgmath.h, I see just checks for 8/9 (i.e.
real/complex) and those woiuldn't be affected by any promotions/decay.
But newer versions of tgmath.h before __builtin_tgmath do check also for
1 and they would be upset if char wasn't promoted to int (including latest
glibc).
systemtap macros also use __builtin_classify_type and do check for pointers
but those seems to be prepared to handle even arrays.

> undocumented built-in, I wouldn't expect anyone outside the project to be
> using it.  So at first glance I'd be inclined to fix it whether or not we
> also allow it to accept a type.  But I don't actually know how it's used, so
> could well be wrong...

The problem with changing the existing builtin after 35+ years especially
when it was poorly documented:
 -- Macro: __builtin_classify_type (OBJECT)
     Since each machine has its own conventions for which data types are
     passed in which kind of register, your implementation of 'va_arg'
     has to embody these conventions.  The easiest way to categorize the
     specified data type is to use '__builtin_classify_type' together
     with 'sizeof' and '__alignof__'.

     '__builtin_classify_type' ignores the value of OBJECT, considering
     only its data type.  It returns an integer describing what kind of
     type that is--integer, floating, pointer, structure, and so on.

     The file 'typeclass.h' defines an enumeration that you can use to
     interpret the values of '__builtin_classify_type'.
is that users then follow what the implementation actually does rather than
what is documented (because it isn't), other compilers implemented the
behavior GCC had and while as the above list shows some macros/headers could
cope with, others can break.  And, even if the array/function decay is
avoided, one still wouldn't be able to get various type classes because of
other decays/argument conversions.

> > @@ -7850,6 +7851,50 @@ cp_parser_postfix_expression (cp_parser
> >   		  = parser->non_integral_constant_expression_p;
> >   		parser->integral_constant_expression_p = false;
> >   	      }
> > +	    else if (TREE_CODE (stripped_expression) == FUNCTION_DECL
> > +		     && fndecl_built_in_p (stripped_expression,
> > +					   BUILT_IN_CLASSIFY_TYPE))
> > +	      {
> > +		/* __builtin_classify_type (type)  */
> > +		auto cl1 = make_temp_override
> > +			     (parser->type_definition_forbidden_message,
> > +			      G_("types may not be defined in "
> > +				 "%<__builtin_classify_type%> calls"));
> > +		auto cl2 = make_temp_override
> > +			     (parser->type_definition_forbidden_message_arg,
> > +			      NULL);
> > +		auto cl3 = make_temp_override (parser->in_type_id_in_expr_p,
> > +					       true);
> > +		cp_evaluated ev;
> > +		++cp_unevaluated_operand;
> > +		++c_inhibit_evaluation_warnings;
> 
> These three lines seem unnecessary for parsing a type.
> 
> > +		tentative_firewall firewall (parser);
> 
> I think you only need a tentative_firewall if you're going to call
> cp_parser_commit_to_tentative_parse yourself, which you don't.

I think I've just copied this from elsewhere, will double check in the
morning which ones aren't really needed.

	Jakub
  
Jakub Jelinek Aug. 11, 2023, 8:48 a.m. UTC | #4
On Fri, Aug 11, 2023 at 01:13:32AM +0200, Jakub Jelinek wrote:
> Looking at the first uses of the builtin back in 90s in va*.h, it certainly
> relied on array/function decay there (the macros would abort e.g. on
> array_type_class, function_type_class and various other return values).
> Looking at older versions of tgmath.h, I see just checks for 8/9 (i.e.
> real/complex) and those woiuldn't be affected by any promotions/decay.
> But newer versions of tgmath.h before __builtin_tgmath do check also for
> 1 and they would be upset if char wasn't promoted to int (including latest
> glibc).
> systemtap macros also use __builtin_classify_type and do check for pointers
> but those seems to be prepared to handle even arrays.

So to sum it up, I think at least the original use of the builtin had a
strong reason to do the array to pointer etc. decay and argument promotion,
because that is what happens with the varargs too and the builtin is still
documented in the internals manual just for that purpose.  It is true GCC
doesn't use the builtin for that reason anymore, but there are numerous
uses in the wild, some might cope well with changing the behavior, others
less so.

> > > +		cp_evaluated ev;
> > > +		++cp_unevaluated_operand;
> > > +		++c_inhibit_evaluation_warnings;
> > 
> > These three lines seem unnecessary for parsing a type.

I had a quick look at this and a reason to do at least some of this
is e.g. array types, __builtin_classify_type (int [foo () + whatever])
will not really evaluate foo () + whatever, all it will care about is that
it is an array, so emiting evaluation warnings for it would be weird.
cp_unevaluated_operand is harder to find out what all the effects are,
but e.g. warnings for missing member initializers in such expressions
isn't needed either.

> > > +		tentative_firewall firewall (parser);
> > 
> > I think you only need a tentative_firewall if you're going to call
> > cp_parser_commit_to_tentative_parse yourself, which you don't.
> 
> I think I've just copied this from elsewhere, will double check in the
> morning which ones aren't really needed.

I admit I still don't understand match what it is doing, but it works
even without that in the limited testsuite coverage it has.

2023-08-11  Jakub Jelinek  <jakub@redhat.com>

gcc/
	* builtins.h (type_to_class): Declare.
	* builtins.cc (type_to_class): No longer static.  Return
	int rather than enum.
	* doc/extend.texi (__builtin_classify_type): Document.
gcc/c/
	* c-parser.cc (c_parser_postfix_expression_after_primary): Parse
	__builtin_classify_type call with typename as argument.
gcc/cp/
	* parser.cc (cp_parser_postfix_expression): Parse
	__builtin_classify_type call with typename as argument.
	* pt.cc (tsubst_copy_and_build): Handle __builtin_classify_type
	with dependent typename as argument.
gcc/testsuite/
	* c-c++-common/builtin-classify-type-1.c: New test.
	* g++.dg/ext/builtin-classify-type-1.C: New test.
	* g++.dg/ext/builtin-classify-type-2.C: New test.
	* gcc.dg/builtin-classify-type-1.c: New test.

--- gcc/builtins.h.jj	2023-01-03 00:20:34.856089856 +0100
+++ gcc/builtins.h	2023-06-12 09:35:20.841902572 +0200
@@ -156,5 +156,6 @@ extern internal_fn associated_internal_f
 extern internal_fn replacement_internal_fn (gcall *);
 
 extern bool builtin_with_linkage_p (tree);
+extern int type_to_class (tree);
 
 #endif /* GCC_BUILTINS_H */
--- gcc/builtins.cc.jj	2023-05-20 15:31:09.066663352 +0200
+++ gcc/builtins.cc	2023-06-12 09:35:31.709751296 +0200
@@ -113,7 +113,6 @@ static rtx expand_builtin_apply_args (vo
 static rtx expand_builtin_apply_args_1 (void);
 static rtx expand_builtin_apply (rtx, rtx, rtx);
 static void expand_builtin_return (rtx);
-static enum type_class type_to_class (tree);
 static rtx expand_builtin_classify_type (tree);
 static rtx expand_builtin_mathfn_3 (tree, rtx, rtx);
 static rtx expand_builtin_mathfn_ternary (tree, rtx, rtx);
@@ -1852,7 +1851,7 @@ expand_builtin_return (rtx result)
 
 /* Used by expand_builtin_classify_type and fold_builtin_classify_type.  */
 
-static enum type_class
+int
 type_to_class (tree type)
 {
   switch (TREE_CODE (type))
--- gcc/doc/extend.texi.jj	2023-06-10 19:58:26.197478291 +0200
+++ gcc/doc/extend.texi	2023-06-12 18:06:24.629413024 +0200
@@ -14354,6 +14354,30 @@ need not be a constant.  @xref{Object Si
 description of the function.
 @enddefbuiltin
 
+@defbuiltin{int __builtin_classify_type (@var{arg})}
+@defbuiltinx{int __builtin_classify_type (@var{type})}
+The @code{__builtin_classify_type} returns a small integer with a category
+of @var{arg} argument's type, like void type, integer type, enumeral type,
+boolean type, pointer type, reference type, offset type, real type, complex
+type, function type, method type, record type, union type, array type,
+string type, etc.  When the argument is an expression, for
+backwards compatibility reason the argument is promoted like arguments
+passed to @code{...} in varargs function, so some classes are never returned
+in certain languages.  Alternatively, the argument of the built-in
+function can be a typename, such as the @code{typeof} specifier.
+
+@smallexample
+int a[2];
+__builtin_classify_type (a) == __builtin_classify_type (int[5]);
+__builtin_classify_type (a) == __builtin_classify_type (void*);
+__builtin_classify_type (typeof (a)) == __builtin_classify_type (int[5]);
+@end smallexample
+
+The first comparison will never be true, as @var{a} is implicitly converted
+to pointer.  The last two comparisons will be true as they classify
+pointers in the second case and arrays in the last case.
+@enddefbuiltin
+
 @defbuiltin{double __builtin_huge_val (void)}
 Returns a positive infinity, if supported by the floating-point format,
 else @code{DBL_MAX}.  This function is suitable for implementing the
--- gcc/c/c-parser.cc.jj	2023-06-10 19:22:15.577205685 +0200
+++ gcc/c/c-parser.cc	2023-06-12 17:32:31.007413019 +0200
@@ -11213,6 +11213,32 @@ c_parser_postfix_expression_after_primar
 	    literal_zero_mask = 0;
 	    if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
 	      exprlist = NULL;
+	    else if (TREE_CODE (expr.value) == FUNCTION_DECL
+		     && fndecl_built_in_p (expr.value, BUILT_IN_CLASSIFY_TYPE)
+		     && c_parser_next_tokens_start_typename (parser,
+							     cla_prefer_id))
+	      {
+		/* __builtin_classify_type (type)  */
+		c_inhibit_evaluation_warnings++;
+		in_typeof++;
+		struct c_type_name *type = c_parser_type_name (parser);
+		c_inhibit_evaluation_warnings--;
+		in_typeof--;
+		struct c_typespec ret;
+		ret.expr = NULL_TREE;
+		ret.spec = error_mark_node;
+		ret.expr_const_operands = false;
+		if (type != NULL)
+		  {
+		    ret.spec = groktypename (type, &ret.expr,
+					     &ret.expr_const_operands);
+		    pop_maybe_used (c_type_variably_modified_p (ret.spec));
+		  }
+		parens.skip_until_found_close (parser);
+		expr.value = build_int_cst (integer_type_node,
+					    type_to_class (ret.spec));
+		break;
+	      }
 	    else
 	      exprlist = c_parser_expr_list (parser, true, false, &origtypes,
 					     sizeof_arg_loc, sizeof_arg,
--- gcc/cp/parser.cc.jj	2023-06-06 20:02:35.631211230 +0200
+++ gcc/cp/parser.cc	2023-06-12 16:19:04.892202240 +0200
@@ -48,6 +48,7 @@ along with GCC; see the file COPYING3.
 #include "c-family/known-headers.h"
 #include "contracts.h"
 #include "bitmap.h"
+#include "builtins.h"
 
 
 /* The lexer.  */
@@ -7850,6 +7851,49 @@ cp_parser_postfix_expression (cp_parser
 		  = parser->non_integral_constant_expression_p;
 		parser->integral_constant_expression_p = false;
 	      }
+	    else if (TREE_CODE (stripped_expression) == FUNCTION_DECL
+		     && fndecl_built_in_p (stripped_expression,
+					   BUILT_IN_CLASSIFY_TYPE))
+	      {
+		/* __builtin_classify_type (type)  */
+		auto cl1 = make_temp_override
+			     (parser->type_definition_forbidden_message,
+			      G_("types may not be defined in "
+				 "%<__builtin_classify_type%> calls"));
+		auto cl2 = make_temp_override
+			     (parser->type_definition_forbidden_message_arg,
+			      NULL);
+		auto cl3 = make_temp_override (parser->in_type_id_in_expr_p,
+					       true);
+		cp_evaluated ev;
+		++cp_unevaluated_operand;
+		++c_inhibit_evaluation_warnings;
+		cp_parser_parse_tentatively (parser);
+		matching_parens parens;
+		parens.consume_open (parser);
+		tree type = cp_parser_type_id (parser);
+		parens.require_close (parser);
+		if (cp_parser_parse_definitely (parser))
+		  {
+		    if (dependent_type_p (type))
+		      {
+			postfix_expression = build_vl_exp (CALL_EXPR, 4);
+			CALL_EXPR_FN (postfix_expression)
+			  = stripped_expression;
+			CALL_EXPR_STATIC_CHAIN (postfix_expression) = type;
+			CALL_EXPR_ARG (postfix_expression, 0)
+			  = build_min (SIZEOF_EXPR, size_type_node, type);
+			TREE_TYPE (postfix_expression) = integer_type_node;
+		      }
+		    else
+		      {
+			postfix_expression
+			  = build_int_cst (integer_type_node,
+					   type_to_class (type));
+		      }
+		    break;
+		  }
+	      }
 	    args = (cp_parser_parenthesized_expression_list
 		    (parser, non_attr,
 		     /*cast_p=*/false, /*allow_expansion_p=*/true,
--- gcc/cp/pt.cc.jj	2023-06-06 20:02:35.000000000 +0200
+++ gcc/cp/pt.cc	2023-06-12 16:19:06.495180108 +0200
@@ -46,6 +46,7 @@ along with GCC; see the file COPYING3.
 #include "gcc-rich-location.h"
 #include "selftest.h"
 #include "target.h"
+#include "builtins.h"
 
 /* The type of functions taking a tree, and some additional data, and
    returning an int.  */
@@ -20963,6 +20964,25 @@ tsubst_copy_and_build (tree t,
 					    /*done=*/false,
 					    /*address_p=*/false);
 	  }
+	else if (CALL_EXPR_STATIC_CHAIN (t)
+		 && TREE_CODE (function) == FUNCTION_DECL
+		 && fndecl_built_in_p (function, BUILT_IN_CLASSIFY_TYPE))
+	  {
+	    tree type = tsubst (CALL_EXPR_STATIC_CHAIN (t), args, complain,
+				in_decl);
+	    if (dependent_type_p (type))
+	      {
+		ret = build_vl_exp (CALL_EXPR, 4);
+		CALL_EXPR_FN (ret) = function;
+		CALL_EXPR_STATIC_CHAIN (ret) = type;
+		CALL_EXPR_ARG (ret, 0)
+		  = build_min (SIZEOF_EXPR, size_type_node, type);
+		TREE_TYPE (ret) = integer_type_node;
+	      }
+	    else
+	      ret = build_int_cst (integer_type_node, type_to_class (type));
+	    RETURN (ret);
+	  }
 	else if (koenig_p
 		 && (identifier_p (function)
 		     || (TREE_CODE (function) == TEMPLATE_ID_EXPR
--- gcc/testsuite/c-c++-common/builtin-classify-type-1.c.jj	2023-06-12 14:27:19.087986210 +0200
+++ gcc/testsuite/c-c++-common/builtin-classify-type-1.c	2023-06-12 16:23:07.029859079 +0200
@@ -0,0 +1,105 @@
+/* { dg-do run { target { c || c++11 } } } */
+
+#if !defined(__cplusplus) && __STDC_VERSION__ <= 201710L
+#define static_assert _Static_assert
+#define bool _Bool
+#define false ((_Bool) 0)
+#endif
+#ifdef __cplusplus
+extern "C" void abort ();
+#else
+extern void abort (void);
+#endif
+
+int
+main ()
+{
+  enum E { E1 } e = E1;
+  struct S { int s; } s = { 0 };
+  union U { int u; } u = { 0 };
+  int a[2] = { 0, 0 };
+  bool b = false;
+  const char *p = (const char *) 0;
+  float f = 0.0;
+  _Complex double c = 0.0;
+#ifdef __cplusplus
+  struct T { void foo (); };
+  int &r = a[0];
+  int S::*q = &S::s;
+#endif
+  static_assert (__builtin_classify_type (void) == 0, "");
+  static_assert (__builtin_classify_type (int) == 1, "");
+  static_assert (__builtin_classify_type (enum E) == 3, "");
+  static_assert (__builtin_classify_type (bool) == 4, "");
+  static_assert (__builtin_classify_type (const char *) == 5, "");
+#ifdef __cplusplus
+  static_assert (__builtin_classify_type (int &) == 6, "");
+  static_assert (__builtin_classify_type (int &&) == 6, "");
+  static_assert (__builtin_classify_type (int S::*) == 7, "");
+#endif
+  static_assert (__builtin_classify_type (float) == 8, "");
+  static_assert (__builtin_classify_type (_Complex double) == 9, "");
+  static_assert (__builtin_classify_type (int (int, int)) == 10, "");
+  static_assert (__builtin_classify_type (struct S) == 12, "");
+  static_assert (__builtin_classify_type (union U) == 13, "");
+  static_assert (__builtin_classify_type (int [2]) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+#ifdef __cplusplus
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+#endif
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (main)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+#ifndef __cplusplus
+  static_assert (__builtin_classify_type (a[0]) == 1, "");
+  static_assert (__builtin_classify_type (e) == 1, "");
+  static_assert (__builtin_classify_type (b) == 1, "");
+  static_assert (__builtin_classify_type (p) == 5, "");
+  static_assert (__builtin_classify_type (f) == 8, "");
+  static_assert (__builtin_classify_type (c) == 9, "");
+  static_assert (__builtin_classify_type (main) == 5, "");
+  static_assert (__builtin_classify_type (s) == 12, "");
+  static_assert (__builtin_classify_type (u) == 13, "");
+  static_assert (__builtin_classify_type (a) == 5, "");
+#endif
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+#ifdef __cplusplus
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+#else
+  if (__builtin_classify_type (e) != 1)
+    abort ();
+  if (__builtin_classify_type (b) != 1)
+    abort ();
+#endif
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+#ifdef __cplusplus
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+#endif
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (main) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
--- gcc/testsuite/g++.dg/ext/builtin-classify-type-1.C.jj	2023-06-12 15:03:37.488813774 +0200
+++ gcc/testsuite/g++.dg/ext/builtin-classify-type-1.C	2023-06-12 16:24:17.787882132 +0200
@@ -0,0 +1,149 @@
+// { dg-do run { target c++11 } }
+
+extern "C" void abort ();
+
+template <int N>
+void
+foo ()
+{
+  enum E { E1 } e = E1;
+  struct S { int s; } s = { 0 };
+  union U { int u; } u = { 0 };
+  int a[2] = { 0, 0 };
+  bool b = false;
+  const char *p = (const char *) 0;
+  float f = 0.0;
+  _Complex double c = 0.0;
+  struct T { void foo (); };
+  int &r = a[0];
+  int S::*q = &S::s;
+  static_assert (__builtin_classify_type (void) == 0, "");
+  static_assert (__builtin_classify_type (int) == 1, "");
+  static_assert (__builtin_classify_type (enum E) == 3, "");
+  static_assert (__builtin_classify_type (bool) == 4, "");
+  static_assert (__builtin_classify_type (const char *) == 5, "");
+  static_assert (__builtin_classify_type (int &) == 6, "");
+  static_assert (__builtin_classify_type (int &&) == 6, "");
+  static_assert (__builtin_classify_type (int S::*) == 7, "");
+  static_assert (__builtin_classify_type (float) == 8, "");
+  static_assert (__builtin_classify_type (_Complex double) == 9, "");
+  static_assert (__builtin_classify_type (int (int, int)) == 10, "");
+  static_assert (__builtin_classify_type (struct S) == 12, "");
+  static_assert (__builtin_classify_type (union U) == 13, "");
+  static_assert (__builtin_classify_type (int [2]) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (abort)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (abort) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
+
+template <typename V, typename I, typename E, typename B, typename P,
+	  typename R1, typename R2, typename PM, typename F,
+	  typename C, typename FN, typename S, typename U, typename A>
+void
+bar ()
+{
+  E e = (E) 0;
+  S s = { 0 };
+  U u = { 0 };
+  A a = { 0, 0 };
+  B b = false;
+  P p = (P) 0;
+  F f = 0.0;
+  C c = 0.0;
+  R1 r = a[0];
+  PM q = &S::s;
+  static_assert (__builtin_classify_type (V) == 0, "");
+  static_assert (__builtin_classify_type (I) == 1, "");
+  static_assert (__builtin_classify_type (E) == 3, "");
+  static_assert (__builtin_classify_type (B) == 4, "");
+  static_assert (__builtin_classify_type (P) == 5, "");
+  static_assert (__builtin_classify_type (R1) == 6, "");
+  static_assert (__builtin_classify_type (R2) == 6, "");
+  static_assert (__builtin_classify_type (PM) == 7, "");
+  static_assert (__builtin_classify_type (F) == 8, "");
+  static_assert (__builtin_classify_type (C) == 9, "");
+  static_assert (__builtin_classify_type (FN) == 10, "");
+  static_assert (__builtin_classify_type (S) == 12, "");
+  static_assert (__builtin_classify_type (U) == 13, "");
+  static_assert (__builtin_classify_type (A) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (abort)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (abort) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
+
+int
+main ()
+{
+  enum E { E1 };
+  struct S { int s; };
+  union U { int u; };
+  foo <0> ();
+  bar <void, int, E, bool, const char *, int &, int &&, int S::*,
+       float, _Complex double, int (int, int), S, U, int [2]> ();
+}
--- gcc/testsuite/g++.dg/ext/builtin-classify-type-2.C.jj	2023-06-12 16:28:17.120577700 +0200
+++ gcc/testsuite/g++.dg/ext/builtin-classify-type-2.C	2023-06-12 16:29:57.860186807 +0200
@@ -0,0 +1,11 @@
+// { dg-do compile { target c++11 } }
+// { dg-options "" }
+
+void
+foo (int n)
+{
+  __builtin_classify_type (enum E { E1, E2 });	// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (struct S { int s; });// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (union U { int u; });	// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (int [2 * n + 36]);
+}
--- gcc/testsuite/gcc.dg/builtin-classify-type-1.c.jj	2023-06-12 17:28:47.118495177 +0200
+++ gcc/testsuite/gcc.dg/builtin-classify-type-1.c	2023-06-12 17:43:46.420114514 +0200
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+void
+foo (int n)
+{
+  _Static_assert (__builtin_classify_type (enum E { E1, E2 }) == 3, "");
+  _Static_assert (__builtin_classify_type (struct S { int s; }) == 12, "");
+  _Static_assert (__builtin_classify_type (union U { int u; }) == 13, "");
+  _Static_assert (__builtin_classify_type (int [2 * n + 36]) == 14, "");
+}


	Jakub
  
Jason Merrill Aug. 11, 2023, 4:12 p.m. UTC | #5
On 8/11/23 04:48, Jakub Jelinek wrote:
> On Fri, Aug 11, 2023 at 01:13:32AM +0200, Jakub Jelinek wrote:
>> Looking at the first uses of the builtin back in 90s in va*.h, it certainly
>> relied on array/function decay there (the macros would abort e.g. on
>> array_type_class, function_type_class and various other return values).
>> Looking at older versions of tgmath.h, I see just checks for 8/9 (i.e.
>> real/complex) and those woiuldn't be affected by any promotions/decay.
>> But newer versions of tgmath.h before __builtin_tgmath do check also for
>> 1 and they would be upset if char wasn't promoted to int (including latest
>> glibc).
>> systemtap macros also use __builtin_classify_type and do check for pointers
>> but those seems to be prepared to handle even arrays.
> 
> So to sum it up, I think at least the original use of the builtin had a
> strong reason to do the array to pointer etc. decay and argument promotion,
> because that is what happens with the varargs too and the builtin is still
> documented in the internals manual just for that purpose.  It is true GCC
> doesn't use the builtin for that reason anymore, but there are numerous
> uses in the wild, some might cope well with changing the behavior, others
> less so.
> 
>>>> +		cp_evaluated ev;
>>>> +		++cp_unevaluated_operand;
>>>> +		++c_inhibit_evaluation_warnings;
>>>
>>> These three lines seem unnecessary for parsing a type.
> 
> I had a quick look at this and a reason to do at least some of this
> is e.g. array types, __builtin_classify_type (int [foo () + whatever])
> will not really evaluate foo () + whatever, all it will care about is that
> it is an array, so emiting evaluation warnings for it would be weird.
> cp_unevaluated_operand is harder to find out what all the effects are,
> but e.g. warnings for missing member initializers in such expressions
> isn't needed either.

Fair enough.  But you should only need a single line

cp_unevaluated ev;

The C++ bits are OK with that change.

Jason
  
Jakub Jelinek Sept. 18, 2023, 9:42 a.m. UTC | #6
Hi!

I'd like to ping this patch.
The C++ FE part has been approved by Jason already with a minor change
I've made in my copy.
Are the remaining parts ok for trunk?

On Fri, Aug 11, 2023 at 10:48:19AM +0200, Jakub Jelinek via Gcc-patches wrote:
> 2023-08-11  Jakub Jelinek  <jakub@redhat.com>
> 
> gcc/
> 	* builtins.h (type_to_class): Declare.
> 	* builtins.cc (type_to_class): No longer static.  Return
> 	int rather than enum.
> 	* doc/extend.texi (__builtin_classify_type): Document.
> gcc/c/
> 	* c-parser.cc (c_parser_postfix_expression_after_primary): Parse
> 	__builtin_classify_type call with typename as argument.
> gcc/cp/
> 	* parser.cc (cp_parser_postfix_expression): Parse
> 	__builtin_classify_type call with typename as argument.
> 	* pt.cc (tsubst_copy_and_build): Handle __builtin_classify_type
> 	with dependent typename as argument.
> gcc/testsuite/
> 	* c-c++-common/builtin-classify-type-1.c: New test.
> 	* g++.dg/ext/builtin-classify-type-1.C: New test.
> 	* g++.dg/ext/builtin-classify-type-2.C: New test.
> 	* gcc.dg/builtin-classify-type-1.c: New test.

Thanks

	Jakub
  
Joseph Myers Sept. 18, 2023, 9:25 p.m. UTC | #7
On Mon, 18 Sep 2023, Jakub Jelinek via Gcc-patches wrote:

> Hi!
> 
> I'd like to ping this patch.
> The C++ FE part has been approved by Jason already with a minor change
> I've made in my copy.
> Are the remaining parts ok for trunk?

In the C front-end changes, since you end up discarding any side effects 
from the type, I'd expect use of in_alignof to be more appropriate than 
in_typeof (and thus not needing to use pop_maybe_used).
  

Patch

--- gcc/builtins.h.jj	2023-01-03 00:20:34.856089856 +0100
+++ gcc/builtins.h	2023-06-12 09:35:20.841902572 +0200
@@ -156,5 +156,6 @@  extern internal_fn associated_internal_f
 extern internal_fn replacement_internal_fn (gcall *);
 
 extern bool builtin_with_linkage_p (tree);
+extern int type_to_class (tree);
 
 #endif /* GCC_BUILTINS_H */
--- gcc/builtins.cc.jj	2023-05-20 15:31:09.066663352 +0200
+++ gcc/builtins.cc	2023-06-12 09:35:31.709751296 +0200
@@ -113,7 +113,6 @@  static rtx expand_builtin_apply_args (vo
 static rtx expand_builtin_apply_args_1 (void);
 static rtx expand_builtin_apply (rtx, rtx, rtx);
 static void expand_builtin_return (rtx);
-static enum type_class type_to_class (tree);
 static rtx expand_builtin_classify_type (tree);
 static rtx expand_builtin_mathfn_3 (tree, rtx, rtx);
 static rtx expand_builtin_mathfn_ternary (tree, rtx, rtx);
@@ -1852,7 +1851,7 @@  expand_builtin_return (rtx result)
 
 /* Used by expand_builtin_classify_type and fold_builtin_classify_type.  */
 
-static enum type_class
+int
 type_to_class (tree type)
 {
   switch (TREE_CODE (type))
--- gcc/doc/extend.texi.jj	2023-06-10 19:58:26.197478291 +0200
+++ gcc/doc/extend.texi	2023-06-12 18:06:24.629413024 +0200
@@ -14354,6 +14354,30 @@  need not be a constant.  @xref{Object Si
 description of the function.
 @enddefbuiltin
 
+@defbuiltin{int __builtin_classify_type (@var{arg})}
+@defbuiltinx{int __builtin_classify_type (@var{type})}
+The @code{__builtin_classify_type} returns a small integer with a category
+of @var{arg} argument's type, like void type, integer type, enumeral type,
+boolean type, pointer type, reference type, offset type, real type, complex
+type, function type, method type, record type, union type, array type,
+string type, etc.  When the argument is an expression, for
+backwards compatibility reason the argument is promoted like arguments
+passed to @code{...} in varargs function, so some classes are never returned
+in certain languages.  Alternatively, the argument of the built-in
+function can be a typename, such as the @code{typeof} specifier.
+
+@smallexample
+int a[2];
+__builtin_classify_type (a) == __builtin_classify_type (int[5]);
+__builtin_classify_type (a) == __builtin_classify_type (void*);
+__builtin_classify_type (typeof (a)) == __builtin_classify_type (int[5]);
+@end smallexample
+
+The first comparison will never be true, as @var{a} is implicitly converted
+to pointer.  The last two comparisons will be true as they classify
+pointers in the second case and arrays in the last case.
+@enddefbuiltin
+
 @defbuiltin{double __builtin_huge_val (void)}
 Returns a positive infinity, if supported by the floating-point format,
 else @code{DBL_MAX}.  This function is suitable for implementing the
--- gcc/c/c-parser.cc.jj	2023-06-10 19:22:15.577205685 +0200
+++ gcc/c/c-parser.cc	2023-06-12 17:32:31.007413019 +0200
@@ -11213,6 +11213,32 @@  c_parser_postfix_expression_after_primar
 	    literal_zero_mask = 0;
 	    if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
 	      exprlist = NULL;
+	    else if (TREE_CODE (expr.value) == FUNCTION_DECL
+		     && fndecl_built_in_p (expr.value, BUILT_IN_CLASSIFY_TYPE)
+		     && c_parser_next_tokens_start_typename (parser,
+							     cla_prefer_id))
+	      {
+		/* __builtin_classify_type (type)  */
+		c_inhibit_evaluation_warnings++;
+		in_typeof++;
+		struct c_type_name *type = c_parser_type_name (parser);
+		c_inhibit_evaluation_warnings--;
+		in_typeof--;
+		struct c_typespec ret;
+		ret.expr = NULL_TREE;
+		ret.spec = error_mark_node;
+		ret.expr_const_operands = false;
+		if (type != NULL)
+		  {
+		    ret.spec = groktypename (type, &ret.expr,
+					     &ret.expr_const_operands);
+		    pop_maybe_used (c_type_variably_modified_p (ret.spec));
+		  }
+		parens.skip_until_found_close (parser);
+		expr.value = build_int_cst (integer_type_node,
+					    type_to_class (ret.spec));
+		break;
+	      }
 	    else
 	      exprlist = c_parser_expr_list (parser, true, false, &origtypes,
 					     sizeof_arg_loc, sizeof_arg,
--- gcc/cp/parser.cc.jj	2023-06-06 20:02:35.631211230 +0200
+++ gcc/cp/parser.cc	2023-06-12 16:19:04.892202240 +0200
@@ -48,6 +48,7 @@  along with GCC; see the file COPYING3.
 #include "c-family/known-headers.h"
 #include "contracts.h"
 #include "bitmap.h"
+#include "builtins.h"
 
 
 /* The lexer.  */
@@ -7850,6 +7851,50 @@  cp_parser_postfix_expression (cp_parser
 		  = parser->non_integral_constant_expression_p;
 		parser->integral_constant_expression_p = false;
 	      }
+	    else if (TREE_CODE (stripped_expression) == FUNCTION_DECL
+		     && fndecl_built_in_p (stripped_expression,
+					   BUILT_IN_CLASSIFY_TYPE))
+	      {
+		/* __builtin_classify_type (type)  */
+		auto cl1 = make_temp_override
+			     (parser->type_definition_forbidden_message,
+			      G_("types may not be defined in "
+				 "%<__builtin_classify_type%> calls"));
+		auto cl2 = make_temp_override
+			     (parser->type_definition_forbidden_message_arg,
+			      NULL);
+		auto cl3 = make_temp_override (parser->in_type_id_in_expr_p,
+					       true);
+		cp_evaluated ev;
+		++cp_unevaluated_operand;
+		++c_inhibit_evaluation_warnings;
+		tentative_firewall firewall (parser);
+		cp_parser_parse_tentatively (parser);
+		matching_parens parens;
+		parens.consume_open (parser);
+		tree type = cp_parser_type_id (parser);
+		parens.require_close (parser);
+		if (cp_parser_parse_definitely (parser))
+		  {
+		    if (dependent_type_p (type))
+		      {
+			postfix_expression = build_vl_exp (CALL_EXPR, 4);
+			CALL_EXPR_FN (postfix_expression)
+			  = stripped_expression;
+			CALL_EXPR_STATIC_CHAIN (postfix_expression) = type;
+			CALL_EXPR_ARG (postfix_expression, 0)
+			  = build_min (SIZEOF_EXPR, size_type_node, type);
+			TREE_TYPE (postfix_expression) = integer_type_node;
+		      }
+		    else
+		      {
+			postfix_expression
+			  = build_int_cst (integer_type_node,
+					   type_to_class (type));
+		      }
+		    break;
+		  }
+	      }
 	    args = (cp_parser_parenthesized_expression_list
 		    (parser, non_attr,
 		     /*cast_p=*/false, /*allow_expansion_p=*/true,
--- gcc/cp/pt.cc.jj	2023-06-06 20:02:35.000000000 +0200
+++ gcc/cp/pt.cc	2023-06-12 16:19:06.495180108 +0200
@@ -46,6 +46,7 @@  along with GCC; see the file COPYING3.
 #include "gcc-rich-location.h"
 #include "selftest.h"
 #include "target.h"
+#include "builtins.h"
 
 /* The type of functions taking a tree, and some additional data, and
    returning an int.  */
@@ -20963,6 +20964,25 @@  tsubst_copy_and_build (tree t,
 					    /*done=*/false,
 					    /*address_p=*/false);
 	  }
+	else if (CALL_EXPR_STATIC_CHAIN (t)
+		 && TREE_CODE (function) == FUNCTION_DECL
+		 && fndecl_built_in_p (function, BUILT_IN_CLASSIFY_TYPE))
+	  {
+	    tree type = tsubst (CALL_EXPR_STATIC_CHAIN (t), args, complain,
+				in_decl);
+	    if (dependent_type_p (type))
+	      {
+		ret = build_vl_exp (CALL_EXPR, 4);
+		CALL_EXPR_FN (ret) = function;
+		CALL_EXPR_STATIC_CHAIN (ret) = type;
+		CALL_EXPR_ARG (ret, 0)
+		  = build_min (SIZEOF_EXPR, size_type_node, type);
+		TREE_TYPE (ret) = integer_type_node;
+	      }
+	    else
+	      ret = build_int_cst (integer_type_node, type_to_class (type));
+	    RETURN (ret);
+	  }
 	else if (koenig_p
 		 && (identifier_p (function)
 		     || (TREE_CODE (function) == TEMPLATE_ID_EXPR
--- gcc/testsuite/c-c++-common/builtin-classify-type-1.c.jj	2023-06-12 14:27:19.087986210 +0200
+++ gcc/testsuite/c-c++-common/builtin-classify-type-1.c	2023-06-12 16:23:07.029859079 +0200
@@ -0,0 +1,105 @@ 
+/* { dg-do run { target { c || c++11 } } } */
+
+#if !defined(__cplusplus) && __STDC_VERSION__ <= 201710L
+#define static_assert _Static_assert
+#define bool _Bool
+#define false ((_Bool) 0)
+#endif
+#ifdef __cplusplus
+extern "C" void abort ();
+#else
+extern void abort (void);
+#endif
+
+int
+main ()
+{
+  enum E { E1 } e = E1;
+  struct S { int s; } s = { 0 };
+  union U { int u; } u = { 0 };
+  int a[2] = { 0, 0 };
+  bool b = false;
+  const char *p = (const char *) 0;
+  float f = 0.0;
+  _Complex double c = 0.0;
+#ifdef __cplusplus
+  struct T { void foo (); };
+  int &r = a[0];
+  int S::*q = &S::s;
+#endif
+  static_assert (__builtin_classify_type (void) == 0, "");
+  static_assert (__builtin_classify_type (int) == 1, "");
+  static_assert (__builtin_classify_type (enum E) == 3, "");
+  static_assert (__builtin_classify_type (bool) == 4, "");
+  static_assert (__builtin_classify_type (const char *) == 5, "");
+#ifdef __cplusplus
+  static_assert (__builtin_classify_type (int &) == 6, "");
+  static_assert (__builtin_classify_type (int &&) == 6, "");
+  static_assert (__builtin_classify_type (int S::*) == 7, "");
+#endif
+  static_assert (__builtin_classify_type (float) == 8, "");
+  static_assert (__builtin_classify_type (_Complex double) == 9, "");
+  static_assert (__builtin_classify_type (int (int, int)) == 10, "");
+  static_assert (__builtin_classify_type (struct S) == 12, "");
+  static_assert (__builtin_classify_type (union U) == 13, "");
+  static_assert (__builtin_classify_type (int [2]) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+#ifdef __cplusplus
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+#endif
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (main)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+#ifndef __cplusplus
+  static_assert (__builtin_classify_type (a[0]) == 1, "");
+  static_assert (__builtin_classify_type (e) == 1, "");
+  static_assert (__builtin_classify_type (b) == 1, "");
+  static_assert (__builtin_classify_type (p) == 5, "");
+  static_assert (__builtin_classify_type (f) == 8, "");
+  static_assert (__builtin_classify_type (c) == 9, "");
+  static_assert (__builtin_classify_type (main) == 5, "");
+  static_assert (__builtin_classify_type (s) == 12, "");
+  static_assert (__builtin_classify_type (u) == 13, "");
+  static_assert (__builtin_classify_type (a) == 5, "");
+#endif
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+#ifdef __cplusplus
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+#else
+  if (__builtin_classify_type (e) != 1)
+    abort ();
+  if (__builtin_classify_type (b) != 1)
+    abort ();
+#endif
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+#ifdef __cplusplus
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+#endif
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (main) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
--- gcc/testsuite/g++.dg/ext/builtin-classify-type-1.C.jj	2023-06-12 15:03:37.488813774 +0200
+++ gcc/testsuite/g++.dg/ext/builtin-classify-type-1.C	2023-06-12 16:24:17.787882132 +0200
@@ -0,0 +1,149 @@ 
+// { dg-do run { target c++11 } }
+
+extern "C" void abort ();
+
+template <int N>
+void
+foo ()
+{
+  enum E { E1 } e = E1;
+  struct S { int s; } s = { 0 };
+  union U { int u; } u = { 0 };
+  int a[2] = { 0, 0 };
+  bool b = false;
+  const char *p = (const char *) 0;
+  float f = 0.0;
+  _Complex double c = 0.0;
+  struct T { void foo (); };
+  int &r = a[0];
+  int S::*q = &S::s;
+  static_assert (__builtin_classify_type (void) == 0, "");
+  static_assert (__builtin_classify_type (int) == 1, "");
+  static_assert (__builtin_classify_type (enum E) == 3, "");
+  static_assert (__builtin_classify_type (bool) == 4, "");
+  static_assert (__builtin_classify_type (const char *) == 5, "");
+  static_assert (__builtin_classify_type (int &) == 6, "");
+  static_assert (__builtin_classify_type (int &&) == 6, "");
+  static_assert (__builtin_classify_type (int S::*) == 7, "");
+  static_assert (__builtin_classify_type (float) == 8, "");
+  static_assert (__builtin_classify_type (_Complex double) == 9, "");
+  static_assert (__builtin_classify_type (int (int, int)) == 10, "");
+  static_assert (__builtin_classify_type (struct S) == 12, "");
+  static_assert (__builtin_classify_type (union U) == 13, "");
+  static_assert (__builtin_classify_type (int [2]) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (abort)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (abort) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
+
+template <typename V, typename I, typename E, typename B, typename P,
+	  typename R1, typename R2, typename PM, typename F,
+	  typename C, typename FN, typename S, typename U, typename A>
+void
+bar ()
+{
+  E e = (E) 0;
+  S s = { 0 };
+  U u = { 0 };
+  A a = { 0, 0 };
+  B b = false;
+  P p = (P) 0;
+  F f = 0.0;
+  C c = 0.0;
+  R1 r = a[0];
+  PM q = &S::s;
+  static_assert (__builtin_classify_type (V) == 0, "");
+  static_assert (__builtin_classify_type (I) == 1, "");
+  static_assert (__builtin_classify_type (E) == 3, "");
+  static_assert (__builtin_classify_type (B) == 4, "");
+  static_assert (__builtin_classify_type (P) == 5, "");
+  static_assert (__builtin_classify_type (R1) == 6, "");
+  static_assert (__builtin_classify_type (R2) == 6, "");
+  static_assert (__builtin_classify_type (PM) == 7, "");
+  static_assert (__builtin_classify_type (F) == 8, "");
+  static_assert (__builtin_classify_type (C) == 9, "");
+  static_assert (__builtin_classify_type (FN) == 10, "");
+  static_assert (__builtin_classify_type (S) == 12, "");
+  static_assert (__builtin_classify_type (U) == 13, "");
+  static_assert (__builtin_classify_type (A) == 14, "");
+  static_assert (__builtin_classify_type (__typeof__ (a[0])) == 1, "");
+  static_assert (__builtin_classify_type (__typeof__ (e)) == 3, "");
+  static_assert (__builtin_classify_type (__typeof__ (b)) == 4, "");
+  static_assert (__builtin_classify_type (__typeof__ (p)) == 5, "");
+  static_assert (__builtin_classify_type (decltype (r)) == 6, "");
+  static_assert (__builtin_classify_type (__typeof__ (q)) == 7, "");
+  static_assert (__builtin_classify_type (__typeof__ (f)) == 8, "");
+  static_assert (__builtin_classify_type (__typeof__ (c)) == 9, "");
+  static_assert (__builtin_classify_type (__typeof__ (abort)) == 10, "");
+  static_assert (__builtin_classify_type (__typeof__ (s)) == 12, "");
+  static_assert (__builtin_classify_type (__typeof__ (u)) == 13, "");
+  static_assert (__builtin_classify_type (__typeof__ (a)) == 14, "");
+  if (__builtin_classify_type (a[0]) != 1)
+    abort ();
+  if (__builtin_classify_type (e) != 3)
+    abort ();
+  if (__builtin_classify_type (b) != 4)
+    abort ();
+  if (__builtin_classify_type (p) != 5)
+    abort ();
+  if (__builtin_classify_type (r) != 1)
+    abort ();
+  if (__builtin_classify_type (q) != 7)
+    abort ();
+  if (__builtin_classify_type (f) != 8)
+    abort ();
+  if (__builtin_classify_type (c) != 9)
+    abort ();
+  if (__builtin_classify_type (abort) != 5)
+    abort ();
+  if (__builtin_classify_type (s) != 12)
+    abort ();
+  if (__builtin_classify_type (u) != 13)
+    abort ();
+  if (__builtin_classify_type (a) != 5)
+    abort ();
+}
+
+int
+main ()
+{
+  enum E { E1 };
+  struct S { int s; };
+  union U { int u; };
+  foo <0> ();
+  bar <void, int, E, bool, const char *, int &, int &&, int S::*,
+       float, _Complex double, int (int, int), S, U, int [2]> ();
+}
--- gcc/testsuite/g++.dg/ext/builtin-classify-type-2.C.jj	2023-06-12 16:28:17.120577700 +0200
+++ gcc/testsuite/g++.dg/ext/builtin-classify-type-2.C	2023-06-12 16:29:57.860186807 +0200
@@ -0,0 +1,11 @@ 
+// { dg-do compile { target c++11 } }
+// { dg-options "" }
+
+void
+foo (int n)
+{
+  __builtin_classify_type (enum E { E1, E2 });	// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (struct S { int s; });// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (union U { int u; });	// { dg-error "types may not be defined in '__builtin_classify_type' calls" }
+  __builtin_classify_type (int [2 * n + 36]);
+}
--- gcc/testsuite/gcc.dg/builtin-classify-type-1.c.jj	2023-06-12 17:28:47.118495177 +0200
+++ gcc/testsuite/gcc.dg/builtin-classify-type-1.c	2023-06-12 17:43:46.420114514 +0200
@@ -0,0 +1,11 @@ 
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+void
+foo (int n)
+{
+  _Static_assert (__builtin_classify_type (enum E { E1, E2 }) == 3, "");
+  _Static_assert (__builtin_classify_type (struct S { int s; }) == 12, "");
+  _Static_assert (__builtin_classify_type (union U { int u; }) == 13, "");
+  _Static_assert (__builtin_classify_type (int [2 * n + 36]) == 14, "");
+}