c, v2: Add __builtin_stdc_rotate_{left, right} builtins [PR117030]

Message ID ZxqGKT7z5mtdjmGQ@tucnak
State New
Headers
Series c, v2: Add __builtin_stdc_rotate_{left, right} builtins [PR117030] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed

Commit Message

Jakub Jelinek Oct. 24, 2024, 5:38 p.m. UTC
  On Thu, Oct 24, 2024 at 02:20:16PM +0000, Joseph Myers wrote:
> On Thu, 24 Oct 2024, Jakub Jelinek wrote:
> 
> > +@defbuiltin{@var{type} __builtin_stdc_rotate_left (@var{type} @var{arg1}, @var{type} @var{arg2})}
> 
> The rotate count doesn't need to be the same type as the value rotated, so 
> saying @{type} for both is misleading.

Ok, switched to type1 and type2.

> > +The @code{__builtin_stdc_rotate_left} function is available only
> > +in C.  It is type-generic, the argument can be any unsigned integer
> > +(standard, extended or bit-precise).  No integral argument promotions are
> > +performed on the argument.  It is equivalent to
> > +@code{(@var{type}) ((@var{arg1} << @var{arg2})
> > +| (@var{arg1} >> ((-(unsigned) @var{arg2}) % @var{prec})))}
> > +where @var{prec} is bit width of @var{type}, except that side-effects
> > +in @var{arg1} and @var{arg2} are evaluated just once, and invokes undefined
> > +behavior also if @var{arg2} >= @var{prec} even when @var{type} normally
> > +promotes to @code{int}.
> > +@enddefbuiltin
> 
> I think it's more appropriate for the built-in functions to handle 
> reducing the rotate count modulo the width of the type, matching the 
> <stdbit.h> functions.  (Negative rotate counts are undefined behavior, but 
> arbitrary nonnegative values are OK.  The rotate count can be of "signed 
> or unsigned integer type, or char" - so bool is OK as the type of the 
> rotate count, but a rotate count of enumerated type is undefined 
> behavior.)

Ok, here is an updated patch.

Added _Bool support for arg2 (and testcase coverage for that in the first
testcase and removed from second), if arg2 is unsigned or when sanitizing
shifts it uses arg2 % prec and if not sanitizing shifts and arg2 is signed,
it just uses (utype2) arg2 % prec because that will IMHO result in
better code generation (e.g. for power of 2 precisions bitwise AND and
on some arches even no bitwise AND, just get it for free).

The only changed parts are extend.texi, c-parser.cc and the 2 testcases.

2024-10-24  Jakub Jelinek  <jakub@redhat.com>

	PR c/117030
gcc/
	* doc/extend.texi (__builtin_stdc_rotate_left,
	__builtin_stdc_rotate_right): Document.
gcc/c-family/
	* c-common.cc (c_common_reswords): Add __builtin_stdc_rotate_left
	and __builtin_stdc_rotate_right.
gcc/c/
	* c-parser.cc: Include asan.h and c-family/c-ubsan.h.
	(c_parser_postfix_expression): Handle __builtin_stdc_rotate_left
	and __builtin_stdc_rotate_right.
	* c-fold.cc (c_fully_fold_internal): Handle LROTATE_EXPR and
	RROTATE_EXPR.
gcc/testsuite/
	* gcc.dg/builtin-stdc-rotate-1.c: New test.
	* gcc.dg/builtin-stdc-rotate-2.c: New test.



	Jakub
  

Comments

Jakub Jelinek Oct. 25, 2024, 12:08 p.m. UTC | #1
On Thu, Oct 24, 2024 at 07:38:49PM +0200, Jakub Jelinek wrote:
> The only changed parts are extend.texi, c-parser.cc and the 2 testcases.

Bootstrapped/regtested successfully on both x86_64-linux and i686-linux.

> 2024-10-24  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c/117030
> gcc/
> 	* doc/extend.texi (__builtin_stdc_rotate_left,
> 	__builtin_stdc_rotate_right): Document.
> gcc/c-family/
> 	* c-common.cc (c_common_reswords): Add __builtin_stdc_rotate_left
> 	and __builtin_stdc_rotate_right.
> gcc/c/
> 	* c-parser.cc: Include asan.h and c-family/c-ubsan.h.
> 	(c_parser_postfix_expression): Handle __builtin_stdc_rotate_left
> 	and __builtin_stdc_rotate_right.
> 	* c-fold.cc (c_fully_fold_internal): Handle LROTATE_EXPR and
> 	RROTATE_EXPR.
> gcc/testsuite/
> 	* gcc.dg/builtin-stdc-rotate-1.c: New test.
> 	* gcc.dg/builtin-stdc-rotate-2.c: New test.

	Jakub
  
Joseph Myers Oct. 25, 2024, 8:06 p.m. UTC | #2
On Thu, 24 Oct 2024, Jakub Jelinek wrote:

> +		if (TYPE_UNSIGNED (TREE_TYPE (arg2))
> +		    || sanitize_flags_p (SANITIZE_SHIFT))
> +		  arg2 = build2_loc (loc, TRUNC_MOD_EXPR, TREE_TYPE (arg2),
> +				     arg2, build_int_cst (TREE_TYPE (arg2),
> +							  prec));
> +		else
> +		  {
> +		    /* When not sanitizing and second argument is signed,
> +		       just do the modulo in unsigned type, that results
> +		       in better generated code (for power of 2 precisions
> +		       bitwise AND).  */
> +		    tree utype = c_common_unsigned_type (TREE_TYPE (arg2));
> +		    arg2 = build2_loc (loc, TRUNC_MOD_EXPR, utype,
> +				       fold_convert (utype, arg2),
> +				       build_int_cst (utype, prec));
> +		  }

