Skip to content

Commit d799a66

Browse files
committed
[clang] Implement __builtin_stdc_rotate_left and __builtin_stdc_rotate_right
This patch adds type-generic rotate builtins that accept any unsigned integer type. These builtins provide: - Support for all unsigned integer types, including _BitInt - Constexpr evaluation capability - Automatic normalization of rotation counts modulo the bit-width - Proper handling of negative rotation counts (converted to equivalent positive rotations in the opposite direction) - Implicit conversion support for both arguments, allowing bool, float, and types with conversion operators (not limited to record types) The builtins follow C23 naming conventions. Resolves #122819
1 parent e706a30 commit d799a66

File tree

9 files changed

+737
-16
lines changed

9 files changed

+737
-16
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3680,6 +3680,41 @@ the arguments. Both arguments and the result have the bitwidth specified
36803680
by the name of the builtin. These builtins can be used within constant
36813681
expressions.
36823682
3683+
``__builtin_stdc_rotate_left`` and ``__builtin_stdc_rotate_right``
3684+
------------------------------------------------------------------
3685+
3686+
**Syntax**:
3687+
3688+
.. code-block:: c
3689+
3690+
T __builtin_stdc_rotate_left(T value, count)
3691+
T __builtin_stdc_rotate_right(T value, count)
3692+
3693+
where ``T`` is any unsigned integer type and ``count`` is any integer type.
3694+
3695+
**Description**:
3696+
3697+
These builtins rotate the bits in ``value`` by ``count`` positions. The
3698+
``__builtin_stdc_rotate_left`` builtin rotates bits to the left, while
3699+
``__builtin_stdc_rotate_right`` rotates bits to the right. The first
3700+
argument (``value``) must be an unsigned integer type, including ``_BitInt`` types.
3701+
The second argument (``count``) can be any integer type. The rotation count is
3702+
normalized modulo the bit-width of the value being rotated, with negative
3703+
counts converted to equivalent positive rotations (e.g., rotating left
3704+
by ``-1`` is equivalent to rotating left by ``BitWidth-1``). These builtins can
3705+
be used within constant expressions.
3706+
3707+
**Example of use**:
3708+
3709+
.. code-block:: c
3710+
3711+
unsigned char rotated_left = __builtin_stdc_rotate_left((unsigned char)0xB1, 3);
3712+
unsigned int rotated_right = __builtin_stdc_rotate_right(0x12345678U, 8);
3713+
3714+
unsigned char neg_rotate = __builtin_stdc_rotate_left((unsigned char)0xB1, -1);
3715+
3716+
unsigned _BitInt(17) rotated_odd = __builtin_stdc_rotate_left(0x1ABCD, 5);
3717+
36833718
``__builtin_unreachable``
36843719
-------------------------
36853720

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ Non-comprehensive list of changes in this release
255255

256256
- Fixed a crash when the second argument to ``__builtin_assume_aligned`` was not constant (#GH161314)
257257

258+
- Added ``__builtin_stdc_rotate_left`` and ``__builtin_stdc_rotate_right``
259+
for bit rotation of unsigned integers including ``_BitInt`` types. Rotation
260+
counts are normalized modulo the bit-width and support negative values.
261+
Usable in constant expressions.
262+
258263
New Compiler Flags
259264
------------------
260265
- 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``).

clang/include/clang/Basic/Builtins.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,12 +767,24 @@ def RotateLeft : BitInt8_16_32_64BuiltinsTemplate, Builtin {
767767
let Prototype = "T(T, T)";
768768
}
769769

770+
def StdcRotateLeft : Builtin {
771+
let Spellings = ["__builtin_stdc_rotate_left"];
772+
let Attributes = [NoThrow, Const, Constexpr, CustomTypeChecking];
773+
let Prototype = "void(...)";
774+
}
775+
770776
def RotateRight : BitInt8_16_32_64BuiltinsTemplate, Builtin {
771777
let Spellings = ["__builtin_rotateright"];
772778
let Attributes = [NoThrow, Const, Constexpr];
773779
let Prototype = "T(T, T)";
774780
}
775781

