https://github.com/chaitanyav updated https://github.com/llvm/llvm-project/pull/160259
>From c66a213692435152de5d8b8aa664cc6578f9a286 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Tue, 23 Sep 2025 02:17:49 -0700 Subject: [PATCH 1/5] [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 --- clang/docs/LanguageExtensions.rst | 36 +++ clang/docs/ReleaseNotes.rst | 5 + clang/include/clang/Basic/Builtins.td | 12 + clang/lib/AST/ExprConstant.cpp | 60 +++-- clang/lib/CodeGen/CGBuiltin.cpp | 35 ++- clang/lib/Sema/SemaChecking.cpp | 67 +++++ clang/test/CodeGen/builtin-rotate.c | 238 ++++++++++++++++++ clang/test/Sema/builtin-stdc-rotate.c | 126 ++++++++++ .../SemaCXX/constexpr-builtin-stdc-rotate.cpp | 199 +++++++++++++++ 9 files changed, 762 insertions(+), 16 deletions(-) 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 80cea2166bc83..03ef8fff7e0d9 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3695,6 +3695,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`` ------------------------- diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 4cfcd37df1866..104edb48f73ad 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -325,6 +325,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``). diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 6d6104a3ddb8d..eec82e0c4a855 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -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(...)"; +} + 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 88d7c79d3b99e..4f5172d199f77 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -15876,23 +15876,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: case Builtin::BI_rotr8: // Microsoft variants of rotate right case Builtin::BI_rotr16: case Builtin::BI_rotr: @@ -15903,7 +15897,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: { diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index da72a43643a54..146f07321a2ff 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2488,12 +2488,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. @@ -3612,6 +3641,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: @@ -3623,6 +3653,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 0ffb4854ba86d..2be6c19cb186f 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2321,6 +2321,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) { @@ -3572,6 +3633,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..ac47bf0082023 100644 --- a/clang/test/CodeGen/builtin-rotate.c +++ b/clang/test/CodeGen/builtin-rotate.c @@ -1,5 +1,7 @@ // RUN: %clang_cc1 %s -emit-llvm -o - | FileCheck %s +#include<stdint.h> + unsigned char rotl8(unsigned char x, unsigned char y) { // CHECK-LABEL: rotl8 // CHECK: [[F:%.*]] = call i8 @llvm.fshl.i8(i8 [[X:%.*]], i8 [[X]], i8 [[Y:%.*]]) @@ -64,3 +66,239 @@ long long rotr64(long long x, unsigned long long y) { return __builtin_rotateright64(x, y); } +// CHECK-LABEL: test_builtin_stdc_rotate_left +// CHECK: call i8 @llvm.fshl.i8(i8 %{{.*}}, i8 %{{.*}}, i8 3) +// CHECK: call i16 @llvm.fshl.i16(i16 %{{.*}}, i16 %{{.*}}, i16 5) +// CHECK: call i32 @llvm.fshl.i32(i32 %{{.*}}, i32 %{{.*}}, i32 8) +// CHECK: call i64 @llvm.fshl.i64(i64 %{{.*}}, i64 %{{.*}}, i64 8) +// CHECK: call i64 @llvm.fshl.i64(i64 %{{.*}}, i64 %{{.*}}, i64 16) +// CHECK: call i128 @llvm.fshl.i128(i128 %{{.*}}, i128 %{{.*}}, i128 32) +// CHECK: call i8 @llvm.fshl.i8(i8 %{{.*}}, i8 %{{.*}}, i8 7) +// CHECK: call i16 @llvm.fshl.i16(i16 %{{.*}}, i16 %{{.*}}, i16 11) +// CHECK: call i32 @llvm.fshl.i32(i32 %{{.*}}, i32 %{{.*}}, i32 29) +// CHECK: call i8 @llvm.fshl.i8(i8 %{{.*}}, i8 %{{.*}}, i8 0) +// CHECK: call i32 @llvm.fshl.i32(i32 %{{.*}}, i32 %{{.*}}, i32 27) +// CHECK: call i32 @llvm.fshl.i32(i32 42, i32 42, i32 %{{.*}}) +// CHECK: call i9 @llvm.fshl.i9(i9 %{{.*}}, i9 %{{.*}}, i9 1) +// CHECK: call i37 @llvm.fshl.i37(i37 %{{.*}}, i37 %{{.*}}, i37 36) +// CHECK: call i9 @llvm.fshl.i9(i9 %{{.*}}, i9 %{{.*}}, i9 8) +// CHECK: call i37 @llvm.fshl.i37(i37 %{{.*}}, i37 %{{.*}}, i37 32) +// CHECK: call i10 @llvm.fshl.i10(i10 %{{.*}}, i10 %{{.*}}, i10 3) +// CHECK: call i16 @llvm.fshl.i16(i16 %{{.*}}, i16 %{{.*}}, i16 8) +// CHECK: call i24 @llvm.fshl.i24(i24 %{{.*}}, i24 %{{.*}}, i24 12) +// CHECK: call i48 @llvm.fshl.i48(i48 %{{.*}}, i48 %{{.*}}, i48 24) +// CHECK: call i8 @llvm.fshl.i8(i8 %{{.*}}, i8 %{{.*}}, i8 0) +// CHECK: call i8 @llvm.fshr.i8(i8 %{{.*}}, i8 %{{.*}}, i8 0) +// CHECK: call i32 @llvm.fshl.i32(i32 %{{.*}}, i32 %{{.*}}, i32 16) +// CHECK: call i32 @llvm.fshl.i32(i32 %{{.*}}, i32 %{{.*}}, i32 31) +// CHECK: call i8 @llvm.fshr.i8(i8 %{{.*}}, i8 %{{.*}}, i8 0) +// CHECK: call i8 @llvm.fshl.i8(i8 %{{.*}}, i8 %{{.*}}, i8 0) +void test_builtin_stdc_rotate_left(uint8_t u8, uint16_t u16, + uint32_t u32, uint64_t u64, + uint64_t u64_2, unsigned __int128 u128, + unsigned _BitInt(9) u9, unsigned _BitInt(37) u37, + unsigned _BitInt(10) u10, unsigned _BitInt(16) u16_bit, + unsigned _BitInt(24) u24, unsigned _BitInt(48) u48) { + + volatile uint8_t result_u8; + volatile uint16_t result_u16; + volatile uint32_t result_u32; + volatile uint64_t result_u64; + volatile uint64_t result_u64_2; + volatile unsigned __int128 result_u128; + volatile unsigned _BitInt(9) result_u9; + volatile unsigned _BitInt(37) result_u37; + volatile unsigned _BitInt(10) result_u10; + volatile unsigned _BitInt(16) result_u16_bit; + volatile unsigned _BitInt(24) result_u24; + volatile unsigned _BitInt(48) result_u48; + + result_u8 = __builtin_stdc_rotate_left(u8, 3); + result_u16 = __builtin_stdc_rotate_left(u16, 5); + result_u32 = __builtin_stdc_rotate_left(u32, 8); + result_u64 = __builtin_stdc_rotate_left(u64, 8); + result_u64_2 = __builtin_stdc_rotate_left(u64_2, 16); + result_u128 = __builtin_stdc_rotate_left(u128, 32); + + result_u8 = __builtin_stdc_rotate_left(u8, -1); + result_u16 = __builtin_stdc_rotate_left(u16, -5); + result_u32 = __builtin_stdc_rotate_left(u32, -3); + result_u8 = __builtin_stdc_rotate_left(u8, -65536); + result_u32 = __builtin_stdc_rotate_left(u32, (int64_t)-4294967333LL); + + int var = 3; + result_u32 = __builtin_stdc_rotate_left(42U, var); + + result_u9 = __builtin_stdc_rotate_left(u9, 1); + result_u37 = __builtin_stdc_rotate_left(u37, 36); + result_u9 = __builtin_stdc_rotate_left(u9, -1); + result_u37 = __builtin_stdc_rotate_left(u37, -5); + + result_u8 = __builtin_stdc_rotate_left((uint8_t)0xAB, 1000000); + result_u32 = __builtin_stdc_rotate_left(0x12345678U, 4294967295U); + result_u8 = __builtin_stdc_rotate_left((uint8_t)0xAB, -1000000); + result_u8 = __builtin_stdc_rotate_left((uint8_t)0x01, 2147483647); + + result_u8 = __builtin_stdc_rotate_left((uint8_t)0xAB, 7); + result_u8 = __builtin_stdc_rotate_left((uint8_t)0xAB, 8); + result_u8 = __builtin_stdc_rotate_left((uint8_t)0xAB, 9); + + result_u8 = __builtin_stdc_rotate_left((uint8_t)0xFF, 1073741824); + result_u32 = __builtin_stdc_rotate_left(0U, 2147483647); + result_u8 = __builtin_stdc_rotate_left((uint8_t)0x01, 1000000007); + + result_u37 = __builtin_stdc_rotate_left((unsigned _BitInt(37))0x1000000000ULL, 1000000000); + + result_u10 = __builtin_stdc_rotate_left(u10, 3); + result_u16_bit = __builtin_stdc_rotate_left(u16_bit, 8); + result_u24 = __builtin_stdc_rotate_left(u24, 12); + result_u48 = __builtin_stdc_rotate_left(u48, 24); + + result_u10 = __builtin_stdc_rotate_left((unsigned _BitInt(10))0x3FF, -1); + result_u16_bit = __builtin_stdc_rotate_left((unsigned _BitInt(16))0xFFFF, -5); + result_u24 = __builtin_stdc_rotate_left((unsigned _BitInt(24))0xABCDEF, 1000000); + result_u48 = __builtin_stdc_rotate_left((unsigned _BitInt(48))0x123456789ABC, -2147483648); + + uint8_t x = 0x42; + uint32_t z = 0x12345678; + result_u8 = __builtin_stdc_rotate_right(__builtin_stdc_rotate_left(x, 1000000), -1000000); + result_u32 = __builtin_stdc_rotate_left(__builtin_stdc_rotate_left(z, 50000), 4294967295U); + + uint8_t temp = (uint8_t)x ^ __builtin_stdc_rotate_right((uint8_t)x, 1073741824); + result_u8 = __builtin_stdc_rotate_left(temp, 0x12345678); +} + +// CHECK-LABEL: test_builtin_stdc_rotate_right +// CHECK: call i8 @llvm.fshr.i8(i8 %{{.*}}, i8 %{{.*}}, i8 3) +// CHECK: call i16 @llvm.fshr.i16(i16 %{{.*}}, i16 %{{.*}}, i16 5) +// CHECK: call i32 @llvm.fshr.i32(i32 %{{.*}}, i32 %{{.*}}, i32 8) +// CHECK: call i64 @llvm.fshr.i64(i64 %{{.*}}, i64 %{{.*}}, i64 8) +// CHECK: call i64 @llvm.fshr.i64(i64 %{{.*}}, i64 %{{.*}}, i64 16) +// CHECK: call i128 @llvm.fshr.i128(i128 %{{.*}}, i128 %{{.*}}, i128 32) +// CHECK: call i8 @llvm.fshr.i8(i8 %{{.*}}, i8 %{{.*}}, i8 7) +// CHECK: call i16 @llvm.fshr.i16(i16 %{{.*}}, i16 %{{.*}}, i16 13) +// CHECK: call i64 @llvm.fshr.i64(i64 %{{.*}}, i64 %{{.*}}, i64 48) +// CHECK: call i9 @llvm.fshr.i9(i9 %{{.*}}, i9 %{{.*}}, i9 1) +// CHECK: call i9 @llvm.fshr.i9(i9 %{{.*}}, i9 %{{.*}}, i9 8) +// CHECK: call i12 @llvm.fshr.i12(i12 %{{.*}}, i12 %{{.*}}, i12 6) +// CHECK: call i20 @llvm.fshr.i20(i20 %{{.*}}, i20 %{{.*}}, i20 10) +// CHECK: call i32 @llvm.fshr.i32(i32 %{{.*}}, i32 %{{.*}}, i32 16) +// CHECK: call i16 @llvm.fshr.i16(i16 %{{.*}}, i16 %{{.*}}, i16 15) +// CHECK: call i16 @llvm.fshl.i16(i16 %{{.*}}, i16 %{{.*}}, i16 1) +void test_builtin_stdc_rotate_right(uint8_t u8, uint16_t u16, + uint32_t u32, uint64_t u64, + uint64_t u64_2, unsigned __int128 u128, + unsigned _BitInt(9) u9, unsigned _BitInt(12) u12, + unsigned _BitInt(20) u20, unsigned _BitInt(32) u32_bit) { + + volatile uint8_t result_u8; + volatile uint16_t result_u16; + volatile uint32_t result_u32; + volatile uint64_t result_u64; + volatile uint64_t result_u64_2; + volatile unsigned __int128 result_u128; + volatile unsigned _BitInt(9) result_u9; + volatile unsigned _BitInt(12) result_u12; + volatile unsigned _BitInt(20) result_u20; + volatile unsigned _BitInt(32) result_u32_bit; + + result_u8 = __builtin_stdc_rotate_right(u8, 3); + result_u16 = __builtin_stdc_rotate_right(u16, 5); + result_u32 = __builtin_stdc_rotate_right(u32, 8); + result_u64 = __builtin_stdc_rotate_right(u64, 8); + result_u64_2 = __builtin_stdc_rotate_right(u64_2, 16); + result_u128 = __builtin_stdc_rotate_right(u128, 32); + + result_u8 = __builtin_stdc_rotate_right(u8, -1); + result_u16 = __builtin_stdc_rotate_right(u16, -3); + result_u64_2 = __builtin_stdc_rotate_right(u64_2, -16); + + result_u9 = __builtin_stdc_rotate_right(u9, 1); + result_u9 = __builtin_stdc_rotate_right(u9, -1); + + result_u16 = __builtin_stdc_rotate_right((uint16_t)0x1234, 2147483647); + result_u16 = __builtin_stdc_rotate_right((uint16_t)0x1234, -2147483647); + result_u8 = __builtin_stdc_rotate_right((uint8_t)0x80, -2147483648); + + result_u16 = __builtin_stdc_rotate_right((uint16_t)0xFFFF, -1073741824); + result_u64_2 = __builtin_stdc_rotate_right(0ULL, -2147483648); + result_u8 = __builtin_stdc_rotate_right((uint8_t)0x80, -1000000007); + + result_u12 = __builtin_stdc_rotate_right(u12, 6); + result_u20 = __builtin_stdc_rotate_right(u20, 10); + result_u32_bit = __builtin_stdc_rotate_right(u32_bit, 16); + + result_u12 = __builtin_stdc_rotate_right((unsigned _BitInt(12))0xFFF, -3); + result_u20 = __builtin_stdc_rotate_right((unsigned _BitInt(20))0x12345, 1000000); + result_u32_bit = __builtin_stdc_rotate_right((unsigned _BitInt(32))0xABCDEF01, -2147483647); + + uint16_t y = 0x1234; + result_u16 = __builtin_stdc_rotate_left(__builtin_stdc_rotate_right(y, 2147483647), -2147483647); +} + +// Test _BitInt types with various bit widths +// CHECK-LABEL: test_bitint_extremes +// CHECK: call i3 @llvm.fshl.i3(i3 %{{.*}}, i3 %{{.*}}, i3 %{{.*}}) +// CHECK: call i23 @llvm.fshl.i23(i23 1193046, i23 1193046, i23 %{{.*}}) +// CHECK: call i37 @llvm.fshl.i37(i37 %{{.*}}, i37 %{{.*}}, i37 %{{.*}}) +// CHECK: call i67 @llvm.fshl.i67(i67 81985529216486895, i67 81985529216486895, i67 %{{.*}}) +// CHECK: call i127 @llvm.fshl.i127(i127 1, i127 1, i127 %{{.*}}) +// CHECK: call i3 @llvm.fshr.i3(i3 %{{.*}}, i3 %{{.*}}, i3 %{{.*}}) +// CHECK: call i23 @llvm.fshr.i23(i23 1193046, i23 1193046, i23 %{{.*}}) +// CHECK: call i37 @llvm.fshr.i37(i37 %{{.*}}, i37 %{{.*}}, i37 %{{.*}}) +// CHECK: call i67 @llvm.fshr.i67(i67 1311768467463790320, i67 1311768467463790320, i67 %{{.*}}) +// CHECK: call i127 @llvm.fshr.i127(i127 1, i127 1, i127 %{{.*}}) +void test_bitint_extremes(unsigned _BitInt(3) u3, unsigned _BitInt(37) u37, int shift) { + volatile unsigned _BitInt(3) result_u3; + volatile unsigned _BitInt(23) result_u23; + volatile unsigned _BitInt(37) result_u37; + volatile unsigned _BitInt(67) result_u67; + volatile unsigned _BitInt(127) result_u127; + + result_u3 = __builtin_stdc_rotate_left(u3, shift); + result_u23 = __builtin_stdc_rotate_left((unsigned _BitInt(23))0x123456, shift); + result_u37 = __builtin_stdc_rotate_left(u37, shift); + result_u67 = __builtin_stdc_rotate_left((unsigned _BitInt(67))0x123456789ABCDEFULL, shift); + result_u127 = __builtin_stdc_rotate_left((unsigned _BitInt(127))1, shift); + + result_u3 = __builtin_stdc_rotate_right(u3, shift); + result_u23 = __builtin_stdc_rotate_right((unsigned _BitInt(23))0x123456, shift); + result_u37 = __builtin_stdc_rotate_right(u37, shift); + result_u67 = __builtin_stdc_rotate_right((unsigned _BitInt(67))0x123456789ABCDEF0ULL, shift); + result_u127 = __builtin_stdc_rotate_right((unsigned _BitInt(127))1, shift); +} + +// CHECK-LABEL: test_wider_shift_amount +// CHECK: call i8 @llvm.fshl.i8(i8 %{{.*}}, i8 %{{.*}}, i8 7) +// CHECK: call i8 @llvm.fshr.i8(i8 %{{.*}}, i8 %{{.*}}, i8 0) +// CHECK: call i16 @llvm.fshl.i16(i16 %{{.*}}, i16 %{{.*}}, i16 11) +// CHECK: call i16 @llvm.fshr.i16(i16 %{{.*}}, i16 %{{.*}}, i16 12) +// CHECK: call i32 @llvm.fshl.i32(i32 %{{.*}}, i32 %{{.*}}, i32 0) +// CHECK: call i32 @llvm.fshr.i32(i32 %{{.*}}, i32 %{{.*}}, i32 0) +// CHECK: call i9 @llvm.fshl.i9(i9 %{{.*}}, i9 %{{.*}}, i9 8) +// CHECK: call i9 @llvm.fshr.i9(i9 %{{.*}}, i9 %{{.*}}, i9 8) +void test_wider_shift_amount(uint8_t u8, uint16_t u16, uint32_t u32, unsigned _BitInt(9) u9) { + volatile uint8_t result_u8; + volatile uint16_t result_u16; + volatile uint32_t result_u32; + volatile unsigned _BitInt(9) result_u9; + + result_u8 = __builtin_stdc_rotate_left(u8, (int64_t)-1); + result_u8 = __builtin_stdc_rotate_right(u8, (int64_t)-1000); + + result_u16 = __builtin_stdc_rotate_left(u16, (int64_t)-5); + result_u16 = __builtin_stdc_rotate_right(u16, (int64_t)-100); + + result_u32 = __builtin_stdc_rotate_left(u32, (int64_t)-2147483648); + result_u32 = __builtin_stdc_rotate_right(u32, (int64_t)-1073741824); + + result_u9 = __builtin_stdc_rotate_left(u9, (int64_t)-1); + result_u9 = __builtin_stdc_rotate_right(u9, (int64_t)-1000000); + + result_u8 = __builtin_stdc_rotate_left((uint8_t)0xFF, (int64_t)-2147483648); + result_u16 = __builtin_stdc_rotate_right((uint16_t)0x1234, (int64_t)-1073741824); + result_u32 = __builtin_stdc_rotate_left(0x12345678U, (int64_t)-4294967296); + + result_u9 = __builtin_stdc_rotate_left((unsigned _BitInt(9))0x1FF, (int64_t)-2147483647); +} + diff --git a/clang/test/Sema/builtin-stdc-rotate.c b/clang/test/Sema/builtin-stdc-rotate.c new file mode 100644 index 0000000000000..d9111c2c6f0a6 --- /dev/null +++ b/clang/test/Sema/builtin-stdc-rotate.c @@ -0,0 +1,126 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fsyntax-only -verify %s + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xB1, 3) == 0x8D, ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned char)0xB1, 3) == 0x36, ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned short)0x1234, 4) == 0x2341, ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, 4) == 0x4123, ""); +_Static_assert(__builtin_stdc_rotate_left(0x12345678U, 8) == 0x34567812U, ""); +_Static_assert(__builtin_stdc_rotate_right(0x12345678U, 8) == 0x78123456U, ""); +_Static_assert(__builtin_stdc_rotate_left(0x123456789ABCDEF0ULL, 16) == 0x56789ABCDEF01234ULL, ""); +_Static_assert(__builtin_stdc_rotate_right(0x123456789ABCDEF0ULL, 16) == 0xDEF0123456789ABCULL, ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned __int128)1, 127) == ((unsigned __int128)1 << 127), ""); +_Static_assert(__builtin_stdc_rotate_right(((unsigned __int128)1 << 127), 127) == (unsigned __int128)1, ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(37))1, 36) == ((unsigned _BitInt(37))1 << 36), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(37))1, 36) == ((unsigned _BitInt(37))1 << 1), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(128))1, 1) == ((unsigned _BitInt(128))2), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(13))1, 12) == ((unsigned _BitInt(13))1 << 12), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(17))0x10000, 16) == ((unsigned _BitInt(17))1), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(65))1, 64) == ((unsigned _BitInt(65))1 << 64), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(7))0x01, 1) == ((unsigned _BitInt(7))0x40), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(9))0x100, 1) == ((unsigned _BitInt(9))0x001), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(9))0x001, 1) == ((unsigned _BitInt(9))0x100), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(9))0x1FF, 4) == ((unsigned _BitInt(9))0x1FF), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(9))1, 8) == ((unsigned _BitInt(9))0x100), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(9))0x100, 8) == ((unsigned _BitInt(9))1), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(9))0x155, 1) == ((unsigned _BitInt(9))0xAB), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(9))1, 9) == ((unsigned _BitInt(9))1), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0x80, 9) == __builtin_stdc_rotate_left((unsigned char)0x80, 1), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned short)0x8000, 17) == __builtin_stdc_rotate_right((unsigned short)0x8000, 1), ""); +_Static_assert(__builtin_stdc_rotate_left(0x80000000U, 33) == __builtin_stdc_rotate_left(0x80000000U, 1), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(37))0x1000000000ULL, 40) == __builtin_stdc_rotate_left((unsigned _BitInt(37))0x1000000000ULL, 3), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(37))0x1000000000ULL, 74) == 0x1000000000ULL, ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0x80, -1) == __builtin_stdc_rotate_left((unsigned char)0x80, 7), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned short)0x8000, -1) == __builtin_stdc_rotate_right((unsigned short)0x8000, 15), ""); +_Static_assert(__builtin_stdc_rotate_left(0x80000000U, -5) == __builtin_stdc_rotate_left(0x80000000U, 27), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(37))0x1000000000ULL, -10) == 0x200ULL, ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -8) == (unsigned char)0xAB, ""); +_Static_assert(__builtin_stdc_rotate_left(0x12345678U, -32) == 0x12345678U, ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0x12, -25) == __builtin_stdc_rotate_left((unsigned char)0x12, 7), ""); +_Static_assert(__builtin_stdc_rotate_right(0x12345678U, -100) == __builtin_stdc_rotate_right(0x12345678U, 28), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned short)0x1234, -65541) == __builtin_stdc_rotate_left((unsigned short)0x1234, -5), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned char)0x80, -261) == __builtin_stdc_rotate_right((unsigned char)0x80, -5), ""); +_Static_assert(__builtin_stdc_rotate_left(0x12345678U, -4294967333LL) == __builtin_stdc_rotate_left(0x12345678U, -37), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, 0) == (unsigned short)0x1234, ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned long long)0x123456789ABCDEF0ULL, 0) == 0x123456789ABCDEF0ULL, ""); +_Static_assert(__builtin_stdc_rotate_left(0U, 15) == 0U, ""); +_Static_assert(__builtin_stdc_rotate_right(0U, 7) == 0U, ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, 1000000) == __builtin_stdc_rotate_left((unsigned char)0xAB, 1000000 % 8), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, 2147483647) == __builtin_stdc_rotate_right((unsigned short)0x1234, 2147483647 % 16), ""); +_Static_assert(__builtin_stdc_rotate_left(0x12345678U, 4294967295U) == __builtin_stdc_rotate_left(0x12345678U, 4294967295U % 32), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -1000000) == __builtin_stdc_rotate_left((unsigned char)0xAB, -1000000 % 8), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, -2147483647) == __builtin_stdc_rotate_right((unsigned short)0x1234, -2147483647 % 16), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(127))1, 1000000) == __builtin_stdc_rotate_left((unsigned _BitInt(127))1, 1000000 % 127), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(127))1, -1000000) == __builtin_stdc_rotate_right((unsigned _BitInt(127))1, -1000000 % 127), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0x01, 2147483647) == __builtin_stdc_rotate_left((unsigned char)0x01, 2147483647 % 8), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned char)0x80, -2147483648) == __builtin_stdc_rotate_right((unsigned char)0x80, -2147483648 % 8), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -9) == __builtin_stdc_rotate_left((unsigned char)0xAB, 7), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -17) == __builtin_stdc_rotate_left((unsigned char)0xAB, 7), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -25) == __builtin_stdc_rotate_left((unsigned char)0xAB, 7), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0x12, 64 + 3) == __builtin_stdc_rotate_left((unsigned char)0x12, 3), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, 128 + 5) == __builtin_stdc_rotate_right((unsigned short)0x1234, 5), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(31))1, 1000) == __builtin_stdc_rotate_left((unsigned _BitInt(31))1, 1000 % 31), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(61))1, -1000) == __builtin_stdc_rotate_right((unsigned _BitInt(61))1, -1000 % 61), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(128))0xFFFFFFFFFFFFFFFFULL, 50000) == __builtin_stdc_rotate_left((unsigned _BitInt(128))0xFFFFFFFFFFFFFFFFULL, 50000 % 128), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(128))0xAAAAAAAAAAAAAAAAULL, -50000) == __builtin_stdc_rotate_right((unsigned _BitInt(128))0xAAAAAAAAAAAAAAAAULL, -50000 % 128), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, 7) == __builtin_stdc_rotate_left((unsigned char)0xAB, 7), ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, 8) == (unsigned char)0xAB, ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, 9) == __builtin_stdc_rotate_left((unsigned char)0xAB, 1), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(9))0x155, 1000) == __builtin_stdc_rotate_left((unsigned _BitInt(9))0x155, 1000 % 9), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(9))0xAA, -1000) == __builtin_stdc_rotate_right((unsigned _BitInt(9))0xAA, -1000 % 9), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0xFF, 1073741824) == (unsigned char)0xFF, ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned short)0xFFFF, -1073741824) == (unsigned short)0xFFFF, ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned char)0x42, 1000000000) == __builtin_stdc_rotate_left((unsigned char)0x42, 1000000000 % 8), ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned char)0x42, -1000000000) == __builtin_stdc_rotate_right((unsigned char)0x42, -1000000000 % 8), ""); + +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(1))0, -1) == (unsigned _BitInt(1))0, ""); +_Static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(1))1, -1) == (unsigned _BitInt(1))1, ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(1))0, 5) == (unsigned _BitInt(1))0, ""); +_Static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(1))1, 5) == (unsigned _BitInt(1))1, ""); + +void test_errors(int si, float f) { + unsigned int ui = 5; + + __builtin_stdc_rotate_left(ui); // expected-error {{too few arguments to function call}} + __builtin_stdc_rotate_left(ui, 1, 2); // expected-error {{too many arguments to function call}} +} + +void test_implicit_conversions(_Bool b, float f, int si) { + unsigned int ui = 5; + + // Test implicit conversions for second argument + (void)__builtin_stdc_rotate_left(ui, b); + (void)__builtin_stdc_rotate_left(ui, f); + (void)__builtin_stdc_rotate_left(ui, 1.5); // expected-warning {{implicit conversion from 'double' to 'int' changes value from 1.5 to 1}} + (void)__builtin_stdc_rotate_right(ui, b); + (void)__builtin_stdc_rotate_right(ui, f); + + // Test implicit conversions for first argument + (void)__builtin_stdc_rotate_left(si, 1); + (void)__builtin_stdc_rotate_left(-5, 1); + (void)__builtin_stdc_rotate_right(3.0, 1.5); // expected-warning {{implicit conversion from 'double' to 'int' changes value from 1.5 to 1}} + + // Test narrowing conversion in assignment + unsigned _BitInt(17) rotated_odd = __builtin_stdc_rotate_left(0x1ABCD, 5); // expected-warning {{implicit conversion from 'unsigned int' to 'unsigned _BitInt(17)' changes value from 3504544 to 96672}} + (void)rotated_odd; +} 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..ab2888fbcb271 --- /dev/null +++ b/clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp @@ -0,0 +1,199 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++14 -fsyntax-only -verify %s + +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(0x12345678U, 4) == 0x23456781U, ""); + +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 + +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_modulo_behavior { + +static_assert(__builtin_stdc_rotate_left((unsigned char)0x80, 9) == __builtin_stdc_rotate_left((unsigned char)0x80, 1), ""); +static_assert(__builtin_stdc_rotate_right((unsigned short)0x8000, 17) == __builtin_stdc_rotate_right((unsigned short)0x8000, 1), ""); +static_assert(__builtin_stdc_rotate_left(0x80000000U, 33) == __builtin_stdc_rotate_left(0x80000000U, 1), ""); +static_assert(__builtin_stdc_rotate_right(0x8000000000000000ULL, 65) == __builtin_stdc_rotate_right(0x8000000000000000ULL, 1), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(37))0x1000000000ULL, 40) == __builtin_stdc_rotate_left((unsigned _BitInt(37))0x1000000000ULL, 3), ""); +static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(37))0x1000000000ULL, 74) == (unsigned _BitInt(37))0x1000000000ULL, ""); + +static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(37))0x123456789ULL, 0) == (unsigned _BitInt(37))0x123456789ULL, ""); +static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(37))0x123456789ULL, 37) == (unsigned _BitInt(37))0x123456789ULL, ""); + +} // namespace test_modulo_behavior + +namespace test_negative_counts { + +static_assert(__builtin_stdc_rotate_left((unsigned char)0x80, -1) == __builtin_stdc_rotate_left((unsigned char)0x80, 7), ""); +static_assert(__builtin_stdc_rotate_right((unsigned short)0x8000, -1) == __builtin_stdc_rotate_right((unsigned short)0x8000, 15), ""); +static_assert(__builtin_stdc_rotate_left(0x80000000U, -5) == __builtin_stdc_rotate_left(0x80000000U, 27), ""); +static_assert(__builtin_stdc_rotate_right(0x8000000000000000ULL, -8) == __builtin_stdc_rotate_right(0x8000000000000000ULL, 56), ""); + +static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(37))0x1000000000ULL, -10) == (unsigned _BitInt(37))0x200ULL, ""); +static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(37))0x800000000ULL, -3) == (unsigned _BitInt(37))0x100000000ULL, ""); + +} // namespace test_negative_counts + +namespace test_boundaries { + +static_assert(__builtin_stdc_rotate_left((unsigned char)0x01, 7) == (unsigned char)0x80, ""); +static_assert(__builtin_stdc_rotate_right((unsigned char)0x80, 7) == (unsigned char)0x01, ""); +static_assert(__builtin_stdc_rotate_left((unsigned char)0xFF, 1) == (unsigned char)0xFF, ""); +static_assert(__builtin_stdc_rotate_left((unsigned short)0x0001, 15) == (unsigned short)0x8000, ""); +static_assert(__builtin_stdc_rotate_right((unsigned short)0x8000, 15) == (unsigned short)0x0001, ""); +static_assert(__builtin_stdc_rotate_left(0x00000001U, 31) == 0x80000000U, ""); +static_assert(__builtin_stdc_rotate_right(0x80000000U, 31) == 0x00000001U, ""); +static_assert(__builtin_stdc_rotate_left(0x0000000000000001ULL, 63) == 0x8000000000000000ULL, ""); +static_assert(__builtin_stdc_rotate_right(0x8000000000000000ULL, 63) == 0x0000000000000001ULL, ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0x01, -7) == (unsigned char)0x02, ""); +static_assert(__builtin_stdc_rotate_right((unsigned char)0x80, -7) == (unsigned char)0x40, ""); +static_assert(__builtin_stdc_rotate_left(0x00000001U, -31) == 0x00000002U, ""); +static_assert(__builtin_stdc_rotate_right(0x80000000U, -31) == 0x40000000U, ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -8) == (unsigned char)0xAB, ""); +static_assert(__builtin_stdc_rotate_left(0x12345678U, -32) == 0x12345678U, ""); +static_assert(__builtin_stdc_rotate_left((unsigned char)0x12, -25) == __builtin_stdc_rotate_left((unsigned char)0x12, 7), ""); +static_assert(__builtin_stdc_rotate_left(0x12345678U, -100) == __builtin_stdc_rotate_left(0x12345678U, 28), ""); + +constexpr unsigned __int128 int128_one = 1; +constexpr unsigned __int128 int128_msb = int128_one << 127; +static_assert(__builtin_stdc_rotate_left(int128_one, 127) == int128_msb, ""); +static_assert(__builtin_stdc_rotate_right(int128_msb, 127) == int128_one, ""); +static_assert(__builtin_stdc_rotate_left(int128_one, -127) == (int128_one << 1), ""); + +constexpr unsigned _BitInt(37) bi37_one = 1; +constexpr unsigned _BitInt(37) bi37_msb = bi37_one << 36; +static_assert(__builtin_stdc_rotate_left(bi37_one, 36) == bi37_msb, ""); +static_assert(__builtin_stdc_rotate_right(bi37_msb, 36) == bi37_one, ""); +static_assert(__builtin_stdc_rotate_left(bi37_one, -36) == (bi37_one << 1), ""); + +} // namespace test_boundaries + +namespace test_extreme_cases { + +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, 1000000) == __builtin_stdc_rotate_left((unsigned char)0xAB, 1000000 % 8), ""); +static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, 2147483647) == __builtin_stdc_rotate_right((unsigned short)0x1234, 2147483647 % 16), ""); +static_assert(__builtin_stdc_rotate_left(0x12345678U, 4294967295U) == __builtin_stdc_rotate_left(0x12345678U, 4294967295U % 32), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -1000000) == __builtin_stdc_rotate_left((unsigned char)0xAB, -1000000 % 8), ""); +static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, -2147483647) == __builtin_stdc_rotate_right((unsigned short)0x1234, -2147483647 % 16), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(127))1, 1000000) == __builtin_stdc_rotate_left((unsigned _BitInt(127))1, 1000000 % 127), ""); +static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(127))1, -1000000) == __builtin_stdc_rotate_right((unsigned _BitInt(127))1, -1000000 % 127), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0x01, 2147483647) == __builtin_stdc_rotate_left((unsigned char)0x01, 2147483647 % 8), ""); +static_assert(__builtin_stdc_rotate_right((unsigned char)0x80, -2147483648) == __builtin_stdc_rotate_right((unsigned char)0x80, -2147483648 % 8), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -9) == __builtin_stdc_rotate_left((unsigned char)0xAB, 7), ""); +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -17) == __builtin_stdc_rotate_left((unsigned char)0xAB, 7), ""); +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, -25) == __builtin_stdc_rotate_left((unsigned char)0xAB, 7), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0x12, 64 + 3) == __builtin_stdc_rotate_left((unsigned char)0x12, 3), ""); +static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, 128 + 5) == __builtin_stdc_rotate_right((unsigned short)0x1234, 5), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(31))1, 1000) == __builtin_stdc_rotate_left((unsigned _BitInt(31))1, 1000 % 31), ""); +static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(61))1, -1000) == __builtin_stdc_rotate_right((unsigned _BitInt(61))1, -1000 % 61), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(128))0xFFFFFFFFFFFFFFFFULL, 50000) == __builtin_stdc_rotate_left((unsigned _BitInt(128))0xFFFFFFFFFFFFFFFFULL, 50000 % 128), ""); +static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(128))0xAAAAAAAAAAAAAAAAULL, -50000) == __builtin_stdc_rotate_right((unsigned _BitInt(128))0xAAAAAAAAAAAAAAAAULL, -50000 % 128), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, 7) == __builtin_stdc_rotate_left((unsigned char)0xAB, 7), ""); +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, 8) == (unsigned char)0xAB, ""); +static_assert(__builtin_stdc_rotate_left((unsigned char)0xAB, 9) == __builtin_stdc_rotate_left((unsigned char)0xAB, 1), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(9))0x155, 1000) == __builtin_stdc_rotate_left((unsigned _BitInt(9))0x155, 1000 % 9), ""); +static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(9))0xAA, -1000) == __builtin_stdc_rotate_right((unsigned _BitInt(9))0xAA, -1000 % 9), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0xFF, 1073741824) == (unsigned char)0xFF, ""); +static_assert(__builtin_stdc_rotate_right((unsigned short)0xFFFF, -1073741824) == (unsigned short)0xFFFF, ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0x42, 1000000000) == __builtin_stdc_rotate_left((unsigned char)0x42, 1000000000 % 8), ""); +static_assert(__builtin_stdc_rotate_right((unsigned char)0x42, -1000000000) == __builtin_stdc_rotate_right((unsigned char)0x42, -1000000000 % 8), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned char)0x12, -1000001) == __builtin_stdc_rotate_right((unsigned char)0x12, 1000001), ""); +static_assert(__builtin_stdc_rotate_right((unsigned short)0x1234, -65537) == __builtin_stdc_rotate_left((unsigned short)0x1234, 65537), ""); + +constexpr unsigned _BitInt(67) large_pattern = 0x123456789ABCDEF0ULL; +static_assert(__builtin_stdc_rotate_left(large_pattern, 1000000) == __builtin_stdc_rotate_left(large_pattern, 1000000 % 67), ""); +static_assert(__builtin_stdc_rotate_right(large_pattern, -1000000) == __builtin_stdc_rotate_right(large_pattern, -1000000 % 67), ""); + +static_assert(__builtin_stdc_rotate_left((unsigned _BitInt(23))0x123456, 1048576) == __builtin_stdc_rotate_left((unsigned _BitInt(23))0x123456, 1048576 % 23), ""); +static_assert(__builtin_stdc_rotate_right((unsigned _BitInt(23))0x123456, -1048576) == __builtin_stdc_rotate_right((unsigned _BitInt(23))0x123456, -1048576 % 23), ""); + +} // namespace test_extreme_cases + +} // namespace test_constexpr_stdc_rotate + +namespace test_conversions { + +struct UnsignedWrapper { + operator unsigned int() const { return 42; } +}; + +struct RotateCount { + operator int() const { return 5; } +}; + +enum RotateAmount { + ROTATE_BY_4 = 4, + ROTATE_BY_8 = 8 +}; + +struct NoConversion {}; + +void test_implicit_conversions() { + UnsignedWrapper uw; + RotateCount rc; + + auto result1 = __builtin_stdc_rotate_left(uw, 3); + auto result2 = __builtin_stdc_rotate_left(5U, rc); + auto result3 = __builtin_stdc_rotate_left(uw, rc); + auto result4 = __builtin_stdc_rotate_right(uw, RotateAmount::ROTATE_BY_4); + + bool b = true; + float f = 3.7f; + auto result5 = __builtin_stdc_rotate_left(10U, b); + auto result6 = __builtin_stdc_rotate_left(10U, f); + auto result7 = __builtin_stdc_rotate_right(10U, 2.9f); // expected-warning {{implicit conversion from 'float' to 'int' changes value from 2.9000001 to 2}} +} + +void test_no_conversions() { + NoConversion nc; + auto result1 = __builtin_stdc_rotate_left(5U, nc); // expected-error {{passing 'NoConversion' to parameter of incompatible type 'int'}} expected-error {{2nd argument must be a scalar signed integer type (was 'NoConversion')}} +} + +} // namespace test_conversions >From ad268dc781af564b8b7c35c46564babdbec050fa Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Tue, 18 Nov 2025 17:30:47 -0800 Subject: [PATCH 2/5] Fix rotate builtins as per review * Make divisor unsigned and unify implementations * Add implementation to InterpBuiltin.cpp --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 67 +++++++++++++++++++++--- clang/lib/AST/ExprConstant.cpp | 22 +++++--- clang/lib/CodeGen/CGBuiltin.cpp | 52 ++++++++++-------- 3 files changed, 105 insertions(+), 36 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 659b6985d3157..5000a8434ffb4 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -3722,11 +3722,6 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, 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: @@ -3735,11 +3730,67 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, 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_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) { + // Normalize shift amount to [0, BitWidth) range to match runtime + // behavior. This matches the algorithm in ExprConstant.cpp. + unsigned BitWidth = Value.getBitWidth(); + unsigned AmtBitWidth = Amount.getBitWidth(); + if (BitWidth == 1) { + // Rotating a 1-bit value is always a no-op + Amount = APSInt(llvm::APInt(AmtBitWidth, 0), Amount.isUnsigned()); + } else { + // Divisor is always unsigned to avoid misinterpreting BitWidth as + // negative in small bit widths (e.g., BitWidth=2 would be -2 if + // signed). + APSInt Divisor; + if (AmtBitWidth > BitWidth) { + Divisor = APSInt(llvm::APInt(AmtBitWidth, BitWidth), + /*isUnsigned=*/true); + } else { + Divisor = + APSInt(llvm::APInt(BitWidth, BitWidth), /*isUnsigned=*/true); + if (AmtBitWidth < BitWidth) { + Amount = Amount.extend(BitWidth); + } + } + + // Normalize to [0, BitWidth) + if (Amount.isSigned()) { + Amount = APSInt(Amount.srem(Divisor), /*isUnsigned=*/false); + if (Amount.isNegative()) { + APSInt SignedDivisor(Divisor, /*isUnsigned=*/false); + Amount += SignedDivisor; + } + } else { + Amount = APSInt(Amount.urem(Divisor), /*isUnsigned=*/true); + } + } + + return IsRotateRight ? Value.rotr(Amount.getZExtValue()) + : Value.rotl(Amount.getZExtValue()); }); + } case Builtin::BI__builtin_ffs: case Builtin::BI__builtin_ffsl: diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 4f5172d199f77..115310d44ddde 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -15901,23 +15901,31 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, unsigned BitWidth = Val.getBitWidth(); unsigned AmtBitWidth = Amt.getBitWidth(); if (BitWidth == 1) { - // if BitWidth is 1, Amt % Divisor = 0 - // No need for rotation + // Rotating a 1-bit value is always a no-op Amt = APSInt(APInt(AmtBitWidth, 0), Amt.isUnsigned()); } else { + // Divisor is always unsigned to avoid misinterpreting BitWidth as + // negative in small bit widths (e.g., BitWidth=2 would be -2 if signed). APSInt Divisor; if (AmtBitWidth > BitWidth) { - Divisor = APSInt(llvm::APInt(AmtBitWidth, BitWidth), Amt.isUnsigned()); + Divisor = + APSInt(llvm::APInt(AmtBitWidth, BitWidth), /*isUnsigned=*/true); } else { - Divisor = APSInt(llvm::APInt(BitWidth, BitWidth), Amt.isUnsigned()); + Divisor = APSInt(llvm::APInt(BitWidth, BitWidth), /*isUnsigned=*/true); if (AmtBitWidth < BitWidth) { Amt = Amt.extend(BitWidth); } } - Amt = Amt % Divisor; - if (Amt.isNegative()) { - Amt += Divisor; + // Normalize to [0, BitWidth) + if (Amt.isSigned()) { + Amt = APSInt(Amt.srem(Divisor), /*isUnsigned=*/false); + if (Amt.isNegative()) { + APSInt SignedDivisor(Divisor, /*isUnsigned=*/false); + Amt += SignedDivisor; + } + } else { + Amt = APSInt(Amt.urem(Divisor), /*isUnsigned=*/true); } } diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 146f07321a2ff..c9fb20be0219b 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2492,31 +2492,41 @@ RValue CodeGenFunction::emitRotate(const CallExpr *E, bool IsRotateRight) { 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); + // 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 { + unsigned ShiftAmtBitWidth = ShiftTy->getIntegerBitWidth(); + bool ShiftAmtIsSigned = E->getArg(1)->getType()->isSignedIntegerType(); - // if ShiftAmt is negative, normalize ShiftAmt to [0, BitWidth - 1] - llvm::Value *RemResult = Builder.CreateSRem(ShiftAmt, BitWidthInShiftTy); - llvm::Value *PositiveShift = - Builder.CreateAdd(RemResult, BitWidthInShiftTy); + // Choose the wider type for the divisor to avoid truncation + llvm::Type *DivisorTy = ShiftAmtBitWidth > BitWidth ? ShiftTy : Ty; + llvm::Value *Divisor = ConstantInt::get(DivisorTy, BitWidth); - llvm::Value *Zero = ConstantInt::get(ShiftTy, 0); - llvm::Value *IsRemNegative = Builder.CreateICmpSLT(RemResult, Zero); + // Extend ShiftAmt to match Divisor width if needed + if (ShiftAmtBitWidth < DivisorTy->getIntegerBitWidth()) { + ShiftAmt = Builder.CreateIntCast(ShiftAmt, DivisorTy, ShiftAmtIsSigned); + } - ShiftAmt = Builder.CreateSelect(IsRemNegative, PositiveShift, RemResult); - } + // 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); + } - 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); + // 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. >From f6fac2c6378eabbc942091620e5cfc7cf3caed8b Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Wed, 26 Nov 2025 16:12:03 -0800 Subject: [PATCH 3/5] Address feedback from code review * Use APInt for Divisor * Move rotation normalization code into ExprConstShared.h --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 36 +---------- clang/lib/AST/ExprConstShared.h | 5 ++ clang/lib/AST/ExprConstant.cpp | 80 +++++++++++++----------- 3 files changed, 50 insertions(+), 71 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 5000a8434ffb4..eb4f3d39441a7 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -3752,41 +3752,7 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, return interp__builtin_elementwise_int_binop( S, OpPC, Call, [IsRotateRight](const APSInt &Value, APSInt Amount) { - // Normalize shift amount to [0, BitWidth) range to match runtime - // behavior. This matches the algorithm in ExprConstant.cpp. - unsigned BitWidth = Value.getBitWidth(); - unsigned AmtBitWidth = Amount.getBitWidth(); - if (BitWidth == 1) { - // Rotating a 1-bit value is always a no-op - Amount = APSInt(llvm::APInt(AmtBitWidth, 0), Amount.isUnsigned()); - } else { - // Divisor is always unsigned to avoid misinterpreting BitWidth as - // negative in small bit widths (e.g., BitWidth=2 would be -2 if - // signed). - APSInt Divisor; - if (AmtBitWidth > BitWidth) { - Divisor = APSInt(llvm::APInt(AmtBitWidth, BitWidth), - /*isUnsigned=*/true); - } else { - Divisor = - APSInt(llvm::APInt(BitWidth, BitWidth), /*isUnsigned=*/true); - if (AmtBitWidth < BitWidth) { - Amount = Amount.extend(BitWidth); - } - } - - // Normalize to [0, BitWidth) - if (Amount.isSigned()) { - Amount = APSInt(Amount.srem(Divisor), /*isUnsigned=*/false); - if (Amount.isNegative()) { - APSInt SignedDivisor(Divisor, /*isUnsigned=*/false); - Amount += SignedDivisor; - } - } else { - Amount = APSInt(Amount.urem(Divisor), /*isUnsigned=*/true); - } - } - + Amount = NormalizeRotateAmount(Value, Amount); return IsRotateRight ? Value.rotr(Amount.getZExtValue()) : Value.rotl(Amount.getZExtValue()); }); diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h index 401ae629c86bf..28fd761c91bcb 100644 --- a/clang/lib/AST/ExprConstShared.h +++ b/clang/lib/AST/ExprConstShared.h @@ -18,6 +18,8 @@ namespace llvm { class APFloat; +class APSInt; +class APInt; } namespace clang { class QualType; @@ -74,4 +76,7 @@ void HandleComplexComplexDiv(llvm::APFloat A, llvm::APFloat B, llvm::APFloat C, CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E, UnaryExprOrTypeTrait ExprKind); +llvm::APSInt NormalizeRotateAmount(const llvm::APSInt &Value, + const llvm::APSInt &Amount); + #endif diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 115310d44ddde..d4ba07439f5b1 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -15892,42 +15892,12 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, 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; - // Normalize shift amount to [0, BitWidth) range to match runtime behavior - unsigned BitWidth = Val.getBitWidth(); - unsigned AmtBitWidth = Amt.getBitWidth(); - if (BitWidth == 1) { - // Rotating a 1-bit value is always a no-op - Amt = APSInt(APInt(AmtBitWidth, 0), Amt.isUnsigned()); - } else { - // Divisor is always unsigned to avoid misinterpreting BitWidth as - // negative in small bit widths (e.g., BitWidth=2 would be -2 if signed). - APSInt Divisor; - if (AmtBitWidth > BitWidth) { - Divisor = - APSInt(llvm::APInt(AmtBitWidth, BitWidth), /*isUnsigned=*/true); - } else { - Divisor = APSInt(llvm::APInt(BitWidth, BitWidth), /*isUnsigned=*/true); - if (AmtBitWidth < BitWidth) { - Amt = Amt.extend(BitWidth); - } - } - - // Normalize to [0, BitWidth) - if (Amt.isSigned()) { - Amt = APSInt(Amt.srem(Divisor), /*isUnsigned=*/false); - if (Amt.isNegative()) { - APSInt SignedDivisor(Divisor, /*isUnsigned=*/false); - Amt += SignedDivisor; - } - } else { - Amt = APSInt(Amt.urem(Divisor), /*isUnsigned=*/true); - } - } + Amount = NormalizeRotateAmount(Value, Amount); switch (BuiltinOp) { case Builtin::BI__builtin_rotateright8: @@ -15940,9 +15910,11 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, case Builtin::BI_rotr: case Builtin::BI_lrotr: case Builtin::BI_rotr64: - return Success(APSInt(Val.rotr(Amt.getZExtValue()), Val.isUnsigned()), E); + return Success( + APSInt(Value.rotr(Amount.getZExtValue()), Value.isUnsigned()), E); default: - return Success(APSInt(Val.rotl(Amt.getZExtValue()), Val.isUnsigned()), E); + return Success( + APSInt(Value.rotl(Amount.getZExtValue()), Value.isUnsigned()), E); } } @@ -19196,6 +19168,42 @@ 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 { + // Divisor is always unsigned to avoid misinterpreting BitWidth as + // negative in small bit widths (e.g., BitWidth=2 would be -2 if signed). + 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); >From 307710baf02356a02d6e7b10b7f74325030f5e06 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Wed, 26 Nov 2025 16:29:37 -0800 Subject: [PATCH 4/5] Specify -ffreestanding to use the stdint header --- clang/test/CodeGen/builtin-rotate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/CodeGen/builtin-rotate.c b/clang/test/CodeGen/builtin-rotate.c index ac47bf0082023..1f498c0294a58 100644 --- a/clang/test/CodeGen/builtin-rotate.c +++ b/clang/test/CodeGen/builtin-rotate.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -ffreestanding %s -emit-llvm -o - | FileCheck %s #include<stdint.h> >From 45105a8c85f06fdd502d7385d35f8a26ff464a80 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Wed, 26 Nov 2025 17:52:18 -0800 Subject: [PATCH 5/5] Allow implicit conversion for class/struct types with conversion operators. Explicit conversion must be done for all other types. --- clang/docs/ReleaseNotes.rst | 3 +- clang/lib/Sema/SemaChecking.cpp | 102 ++++++++++++------ clang/test/Sema/builtin-stdc-rotate.c | 30 +++--- .../SemaCXX/constexpr-builtin-stdc-rotate.cpp | 11 +- 4 files changed, 96 insertions(+), 50 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 104edb48f73ad..88d58bc1f1e88 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -328,7 +328,8 @@ Non-comprehensive list of changes in this release - 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. + Usable in constant expressions. Implicit conversion is supported for + class/struct types with conversion operators. New Compiler Flags ------------------ diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 2be6c19cb186f..40c5882f806fe 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2321,6 +2321,66 @@ static bool BuiltinCountZeroBitsGeneric(Sema &S, CallExpr *TheCall) { return false; } +class RotateIntegerConverter : public Sema::ContextualImplicitConverter { + unsigned ArgIndex; + bool OnlyUnsigned; + + Sema::SemaDiagnosticBuilder emitError(Sema &S, SourceLocation Loc, + QualType T) { + return S.Diag(Loc, diag::err_builtin_invalid_arg_type) + << ArgIndex << /*scalar*/ 1 + << (OnlyUnsigned ? /*unsigned integer*/ 3 : /*integer*/ 1) + << /*no fp*/ 0 << T; + } + +public: + RotateIntegerConverter(unsigned ArgIndex, bool OnlyUnsigned) + : ContextualImplicitConverter(/*Suppress=*/false, + /*SuppressConversion=*/true), + ArgIndex(ArgIndex), OnlyUnsigned(OnlyUnsigned) {} + + bool match(QualType T) override { + return OnlyUnsigned ? T->isUnsignedIntegerType() : T->isIntegerType(); + } + + Sema::SemaDiagnosticBuilder diagnoseNoMatch(Sema &S, SourceLocation Loc, + QualType T) override { + return emitError(S, Loc, T); + } + + Sema::SemaDiagnosticBuilder diagnoseIncomplete(Sema &S, SourceLocation Loc, + QualType T) override { + return emitError(S, Loc, T); + } + + Sema::SemaDiagnosticBuilder diagnoseExplicitConv(Sema &S, SourceLocation Loc, + QualType T, + QualType ConvTy) override { + return emitError(S, Loc, T); + } + + Sema::SemaDiagnosticBuilder noteExplicitConv(Sema &S, CXXConversionDecl *Conv, + QualType ConvTy) override { + return S.Diag(Conv->getLocation(), diag::note_conv_function_declared_at); + } + + Sema::SemaDiagnosticBuilder diagnoseAmbiguous(Sema &S, SourceLocation Loc, + QualType T) override { + return emitError(S, Loc, T); + } + + Sema::SemaDiagnosticBuilder noteAmbiguous(Sema &S, CXXConversionDecl *Conv, + QualType ConvTy) override { + return S.Diag(Conv->getLocation(), diag::note_conv_function_declared_at); + } + + Sema::SemaDiagnosticBuilder diagnoseConversion(Sema &S, SourceLocation Loc, + QualType T, + QualType ConvTy) override { + llvm_unreachable("conversion functions are permitted"); + } +}; + /// 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. @@ -2328,7 +2388,10 @@ static bool BuiltinRotateGeneric(Sema &S, CallExpr *TheCall) { if (S.checkArgCount(TheCall, 2)) return true; - ExprResult Arg0Res = S.DefaultLvalueConversion(TheCall->getArg(0)); + // First argument (value to rotate) must be unsigned integer type. + RotateIntegerConverter Arg0Converter(1, /*OnlyUnsigned=*/true); + ExprResult Arg0Res = S.PerformContextualImplicitConversion( + TheCall->getArg(0)->getBeginLoc(), TheCall->getArg(0), Arg0Converter); if (Arg0Res.isInvalid()) return true; @@ -2336,24 +2399,13 @@ static bool BuiltinRotateGeneric(Sema &S, CallExpr *TheCall) { 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; + if (!Arg0Ty->isUnsignedIntegerType()) return true; - } - ExprResult Arg1Res = S.DefaultLvalueConversion(TheCall->getArg(1)); + // Second argument (rotation count) must be integer type. + RotateIntegerConverter Arg1Converter(2, /*OnlyUnsigned=*/false); + ExprResult Arg1Res = S.PerformContextualImplicitConversion( + TheCall->getArg(1)->getBeginLoc(), TheCall->getArg(1), Arg1Converter); if (Arg1Res.isInvalid()) return true; @@ -2361,22 +2413,8 @@ static bool BuiltinRotateGeneric(Sema &S, CallExpr *TheCall) { 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; + if (!Arg1Ty->isIntegerType()) return true; - } TheCall->setType(Arg0Ty); return false; diff --git a/clang/test/Sema/builtin-stdc-rotate.c b/clang/test/Sema/builtin-stdc-rotate.c index d9111c2c6f0a6..d8b506dbd6e46 100644 --- a/clang/test/Sema/builtin-stdc-rotate.c +++ b/clang/test/Sema/builtin-stdc-rotate.c @@ -105,22 +105,28 @@ void test_errors(int si, float f) { __builtin_stdc_rotate_left(ui, 1, 2); // expected-error {{too many arguments to function call}} } -void test_implicit_conversions(_Bool b, float f, int si) { +void test_valid_conversions(_Bool b, int si) { unsigned int ui = 5; - // Test implicit conversions for second argument + // Valid: bool converts to int for second argument (void)__builtin_stdc_rotate_left(ui, b); - (void)__builtin_stdc_rotate_left(ui, f); - (void)__builtin_stdc_rotate_left(ui, 1.5); // expected-warning {{implicit conversion from 'double' to 'int' changes value from 1.5 to 1}} (void)__builtin_stdc_rotate_right(ui, b); - (void)__builtin_stdc_rotate_right(ui, f); - // Test implicit conversions for first argument - (void)__builtin_stdc_rotate_left(si, 1); - (void)__builtin_stdc_rotate_left(-5, 1); - (void)__builtin_stdc_rotate_right(3.0, 1.5); // expected-warning {{implicit conversion from 'double' to 'int' changes value from 1.5 to 1}} + // Valid: signed int is an integer type for second argument + (void)__builtin_stdc_rotate_left(ui, si); + (void)__builtin_stdc_rotate_right(ui, si); +} + +void test_invalid_types(float f, int si) { + unsigned int ui = 5; + + // Invalid: float is not an integer type for second argument + (void)__builtin_stdc_rotate_left(ui, f); // expected-error {{2nd argument must be a scalar integer type (was 'float')}} + (void)__builtin_stdc_rotate_left(ui, 1.5); // expected-error {{2nd argument must be a scalar integer type (was 'double')}} + (void)__builtin_stdc_rotate_right(ui, f); // expected-error {{2nd argument must be a scalar integer type (was 'float')}} - // Test narrowing conversion in assignment - unsigned _BitInt(17) rotated_odd = __builtin_stdc_rotate_left(0x1ABCD, 5); // expected-warning {{implicit conversion from 'unsigned int' to 'unsigned _BitInt(17)' changes value from 3504544 to 96672}} - (void)rotated_odd; + // Invalid: signed int is not unsigned for first argument + (void)__builtin_stdc_rotate_left(si, 1); // expected-error {{1st argument must be a scalar unsigned integer type (was 'int')}} + (void)__builtin_stdc_rotate_left(-5, 1); // expected-error {{1st argument must be a scalar unsigned integer type (was 'int')}} + (void)__builtin_stdc_rotate_right(3.0, 1); // expected-error {{1st argument must be a scalar unsigned integer type (was 'double')}} } diff --git a/clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp b/clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp index ab2888fbcb271..4827c4091a18d 100644 --- a/clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-stdc-rotate.cpp @@ -185,15 +185,16 @@ void test_implicit_conversions() { auto result4 = __builtin_stdc_rotate_right(uw, RotateAmount::ROTATE_BY_4); bool b = true; - float f = 3.7f; auto result5 = __builtin_stdc_rotate_left(10U, b); - auto result6 = __builtin_stdc_rotate_left(10U, f); - auto result7 = __builtin_stdc_rotate_right(10U, 2.9f); // expected-warning {{implicit conversion from 'float' to 'int' changes value from 2.9000001 to 2}} } -void test_no_conversions() { +void test_invalid_types() { + float f = 3.7f; + auto result6 = __builtin_stdc_rotate_left(10U, f); // expected-error {{2nd argument must be a scalar integer type (was 'float')}} + auto result7 = __builtin_stdc_rotate_right(10U, 2.9f); // expected-error {{2nd argument must be a scalar integer type (was 'float')}} + NoConversion nc; - auto result1 = __builtin_stdc_rotate_left(5U, nc); // expected-error {{passing 'NoConversion' to parameter of incompatible type 'int'}} expected-error {{2nd argument must be a scalar signed integer type (was 'NoConversion')}} + auto result1 = __builtin_stdc_rotate_left(5U, nc); // expected-error {{2nd argument must be a scalar integer type (was 'NoConversion')}} } } // namespace test_conversions _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
