Skip to content

[CIR] Add bit reverse and byte reverse operations #147200

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 2 commits into
base: main
Choose a base branch
from

Conversation

Lancern
Copy link
Member

@Lancern Lancern commented Jul 6, 2025

This patch adds support for the following two builtin functions:

  • __builtin_bswap, represented by the cir.bswap operation.
  • __builtin_bitreverse, represented by the cir.bit.reverse operation.

@Lancern Lancern requested a review from andykaylor July 6, 2025 16:49
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Jul 6, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 6, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clangir

Author: Sirui Mu (Lancern)

Changes

This patch adds support for the following two builtin functions:

  • __builtin_bswap, represented by the cir.bswap operation.
  • __builtin_bitreverse, represented by the cir.bit.reverse operation.

Full diff: https://github.com/llvm/llvm-project/pull/147200.diff

5 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+49)
  • (modified) clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp (+20)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+16)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h (+20)
  • (modified) clang/test/CIR/CodeGen/builtin_bit.cpp (+91)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 6529f1386599c..e26942d5cf511 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2661,6 +2661,55 @@ def BitPopcountOp : CIR_BitOpBase<"bit.popcnt",
   }];
 }
 
+def BitReverseOp : CIR_BitOpBase<"bit.reverse", CIR_UIntOfWidths<[8, 16, 32, 64]>> {
+  let summary = "Reverse the bit pattern of the operand integer";
+  let description = [{
+    The `cir.bit.reverse` operation reverses the bits of the operand integer.
+    Its only argument must be of unsigned integer types of width 8, 16, 32, or
+    64.
+
+    This operation covers the C/C++ builtin function `__builtin_bitreverse`.
+
+    Example:
+
+    ```mlir
+    %1 = cir.bit.reverse %0 : !u32i
+    ```
+  }];
+}
+
+//===----------------------------------------------------------------------===//
+// ByteswapOp
+//===----------------------------------------------------------------------===//
+
+def ByteSwapOp : CIR_Op<"bswap", [Pure, SameOperandsAndResultType]> {
+  let summary = "Reverse the bytes in the object representation of the operand";
+  let description = [{
+    The `cir.bswap` operation takes an integer as operand, reverse the bytes in
+    the object representation of the operand integer, and returns the result.
+
+    The operand integer must be an unsigned integer. Its widths must be either
+    16, 32, or 64.
+
+    Example:
+
+    ```mlir
+    // %0 = 0x12345678
+    %0 = cir.const #cir.int<305419896> : !u32i
+
+    // %1 should be 0x78563412
+    %1 = cir.bswap(%0 : !u32i) : !u32i
+    ```
+  }];
+
+  let results = (outs CIR_IntType:$result);
+  let arguments = (ins CIR_UIntOfWidths<[16, 32, 64]>:$input);
+
+  let assemblyFormat = [{
+    `(` $input `:` type($input) `)` `:` type($result) attr-dict
+  }];
+}
+
 //===----------------------------------------------------------------------===//
 // Assume Operations
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index fb046533a91b8..6c90bb7975630 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -190,6 +190,26 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
                                                 expectedValue, probAttr);
     return RValue::get(result);
   }
+
+  case Builtin::BI__builtin_bswap16:
+  case Builtin::BI__builtin_bswap32:
+  case Builtin::BI__builtin_bswap64:
+  case Builtin::BI_byteswap_ushort:
+  case Builtin::BI_byteswap_ulong:
+  case Builtin::BI_byteswap_uint64: {
+    mlir::Value arg = emitScalarExpr(e->getArg(0));
+    return RValue::get(
+        builder.create<cir::ByteSwapOp>(getLoc(e->getSourceRange()), arg));
+  }
+
+  case Builtin::BI__builtin_bitreverse8:
+  case Builtin::BI__builtin_bitreverse16:
+  case Builtin::BI__builtin_bitreverse32:
+  case Builtin::BI__builtin_bitreverse64: {
+    mlir::Value arg = emitScalarExpr(e->getArg(0));
+    return RValue::get(
+        builder.create<cir::BitReverseOp>(getLoc(e->getSourceRange()), arg));
+  }
   }
 
   cgm.errorNYI(e->getSourceRange(), "unimplemented builtin call");
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 5ac42b6a63b09..acbaae1d227d0 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -535,6 +535,13 @@ mlir::LogicalResult CIRToLLVMBitPopcountOpLowering::matchAndRewrite(
   return mlir::LogicalResult::success();
 }
 