782+
def StdcRotateRight : Builtin {
783+
let Spellings = ["__builtin_stdc_rotate_right"];
784+
let Attributes = [NoThrow, Const, Constexpr, CustomTypeChecking];
785+
let Prototype = "void(...)";
786+
}
787+
776788
// Random GCC builtins
777789
// FIXME: The builtins marked FunctionWithBuiltinPrefix below should be
778790
// merged with the library definitions. They are currently not because

clang/lib/AST/ExprConstant.cpp

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14346,23 +14346,17 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
1434614346
case Builtin::BI__builtin_rotateleft16:
1434714347
case Builtin::BI__builtin_rotateleft32:
1434814348
case Builtin::BI__builtin_rotateleft64:
14349-
case Builtin::BI_rotl8: // Microsoft variants of rotate right
14350-
case Builtin::BI_rotl16:
14351-
case Builtin::BI_rotl:
14352-
case Builtin::BI_lrotl:
14353-
case Builtin::BI_rotl64: {
14354-
APSInt Val, Amt;
14355-
if (!EvaluateInteger(E->getArg(0), Val, Info) ||
14356-
!EvaluateInteger(E->getArg(1), Amt, Info))
14357-
return false;
14358-
14359-
return Success(Val.rotl(Amt), E);
14360-
}
14361-
1436214349
case Builtin::BI__builtin_rotateright8:
1436314350
case Builtin::BI__builtin_rotateright16:
1436414351
case Builtin::BI__builtin_rotateright32:
1436514352
case Builtin::BI__builtin_rotateright64:
14353+
case Builtin::BI__builtin_stdc_rotate_left:
14354+
case Builtin::BI__builtin_stdc_rotate_right:
14355+
case Builtin::BI_rotl8: // Microsoft variants of rotate left
14356+
case Builtin::BI_rotl16:
14357+
case Builtin::BI_rotl:
14358+
case Builtin::BI_lrotl:
14359+
case Builtin::BI_rotl64:
1436614360
case Builtin::BI_rotr8: // Microsoft variants of rotate right
1436714361
case Builtin::BI_rotr16:
1436814362
case Builtin::BI_rotr:
@@ -14373,7 +14367,45 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
1437314367
!EvaluateInteger(E->getArg(1), Amt, Info))
1437414368
return false;
1437514369

14376-
return Success(Val.rotr(Amt), E);
14370+
// Normalize shift amount to [0, BitWidth) range to match runtime behavior
14371+
unsigned BitWidth = Val.getBitWidth();
14372+
unsigned AmtBitWidth = Amt.getBitWidth();
14373+
if (BitWidth == 1) {
14374+
// if BitWidth is 1, Amt % Divisor = 0
14375+
// No need for rotation
14376+
Amt = APSInt(APInt(AmtBitWidth, 0), Amt.isUnsigned());
14377+
} else {
14378+
APSInt Divisor;
14379+
if (AmtBitWidth > BitWidth) {
14380+
Divisor = APSInt(llvm::APInt(AmtBitWidth, BitWidth), Amt.isUnsigned());
14381+
} else {
14382+
Divisor = APSInt(llvm::APInt(BitWidth, BitWidth), Amt.isUnsigned());
14383+
if (AmtBitWidth < BitWidth) {
14384+
Amt = Amt.extend(BitWidth);
14385+
}
14386+
}
14387+
14388+
Amt = Amt % Divisor;
14389+
if (Amt.isNegative()) {
14390+
Amt += Divisor;
14391+
}
14392+
}
14393+
14394+
switch (BuiltinOp) {
14395+
case Builtin::BI__builtin_rotateright8:
14396+
case Builtin::BI__builtin_rotateright16:
14397+
case Builtin::BI__builtin_rotateright32:
14398+
case Builtin::BI__builtin_rotateright64:
14399+
case Builtin::BI__builtin_stdc_rotate_right:
14400+
case Builtin::BI_rotr8:
14401+
case Builtin::BI_rotr16:
14402+
case Builtin::BI_rotr:
14403+
case Builtin::BI_lrotr:
14404+
case Builtin::BI_rotr64:
14405+
return Success(APSInt(Val.rotr(Amt.getZExtValue()), Val.isUnsigned()), E);
14406+
default:
14407+
return Success(APSInt(Val.rotl(Amt.getZExtValue()), Val.isUnsigned()), E);
14408+
}
1437714409
}
1437814410

