[clang] [clang] Implement __builtin_rotate{left,right}g (PR #160259)

NagaChaitanya Vellanki via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 24 05:35:23 PDT 2025


https://github.com/chaitanyav updated https://github.com/llvm/llvm-project/pull/160259

>From 2abeeade247556dcff1dff4f6e12599369155884 Mon Sep 17 00:00:00 2001
From: NagaChaitanya Vellanki <pnagato at protonmail.com>
Date: Tue, 23 Sep 2025 02:17:49 -0700
Subject: [PATCH] [clang] Implement __builtin_stdc_rotate_{left,right}

Resolves #122819
---
 clang/docs/LanguageExtensions.rst             | 29 ++++++++
 clang/include/clang/Basic/Builtins.td         | 12 +++
 clang/lib/AST/ExprConstant.cpp                |  2 +
 clang/lib/CodeGen/CGBuiltin.cpp               |  2 +
 clang/lib/Sema/SemaChecking.cpp               | 48 ++++++++++++
 clang/test/CodeGen/builtin-rotate.c           | 73 +++++++++++++++++++
 clang/test/Sema/builtin-stdc-rotate.c         | 20 +++++
 .../SemaCXX/constexpr-builtin-stdc-rotate.cpp | 47 ++++++++++++
 8 files changed, 233 insertions(+)
 create mode 100644 clang/test/Sema/builtin-stdc-rotate.c
 create mode 100644 clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 25f4e3b3fbd26..4c5f8d03558cd 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -3679,6 +3679,35 @@ the arguments. Both arguments and the result have the bitwidth specified
 by the name of the builtin. These builtins can be used within constant
 expressions.
 
+``__builtin_stdc_rotate_left`` and ``__builtin_stdc_rotate_right``
+------------------------------------------------------------------
+
+**Syntax**:
+
+.. code-block:: c
+
+    __builtin_stdc_rotate_left(value, count)
+    __builtin_stdc_rotate_right(value, count)
+
+**Description**:
+
+These builtins rotate the bits in ``value`` by ``count`` positions. The
+``__builtin_stdc_rotate_left`` builtin rotates bits to the left, while
+``__builtin_stdc_rotate_right`` rotates bits to the right. These builtins
+accept any unsigned integer type, including ``_BitInt`` types. The rotation
+count is taken modulo the bit-width of the value being rotated. These builtins
+can be used within constant expressions.
+
+**Example of use**:
+
+.. code-block:: c
+
+  unsigned char rotated_left = __builtin_stdc_rotate_left((unsigned char)0xB1, 3);
+  unsigned int rotated_right = __builtin_stdc_rotate_right(0x12345678U, 8);
+
+  unsigned _BitInt(128) big_val = 0x123456789ULL;
+  unsigned _BitInt(128) rotated = __builtin_stdc_rotate_left(big_val, 5);
+
 ``__builtin_unreachable``
 -------------------------
 
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 35d2c3e19fdf9..49987df6a380b 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -767,12 +767,24 @@ def RotateLeft : BitInt8_16_32_64BuiltinsTemplate, Builtin {
   let Prototype = "T(T, T)";
 }
 
+def StdcRotateLeft : Builtin {
+  let Spellings = ["__builtin_stdc_rotate_left"];
+  let Attributes = [NoThrow, Const, Constexpr, CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
 def RotateRight : BitInt8_16_32_64BuiltinsTemplate, Builtin {
   let Spellings = ["__builtin_rotateright"];
   let Attributes = [NoThrow, Const, Constexpr];
   let Prototype = "T(T, T)";
 }
 
+def StdcRotateRight : Builtin {
+  let Spellings = ["__builtin_stdc_rotate_right"];
+  let Attributes = [NoThrow, Const, Constexpr, CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
 // Random GCC builtins
 // FIXME: The builtins marked FunctionWithBuiltinPrefix below should be
 //        merged with the library definitions. They are currently not because
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d10e2afeb2341..4780f1b73232d 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -14077,6 +14077,7 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
   case Builtin::BI__builtin_rotateleft16:
   case Builtin::BI__builtin_rotateleft32:
   case Builtin::BI__builtin_rotateleft64:
+  case Builtin::BI__builtin_stdc_rotate_left:
   case Builtin::BI_rotl8: // Microsoft variants of rotate right
   case Builtin::BI_rotl16:
   case Builtin::BI_rotl:
@@ -14094,6 +14095,7 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
   case Builtin::BI__builtin_rotateright16:
   case Builtin::BI__builtin_rotateright32:
   case Builtin::BI__builtin_rotateright64:
+  case Builtin::BI__builtin_stdc_rotate_right:
   case Builtin::BI_rotr8: // Microsoft variants of rotate right
   case Builtin::BI_rotr16:
   case Builtin::BI_rotr:
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index f7c3dea257d50..9f96b6f940b5c 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -3642,6 +3642,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
   case Builtin::BI__builtin_rotateleft16:
   case Builtin::BI__builtin_rotateleft32:
   case Builtin::BI__builtin_rotateleft64:
+  case Builtin::BI__builtin_stdc_rotate_left:
   case Builtin::BI_rotl8: // Microsoft variants of rotate left
   case Builtin::BI_rotl16:
   case Builtin::BI_rotl:
@@ -3653,6 +3654,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
   case Builtin::BI__builtin_rotateright16:
   case Builtin::BI__builtin_rotateright32:
   case Builtin::BI__builtin_rotateright64:
+  case Builtin::BI__builtin_stdc_rotate_right:
   case Builtin::BI_rotr8: // Microsoft variants of rotate right
   case Builtin::BI_rotr16:
   case Builtin::BI_rotr:
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index e73e81c440cc1..43a77a53fb436 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2267,6 +2267,48 @@ static bool BuiltinCountZeroBitsGeneric(Sema &S, CallExpr *TheCall) {
   return false;
 }
 
+/// Checks that __builtin_stdc_rotate_{left,right} was called with two
+/// arguments, that the first argument is an unsigned integer type, and that
+/// the second argument is an integer type.
+static bool BuiltinRotateGeneric(Sema &S, CallExpr *TheCall) {
+  if (S.checkArgCount(TheCall, 2))
+    return true;
+
+  ExprResult Arg0Res = S.DefaultLvalueConversion(TheCall->getArg(0));
+  if (Arg0Res.isInvalid())
+    return true;
+
+  Expr *Arg0 = Arg0Res.get();
+  TheCall->setArg(0, Arg0);
+
+  QualType Arg0Ty = Arg0->getType();
+
+  if (!Arg0Ty->isUnsignedIntegerType()) {
+    S.Diag(Arg0->getBeginLoc(), diag::err_builtin_invalid_arg_type)
+        << 1 << /* scalar */ 1 << /* unsigned integer ty */ 3 << /* no fp */ 0
+        << Arg0Ty;
+    return true;
+  }
+
+  ExprResult Arg1Res = S.DefaultLvalueConversion(TheCall->getArg(1));
+  if (Arg1Res.isInvalid())
+    return true;
+
+  Expr *Arg1 = Arg1Res.get();
+  TheCall->setArg(1, Arg1);
+
+  QualType Arg1Ty = Arg1->getType();
+
+  if (!Arg1Ty->isIntegerType()) {
+    S.Diag(Arg1->getBeginLoc(), diag::err_builtin_invalid_arg_type)
+        << 2 << /* scalar */ 1 << /* integer ty */ 2 << /* no fp */ 0 << Arg1Ty;
+    return true;
+  }
+
+  TheCall->setType(Arg0Ty);
+  return false;
+}
+
 static bool CheckMaskedBuiltinArgs(Sema &S, Expr *MaskArg, Expr *PtrArg,
                                    unsigned Pos, bool Vector = true) {
   QualType MaskTy = MaskArg->getType();
@@ -3416,6 +3458,12 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
       return ExprError();
     break;
 
+  case Builtin::BI__builtin_stdc_rotate_left:
+  case Builtin::BI__builtin_stdc_rotate_right:
+    if (BuiltinRotateGeneric(*this, TheCall))
+      return ExprError();
+    break;
+
   case Builtin::BI__builtin_allow_runtime_check: {
     Expr *Arg = TheCall->getArg(0);
     // Check if the argument is a string literal.
diff --git a/clang/test/CodeGen/builtin-rotate.c b/clang/test/CodeGen/builtin-rotate.c
index 8fc1701c6c9bb..5a3e5501e9c45 100644
--- a/clang/test/CodeGen/builtin-rotate.c
+++ b/clang/test/CodeGen/builtin-rotate.c
@@ -32,6 +32,43 @@ unsigned long long rotl64(unsigned long long x, long long y) {
   return __builtin_rotateleft64(x, y);
 }
 
+// CHECK-LABEL: test_builtin_stdc_rotate_left
+void test_builtin_stdc_rotate_left(unsigned char uc, unsigned short us,
+                               unsigned int ui, unsigned long ul,
+                               unsigned long long ull, unsigned __int128 ui128,
+                               unsigned _BitInt(128) ubi128) {
+
+volatile unsigned char result_uc;
+volatile unsigned int result_ui;
+volatile unsigned short result_us;
+volatile unsigned long result_ul;
+volatile unsigned long long result_ull;
+volatile unsigned __int128 result_ui128;
+volatile unsigned _BitInt(128) result_ubi128;
+
+  // CHECK: call i8 @llvm.fshl.i8(i8 %{{.*}}, i8 %{{.*}}, i8 3)
+  result_uc = __builtin_stdc_rotate_left(uc, 3);
+
+  // CHECK: call i16 @llvm.fshl.i16(i16 %{{.*}}, i16 %{{.*}}, i16 5)
+  result_us = __builtin_stdc_rotate_left(us, 5);
+
+  // CHECK: call i32 @llvm.fshl.i32(i32 %{{.*}}, i32 %{{.*}}, i32 8)
+  result_ui = __builtin_stdc_rotate_left(ui, 8);
+
+  // CHECK: call i{{32|64}} @llvm.fshl.i{{32|64}}(i{{32|64}} %{{.*}}, i{{32|64}} %{{.*}}, i{{32|64}} 8)
+  result_ul = __builtin_stdc_rotate_left(ul, 8);
+
+  // CHECK: call i64 @llvm.fshl.i64(i64 %{{.*}}, i64 %{{.*}}, i64 16)
+  result_ull = __builtin_stdc_rotate_left(ull, 16);
+
+  // CHECK: call i128 @llvm.fshl.i128(i128 %{{.*}}, i128 %{{.*}}, i128 32)
+  result_ui128 = __builtin_stdc_rotate_left(ui128, 32);
+
+  // CHECK: call i128 @llvm.fshl.i128(i128 %{{.*}}, i128 %{{.*}}, i128 64)
+  result_ubi128 = __builtin_stdc_rotate_left(ubi128, 64);
+
+}
+
 char rotr8(char x, char y) {
 // CHECK-LABEL: rotr8
 // CHECK: [[F:%.*]] = call i8 @llvm.fshr.i8(i8 [[X:%.*]], i8 [[X]], i8 [[Y:%.*]])
@@ -64,3 +101,39 @@ long long rotr64(long long x, unsigned long long y) {
   return __builtin_rotateright64(x, y);
 }
 
+// CHECK-LABEL: test_builtin_stdc_rotate_right
+void test_builtin_stdc_rotate_right(unsigned char uc, unsigned short us,
+                               unsigned int ui, unsigned long ul,
+                               unsigned long long ull, unsigned __int128 ui128,
+                               unsigned _BitInt(128) ubi128) {
+
+  volatile unsigned char result_uc;
+  volatile unsigned int result_ui;
+  volatile unsigned short result_us;
+  volatile unsigned long result_ul;
+  volatile unsigned long long result_ull;
+  volatile unsigned __int128 result_ui128;
+  volatile unsigned _BitInt(128) result_ubi128;
+
+  // CHECK: call i8 @llvm.fshr.i8(i8 %{{.*}}, i8 %{{.*}}, i8 3)
+  result_uc = __builtin_stdc_rotate_right(uc, 3);
+
+  // CHECK: call i16 @llvm.fshr.i16(i16 %{{.*}}, i16 %{{.*}}, i16 5)
+  result_us = __builtin_stdc_rotate_right(us, 5);
+
+  // CHECK: call i32 @llvm.fshr.i32(i32 %{{.*}}, i32 %{{.*}}, i32 8)
+  result_ui = __builtin_stdc_rotate_right(ui, 8);
+
+  // CHECK: call i{{32|64}} @llvm.fshr.i{{32|64}}(i{{32|64}} %{{.*}}, i{{32|64}} %{{.*}}, i{{32|64}} 8)
+  result_ul = __builtin_stdc_rotate_right(ul, 8);
+
+  // CHECK: call i64 @llvm.fshr.i64(i64 %{{.*}}, i64 %{{.*}}, i64 16)
+  result_ull = __builtin_stdc_rotate_right(ull, 16);
+
+  // CHECK: call i128 @llvm.fshr.i128(i128 %{{.*}}, i128 %{{.*}}, i128 32)
+  result_ui128 = __builtin_stdc_rotate_right(ui128, 32);
+
+  // CHECK: call i128 @llvm.fshr.i128(i128 %{{.*}}, i128 %{{.*}}, i128 64)
+  result_ubi128 = __builtin_stdc_rotate_right(ubi128, 64);
+
+}
diff --git a/clang/test/Sema/builtin-stdc-rotate.c b/clang/test/Sema/builtin-stdc-rotate.c
new file mode 100644
index 0000000000000..0b11cb25d2773
--- /dev/null
+++ b/clang/test/Sema/builtin-stdc-rotate.c
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+char stdc_rotateleft1[__builtin_stdc_rotate_left((unsigned char)0xB1, 3) == 0x8D ? 1 : -1];
+char stdc_rotateright1[__builtin_stdc_rotate_right((unsigned char)0xB1, 3) == 0x36 ? 1 : -1];
+char stdc_rotateleft2[__builtin_stdc_rotate_left((unsigned short)0x1234, 4) == 0x2341 ? 1 : -1];
+char stdc_rotateright2[__builtin_stdc_rotate_right((unsigned short)0x1234, 4) == 0x4123 ? 1 : -1];
+char stdc_rotateleft3[__builtin_stdc_rotate_left(0x12345678U, 8) == 0x34567812U ? 1 : -1];
+char stdc_rotateright3[__builtin_stdc_rotate_right(0x12345678U, 8) == 0x78123456U ? 1 : -1];
+char stdc_rotateleft4[__builtin_stdc_rotate_left(0x123456789ABCDEF0ULL, 16) == 0x56789ABCDEF01234ULL ? 1 : -1];
+char stdc_rotateright4[__builtin_stdc_rotate_right(0x123456789ABCDEF0ULL, 16) == 0xDEF0123456789ABCULL ? 1 : -1];
+
+#ifdef __SIZEOF_INT128__
+char stdc_rotateleft5[__builtin_stdc_rotate_left((unsigned __int128)1, 127) == ((unsigned __int128)1 << 127) ? 1 : -1];
+char stdc_rotateright5[__builtin_stdc_rotate_right(((unsigned __int128)1 << 127), 127) == (unsigned __int128)1 ? 1 : -1];
+#endif
+
+char stdc_rotateleft6[__builtin_stdc_rotate_left((unsigned _BitInt(37))1, 36) == ((unsigned _BitInt(37))1 << 36) ? 1 : -1];
+char stdc_rotateright6[__builtin_stdc_rotate_right((unsigned _BitInt(37))1, 36) == ((unsigned _BitInt(37))1 << 1) ? 1 : -1];
+char stdc_rotateleft7[__builtin_stdc_rotate_left((unsigned _BitInt(128))1, 1) == ((unsigned _BitInt(128))2) ? 1 : -1];
diff --git a/clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp b/clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp
new file mode 100644
index 0000000000000..de5aca3aa78c0
--- /dev/null
+++ b/clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp
@@ -0,0 +1,47 @@
+// RUN: %clang_cc1 -std=c++14 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+namespace test_constexpr_stdc_rotate {
+
+static_assert(__builtin_stdc_rotate_left((unsigned char)0b10110001, 3) == (unsigned char)0b10001101, "");
+static_assert(__builtin_stdc_rotate_left((unsigned short)0x1234, 4) == (unsigned short)0x2341, "");
+static_assert(__builtin_stdc_rotate_left(0x12345678U, 8) == 0x34567812U, "");
+static_assert(__builtin_stdc_rotate_left(0x123456789ABCDEF0ULL, 16) == 0x56789ABCDEF01234ULL, "");
+static_assert(__builtin_stdc_rotate_right((unsigned char)0b10110001, 3) == (unsigned char)0b00110110, "");
+static_assert(__builtin_stdc_rotate_right(0x12345678U, 8) == 0x78123456U, "");
+static_assert(__builtin_stdc_rotate_right(0x123456789ABCDEF0ULL, 16) == 0xDEF0123456789ABCULL, "");
+static_assert(__builtin_stdc_rotate_left(0x12345678U, 0) == 0x12345678U, "");
+static_assert(__builtin_stdc_rotate_left(0x12345678U, 32) == 0x12345678U, "");
+static_assert(__builtin_stdc_rotate_left(0x80000000U, 1) == 0x00000001U, "");
+static_assert(__builtin_stdc_rotate_right(__builtin_stdc_rotate_left(0x12345678U, 8), 8) == 0x12345678U, "");
+static_assert(__builtin_stdc_rotate_left(0x12345678U, 40) == __builtin_stdc_rotate_left(0x12345678U, 8), "");
+static_assert(__builtin_stdc_rotate_left(0x00000000U, 7) == 0x00000000U, "");
+static_assert(__builtin_stdc_rotate_left((unsigned char)0x01, 2) == (unsigned char)0x04, "");
+static_assert(__builtin_stdc_rotate_left((unsigned char)0xAA, 1) == (unsigned char)0x55, "");
+static_assert(__builtin_stdc_rotate_left((unsigned char)0x12, 4) == (unsigned char)0x21, "");
+static_assert(__builtin_stdc_rotate_left((unsigned short)0x1234, 4) == (unsigned short)0x2341, "");
+static_assert(__builtin_stdc_rotate_left(0x12345678U, 4) == 0x23456781U, "");
+
+#ifdef __SIZEOF_INT128__
+namespace test_int128 {
+
+static_assert(__builtin_stdc_rotate_left((unsigned __int128)1, 127) == (unsigned __int128)1 << 127, "");
+
+constexpr unsigned __int128 test_pattern = 0x123456789ABCDEF0ULL;
+static_assert(__builtin_stdc_rotate_left(test_pattern, 1) == test_pattern << 1, "");
+
+} // namespace test_int128
+#endif
+
+namespace test_bitint {
+
+static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(37))1, 36) == ((unsigned _BitInt(37))1 << 36), "");
+
+constexpr unsigned _BitInt(128) bi128_pattern = 0x123456789ABCDEF0ULL;
+static_assert(__builtin_stdc_rotate_left(bi128_pattern, 1) == bi128_pattern << 1, "");
+
+static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(3))0b101, 1) == (unsigned _BitInt(3))0b011, "");
+
+} // namespace test_bitint
+
+} // namespace test_constexpr_stdc_rotate



More information about the cfe-commits mailing list