[clang] [CIR] Add rotate operation (PR #148426)

Sirui Mu via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 16 06:49:39 PDT 2025


https://github.com/Lancern updated https://github.com/llvm/llvm-project/pull/148426

>From 06a18f792e92ea4ff7f576f33d92c26c30045f85 Mon Sep 17 00:00:00 2001
From: Sirui Mu <msrlancern at gmail.com>
Date: Sun, 13 Jul 2025 20:14:41 +0800
Subject: [PATCH] [CIR] Add rotate operation

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  39 +++++
 clang/include/clang/CIR/MissingFeatures.h     |   1 +
 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp       |  26 ++++
 clang/lib/CIR/CodeGen/CIRGenFunction.h        |   2 +
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |  16 ++
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.h   |  10 ++
 clang/test/CIR/CodeGen/builtin_bit.cpp        | 138 ++++++++++++++++++
 7 files changed, 232 insertions(+)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 2ce23dbb27ec6..d19cd83d78b40 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2934,6 +2934,45 @@ def CIR_ByteSwapOp : CIR_BitOpBase<"byte_swap",
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// RotateOp
+//===----------------------------------------------------------------------===//
+
+def CIR_RotateOp : CIR_Op<"rotate", [Pure, SameOperandsAndResultType]> {
+  let summary = "Rotate the bits in the operand integer";
+  let description = [{
+    The `cir.rotate` rotates the bits in `input` by the given amount `amount`.
+    The rotate direction is specified by the `left` and `right` keyword.
+
+    `input` must be an unsigned integer and its width must be either 8, 16, 32,
+    or 64. The types of `input`, `amount`, and the result must all match.
+
+    Example:
+
+    ```mlir
+    %r = cir.rotate left %0, %1 : !u32i
+    %r = cir.rotate right %0, %1 : !u32i
+    ```
+  }];
+
+  let results = (outs CIR_IntType:$result);
+  let arguments = (ins
+    CIR_UIntOfWidths<[8, 16, 32, 64]>:$input,
+    CIR_IntType:$amount,
+    UnitAttr:$rotateLeft
+  );
+
+  let assemblyFormat = [{
+    (`left` $rotateLeft^) : (`right`)?
+    $input `,` $amount `:` type($result) attr-dict
+  }];
+
+  let extraClassDeclaration = [{
+    bool isRotateLeft() { return getRotateLeft(); }
+    bool isRotateRight() { return !getRotateLeft(); }
+  }];
+}
+
 //===----------------------------------------------------------------------===//
 // Assume Operations
 //===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 48e309063d38b..182e4b6784d2f 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -254,6 +254,7 @@ struct MissingFeatures {
   static bool dtorCleanups() { return false; }
   static bool completeDtors() { return false; }
   static bool vtableInitialization() { return false; }
+  static bool msvcBuiltins() { return false; }
 
   // Missing types
   static bool dataMemberType() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 72e8d71c366d8..476f994959285 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -57,6 +57,20 @@ static RValue emitBuiltinBitOp(CIRGenFunction &cgf, const CallExpr *e,
   return RValue::get(result);
 }
 
+RValue CIRGenFunction::emitRotate(const CallExpr *e, bool isRotateLeft) {
+  mlir::Value input = emitScalarExpr(e->getArg(0));
+  mlir::Value amount = emitScalarExpr(e->getArg(1));
+
+  // TODO(cir): MSVC flavor bit rotate builtins use different types for input
+  // and amount, but cir.rotate requires them to have the same type. Cast amount
+  // to the type of input when necessary.
+  assert(!cir::MissingFeatures::msvcBuiltins());
+
+  auto r = builder.create<cir::RotateOp>(getLoc(e->getSourceRange()), input,
+                                         amount, isRotateLeft);
+  return RValue::get(r);
+}
+
 RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
                                        const CallExpr *e,
                                        ReturnValueSlot returnValue) {
@@ -219,6 +233,18 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
     mlir::Value arg = emitScalarExpr(e->getArg(0));
     return RValue::get(builder.create<cir::BitReverseOp>(loc, arg));
   }
+
+  case Builtin::BI__builtin_rotateleft8:
+  case Builtin::BI__builtin_rotateleft16:
+  case Builtin::BI__builtin_rotateleft32:
+  case Builtin::BI__builtin_rotateleft64:
+    return emitRotate(e, /*isRotateLeft=*/true);
+
+  case Builtin::BI__builtin_rotateright8:
+  case Builtin::BI__builtin_rotateright16:
+  case Builtin::BI__builtin_rotateright32:
+  case Builtin::BI__builtin_rotateright64:
+    return emitRotate(e, /*isRotateLeft=*/false);
   }
 
   // If this is an alias for a lib function (e.g. __builtin_sin), emit
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 1346333739bc1..3baabba5adfe1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1030,6 +1030,8 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   mlir::LogicalResult emitReturnStmt(const clang::ReturnStmt &s);
 
