Skip to content

[CIR] Add rotate operation #148426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
26 changes: 26 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -2077,6 +2092,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
CIRToLLVMGetBitfieldOpLowering,
CIRToLLVMGetGlobalOpLowering,
CIRToLLVMGetMemberOpLowering,
CIRToLLVMRotateOpLowering,
CIRToLLVMSelectOpLowering,
CIRToLLVMSetBitfieldOpLowering,
CIRToLLVMShiftOpLowering,
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
138 changes: 138 additions & 0 deletions clang/test/CIR/CodeGen/builtin_bit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]])