+mlir::LogicalResult CIRToLLVMBitReverseOpLowering::matchAndRewrite(
+    cir::BitReverseOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  rewriter.replaceOpWithNewOp<mlir::LLVM::BitReverseOp>(op, adaptor.getInput());
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMBrCondOpLowering::matchAndRewrite(
     cir::BrCondOp brOp, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
@@ -551,6 +558,13 @@ mlir::LogicalResult CIRToLLVMBrCondOpLowering::matchAndRewrite(
   return mlir::success();
 }
 
+mlir::LogicalResult CIRToLLVMByteSwapOpLowering::matchAndRewrite(
+    cir::ByteSwapOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  rewriter.replaceOpWithNewOp<mlir::LLVM::ByteSwapOp>(op, adaptor.getInput());
+  return mlir::LogicalResult::success();
+}
+
 mlir::Type CIRToLLVMCastOpLowering::convertTy(mlir::Type ty) const {
   return getTypeConverter()->convertType(ty);
 }
@@ -2035,8 +2049,10 @@ void ConvertCIRToLLVMPass::runOnOperation() {
                CIRToLLVMBitCtzOpLowering,
                CIRToLLVMBitParityOpLowering,
                CIRToLLVMBitPopcountOpLowering,
+               CIRToLLVMBitReverseOpLowering,
                CIRToLLVMBrCondOpLowering,
                CIRToLLVMBrOpLowering,
+               CIRToLLVMByteSwapOpLowering,
                CIRToLLVMCallOpLowering,
                CIRToLLVMCmpOpLowering,
                CIRToLLVMComplexCreateOpLowering,
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index d9fb91066317b..83e692d6dcbac 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -94,6 +94,16 @@ class CIRToLLVMBitPopcountOpLowering
                   mlir::ConversionPatternRewriter &) const override;
 };
 
+class CIRToLLVMBitReverseOpLowering
+    : public mlir::OpConversionPattern<cir::BitReverseOp> {
+public:
+  using mlir::OpConversionPattern<cir::BitReverseOp>::OpConversionPattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::BitReverseOp op, OpAdaptor,
+                  mlir::ConversionPatternRewriter &) const override;
+};
+
 class CIRToLLVMBrCondOpLowering
     : public mlir::OpConversionPattern<cir::BrCondOp> {
 public:
@@ -104,6 +114,16 @@ class CIRToLLVMBrCondOpLowering
                   mlir::ConversionPatternRewriter &) const override;
 };
 