+  RValue emitRotate(const CallExpr *e, bool isRotateLeft);
+
   mlir::Value emitScalarConstant(const ConstantEmission &constant, Expr *e);
 
   /// Emit a conversion from the specified type to the specified destination
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 7dcea0c8eb529..840e856ba0cf8 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -872,6 +872,21 @@ mlir::LogicalResult CIRToLLVMReturnOpLowering::matchAndRewrite(
   return mlir::LogicalResult::success();
 }
 
+mlir::LogicalResult CIRToLLVMRotateOpLowering::matchAndRewrite(
+    cir::RotateOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  // Note that LLVM intrinsic calls to @llvm.fsh{r,l}.i* have the same type as
+  // the operand.
+  mlir::Value input = adaptor.getInput();
+  if (op.isRotateLeft())
+    rewriter.replaceOpWithNewOp<mlir::LLVM::FshlOp>(op, input, input,
+                                                    adaptor.getAmount());
+  else
+    rewriter.replaceOpWithNewOp<mlir::LLVM::FshrOp>(op, input, input,
+                                                    adaptor.getAmount());
+  return mlir::LogicalResult::success();
+}
+
 static mlir::LogicalResult
 rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
                     mlir::ConversionPatternRewriter &rewriter,
@@ -2077,6 +2092,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
                CIRToLLVMGetBitfieldOpLowering,
                CIRToLLVMGetGlobalOpLowering,
                CIRToLLVMGetMemberOpLowering,
+               CIRToLLVMRotateOpLowering,
                CIRToLLVMSelectOpLowering,
                CIRToLLVMSetBitfieldOpLowering,
                CIRToLLVMShiftOpLowering,
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index 3c30b1bc5b072..3faf1e900848e 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -160,6 +160,16 @@ class CIRToLLVMReturnOpLowering
                   mlir::ConversionPatternRewriter &) const override;
 };
 
