Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3680,6 +3680,42 @@ 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

T __builtin_stdc_rotate_left(T value, count)
T __builtin_stdc_rotate_right(T value, count)

where ``T`` is any unsigned integer type and ``count`` is any integer type.

**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. The first
argument (``value``) must be an unsigned integer type, including ``_BitInt`` types.
The second argument (``count``) can be any integer type. The rotation count is
normalized modulo the bit-width of the value being rotated, with negative
counts converted to equivalent positive rotations (e.g., rotating left
by ``-1`` is equivalent to rotating left by ``BitWidth-1``). 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 char neg_rotate = __builtin_stdc_rotate_left((unsigned char)0xB1, -1);

unsigned _BitInt(20) value = 0xABCDE;
unsigned _BitInt(20) rotated = __builtin_stdc_rotate_left(value, 5);

``__builtin_unreachable``
-------------------------

Expand Down
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ Non-comprehensive list of changes in this release

- Clang now rejects the invalid use of ``constexpr`` with ``auto`` and an explicit type in C. (#GH163090)

- Added ``__builtin_stdc_rotate_left`` and ``__builtin_stdc_rotate_right``
for bit rotation of unsigned integers including ``_BitInt`` types. Rotation
counts are normalized modulo the bit-width and support negative values.
Usable in constant expressions.

New Compiler Flags
------------------
- New option ``-fno-sanitize-debug-trap-reasons`` added to disable emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``).
Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -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(...)";
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pinskia Did these name ship in gcc already? the "stdc" is a bit novel and does not add much

Copy link

@pinskia pinskia Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it shipped with GCC 15.1.0: https://gcc.gnu.org/onlinedocs/gcc-15.1.0/gcc/Bit-Operation-Builtins.html#index-_005f_005fbuiltin_005fstdc_005frotate_005fleft

stdc is there because that are the function names in specified in C23's stdbit.h header. GCC just added _builtin in front of them here. The GCC builtins in this case is supposed to be exactly the same constaints as the C23's builtins except for allowing for any unsigned integer (standard, extended or bit-precise).

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
Expand Down
60 changes: 46 additions & 14 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14720,23 +14720,17 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
case Builtin::BI__builtin_rotateleft16:
case Builtin::BI__builtin_rotateleft32:
case Builtin::BI__builtin_rotateleft64:
case Builtin::BI_rotl8: // Microsoft variants of rotate right
case Builtin::BI_rotl16:
case Builtin::BI_rotl:
case Builtin::BI_lrotl:
case Builtin::BI_rotl64: {
APSInt Val, Amt;
if (!EvaluateInteger(E->getArg(0), Val, Info) ||
!EvaluateInteger(E->getArg(1), Amt, Info))
return false;

return Success(Val.rotl(Amt), E);
}

case Builtin::BI__builtin_rotateright8:
case Builtin::BI__builtin_rotateright16:
case Builtin::BI__builtin_rotateright32:
case Builtin::BI__builtin_rotateright64:
case Builtin::BI__builtin_stdc_rotate_left:
case Builtin::BI__builtin_stdc_rotate_right:
case Builtin::BI_rotl8: // Microsoft variants of rotate left
case Builtin::BI_rotl16:
case Builtin::BI_rotl:
case Builtin::BI_lrotl:
case Builtin::BI_rotl64:
Comment on lines +14727 to +14733
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are changing the behavior of existing builtins, is that intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought these generics will also use (unsigned, unsigned), but they evolved during the review. Those functions will still work as expected. I understand the concern (now that we are allowing more types) please let me know if we have to separate them out.

case Builtin::BI_rotr8: // Microsoft variants of rotate right
case Builtin::BI_rotr16:
case Builtin::BI_rotr:
Expand All @@ -14747,7 +14741,45 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
!EvaluateInteger(E->getArg(1), Amt, Info))
return false;

return Success(Val.rotr(Amt), E);
// Normalize shift amount to [0, BitWidth) range to match runtime behavior
unsigned BitWidth = Val.getBitWidth();
unsigned AmtBitWidth = Amt.getBitWidth();
if (BitWidth == 1) {
// if BitWidth is 1, Amt % Divisor = 0
// No need for rotation
Amt = APSInt(APInt(AmtBitWidth, 0), Amt.isUnsigned());
} else {
APSInt Divisor;
if (AmtBitWidth > BitWidth) {
Divisor = APSInt(llvm::APInt(AmtBitWidth, BitWidth), Amt.isUnsigned());
} else {
Divisor = APSInt(llvm::APInt(BitWidth, BitWidth), Amt.isUnsigned());
if (AmtBitWidth < BitWidth) {
Amt = Amt.extend(BitWidth);
}
}

Amt = Amt % Divisor;
if (Amt.isNegative()) {
Amt += Divisor;
}
}