If sanitizing makes sense for these built-in functions, surely it should 
check for all negative shifts, including those that are multiples of the 
width (and there should be tests for it in the testsuite).  So sanitizing 
would require more complicated logic to avoid reducing a negative shift 
modulo the width at all.  Or in the absence of support for sanitizing 
these functions, the logic for modulo reduction shouldn't need to depend 
on whether sanitizing.

> @@ -409,7 +416,9 @@ c_fully_fold_internal (tree expr, bool i
>  	    warning_at (loc, OPT_Wshift_count_overflow,
>  			(code == LSHIFT_EXPR
>  			 ? G_("left shift count >= width of type")
> -			 : G_("right shift count >= width of type")));
> +			 : code == RSHIFT_EXPR
> +			 ? G_("right shift count >= width of type")
> +			 : G_("rotate count >= width of type")));

This shouldn't be reachable, since larger rotate counts are valid and 
should have been reduced modulo the width.
  

Patch

--- gcc/c-family/c-common.cc.jj	2024-10-24 18:53:37.640095485 +0200
+++ gcc/c-family/c-common.cc	2024-10-24 18:53:59.673786851 +0200
@@ -448,6 +448,8 @@  const struct c_common_resword c_common_r
   { "__builtin_stdc_has_single_bit", RID_BUILTIN_STDC, D_CONLY },
   { "__builtin_stdc_leading_ones", RID_BUILTIN_STDC, D_CONLY },
   { "__builtin_stdc_leading_zeros", RID_BUILTIN_STDC, D_CONLY },
+  { "__builtin_stdc_rotate_left", RID_BUILTIN_STDC, D_CONLY },
+  { "__builtin_stdc_rotate_right", RID_BUILTIN_STDC, D_CONLY },
   { "__builtin_stdc_trailing_ones", RID_BUILTIN_STDC, D_CONLY },
   { "__builtin_stdc_trailing_zeros", RID_BUILTIN_STDC, D_CONLY },
   { "__builtin_tgmath", RID_BUILTIN_TGMATH, D_CONLY },
--- gcc/c/c-parser.cc.jj	2024-10-24 12:32:52.830807808 +0200
+++ gcc/c/c-parser.cc	2024-10-24 19:17:06.039371439 +0200
@@ -74,6 +74,8 @@  along with GCC; see the file COPYING3.
 #include "bitmap.h"
 #include "analyzer/analyzer-language.h"
 #include "toplev.h"
+#include "asan.h"
+#include "c-family/c-ubsan.h"
 
 /* We need to walk over decls with incomplete struct/union/enum types
    after parsing the whole translation unit.
@@ -12262,12 +12264,15 @@  c_parser_postfix_expression (c_parser *p
 	      C_BUILTIN_STDC_HAS_SINGLE_BIT,
 	      C_BUILTIN_STDC_LEADING_ONES,
 	      C_BUILTIN_STDC_LEADING_ZEROS,
+	      C_BUILTIN_STDC_ROTATE_LEFT,
+	      C_BUILTIN_STDC_ROTATE_RIGHT,
 	      C_BUILTIN_STDC_TRAILING_ONES,
 	      C_BUILTIN_STDC_TRAILING_ZEROS,
 	      C_BUILTIN_STDC_MAX
 	    } stdc_rid = C_BUILTIN_STDC_MAX;
 	    const char *name
 	      = IDENTIFIER_POINTER (c_parser_peek_token (parser)->value);
+	    unsigned num_args = 1;
 	    switch (name[sizeof ("__builtin_stdc_") - 1])
 	      {
 	      case 'b':
@@ -12316,6 +12321,13 @@  c_parser_postfix_expression (c_parser *p
 		else
 		  stdc_rid = C_BUILTIN_STDC_LEADING_ZEROS;
 		break;
+	      case 'r':
+		if (name[sizeof ("__builtin_stdc_rotate_") - 1] == 'l')
+		  stdc_rid = C_BUILTIN_STDC_ROTATE_LEFT;
+		else
+		  stdc_rid = C_BUILTIN_STDC_ROTATE_RIGHT;
+		num_args = 2;
+		break;
 	      case 't':
 		if (name[sizeof ("__builtin_stdc_trailing_") - 1] == 'o')
 		  stdc_rid = C_BUILTIN_STDC_TRAILING_ONES;
@@ -12334,7 +12346,7 @@  c_parser_postfix_expression (c_parser *p
 		break;
 	      }
 
-	    if (vec_safe_length (cexpr_list) != 1)
+	    if (vec_safe_length (cexpr_list) != num_args)
 	      {
 		error_at (loc, "wrong number of arguments to %qs", name);
 		expr.set_error ();
@@ -12406,6 +12418,80 @@  c_parser_postfix_expression (c_parser *p
 	       without evaluating arg multiple times, type being
 	       __typeof (arg) and prec __builtin_popcountg ((type) ~0)).  */
 	    int prec = TYPE_PRECISION (type);
