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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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 @@ -2898,6 +2898,45 @@ def ByteSwapOp : CIR_BitOpBase<"byte_swap", CIR_UIntOfWidths<[16, 32, 64]>> {
}];
}

//===----------------------------------------------------------------------===//
// RotateOp
//===----------------------------------------------------------------------===//

def RotateOp : CIR_Op<"rotate", [Pure, SameOperandsAndResultType]> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why doesn't this derive from CIR_BitOpBase? I'm not clear what we gain by having anything derive from that, but it seems like this should if other do.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the goal was to unify the assembly format and traits for all pure unary operations of the form T -> T in a single place. However, the name BitOpBase might misleadingly suggest that it specifically involves some bit-level semantics.

Formally, such operations are endomorphisms. That said, it's unclear to me whether names like EndomorphismOpBase or EndoOpBase are desirable or intuitive enough for this purpose.

Copy link
Member Author

Choose a reason for hiding this comment

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

Formally, such operations are endomorphisms. That said, it's unclear to me whether names like EndomorphismOpBase or EndoOpBase are desirable or intuitive enough for this purpose.

The LLVM dialect could be a good reference in their LLVM intrinsic op definitions, see https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td .

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, though with LLVM dialect one needs to be cautious that it was written at the beginnings of MLIR, and many parts have not been updated since.

Maybe UnaryTypePreservingOp might be a good name candidate?

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def RotateOp : CIR_Op<"rotate", [Pure, SameOperandsAndResultType]> {
def CIR_RotateOp : CIR_Op<"rotate", [Pure, SameOperandsAndResultType]> {

To mirror changes from llvm/clangir#1741

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]])