switch (BuiltinOp) {
case Builtin::BI__builtin_rotateright8:
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:
case Builtin::BI_rotr16:
case Builtin::BI_rotr:
case Builtin::BI_lrotr:
case Builtin::BI_rotr64:
return Success(APSInt(Val.rotr(Amt.getZExtValue()), Val.isUnsigned()), E);
default:
return Success(APSInt(Val.rotl(Amt.getZExtValue()), Val.isUnsigned()), E);
}
}

case Builtin::BI__builtin_elementwise_add_sat: {
Expand Down
35 changes: 33 additions & 2 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2492,12 +2492,41 @@ RValue CodeGenFunction::emitRotate(const CallExpr *E, bool IsRotateRight) {
// The builtin's shift arg may have a different type than the source arg and
// result, but the LLVM intrinsic uses the same type for all values.
llvm::Type *Ty = Src->getType();
ShiftAmt = Builder.CreateIntCast(ShiftAmt, Ty, false);
llvm::Type *ShiftTy = ShiftAmt->getType();

unsigned BitWidth = Ty->getIntegerBitWidth();

if (!llvm::isPowerOf2_32(BitWidth) &&
E->getArg(1)->getType()->isSignedIntegerType()) {
// converting BitWidth to the type of ShiftAmt
llvm::Value *BitWidthInShiftTy = ConstantInt::get(ShiftTy, BitWidth);

// if ShiftAmt is negative, normalize ShiftAmt to [0, BitWidth - 1]
llvm::Value *RemResult = Builder.CreateSRem(ShiftAmt, BitWidthInShiftTy);
llvm::Value *PositiveShift =
Builder.CreateAdd(RemResult, BitWidthInShiftTy);

llvm::Value *Zero = ConstantInt::get(ShiftTy, 0);
llvm::Value *IsRemNegative = Builder.CreateICmpSLT(RemResult, Zero);

ShiftAmt = Builder.CreateSelect(IsRemNegative, PositiveShift, RemResult);
}

unsigned ShiftAmtBitWidth = ShiftTy->getIntegerBitWidth();
if (ShiftAmtBitWidth > BitWidth) {
llvm::Value *BitWidthInShiftTy = ConstantInt::get(ShiftTy, BitWidth);
ShiftAmt = Builder.CreateURem(ShiftAmt, BitWidthInShiftTy);
ShiftAmt = Builder.CreateIntCast(ShiftAmt, Ty, false);
} else {
llvm::Value *BitWidthInShiftTy = ConstantInt::get(Ty, BitWidth);
ShiftAmt = Builder.CreateIntCast(ShiftAmt, Ty, false);
ShiftAmt = Builder.CreateURem(ShiftAmt, BitWidthInShiftTy);
}

// Rotate is a special case of LLVM funnel shift - 1st 2 args are the same.
unsigned IID = IsRotateRight ? Intrinsic::fshr : Intrinsic::fshl;
Function *F = CGM.getIntrinsic(IID, Ty);
return RValue::get(Builder.CreateCall(F, { Src, Src, ShiftAmt }));
return RValue::get(Builder.CreateCall(F, {Src, Src, ShiftAmt}));
}

// Map math builtins for long-double to f128 version.
Expand Down Expand Up @@ -3642,6 +3671,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:
Expand All @@ -3653,6 +3683,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:
Expand Down
67 changes: 67 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,67 @@ 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()) {
ExprResult ConvArg0Res = S.PerformImplicitConversion(
TheCall->getArg(0), S.Context.UnsignedIntTy, AssignmentAction::Passing);
if (ConvArg0Res.isUsable()) {
Arg0 = ConvArg0Res.get();
Arg0Ty = Arg0->getType();
TheCall->setArg(0, Arg0);
}
}

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()) {
ExprResult ConvArg1Res = S.PerformImplicitConversion(
TheCall->getArg(1), S.Context.IntTy, AssignmentAction::Passing);
if (ConvArg1Res.isUsable()) {
Arg1 = ConvArg1Res.get();
Arg1Ty = Arg1->getType();
TheCall->setArg(1, Arg1);
}
}

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 AllowConst,
bool AllowAS) {
Expand Down Expand Up @@ -3458,6 +3519,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.
Expand Down
Loading