+class CIRToLLVMRotateOpLowering
+    : public mlir::OpConversionPattern<cir::RotateOp> {
+public:
+  using mlir::OpConversionPattern<cir::RotateOp>::OpConversionPattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::RotateOp op, OpAdaptor,
+                  mlir::ConversionPatternRewriter &) const override;
+};
+
 class CIRToLLVMCallOpLowering : public mlir::OpConversionPattern<cir::CallOp> {
 public:
   using mlir::OpConversionPattern<cir::CallOp>::OpConversionPattern;
diff --git a/clang/test/CIR/CodeGen/builtin_bit.cpp b/clang/test/CIR/CodeGen/builtin_bit.cpp
index f017b6eb51971..4ac82bd749e8a 100644
--- a/clang/test/CIR/CodeGen/builtin_bit.cpp
+++ b/clang/test/CIR/CodeGen/builtin_bit.cpp
@@ -416,3 +416,141 @@ unsigned long long test_builtin_bswap64(unsigned long long x) {
 
 // OGCG-LABEL: @_Z20test_builtin_bswap64y
 // OGCG:         %{{.+}} = call i64 @llvm.bswap.i64(i64 %{{.+}})
+
+unsigned char test_builtin_rotateleft8(unsigned char x, unsigned char y) {
+  return __builtin_rotateleft8(x, y);
+}
+
+// CIR-LABEL: @_Z24test_builtin_rotateleft8hh
+// CIR:         %{{.+}} = cir.rotate left %{{.+}}, %{{.+}} : !u8i
+
+// LLVM-LABEL: @_Z24test_builtin_rotateleft8hh
+// LLVM:         %[[INPUT:.+]] = load i8, ptr %{{.+}}, align 1
+// LLVM-NEXT:    %[[AMOUNT:.+]] = load i8, ptr %{{.+}}, align 1
+// LLVM-NEXT:    %{{.+}} = call i8 @llvm.fshl.i8(i8 %[[INPUT]], i8 %[[INPUT]], i8 %[[AMOUNT]])
+
+// OGCG-LABEL: @_Z24test_builtin_rotateleft8hh
+// OGCG:         %[[INPUT:.+]] = load i8, ptr %{{.+}}, align 1
+// OGCG-NEXT:    %[[AMOUNT:.+]] = load i8, ptr %{{.+}}, align 1
+// OGCG-NEXT:    %{{.+}} = call i8 @llvm.fshl.i8(i8 %[[INPUT]], i8 %[[INPUT]], i8 %[[AMOUNT]])
+
+unsigned short test_builtin_rotateleft16(unsigned short x, unsigned short y) {
+  return __builtin_rotateleft16(x, y);
+}
+
+// CIR-LABEL: @_Z25test_builtin_rotateleft16tt
+// CIR:         %{{.+}} = cir.rotate left %{{.+}}, %{{.+}} : !u16i
+
+// LLVM-LABEL: @_Z25test_builtin_rotateleft16tt
+// LLVM:         %[[INPUT:.+]] = load i16, ptr %{{.+}}, align 2
+// LLVM-NEXT:    %[[AMOUNT:.+]] = load i16, ptr %{{.+}}, align 2
+// LLVM-NEXT:    %{{.+}} = call i16 @llvm.fshl.i16(i16 %[[INPUT]], i16 %[[INPUT]], i16 %[[AMOUNT]])
+
+// OGCG-LABEL: @_Z25test_builtin_rotateleft16tt
+// OGCG:         %[[INPUT:.+]] = load i16, ptr %{{.+}}, align 2
+// OGCG-NEXT:    %[[AMOUNT:.+]] = load i16, ptr %{{.+}}, align 2
+// OGCG-NEXT:    %{{.+}} = call i16 @llvm.fshl.i16(i16 %[[INPUT]], i16 %[[INPUT]], i16 %[[AMOUNT]])
+
+unsigned test_builtin_rotateleft32(unsigned x, unsigned y) {
+  return __builtin_rotateleft32(x, y);
+}
+
+// CIR-LABEL: @_Z25test_builtin_rotateleft32jj
+// CIR:         %{{.+}} = cir.rotate left %{{.+}}, %{{.+}} : !u32i
+
+// LLVM-LABEL: @_Z25test_builtin_rotateleft32jj
+// LLVM:         %[[INPUT:.+]] = load i32, ptr %{{.+}}, align 4
+// LLVM-NEXT:    %[[AMOUNT:.+]] = load i32, ptr %{{.+}}, align 4
+// LLVM-NEXT:    %{{.+}} = call i32 @llvm.fshl.i32(i32 %[[INPUT]], i32 %[[INPUT]], i32 %[[AMOUNT]])
+
+// OGCG-LABEL: @_Z25test_builtin_rotateleft32jj
+// OGCG:         %[[INPUT:.+]] = load i32, ptr %{{.+}}, align 4
+// OGCG-NEXT:    %[[AMOUNT:.+]] = load i32, ptr %{{.+}}, align 4
+// OGCG-NEXT:    %{{.+}} = call i32 @llvm.fshl.i32(i32 %[[INPUT]], i32 %[[INPUT]], i32 %[[AMOUNT]])
+
+unsigned long long test_builtin_rotateleft64(unsigned long long x,
+                                             unsigned long long y) {
+  return __builtin_rotateleft64(x, y);
+}
+
+// CIR-LABEL: @_Z25test_builtin_rotateleft64yy
+// CIR:         %{{.+}} = cir.rotate left %{{.+}}, %{{.+}} : !u64i
+
+// LLVM-LABEL: @_Z25test_builtin_rotateleft64yy
+// LLVM:         %[[INPUT:.+]] = load i64, ptr %{{.+}}, align 8
+// LLVM-NEXT:    %[[AMOUNT:.+]] = load i64, ptr %{{.+}}, align 8
+// LLVM-NEXT:    %{{.+}} = call i64 @llvm.fshl.i64(i64 %[[INPUT]], i64 %[[INPUT]], i64 %[[AMOUNT]])
+
+// OGCG-LABEL: @_Z25test_builtin_rotateleft64yy
+// OGCG:         %[[INPUT:.+]] = load i64, ptr %{{.+}}, align 8
+// OGCG-NEXT:    %[[AMOUNT:.+]] = load i64, ptr %{{.+}}, align 8
+// OGCG-NEXT:    %{{.+}} = call i64 @llvm.fshl.i64(i64 %[[INPUT]], i64 %[[INPUT]], i64 %[[AMOUNT]])
+
+unsigned char test_builtin_rotateright8(unsigned char x, unsigned char y) {
+  return __builtin_rotateright8(x, y);
+}
+
+// CIR-LABEL: @_Z25test_builtin_rotateright8hh
+// CIR:         %{{.+}} = cir.rotate right %{{.+}}, %{{.+}} : !u8i
+
+// LLVM-LABEL: @_Z25test_builtin_rotateright8hh
+// LLVM:         %[[INPUT:.+]] = load i8, ptr %{{.+}}, align 1
+// LLVM-NEXT:    %[[AMOUNT:.+]] = load i8, ptr %{{.+}}, align 1
+// LLVM-NEXT:    %{{.+}} = call i8 @llvm.fshr.i8(i8 %[[INPUT]], i8 %[[INPUT]], i8 %[[AMOUNT]])
+
+// OGCG-LABEL: @_Z25test_builtin_rotateright8hh
+// OGCG:         %[[INPUT:.+]] = load i8, ptr %{{.+}}, align 1
+// OGCG-NEXT:    %[[AMOUNT:.+]] = load i8, ptr %{{.+}}, align 1
+// OGCG-NEXT:    %{{.+}} = call i8 @llvm.fshr.i8(i8 %[[INPUT]], i8 %[[INPUT]], i8 %[[AMOUNT]])
+
+unsigned short test_builtin_rotateright16(unsigned short x, unsigned short y) {
+  return __builtin_rotateright16(x, y);
+}
+
+// CIR-LABEL: @_Z26test_builtin_rotateright16tt
+// CIR:         %{{.+}} = cir.rotate right %{{.+}}, %{{.+}} : !u16i
+
+// LLVM-LABEL: @_Z26test_builtin_rotateright16tt
+// LLVM:         %[[INPUT:.+]] = load i16, ptr %{{.+}}, align 2
+// LLVM-NEXT:    %[[AMOUNT:.+]] = load i16, ptr %{{.+}}, align 2
+// LLVM-NEXT:    %{{.+}} = call i16 @llvm.fshr.i16(i16 %[[INPUT]], i16 %[[INPUT]], i16 %[[AMOUNT]])
+
+// OGCG-LABEL: @_Z26test_builtin_rotateright16tt
+// OGCG:         %[[INPUT:.+]] = load i16, ptr %{{.+}}, align 2
+// OGCG-NEXT:    %[[AMOUNT:.+]] = load i16, ptr %{{.+}}, align 2
+// OGCG-NEXT:    %{{.+}} = call i16 @llvm.fshr.i16(i16 %[[INPUT]], i16 %[[INPUT]], i16 %[[AMOUNT]])
+
+unsigned test_builtin_rotateright32(unsigned x, unsigned y) {
+  return __builtin_rotateright32(x, y);
+}
+
+// CIR-LABEL: @_Z26test_builtin_rotateright32jj
+// CIR:         %{{.+}} = cir.rotate right %{{.+}}, %{{.+}} : !u32i
+
+// LLVM-LABEL: @_Z26test_builtin_rotateright32jj
+// LLVM:         %[[INPUT:.+]] = load i32, ptr %{{.+}}, align 4
+// LLVM-NEXT:    %[[AMOUNT:.+]] = load i32, ptr %{{.+}}, align 4
+// LLVM-NEXT:    %{{.+}} = call i32 @llvm.fshr.i32(i32 %[[INPUT]], i32 %[[INPUT]], i32 %[[AMOUNT]])
+
+// OGCG-LABEL: @_Z26test_builtin_rotateright32jj
+// OGCG:         %[[INPUT:.+]] = load i32, ptr %{{.+}}, align 4
+// OGCG-NEXT:    %[[AMOUNT:.+]] = load i32, ptr %{{.+}}, align 4
+// OGCG-NEXT:    %{{.+}} = call i32 @llvm.fshr.i32(i32 %[[INPUT]], i32 %[[INPUT]], i32 %[[AMOUNT]])
+
+unsigned long long test_builtin_rotateright64(unsigned long long x,
+                                              unsigned long long y) {
+  return __builtin_rotateright64(x, y);
+}
+
+// CIR-LABEL: @_Z26test_builtin_rotateright64yy
+// CIR:         %{{.+}} = cir.rotate right %{{.+}}, %{{.+}} : !u64i
+
+// LLVM-LABEL: @_Z26test_builtin_rotateright64yy
+// LLVM:         %[[INPUT:.+]] = load i64, ptr %{{.+}}, align 8
+// LLVM-NEXT:    %[[AMOUNT:.+]] = load i64, ptr %{{.+}}, align 8
+// LLVM-NEXT:    %{{.+}} = call i64 @llvm.fshr.i64(i64 %[[INPUT]], i64 %[[INPUT]], i64 %[[AMOUNT]])
+
+// OGCG-LABEL: @_Z26test_builtin_rotateright64yy
+// OGCG:         %[[INPUT:.+]] = load i64, ptr %{{.+}}, align 8
+// OGCG-NEXT:    %[[AMOUNT:.+]] = load i64, ptr %{{.+}}, align 8
+// OGCG-NEXT:    %{{.+}} = call i64 @llvm.fshr.i64(i64 %[[INPUT]], i64 %[[INPUT]], i64 %[[AMOUNT]])



More information about the cfe-commits mailing list