Skip to content
Open
36 changes: 36 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3701,6 +3701,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
6 changes: 6 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ 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. Implicit conversion is supported for
class/struct types with conversion operators.

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 @@ -773,12 +773,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
39 changes: 29 additions & 10 deletions clang/lib/AST/ByteCode/InterpBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ static void pushInteger(InterpState &S, const APSInt &Val, QualType QT) {
QT->isUnsignedIntegerOrEnumerationType());
OptPrimType T = S.getContext().classify(QT);
assert(T);

unsigned BitWidth = S.getASTContext().getTypeSize(QT);
unsigned BitWidth = S.getASTContext().getIntWidth(QT);

if (T == PT_IntAPS) {
auto Result = S.allocAP<IntegralAP<true>>(BitWidth);
Expand Down Expand Up @@ -4110,29 +4109,49 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
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:
case Builtin::BI_lrotl:
case Builtin::BI_rotl64:
return interp__builtin_elementwise_int_binop(
S, OpPC, Call, [](const APSInt &Value, const APSInt &Amount) {
return Value.rotl(Amount);
});

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: // Microsoft variants of rotate right
case Builtin::BI_rotr16:
case Builtin::BI_rotr:
case Builtin::BI_lrotr:
case Builtin::BI_rotr64:
case Builtin::BI_rotr64: {
// Determine if this is a rotate right operation
bool IsRotateRight;
switch (BuiltinID) {
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:
IsRotateRight = true;
break;
default:
IsRotateRight = false;
break;
}

return interp__builtin_elementwise_int_binop(
S, OpPC, Call, [](const APSInt &Value, const APSInt &Amount) {
return Value.rotr(Amount);
S, OpPC, Call, [IsRotateRight](const APSInt &Value, APSInt Amount) {
Amount = NormalizeRotateAmount(Value, Amount);
return IsRotateRight ? Value.rotr(Amount.getZExtValue())
: Value.rotl(Amount.getZExtValue());
});
}

case Builtin::BI__builtin_ffs:
case Builtin::BI__builtin_ffsl:
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/AST/ExprConstShared.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@

namespace llvm {
class APFloat;
class APInt;
class APSInt;
class APInt;
}
namespace clang {
class QualType;
Expand Down Expand Up @@ -81,5 +81,7 @@ uint8_t GFNIMultiplicativeInverse(uint8_t Byte);
uint8_t GFNIMul(uint8_t AByte, uint8_t BByte);
uint8_t GFNIAffine(uint8_t XByte, const llvm::APInt &AQword,
const llvm::APSInt &Imm, bool Inverse = false);
llvm::APSInt NormalizeRotateAmount(const llvm::APSInt &Value,
const llvm::APSInt &Amount);

#endif
86 changes: 69 additions & 17 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16336,34 +16336,46 @@ 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:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Per https://discourse.llvm.org/t/updating-expectations-for-the-new-constexpr-engine/88853, please also update clang/lib/AST/ByteCode/InterpBuiltin.cpp .

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 have added the implementation in InterpBuiltin.cpp

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 +16343 to +16349
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:
case Builtin::BI_lrotr:
case Builtin::BI_rotr64: {
APSInt Val, Amt;
if (!EvaluateInteger(E->getArg(0), Val, Info) ||
!EvaluateInteger(E->getArg(1), Amt, Info))
APSInt Value, Amount;
if (!EvaluateInteger(E->getArg(0), Value, Info) ||
!EvaluateInteger(E->getArg(1), Amount, Info))
return false;

return Success(Val.rotr(Amt), E);
Amount = NormalizeRotateAmount(Value, Amount);

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(Value.rotr(Amount.getZExtValue()), Value.isUnsigned()), E);
default:
return Success(
APSInt(Value.rotl(Amount.getZExtValue()), Value.isUnsigned()), E);
}
}

case Builtin::BI__builtin_elementwise_add_sat: {
Expand Down Expand Up @@ -19741,6 +19753,46 @@ void HandleComplexComplexDiv(APFloat A, APFloat B, APFloat C, APFloat D,
}
}

APSInt NormalizeRotateAmount(const APSInt &Value, const APSInt &Amount) {
// Normalize shift amount to [0, BitWidth) range to match runtime behavior
APSInt NormAmt = Amount;
unsigned BitWidth = Value.getBitWidth();
unsigned AmtBitWidth = NormAmt.getBitWidth();
if (BitWidth == 1) {
// Rotating a 1-bit value is always a no-op
NormAmt = APSInt(APInt(AmtBitWidth, 0), NormAmt.isUnsigned());
} else if (BitWidth == 2) {
// For 2-bit values: rotation amount is 0 or 1 based on
// whether the amount is even or odd. We can't use srem here because
// the divisor (2) would be misinterpreted as -2 in 2-bit signed arithmetic.
NormAmt =
APSInt(APInt(AmtBitWidth, NormAmt[0] ? 1 : 0), NormAmt.isUnsigned());
} else {
APInt Divisor;
if (AmtBitWidth > BitWidth) {
Divisor = llvm::APInt(AmtBitWidth, BitWidth);
} else {
Divisor = llvm::APInt(BitWidth, BitWidth);
if (AmtBitWidth < BitWidth) {
NormAmt = NormAmt.extend(BitWidth);
}
}

// Normalize to [0, BitWidth)
if (NormAmt.isSigned()) {
NormAmt = APSInt(NormAmt.srem(Divisor), /*isUnsigned=*/false);
if (NormAmt.isNegative()) {
APSInt SignedDivisor(Divisor, /*isUnsigned=*/false);
NormAmt += SignedDivisor;
}
} else {
NormAmt = APSInt(NormAmt.urem(Divisor), /*isUnsigned=*/true);
}
}

return NormAmt;
}

bool ComplexExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) {
if (E->isPtrMemOp() || E->isAssignmentOp() || E->getOpcode() == BO_Comma)
return ExprEvaluatorBaseTy::VisitBinaryOperator(E);
Expand Down
51 changes: 49 additions & 2 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2488,12 +2488,57 @@ 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();

// Normalize shift amount to [0, BitWidth) range to match runtime behavior.
// This matches the algorithm in ExprConstant.cpp for constant evaluation.
if (BitWidth == 1) {
// Rotating a 1-bit value is always a no-op
ShiftAmt = ConstantInt::get(ShiftTy, 0);
} else if (BitWidth == 2) {
// For 2-bit values: rotation amount is 0 or 1 based on
// whether the amount is even or odd. We can't use srem here because
// the divisor (2) would be misinterpreted as -2 in 2-bit signed arithmetic.
llvm::Value *One = ConstantInt::get(ShiftTy, 1);
ShiftAmt = Builder.CreateAnd(ShiftAmt, One);
} else {
unsigned ShiftAmtBitWidth = ShiftTy->getIntegerBitWidth();
bool ShiftAmtIsSigned = E->getArg(1)->getType()->isSignedIntegerType();

// Choose the wider type for the divisor to avoid truncation
llvm::Type *DivisorTy = ShiftAmtBitWidth > BitWidth ? ShiftTy : Ty;
llvm::Value *Divisor = ConstantInt::get(DivisorTy, BitWidth);

// Extend ShiftAmt to match Divisor width if needed
if (ShiftAmtBitWidth < DivisorTy->getIntegerBitWidth()) {
ShiftAmt = Builder.CreateIntCast(ShiftAmt, DivisorTy, ShiftAmtIsSigned);
}

// Normalize to [0, BitWidth)
llvm::Value *RemResult;
if (ShiftAmtIsSigned) {
RemResult = Builder.CreateSRem(ShiftAmt, Divisor);
// Signed remainder can be negative, convert to positive equivalent
llvm::Value *Zero = ConstantInt::get(DivisorTy, 0);
llvm::Value *IsNegative = Builder.CreateICmpSLT(RemResult, Zero);
llvm::Value *PositiveShift = Builder.CreateAdd(RemResult, Divisor);
ShiftAmt = Builder.CreateSelect(IsNegative, PositiveShift, RemResult);
} else {
ShiftAmt = Builder.CreateURem(ShiftAmt, Divisor);
}
}

// Convert to the source type if needed
if (ShiftAmt->getType() != Ty) {
ShiftAmt = Builder.CreateIntCast(ShiftAmt, Ty, false);
}

// 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 @@ -3611,6 +3656,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 @@ -3622,6 +3668,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
Loading
Loading