+	    if (num_args == 2)
+	      {
+		/* Expand:
+		   __builtin_stdc_rotate_left (arg1, arg2) as
+		   arg1 r<< (arg2 % prec)
+		   __builtin_stdc_rotate_right (arg1, arg2) as
+		   arg1 r>> (arg2 % prec).  */
+		arg_p = &(*cexpr_list)[1];
+		*arg_p = convert_lvalue_to_rvalue (loc, *arg_p, true, true);
+		if (!INTEGRAL_TYPE_P (TREE_TYPE (arg_p->value)))
+		  {
+		    error_at (loc, "%qs operand not an integral type", name);
+		    expr.set_error ();
+		    break;
+		  }
+		if (TREE_CODE (TREE_TYPE (arg_p->value)) == ENUMERAL_TYPE)
+		  {
+		    error_at (loc, "argument %u in call to function "
+				   "%qs has enumerated type", 2, name);
+		    expr.set_error ();
+		    break;
+		  }
+		tree arg1 = save_expr (arg);
+		tree arg2 = arg_p->value;
+		tree_code code;
+		if (stdc_rid == C_BUILTIN_STDC_ROTATE_LEFT)
+		  code = LROTATE_EXPR;
+		else
+		  code = RROTATE_EXPR;
+
+		if (TREE_CODE (arg2) == INTEGER_CST
+		    && tree_int_cst_sgn (arg2) < 0)
+		  warning_at (loc, OPT_Wshift_count_negative,
+			      "rotate count is negative");
+
+		/* Promote arg2 to int/unsigned just so that we don't
+		   need to deal with arg2 type not being able to represent
+		   prec.  In the end gimplification uses unsigned int
+		   for all shifts/rotates anyway.  */
+		if (TYPE_PRECISION (TREE_TYPE (arg2))
+		    < TYPE_PRECISION (integer_type_node))
+		  arg2 = fold_convert (TYPE_UNSIGNED (TREE_TYPE (arg2))
+				       ? unsigned_type_node
+				       : integer_type_node, arg2);
+
+		if (TYPE_UNSIGNED (TREE_TYPE (arg2))
+		    || sanitize_flags_p (SANITIZE_SHIFT))
+		  arg2 = build2_loc (loc, TRUNC_MOD_EXPR, TREE_TYPE (arg2),
+				     arg2, build_int_cst (TREE_TYPE (arg2),
+							  prec));
+		else
+		  {
+		    /* When not sanitizing and second argument is signed,
+		       just do the modulo in unsigned type, that results
+		       in better generated code (for power of 2 precisions
+		       bitwise AND).  */
+		    tree utype = c_common_unsigned_type (TREE_TYPE (arg2));
+		    arg2 = build2_loc (loc, TRUNC_MOD_EXPR, utype,
+				       fold_convert (utype, arg2),
+				       build_int_cst (utype, prec));
+		  }
+
+		tree instrument_expr = NULL_TREE;
+		if (sanitize_flags_p (SANITIZE_SHIFT))
+		  instrument_expr = ubsan_instrument_shift (loc, code,
+							    arg1, arg2);
+		expr.value = build2_loc (loc, code, TREE_TYPE (arg1), arg1,
+					 arg2);
+		if (instrument_expr)
+		  expr.value = build2_loc (loc, COMPOUND_EXPR,
+					   TREE_TYPE (expr.value),
+					   instrument_expr, expr.value);
+		break;
+	      }
 	    tree barg1 = arg;
 	    switch (stdc_rid)
 	      {
--- gcc/c/c-fold.cc.jj	2024-10-24 18:53:37.741094071 +0200
+++ gcc/c/c-fold.cc	2024-10-24 18:53:59.677786795 +0200
@@ -328,6 +328,8 @@  c_fully_fold_internal (tree expr, bool i
     case EXACT_DIV_EXPR:
     case LSHIFT_EXPR:
     case RSHIFT_EXPR:
+    case LROTATE_EXPR:
+    case RROTATE_EXPR:
     case BIT_IOR_EXPR:
     case BIT_XOR_EXPR:
     case BIT_AND_EXPR:
@@ -389,7 +391,10 @@  c_fully_fold_internal (tree expr, bool i
 	  && tree_int_cst_sgn (op0) < 0)
 	warning_at (loc, OPT_Wshift_negative_value,
 		    "left shift of negative value");
-      if ((code == LSHIFT_EXPR || code == RSHIFT_EXPR)
+      if ((code == LSHIFT_EXPR
+	   || code == RSHIFT_EXPR
+	   || code == LROTATE_EXPR
+	   || code == RROTATE_EXPR)
 	  && TREE_CODE (orig_op1) != INTEGER_CST
 	  && TREE_CODE (op1) == INTEGER_CST
 	  && TREE_CODE (TREE_TYPE (orig_op1)) == INTEGER_TYPE
@@ -399,7 +404,9 @@  c_fully_fold_internal (tree expr, bool i
 	    warning_at (loc, OPT_Wshift_count_negative,
 			(code == LSHIFT_EXPR
 			 ? G_("left shift count is negative")
-			 : G_("right shift count is negative")));
+			 : code == RSHIFT_EXPR
+			 ? G_("right shift count is negative")
+			 : G_("rotate count is negative")));
 	  else if ((TREE_CODE (TREE_TYPE (orig_op0)) == INTEGER_TYPE
 		    || TREE_CODE (TREE_TYPE (orig_op0)) == BITINT_TYPE
 		    || TREE_CODE (TREE_TYPE (orig_op0)) == FIXED_POINT_TYPE)
@@ -409,7 +416,9 @@  c_fully_fold_internal (tree expr, bool i
 	    warning_at (loc, OPT_Wshift_count_overflow,
 			(code == LSHIFT_EXPR
 			 ? G_("left shift count >= width of type")
-			 : G_("right shift count >= width of type")));
+			 : code == RSHIFT_EXPR
+			 ? G_("right shift count >= width of type")
+			 : G_("rotate count >= width of type")));
 	  else if (TREE_CODE (TREE_TYPE (orig_op0)) == VECTOR_TYPE
 		   && compare_tree_int (op1,
 					TYPE_PRECISION (TREE_TYPE (TREE_TYPE (orig_op0))))
--- gcc/doc/extend.texi.jj	2024-10-24 12:33:21.052412799 +0200
+++ gcc/doc/extend.texi	2024-10-24 19:01:15.694680231 +0200
@@ -16129,6 +16129,32 @@  performed on the argument.  It is equiva
 @code{(unsigned int) __builtin_ctzg (@var{arg}, @var{prec})}
 @enddefbuiltin
 
+@defbuiltin{@var{type1} __builtin_stdc_rotate_left (@var{type1} @var{arg1}, @var{type2} @var{arg2})}
+The @code{__builtin_stdc_rotate_left} function is available only
+in C.  It is type-generic, the first argument can be any unsigned integer
+(standard, extended or bit-precise) and second argument any signed or
+unsigned integer or @code{char}.  No integral argument promotions are
+performed on the arguments.  It is equivalent to
+@code{(@var{type1}) ((@var{arg1} << (@var{arg2} % @var{prec}))
+| (@var{arg1} >> ((-(unsigned @var{type2}) @var{arg2}) % @var{prec})))}
+where @var{prec} is bit width of @var{type1}, except that side-effects
+in @var{arg1} and @var{arg2} are evaluated just once.  The behavior is
+undefined if @var{arg2} is negative.
+@enddefbuiltin
+
+@defbuiltin{@var{type1} __builtin_stdc_rotate_right (@var{type1} @var{arg1}, @var{type2} @var{arg2})}
+The @code{__builtin_stdc_rotate_right} function is available only
+in C.  It is type-generic, the first argument can be any unsigned integer
+(standard, extended or bit-precise) and second argument any signed or
+unsigned integer or @code{char}.  No integral argument promotions are
+performed on the arguments.  It is equivalent to
+@code{(@var{type1}) ((@var{arg1} >> (@var{arg2} % @var{prec}))
+| (@var{arg1} << ((-(unsigned @var{type2}) @var{arg2}) % @var{prec})))}
+where @var{prec} is bit width of @var{type1}, except that side-effects
+in @var{arg1} and @var{arg2} are evaluated just once.  The behavior is
+undefined if @var{arg2} is negative.
+@enddefbuiltin
+
 @defbuiltin{double __builtin_powi (double, int)}
 @defbuiltinx{float __builtin_powif (float, int)}
 @defbuiltinx{{long double} __builtin_powil (long double, int)}
--- gcc/testsuite/gcc.dg/builtin-stdc-rotate-1.c.jj	2024-10-24 18:53:59.681786740 +0200
+++ gcc/testsuite/gcc.dg/builtin-stdc-rotate-1.c	2024-10-24 19:29:52.700634558 +0200
@@ -0,0 +1,235 @@ 
+/* { dg-do run } */
+/* { dg-options "-std=c11" } */
+
+unsigned long long
+rotate_left (unsigned char a, unsigned short b, unsigned int c,
+	     unsigned long d, unsigned long long e, int n)
+{
+  return (__builtin_stdc_rotate_left (a, n)
+	  + __builtin_stdc_rotate_left (b, n)
+	  + __builtin_stdc_rotate_left (c, n)
+	  + __builtin_stdc_rotate_left (d, n)
+	  + __builtin_stdc_rotate_left (e, n));
+}
+
+unsigned long long
+rotate_right (unsigned char a, unsigned short b, unsigned int c,
+	      unsigned long d, unsigned long long e, int n)
+{
+  return (__builtin_stdc_rotate_right (a, n)
+	  + __builtin_stdc_rotate_right (b, n)
+	  + __builtin_stdc_rotate_right (c, n)
+	  + __builtin_stdc_rotate_right (d, n)
+	  + __builtin_stdc_rotate_right (e, n));
+}
+
+#define expr_has_type(e, t) _Generic (e, default : 0, t : 1)
+
+int
+main ()
+{
+  if (__builtin_stdc_rotate_left ((unsigned char) 0, 0) != 0
+      || __builtin_stdc_rotate_left ((unsigned char) 0xdcU, (char) 0) != 0xdcU
+      || __builtin_stdc_rotate_left ((unsigned char) 0xdcU, __CHAR_BIT__) != 0xdcU
+      || __builtin_stdc_rotate_left ((unsigned char) 0xdcU, 2 * __CHAR_BIT__) != 0xdcU
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned char) 0, 0), unsigned char)
+      || __builtin_stdc_rotate_left ((unsigned char) 1, (_Bool) 1) != 2
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned char) 1, (unsigned char) 1), unsigned char)
+      || __builtin_stdc_rotate_left ((unsigned char) ~1U, 3) != (unsigned char) ~8U
+      || __builtin_stdc_rotate_left ((unsigned char) (2U + __SCHAR_MAX__), 1) != 3
+      || __builtin_stdc_rotate_left ((unsigned char) (2U + __SCHAR_MAX__), __CHAR_BIT__ + 1) != 3
+      || __builtin_stdc_rotate_left ((unsigned short) 0, 0) != 0
+      || __builtin_stdc_rotate_left ((unsigned short) 0xdcabU, 0) != 0xdcabU
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned short) 0, (signed char) 0), unsigned short)
+      || __builtin_stdc_rotate_left ((unsigned short) 1, 13) != (1U << 13)
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned short) 1, 13), unsigned short)
+      || __builtin_stdc_rotate_left ((unsigned short) ~1U, 7) != (unsigned short) ~0x80U
+      || __builtin_stdc_rotate_left ((unsigned short) (2U + __SHRT_MAX__), 1) != 3
+      || __builtin_stdc_rotate_left (0U, 0) != 0
+      || __builtin_stdc_rotate_left (0xcdbaU, (short int) 0) != 0xcdbaU
+      || !expr_has_type (__builtin_stdc_rotate_left (0U, 0), unsigned int)
+      || __builtin_stdc_rotate_left (1U, (char) 15) != (1U << 15)
+      || !expr_has_type (__builtin_stdc_rotate_left (1U, 15), unsigned int)
+      || __builtin_stdc_rotate_left (~1U, 9) != ~0x200U
+      || __builtin_stdc_rotate_left (2U + __INT_MAX__, 1) != 3
+      || __builtin_stdc_rotate_left (0UL, 0) != 0
+      || __builtin_stdc_rotate_left (0xdc8971baUL, 0) != 0xdc8971baUL
+      || !expr_has_type (__builtin_stdc_rotate_left (0UL, 0LL), unsigned long)
+      || __builtin_stdc_rotate_left (1UL, 30) != (1UL << 30)
+      || !expr_has_type (__builtin_stdc_rotate_left (1UL, 30), unsigned long)
+      || __builtin_stdc_rotate_left (~1UL, 22) != ~0x400000UL
+      || __builtin_stdc_rotate_left (2UL + __LONG_MAX__, 1) != 3
+      || __builtin_stdc_rotate_left (2UL + __LONG_MAX__, (int) (sizeof (unsigned long) * __CHAR_BIT__) + 1) != 3
+      || __builtin_stdc_rotate_left (0ULL, (_Bool) 0) != 0
+      || __builtin_stdc_rotate_left (0xdc897143985734baULL, 0) != 0xdc897143985734baULL
+      || __builtin_stdc_rotate_left (0xdc897143985734baULL, 4 * (int) (sizeof (unsigned long long) * __CHAR_BIT__)) != 0xdc897143985734baULL
+      || !expr_has_type (__builtin_stdc_rotate_left (0ULL, 0), unsigned long long)
+      || __builtin_stdc_rotate_left (1ULL, 62) != (1ULL << 62)
+      || !expr_has_type (__builtin_stdc_rotate_left (1ULL, 62ULL), unsigned long long)
+      || __builtin_stdc_rotate_left (~1ULL, 53) != ~0x20000000000000ULL
+      || __builtin_stdc_rotate_left (2ULL + __LONG_LONG_MAX__, 1) != 3)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_right ((unsigned char) 0, 0) != 0
+      || __builtin_stdc_rotate_right ((unsigned char) 0xdcU, 0) != 0xdcU
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned char) 0, 0), unsigned char)
+      || __builtin_stdc_rotate_right ((unsigned char) 1, 1) != (1U + __SCHAR_MAX__)
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned char) 1, 1), unsigned char)
+      || __builtin_stdc_rotate_right ((unsigned char) ~1U, (char) 3) != (unsigned char) ~((1U + __SCHAR_MAX__) >> 2)
+      || __builtin_stdc_rotate_right ((unsigned char) 3, 1) != (2U + __SCHAR_MAX__)
+      || __builtin_stdc_rotate_right ((unsigned short) 0, 0) != 0
+      || __builtin_stdc_rotate_right ((unsigned short) 0xdcabU, 0) != 0xdcabU
+      || __builtin_stdc_rotate_right ((unsigned short) 0xdcabU, sizeof (unsigned short) * __CHAR_BIT__) != 0xdcabU
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned short) 0, 0), unsigned short)
+      || __builtin_stdc_rotate_right ((unsigned short) (1U << 13), 12) != 2
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned short) (1U << 13), 2), unsigned short)
+      || __builtin_stdc_rotate_right ((unsigned short) ~1U, 7) != (unsigned short) ~((1U + __SHRT_MAX__) >> 6)
+      || __builtin_stdc_rotate_right ((unsigned short) 3, 1) != (2U + __SHRT_MAX__)
+      || __builtin_stdc_rotate_right (0U, 0) != 0
+      || __builtin_stdc_rotate_right (0xcdbaU, 0) != 0xcdbaU
+      || !expr_has_type (__builtin_stdc_rotate_right (0U, 0), unsigned int)
+      || __builtin_stdc_rotate_right (1U << 15, 13) != 4U
+      || !expr_has_type (__builtin_stdc_rotate_right (1U << 15, 13), unsigned int)
+      || __builtin_stdc_rotate_right (~1U, 9) != ~((1U + __INT_MAX__) >> 8)
+      || __builtin_stdc_rotate_right (3U, (_Bool) 1) != (2U + __INT_MAX__)
+      || __builtin_stdc_rotate_right (3U, (int) (sizeof (unsigned) * __CHAR_BIT__) + 1) != (2U + __INT_MAX__)
+      || __builtin_stdc_rotate_right (0UL, (_Bool) 0) != 0
+      || __builtin_stdc_rotate_right (0xdc8971baUL, 0) != 0xdc8971baUL
+      || !expr_has_type (__builtin_stdc_rotate_right (0UL, 0), unsigned long)
+      || __builtin_stdc_rotate_right (1UL << 30, 27) != 8UL
+      || !expr_has_type (__builtin_stdc_rotate_right (1UL << 30, 27), unsigned long)
+      || __builtin_stdc_rotate_right (~1UL, 22) != ~((1UL + __LONG_MAX__) >> 21)
+      || __builtin_stdc_rotate_right (3UL, 1) != (2UL + __LONG_MAX__)
+      || __builtin_stdc_rotate_right (0ULL, 0) != 0
+      || __builtin_stdc_rotate_right (0xdc897143985734baULL, 0) != 0xdc897143985734baULL
+      || !expr_has_type (__builtin_stdc_rotate_right (0ULL, 0), unsigned long long)
+      || __builtin_stdc_rotate_right (1ULL << 62, 60) != 4ULL
+      || !expr_has_type (__builtin_stdc_rotate_right (1ULL << 62, 60), unsigned long long)
+      || __builtin_stdc_rotate_right (~1ULL, 53) != ~((1ULL + __LONG_LONG_MAX__) >> 52)
+      || __builtin_stdc_rotate_right (3ULL, 1) != (2ULL + __LONG_LONG_MAX__))
+    __builtin_abort ();
+#ifdef __SIZEOF_INT128__
+  if (__builtin_stdc_rotate_left ((unsigned __int128) 0U, 0) != 0
+      || __builtin_stdc_rotate_left (((unsigned __int128) 0x43256567547ULL) << 64 | 0xdc897143985734baULL, 0) != (((unsigned __int128) 0x43256567547ULL) << 64 | 0xdc897143985734baULL)
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned __int128) 0U, 0), unsigned __int128)
+      || __builtin_stdc_rotate_left ((unsigned __int128) 1U, 115) != ((unsigned __int128) 1U << 115)
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned __int128) 1U, 115), unsigned __int128)
+      || __builtin_stdc_rotate_left (~(unsigned __int128) 1ULL, 53) != ~(unsigned __int128) 0x20000000000000ULL
+      || __builtin_stdc_rotate_left (1ULL + ((unsigned __int128) 1U << 127), 1) != 3)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_right (0ULL, 0) != 0
+      || __builtin_stdc_rotate_right (((unsigned __int128) 0x43256567547ULL) << 64 | 0xdc897143985734baULL, 0) != (((unsigned __int128) 0x43256567547ULL) << 64 | 0xdc897143985734baULL)
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned __int128) 0U, 0), unsigned __int128)
+      || __builtin_stdc_rotate_right ((unsigned __int128) 1ULL << 115, 110) != 32U
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned __int128) 1ULL << 115, 110), unsigned __int128)
+      || __builtin_stdc_rotate_right (~(unsigned __int128) 1ULL, 53) != ~((unsigned __int128) 1 << 75)
+      || __builtin_stdc_rotate_right ((unsigned __int128) 3ULL, 1) != 1U + ((unsigned __int128) 1U << 127))
+    __builtin_abort ();
+#endif
+#if __has_builtin (__builtin_stdc_rotate_left) != 1
+#error __builtin_stdc_rotate_left not implemented
+#endif
+#if __has_builtin (__builtin_stdc_rotate_right) != 1
+#error __builtin_stdc_rotate_right not implemented
+#endif
+  unsigned char a = 1;
+  unsigned short b = 1;
+  unsigned int c = 1;
+  unsigned long d = 1;
+  unsigned long long e = 1;
+  int f = 1;
+  if (__builtin_stdc_rotate_left (a++, f++) != 2 || a != 2 || f != 2)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_left (b++, f++) != 4 || b != 2 || f != 3)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_left (c++, f++) != 8 || c != 2 || f != 4)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_left (d++, f++) != 16 || d != 2 || f != 5)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_left (e++, f++) != 32 || e != 2 || f != 6)
+    __builtin_abort ();
+  f = 1;
+  if (__builtin_stdc_rotate_right (a++, f++) != 1 || a != 3 || f != 2)
+    __builtin_abort ();
+  f = 1;
+  if (__builtin_stdc_rotate_right (b++, f++) != 1 || b != 3 || f != 2)
+    __builtin_abort ();
+  f = 1;
+  if (__builtin_stdc_rotate_right (c++, f++) != 1 || c != 3 || f != 2)
+    __builtin_abort ();
+  f = 1;
+  if (__builtin_stdc_rotate_right (d++, f++) != 1 || d != 3 || f != 2)
+    __builtin_abort ();
+  f = 1;
+  if (__builtin_stdc_rotate_right (e++, f++) != 1 || e != 3 || f != 2)
+    __builtin_abort ();
+#ifdef __SIZEOF_INT128__
+  unsigned __int128 g = 1;
+  if (__builtin_stdc_rotate_left (g++, f++) != 4 || g != 2 || f != 3)
+    __builtin_abort ();
+  f = 1;
+  if (__builtin_stdc_rotate_right (g++, f++) != 1 || g != 3 || f != 2)
+    __builtin_abort ();
+#endif
+#if __BITINT_MAXWIDTH__ >= 64
+  if (__builtin_stdc_rotate_left (0uwb, 0) != 0
+      || __builtin_stdc_rotate_left (1uwb, 0) != 1uwb
+      || !expr_has_type (__builtin_stdc_rotate_left (0uwb, 0), unsigned _BitInt(1)))
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_left ((unsigned _BitInt(2)) 0, 0) != 0
+      || __builtin_stdc_rotate_left ((unsigned _BitInt(2)) 1, (_BitInt(27)) 1) != 2uwb
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned _BitInt(2)) 0, 0), unsigned _BitInt(2))
+      || __builtin_stdc_rotate_left ((unsigned _BitInt(2)) 1, 1) != 2
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned _BitInt(2)) 1, (unsigned _BitInt(2)) 1), unsigned _BitInt(2))
+      || __builtin_stdc_rotate_left ((unsigned _BitInt(2)) 2, 1) != 1)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_right (0uwb, 0) != 0
+      || __builtin_stdc_rotate_right (1uwb, 0) != 1uwb
+      || !expr_has_type (__builtin_stdc_rotate_right (0uwb, 0), unsigned _BitInt(1)))
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_right ((unsigned _BitInt(2)) 0, (_BitInt(3)) 0) != 0
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(2)) 1, 1) != 2uwb
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned _BitInt(2)) 0, 0), unsigned _BitInt(2))
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(2)) 1, 1) != 2
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned _BitInt(2)) 1, 1), unsigned _BitInt(2))
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(2)) 2, 1) != 1)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_left ((unsigned _BitInt(59)) 0U, 0) != 0
+      || __builtin_stdc_rotate_left ((unsigned _BitInt(59)) 0x43256567547ULL, 0) != ((unsigned _BitInt(59)) 0x43256567547ULL)
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned _BitInt(59)) 0U, 0), unsigned _BitInt(59))
+      || __builtin_stdc_rotate_left ((unsigned _BitInt(59)) 1U, 57) != ((unsigned _BitInt(59)) 1U << 57)
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned _BitInt(59)) 1U, 57), unsigned _BitInt(59))
+      || __builtin_stdc_rotate_left (~(unsigned _BitInt(59)) 1ULL, 53) != ~(unsigned _BitInt(59)) 0x20000000000000ULL
+      || __builtin_stdc_rotate_left (1uwb + ((unsigned _BitInt(59)) 1U << 58), 1) != 3)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_right (0ULL, 0) != 0
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(59)) 0x43256567547ULL, 0) != ((unsigned _BitInt(59)) 0x43256567547ULL)
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned _BitInt(59)) 0U, 0), unsigned _BitInt(59))
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(59)) 1ULL << 57, 55) != 4U
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned _BitInt(59)) 1U << 57, 55), unsigned _BitInt(59))
+      || __builtin_stdc_rotate_right (~(unsigned _BitInt(59)) 1ULL, 53) != ~((unsigned _BitInt(59)) 1 << 6)
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(59)) 3ULL, 1) != 1uwb + ((unsigned _BitInt(59)) 1U << 58))
+    __builtin_abort ();
+#endif
+#if __BITINT_MAXWIDTH__ >= 512
+  if (__builtin_stdc_rotate_left ((unsigned _BitInt(510)) 0U, (_BitInt(503)) 0) != 0
+      || __builtin_stdc_rotate_left (((unsigned _BitInt(510)) 0x43256567547ULL) << 64 | 0xdc897143985734baULL, 0) != (((unsigned _BitInt(510)) 0x43256567547ULL) << 64 | 0xdc897143985734baULL)
+      || __builtin_stdc_rotate_left (((unsigned _BitInt(510)) 0x43256567547ULL) << 64 | 0xdc897143985734baULL, 510) != (((unsigned _BitInt(510)) 0x43256567547ULL) << 64 | 0xdc897143985734baULL)
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned _BitInt(510)) 0U, 0), unsigned _BitInt(510))
+      || __builtin_stdc_rotate_left ((unsigned _BitInt(510)) 1U, 508) != ((unsigned _BitInt(510)) 1U << 508)
+      || !expr_has_type (__builtin_stdc_rotate_left ((unsigned _BitInt(510)) 1U, (unsigned _BitInt(505)) 508), unsigned _BitInt(510))
+      || __builtin_stdc_rotate_left (~(unsigned _BitInt(510)) 1ULL, 53) != ~(unsigned _BitInt(510)) 0x20000000000000ULL
+      || __builtin_stdc_rotate_left (1uwb + ((unsigned _BitInt(510)) 1U << 509), 1) != 3
+      || __builtin_stdc_rotate_left (1uwb + ((unsigned _BitInt(510)) 1U << 509), 511U) != 3)
+    __builtin_abort ();
+  if (__builtin_stdc_rotate_right (0ULL, 0) != 0
+      || __builtin_stdc_rotate_right (((unsigned _BitInt(510)) 0x43256567547ULL) << 64 | 0xdc897143985734baULL, 0) != (((unsigned _BitInt(510)) 0x43256567547ULL) << 64 | 0xdc897143985734baULL)
+      || __builtin_stdc_rotate_right (((unsigned _BitInt(510)) 0x43256567547ULL) << 64 | 0xdc897143985734baULL, 510U * 2) != (((unsigned _BitInt(510)) 0x43256567547ULL) << 64 | 0xdc897143985734baULL)
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned _BitInt(510)) 0U, 0), unsigned _BitInt(510))
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(510)) 1ULL << 508, 503) != 32U
+      || !expr_has_type (__builtin_stdc_rotate_right ((unsigned _BitInt(510)) 1ULL << 508, 503), unsigned _BitInt(510))
+      || __builtin_stdc_rotate_right (~(unsigned _BitInt(510)) 1ULL, 499) != ~((unsigned _BitInt(510)) 1 << 11)
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(510)) 3ULL, 1) != 1uwb + ((unsigned _BitInt(510)) 1U << 509)
+      || __builtin_stdc_rotate_right ((unsigned _BitInt(510)) 3ULL, 511) != 1uwb + ((unsigned _BitInt(510)) 1U << 509))
+    __builtin_abort ();
+#endif
+}
--- gcc/testsuite/gcc.dg/builtin-stdc-rotate-2.c.jj	2024-10-24 18:53:59.681786740 +0200
+++ gcc/testsuite/gcc.dg/builtin-stdc-rotate-2.c	2024-10-24 19:30:53.644781051 +0200
@@ -0,0 +1,70 @@ 
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+void
+foo (void)
+{
+  typedef int V __attribute__ ((vector_size (4 * sizeof (int))));
+  struct S { int s; };
+  enum E { E0, E1 };
+  __builtin_stdc_rotate_left (0.0f, 0);			/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left (0.0, 0);			/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left (0.0L, 0);			/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left ((V) {}, 0);		/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left ((struct S) { 0 }, 0);	/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left ();			/* { dg-error "wrong number of arguments to '__builtin_stdc_rotate_left'" } */
+  __builtin_stdc_rotate_left (0U);			/* { dg-error "wrong number of arguments to '__builtin_stdc_rotate_left'" } */
+  __builtin_stdc_rotate_left (0U, 0U, 0U);		/* { dg-error "wrong number of arguments to '__builtin_stdc_rotate_left'" } */
+  __builtin_stdc_rotate_left ((_Bool) 0, 0);		/* { dg-error "argument 1 in call to function '__builtin_stdc_rotate_left' has boolean type" } */
+  __builtin_stdc_rotate_left ((enum E) E0, 0);		/* { dg-error "argument 1 in call to function '__builtin_stdc_rotate_left' has enumerated type" } */
+  __builtin_stdc_rotate_left (0, 0);			/* { dg-error "argument 1 in call to function '__builtin_stdc_rotate_left' has signed type" } */
+  __builtin_stdc_rotate_left (0U, 0.0f);		/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left (0U, 0.0);			/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left (0U, 0.0L);		/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left (0U, (V) {});		/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left (0U, (struct S) { 0 });	/* { dg-error "'__builtin_stdc_rotate_left' operand not an integral type" } */
+  __builtin_stdc_rotate_left (0U, (enum E) E0);		/* { dg-error "argument 2 in call to function '__builtin_stdc_rotate_left' has enumerated type" } */
+  __builtin_stdc_rotate_right (0.0f, 0);		/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right (0.0, 0);			/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right (0.0L, 0);		/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right ((V) {}, 0);		/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right ((struct S) { 0 }, 0);	/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right ();			/* { dg-error "wrong number of arguments to '__builtin_stdc_rotate_right'" } */
+  __builtin_stdc_rotate_right (0U);			/* { dg-error "wrong number of arguments to '__builtin_stdc_rotate_right'" } */
+  __builtin_stdc_rotate_right (0U, 0U, 0U);		/* { dg-error "wrong number of arguments to '__builtin_stdc_rotate_right'" } */
+  __builtin_stdc_rotate_right ((_Bool) 0, 0);		/* { dg-error "argument 1 in call to function '__builtin_stdc_rotate_right' has boolean type" } */
+  __builtin_stdc_rotate_right ((enum E) E0, 0);		/* { dg-error "argument 1 in call to function '__builtin_stdc_rotate_right' has enumerated type" } */
+  __builtin_stdc_rotate_right (0, 0);			/* { dg-error "argument 1 in call to function '__builtin_stdc_rotate_right' has signed type" } */
+  __builtin_stdc_rotate_right (0U, 0.0f);		/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right (0U, 0.0);		/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right (0U, 0.0L);		/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right (0U, (V) {});		/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right (0U, (struct S) { 0 });	/* { dg-error "'__builtin_stdc_rotate_right' operand not an integral type" } */
+  __builtin_stdc_rotate_right (0U, (enum E) E0);	/* { dg-error "argument 2 in call to function '__builtin_stdc_rotate_right' has enumerated type" } */
+  __builtin_stdc_rotate_left ((unsigned char) 0, -1);	/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_right ((unsigned char) 0, -1);	/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_left ((unsigned short) 0, -1);	/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_right ((unsigned short) 0, -1);	/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_left (0U, -1);			/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_right (0U, -1);			/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_left (0UL, -1);			/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_right (0UL, -1);		/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_left (0ULL, -1);		/* { dg-warning "rotate count is negative" } */
+  __builtin_stdc_rotate_right (0ULL, -1);		/* { dg-warning "rotate count is negative" } */
+#ifdef __SIZEOF_INT128__
+  __builtin_stdc_rotate_left ((unsigned __int128) 0, -1); /* { dg-warning "rotate count is negative" "" { target int128 } } */
+  __builtin_stdc_rotate_right ((unsigned __int128) 0, -1); /* { dg-warning "rotate count is negative" "" { target int128 } } */
+#endif
+#if __BITINT_MAXWIDTH__ >= 64
+  __builtin_stdc_rotate_left (0uwb, -1);		/* { dg-warning "rotate count is negative" "" { target bitint } } */
+  __builtin_stdc_rotate_right (0uwb, -1);		/* { dg-warning "rotate count is negative" "" { target bitint } } */
+  __builtin_stdc_rotate_left ((unsigned _BitInt(2)) 0, -1); /* { dg-warning "rotate count is negative" "" { target bitint } } */
+  __builtin_stdc_rotate_right ((unsigned _BitInt(2)) 0, -1); /* { dg-warning "rotate count is negative" "" { target bitint } } */
+  __builtin_stdc_rotate_left ((unsigned _BitInt(59)) 0, -1); /* { dg-warning "rotate count is negative" "" { target bitint } } */
+  __builtin_stdc_rotate_right ((unsigned _BitInt(59)) 0, -1); /* { dg-warning "rotate count is negative" "" { target bitint } } */
+#endif
+#if __BITINT_MAXWIDTH__ >= 575
+  __builtin_stdc_rotate_left ((unsigned _BitInt(525)) 0, -1); /* { dg-warning "rotate count is negative" "" { target bitint575 } } */
+  __builtin_stdc_rotate_right ((unsigned _BitInt(525)) 0, -1); /* { dg-warning "rotate count is negative" "" { target bitint575 } } */
+#endif
+}