+class CIRToLLVMByteSwapOpLowering
+    : public mlir::OpConversionPattern<cir::ByteSwapOp> {
+public:
+  using mlir::OpConversionPattern<cir::ByteSwapOp>::OpConversionPattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::ByteSwapOp op, OpAdaptor,
+                  mlir::ConversionPatternRewriter &) const override;
+};
+
 class CIRToLLVMCastOpLowering : public mlir::OpConversionPattern<cir::CastOp> {
   mlir::DataLayout const &dataLayout;
 
diff --git a/clang/test/CIR/CodeGen/builtin_bit.cpp b/clang/test/CIR/CodeGen/builtin_bit.cpp
index ba56c91ce7401..55377ce3b80fa 100644
--- a/clang/test/CIR/CodeGen/builtin_bit.cpp
+++ b/clang/test/CIR/CodeGen/builtin_bit.cpp
@@ -325,3 +325,94 @@ int test_builtin_popcountg(unsigned x) {
 
 // OGCG-LABEL: _Z22test_builtin_popcountgj
 // OGCG:         %{{.+}} = call i32 @llvm.ctpop.i32(i32 %{{.+}})
+
+unsigned char test_builtin_bitreverse8(unsigned char x) {
+  return __builtin_bitreverse8(x);
+}
+
+// CIR-LABEL: @_Z24test_builtin_bitreverse8h
+// CIR:         %{{.+}} = cir.bit.reverse(%{{.+}} : !u8i) : !u8i
+
+// LLVM-LABEL: @_Z24test_builtin_bitreverse8h
+// LLVM:         %{{.+}} = call i8 @llvm.bitreverse.i8(i8 %{{.+}})
+
+// OGCG-LABEL: @_Z24test_builtin_bitreverse8h
+// OGCG:         %{{.+}} = call i8 @llvm.bitreverse.i8(i8 %{{.+}})
+
+unsigned short test_builtin_bitreverse16(unsigned short x) {
+  return __builtin_bitreverse16(x);
+}
+
+// CIR-LABEL: @_Z25test_builtin_bitreverse16t
+// CIR:         %{{.+}} = cir.bit.reverse(%{{.+}} : !u16i) : !u16i
+
+// LLVM-LABEL: @_Z25test_builtin_bitreverse16t
+// LLVM:         %{{.+}} = call i16 @llvm.bitreverse.i16(i16 %{{.+}})
+
+// OGCG-LABEL: @_Z25test_builtin_bitreverse16t
+// OGCG:         %{{.+}} = call i16 @llvm.bitreverse.i16(i16 %{{.+}})
+
+unsigned test_builtin_bitreverse32(unsigned x) {
+  return __builtin_bitreverse32(x);
+}
+
+// CIR-LABEL: @_Z25test_builtin_bitreverse32j
+// CIR:         %{{.+}} = cir.bit.reverse(%{{.+}} : !u32i) : !u32i
+
+// LLVM-LABEL: @_Z25test_builtin_bitreverse32j
+// LLVM:         %{{.+}} = call i32 @llvm.bitreverse.i32(i32 %{{.+}})
+
+// OGCG-LABEL: @_Z25test_builtin_bitreverse32j
+// OGCG:         %{{.+}} = call i32 @llvm.bitreverse.i32(i32 %{{.+}})
+
+unsigned long long test_builtin_bitreverse64(unsigned long long x) {
+  return __builtin_bitreverse64(x);
+}
+
+// CIR-LABEL: @_Z25test_builtin_bitreverse64y
+// CIR:         %{{.+}} = cir.bit.reverse(%{{.+}} : !u64i) : !u64i
+
+// LLVM-LABEL: @_Z25test_builtin_bitreverse64y
+// LLVM:         %{{.+}} = call i64 @llvm.bitreverse.i64(i64 %{{.+}})
+
+// OGCG-LABEL: @_Z25test_builtin_bitreverse64y
+// OGCG:         %{{.+}} = call i64 @llvm.bitreverse.i64(i64 %{{.+}})
+
+unsigned short test_builtin_bswap16(unsigned short x) {
+  return __builtin_bswap16(x);
+}
+
+// CIR-LABEL: @_Z20test_builtin_bswap16t
+// CIR:         %{{.+}} = cir.bswap(%{{.+}} : !u16i) : !u16i
+
+// LLVM-LABEL: @_Z20test_builtin_bswap16t
+// LLVM:         %{{.+}} = call i16 @llvm.bswap.i16(i16 %{{.+}})
+
+// OGCG-LABEL: @_Z20test_builtin_bswap16t
+// OGCG:         %{{.+}} = call i16 @llvm.bswap.i16(i16 %{{.+}})
+
+unsigned test_builtin_bswap32(unsigned x) {
+  return __builtin_bswap32(x);
+}
+
+// CIR-LABEL: @_Z20test_builtin_bswap32j
+// CIR:         %{{.+}} = cir.bswap(%{{.+}} : !u32i) : !u32i
+
+// LLVM-LABEL: @_Z20test_builtin_bswap32j
+// LLVM:         %{{.+}} = call i32 @llvm.bswap.i32(i32 %{{.+}})
+
+// OGCG-LABEL: @_Z20test_builtin_bswap32j
+// OGCG:         %{{.+}} = call i32 @llvm.bswap.i32(i32 %{{.+}})
+
+unsigned long long test_builtin_bswap64(unsigned long long x) {
+  return __builtin_bswap64(x);
+}
+
+// CIR-LABEL: @_Z20test_builtin_bswap64y
+// CIR:         %{{.+}} = cir.bswap(%{{.+}} : !u64i) : !u64i
+
+// LLVM-LABEL: @_Z20test_builtin_bswap64y
+// LLVM:         %{{.+}} = call i64 @llvm.bswap.i64(i64 %{{.+}})
+
+// OGCG-LABEL: @_Z20test_builtin_bswap64y
+// OGCG:         %{{.+}} = call i64 @llvm.bswap.i64(i64 %{{.+}})

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

LGTM, one minor change needed.

Copy link
Contributor

@andykaylor andykaylor left a comment

Choose a reason for hiding this comment

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

Looks good, with one question

@@ -2661,6 +2661,55 @@ def BitPopcountOp : CIR_BitOpBase<"bit.popcnt",
}];
}