1437914411
case Builtin::BI__builtin_elementwise_add_sat: {

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2492,12 +2492,41 @@ RValue CodeGenFunction::emitRotate(const CallExpr *E, bool IsRotateRight) {
24922492
// The builtin's shift arg may have a different type than the source arg and
24932493
// result, but the LLVM intrinsic uses the same type for all values.
24942494
llvm::Type *Ty = Src->getType();
2495-
ShiftAmt = Builder.CreateIntCast(ShiftAmt, Ty, false);
2495+
llvm::Type *ShiftTy = ShiftAmt->getType();
2496+
2497+
unsigned BitWidth = Ty->getIntegerBitWidth();
2498+
2499+
if (!llvm::isPowerOf2_32(BitWidth) &&
2500+
E->getArg(1)->getType()->isSignedIntegerType()) {
2501+
// converting BitWidth to the type of ShiftAmt
2502+
llvm::Value *BitWidthInShiftTy = ConstantInt::get(ShiftTy, BitWidth);
2503+
2504+
// if ShiftAmt is negative, normalize ShiftAmt to [0, BitWidth - 1]
2505+
llvm::Value *RemResult = Builder.CreateSRem(ShiftAmt, BitWidthInShiftTy);
2506+
llvm::Value *PositiveShift =
2507+
Builder.CreateAdd(RemResult, BitWidthInShiftTy);
2508+
2509+
llvm::Value *Zero = ConstantInt::get(ShiftTy, 0);
2510+
llvm::Value *IsRemNegative = Builder.CreateICmpSLT(RemResult, Zero);
2511+
2512+
ShiftAmt = Builder.CreateSelect(IsRemNegative, PositiveShift, RemResult);
2513+
}
2514+
2515+
unsigned ShiftAmtBitWidth = ShiftTy->getIntegerBitWidth();
2516+
if (ShiftAmtBitWidth > BitWidth) {
2517+
llvm::Value *BitWidthInShiftTy = ConstantInt::get(ShiftTy, BitWidth);
2518+
ShiftAmt = Builder.CreateURem(ShiftAmt, BitWidthInShiftTy);
2519+
ShiftAmt = Builder.CreateIntCast(ShiftAmt, Ty, false);
2520+
} else {
2521+
llvm::Value *BitWidthInShiftTy = ConstantInt::get(Ty, BitWidth);
2522+
ShiftAmt = Builder.CreateIntCast(ShiftAmt, Ty, false);
2523+
ShiftAmt = Builder.CreateURem(ShiftAmt, BitWidthInShiftTy);
2524+
}
24962525

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

25032532
// Map math builtins for long-double to f128 version.
@@ -3642,6 +3671,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
36423671
case Builtin::BI__builtin_rotateleft16:
36433672
case Builtin::BI__builtin_rotateleft32:
36443673
case Builtin::BI__builtin_rotateleft64:
3674+
case Builtin::BI__builtin_stdc_rotate_left:
36453675
case Builtin::BI_rotl8: // Microsoft variants of rotate left
36463676
case Builtin::BI_rotl16:
36473677
case Builtin::BI_rotl:
@@ -3653,6 +3683,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
36533683
case Builtin::BI__builtin_rotateright16:
36543684
case Builtin::BI__builtin_rotateright32:
36553685
case Builtin::BI__builtin_rotateright64:
3686+
case Builtin::BI__builtin_stdc_rotate_right:
36563687
case Builtin::BI_rotr8: // Microsoft variants of rotate right
36573688
case Builtin::BI_rotr16:
36583689
case Builtin::BI_rotr:

clang/lib/Sema/SemaChecking.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,6 +2267,67 @@ static bool BuiltinCountZeroBitsGeneric(Sema &S, CallExpr *TheCall) {
22672267
return false;
22682268
}
22692269

2270+
/// Checks that __builtin_stdc_rotate_{left,right} was called with two
2271+
/// arguments, that the first argument is an unsigned integer type, and that
2272+
/// the second argument is an integer type.
2273+
static bool BuiltinRotateGeneric(Sema &S, CallExpr *TheCall) {
2274+
if (S.checkArgCount(TheCall, 2))
2275+
return true;
2276+
2277+
ExprResult Arg0Res = S.DefaultLvalueConversion(TheCall->getArg(0));
2278+
if (Arg0Res.isInvalid())
2279+
return true;
2280+
2281+
Expr *Arg0 = Arg0Res.get();
2282+
TheCall->setArg(0, Arg0);
2283+
2284+
QualType Arg0Ty = Arg0->getType();
2285+
if (!Arg0Ty->isUnsignedIntegerType()) {
2286+
ExprResult ConvArg0Res = S.PerformImplicitConversion(
2287+
TheCall->getArg(0), S.Context.UnsignedIntTy, AssignmentAction::Passing);
2288+
if (ConvArg0Res.isUsable()) {
2289+
Arg0 = ConvArg0Res.get();
2290+
Arg0Ty = Arg0->getType();
2291+
TheCall->setArg(0, Arg0);
2292+
}
2293+
}
2294+
2295+
if (!Arg0Ty->isUnsignedIntegerType()) {
2296+
S.Diag(Arg0->getBeginLoc(), diag::err_builtin_invalid_arg_type)
2297+
<< 1 << /* scalar */ 1 << /* unsigned integer ty */ 3 << /* no fp */ 0
2298+
<< Arg0Ty;
2299+
return true;
2300+
}
2301+
2302+
ExprResult Arg1Res = S.DefaultLvalueConversion(TheCall->getArg(1));
2303+
if (Arg1Res.isInvalid())
2304+
return true;
2305+
2306+
Expr *Arg1 = Arg1Res.get();
2307+
TheCall->setArg(1, Arg1);
2308+
2309+
QualType Arg1Ty = Arg1->getType();
2310+
2311+
if (!Arg1Ty->isIntegerType()) {
2312+
ExprResult ConvArg1Res = S.PerformImplicitConversion(
2313+
TheCall->getArg(1), S.Context.IntTy, AssignmentAction::Passing);
2314+
if (ConvArg1Res.isUsable()) {
2315+
Arg1 = ConvArg1Res.get();
2316+
Arg1Ty = Arg1->getType();
2317+
TheCall->setArg(1, Arg1);
2318+
}
2319+
}
2320+
2321+
if (!Arg1Ty->isIntegerType()) {
2322+
S.Diag(Arg1->getBeginLoc(), diag::err_builtin_invalid_arg_type)
2323+
<< 2 << /* scalar */ 1 << /* integer ty */ 2 << /* no fp */ 0 << Arg1Ty;
2324+
return true;
2325+
}
2326+
2327+
TheCall->setType(Arg0Ty);
2328+
return false;
2329+
}
2330+
22702331
static bool CheckMaskedBuiltinArgs(Sema &S, Expr *MaskArg, Expr *PtrArg,
22712332
unsigned Pos, bool AllowConst,
22722333
bool AllowAS) {
@@ -3458,6 +3519,12 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
34583519
return ExprError();
34593520
break;
34603521

3522+
case Builtin::BI__builtin_stdc_rotate_left:
3523+
case Builtin::BI__builtin_stdc_rotate_right:
3524+
if (BuiltinRotateGeneric(*this, TheCall))
3525+
return ExprError();
3526+
break;
3527+
34613528
case Builtin::BI__builtin_allow_runtime_check: {
34623529
Expr *Arg = TheCall->getArg(0);
34633530
// Check if the argument is a string literal.

0 commit comments

Comments
 (0)