def BitReverseOp : CIR_BitOpBase<"bit.reverse", CIR_UIntOfWidths<[8, 16, 32, 64]>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this have the same constraints ([Pure, SameOperandsAndResultType]) as the operation below?

Copy link
Contributor

Choose a reason for hiding this comment

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

These are inherited from CIR_BitOpBase.

@@ -2661,6 +2661,55 @@ def BitPopcountOp : CIR_BitOpBase<"bit.popcnt",
}];
}

def BitReverseOp : CIR_BitOpBase<"bit.reverse", CIR_UIntOfWidths<[8, 16, 32, 64]>> {
Copy link
Contributor

@xlauko xlauko Jul 8, 2025

Choose a reason for hiding this comment

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

Suggested change
def BitReverseOp : CIR_BitOpBase<"bit.reverse", CIR_UIntOfWidths<[8, 16, 32, 64]>> {
def CIR_BitReverseOp : CIR_BitOpBase<"bit.reverse",
CIR_UIntOfWidths<[8, 16, 32, 64]>
> {

Copy link
Contributor

Choose a reason for hiding this comment

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

I would also suggest to rename to bitreverse to mirror builtins name. Also dot slightly implies some "namespace".

Copy link
Member Author

Choose a reason for hiding this comment

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

Well I think we need to think about what the exact scope of CIR_BitOpBase is and whether the bit reverse operation should be one of it. I'm naming it bit.reverse because I think it should follow the CIR_BitOpBase convention.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, ok. Lets keep it then for now with bit..

@@ -2661,6 +2661,55 @@ def BitPopcountOp : CIR_BitOpBase<"bit.popcnt",
}];
}

def BitReverseOp : CIR_BitOpBase<"bit.reverse", CIR_UIntOfWidths<[8, 16, 32, 64]>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

These are inherited from CIR_BitOpBase.

// ByteswapOp
//===----------------------------------------------------------------------===//

def ByteSwapOp : CIR_Op<"bswap", [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.

Suggested change
def ByteSwapOp : CIR_Op<"bswap", [Pure, SameOperandsAndResultType]> {
def CIR_ByteSwapOp : CIR_Op<"bswap", [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.

This operation has same format results and arguments as CIR_BitOpBase.
It can be used here too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated. Also the mnemonic is updated to cir.bit.bswap to match the naming convention for CIR_BitOpBase.

@Lancern Lancern force-pushed the cir/bit-byte-swap branch from 35ca858 to 2dc9775 Compare July 8, 2025 14:11
@Lancern Lancern force-pushed the cir/bit-byte-swap branch from 2dc9775 to cf2bf0b Compare July 9, 2025 13:54
}];
}

def ByteSwapOp : CIR_BitOpBase<"bit.bswap", CIR_UIntOfWidths<[16, 32, 64]>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really like bit.bswap. I know this is lumped in as a bit-manipulation operation, but it makes the referent of the b very unclear. Can we maybe spell it out, bit.byte_swap?

Copy link
Contributor

Choose a reason for hiding this comment

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

Same here — I’d also prefer to keep the names as close as possible to Clang builtins. Using similar names reduces cognitive load and makes it easier to trace things through the entire pipeline.

For the same reason, I’d like to eliminate the bit. prefix entirely and use the builtin names directly, like bitreverse (This can be, and probably should be done as separate PR). I tend to interpret dot-separated names as namespaces, so using them just for readability feels a bit off. Grouping things like complex. or libc. makes sense, as they are in a way subdialects, but when the goal is simply improved readability, I’d lean toward using underscores instead in general.

Copy link
Member Author

Choose a reason for hiding this comment

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

For the same reason, I’d like to eliminate the bit. prefix entirely and use the builtin names directly, like bitreverse (This can be, and probably should be done as separate PR).

I'll send a separate PR for this after this PR lands.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can we maybe spell it out, bit.byte_swap?

Updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants