[clang] [CIR] Lower __builtin_bswapg (PR #203618)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 12 12:56:52 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: adams381
<details>
<summary>Changes</summary>
C++23 `std::byteswap` lowers every value wider than a single byte through the type-generic `__builtin_bswapg` builtin, which CIRGen had no case for, so `std/numerics/bit/byteswap.pass.cpp` hit `errorBuiltinNYI`.
This handles `__builtin_bswapg` the way classic CodeGen does (`CGBuiltin.cpp`): a bool or single-byte integer is returned unchanged, and wider values go through `cir.byte_swap`. Unlike the unsigned-only `__builtin_bswap16/32/64`, the generic builtin also accepts signed operands, so a signed argument is reinterpreted as unsigned of the same width before the swap and cast back afterward.
`cir.byte_swap` previously accepted only 16/32/64-bit operands, but `std::byteswap` instantiates it for `__int128` and wide `_BitInt` too (the libc++ test reaches `_BitInt(256)`). The operand constraint is widened to any unsigned integer whose width is a multiple of 16 bits -- which is what `llvm.bswap` requires -- and the existing CIR-to-LLVM lowering already handles any such width. With the fix, `byteswap.pass.cpp` passes under `-fclangir`.
---
Full diff: https://github.com/llvm/llvm-project/pull/203618.diff
5 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+3-3)
- (modified) clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td (+4)
- (modified) clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp (+15)
- (modified) clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp (+139)
- (added) clang/test/CIR/IR/invalid-bit.cir (+12)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 557e279d9bc71..9dae3534991e5 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -6305,15 +6305,15 @@ def CIR_BitReverseOp : CIR_BitOpBase<"bitreverse",
}
def CIR_ByteSwapOp : CIR_BitOpBase<"byte_swap",
- CIR_UIntOfWidths<[16, 32, 64]>
+ CIR_UIntMultipleOf16
> {
let summary = "Reverse the bytes in the object representation of the operand";
let description = [{
The `cir.byte_swap` 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.
+ The operand integer must be an unsigned integer whose width is a multiple of
+ 16 bits (e.g. 16, 32, 64, 128, or a wider `_BitInt`).
Example:
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
index 322eec853d821..a8643ec21af80 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
@@ -82,6 +82,10 @@ class CIR_UIntOfWidths<list<int> widths>
: CIR_ConfinedType<CIR_AnyUIntType, [CIR_IntOfWidthsPred<widths>],
"unsigned integer type of widths " # !interleave(widths, "/")>;
+def CIR_UIntMultipleOf16
+ : CIR_ConfinedType<CIR_AnyUIntType, [CPred<"$_self.getWidth() % 16 == 0">],
+ "unsigned integer type with a width that is a multiple of 16 bits">;
+
class CIR_UInt<int width>
: CIR_ConfinedType<CIR_AnyUIntType, [CIR_HasWidthPred<width>],
width # "-bit unsigned integer">,
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index e6de3954a8c69..049a7d5f255a0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -1277,6 +1277,21 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
return RValue::get(cir::ByteSwapOp::create(builder, loc, arg));
}
+ case Builtin::BI__builtin_bswapg: {
+ mlir::Value arg = emitScalarExpr(e->getArg(0));
+ // A bool or any single-byte integer byte-swaps to itself; cir.byte_swap
+ // only accepts an unsigned integer whose width is a multiple of 16 bits.
+ auto argTy = mlir::dyn_cast<cir::IntType>(arg.getType());
+ if (!argTy || argTy.getWidth() <= 8)
+ return RValue::get(arg);
+ // cir.byte_swap requires an unsigned operand. Reinterpret a signed
+ // argument as unsigned of the same width; createBuiltinBitOp casts the
+ // swapped result back to the builtin's (possibly signed) return type.
+ if (argTy.isSigned())
+ arg = builder.createIntCast(arg, builder.getUIntNTy(argTy.getWidth()));
+ return RValue::get(createBuiltinBitOp<cir::ByteSwapOp>(*this, e, arg));
+ }
+
case Builtin::BI__builtin_bitreverse8:
case Builtin::BI__builtin_bitreverse16:
case Builtin::BI__builtin_bitreverse32:
diff --git a/clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp b/clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp
index 23e9c77e11130..2d3900caeca3a 100644
--- a/clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp
+++ b/clang/test/CIR/CodeGenBuiltins/builtin-bit.cpp
@@ -550,6 +550,145 @@ unsigned long long test_builtin_bswap64(unsigned long long x) {
// OGCG-LABEL: @_Z20test_builtin_bswap64y
// OGCG: %{{.+}} = call i64 @llvm.bswap.i64(i64 %{{.+}})
+unsigned short test_bswapg_u16(unsigned short x) {
+ return __builtin_bswapg(x);
+}
+
+// CIR-LABEL: @_Z15test_bswapg_u16t
+// CIR: %{{.+}} = cir.byte_swap %{{.+}} : !u16i
+
+// LLVM-LABEL: @_Z15test_bswapg_u16t
+// LLVM: %{{.+}} = call i16 @llvm.bswap.i16(i16 %{{.+}})
+
+// OGCG-LABEL: @_Z15test_bswapg_u16t
+// OGCG: %{{.+}} = call i16 @llvm.bswap.i16(i16 %{{.+}})
+
+short test_bswapg_s16(short x) {
+ return __builtin_bswapg(x);
+}
+
+// A signed operand is reinterpreted as unsigned of the same width, byte
+// swapped, then cast back to the signed return type.
+// CIR-LABEL: @_Z15test_bswapg_s16s
+// CIR: %[[ARG:.+]] = cir.cast integral %{{.+}} : !s16i -> !u16i
+// CIR: %[[RES:.+]] = cir.byte_swap %[[ARG]] : !u16i
+// CIR: %{{.+}} = cir.cast integral %[[RES]] : !u16i -> !s16i
+
+// LLVM-LABEL: @_Z15test_bswapg_s16s
+// LLVM: %{{.+}} = call i16 @llvm.bswap.i16(i16 %{{.+}})
+
+// OGCG-LABEL: @_Z15test_bswapg_s16s
+// OGCG: %{{.+}} = call i16 @llvm.bswap.i16(i16 %{{.+}})
+
+unsigned test_bswapg_u32(unsigned x) {
+ return __builtin_bswapg(x);
+}
+
+// CIR-LABEL: @_Z15test_bswapg_u32j
+// CIR: %{{.+}} = cir.byte_swap %{{.+}} : !u32i
+
+// LLVM-LABEL: @_Z15test_bswapg_u32j
+// LLVM: %{{.+}} = call i32 @llvm.bswap.i32(i32 %{{.+}})
+
+// OGCG-LABEL: @_Z15test_bswapg_u32j
+// OGCG: %{{.+}} = call i32 @llvm.bswap.i32(i32 %{{.+}})
+
+unsigned long long test_bswapg_u64(unsigned long long x) {
+ return __builtin_bswapg(x);
+}
+
+// CIR-LABEL: @_Z15test_bswapg_u64y
+// CIR: %{{.+}} = cir.byte_swap %{{.+}} : !u64i
+
+// LLVM-LABEL: @_Z15test_bswapg_u64y
+// LLVM: %{{.+}} = call i64 @llvm.bswap.i64(i64 %{{.+}})
+
+// OGCG-LABEL: @_Z15test_bswapg_u64y
+// OGCG: %{{.+}} = call i64 @llvm.bswap.i64(i64 %{{.+}})
+
+unsigned __int128 test_bswapg_u128(unsigned __int128 x) {
+ return __builtin_bswapg(x);
+}
+
+// CIR-LABEL: @_Z16test_bswapg_u128o
+// CIR: %{{.+}} = cir.byte_swap %{{.+}} : !u128i
+
+// LLVM-LABEL: @_Z16test_bswapg_u128o
+// LLVM: %{{.+}} = call i128 @llvm.bswap.i128(i128 %{{.+}})
+
+// OGCG-LABEL: @_Z16test_bswapg_u128o
+// OGCG: %{{.+}} = call i128 @llvm.bswap.i128(i128 %{{.+}})
+
+unsigned _BitInt(256) test_bswapg_u256(unsigned _BitInt(256) x) {
+ return __builtin_bswapg(x);
+}
+
+// A wide _BitInt exercises the multiple-of-16 width constraint on the op.
+// CIR-LABEL: @_Z16test_bswapg_u256DU256_
+// CIR: %{{.+}} = cir.byte_swap %{{.+}} : !u256i_bitint
+
+// LLVM-LABEL: @_Z16test_bswapg_u256DU256_
+// LLVM: %{{.+}} = call i256 @llvm.bswap.i256(i256 %{{.+}})
+
+// OGCG-LABEL: @_Z16test_bswapg_u256DU256_
+// OGCG: %{{.+}} = call i256 @llvm.bswap.i256(i256 %{{.+}})
+
+_BitInt(256) test_bswapg_s256(_BitInt(256) x) {
+ return __builtin_bswapg(x);
+}
+
+// CIR-LABEL: @_Z16test_bswapg_s256DB256_
+// CIR: %[[ARG:.+]] = cir.cast integral %{{.+}} : !s256i_bitint -> !u256i
+// CIR: %[[RES:.+]] = cir.byte_swap %[[ARG]] : !u256i
+// CIR: %{{.+}} = cir.cast integral %[[RES]] : !u256i -> !s256i_bitint
+
+// LLVM-LABEL: @_Z16test_bswapg_s256DB256_
+// LLVM: %{{.+}} = call i256 @llvm.bswap.i256(i256 %{{.+}})
+
+// OGCG-LABEL: @_Z16test_bswapg_s256DB256_
+// OGCG: %{{.+}} = call i256 @llvm.bswap.i256(i256 %{{.+}})
+
+unsigned char test_bswapg_u8(unsigned char x) {
+ return __builtin_bswapg(x);
+}
+
+// A single-byte swap is the identity, so no cir.byte_swap / llvm.bswap.
+// CIR-LABEL: @_Z14test_bswapg_u8h
+// CIR-NOT: cir.byte_swap
+
+// LLVM-LABEL: @_Z14test_bswapg_u8h
+// LLVM-NOT: @llvm.bswap
+
+// OGCG-LABEL: @_Z14test_bswapg_u8h
+// OGCG-NOT: @llvm.bswap
+
+bool test_bswapg_bool(bool x) {
+ return __builtin_bswapg(x);
+}
+
+// A bool is a single-byte value, so it is returned unchanged.
+// CIR-LABEL: @_Z16test_bswapg_boolb
+// CIR-NOT: cir.byte_swap
+
+// LLVM-LABEL: @_Z16test_bswapg_boolb
+// LLVM-NOT: @llvm.bswap
+
+// OGCG-LABEL: @_Z16test_bswapg_boolb
+// OGCG-NOT: @llvm.bswap
+
+signed char test_bswapg_s8(signed char x) {
+ return __builtin_bswapg(x);
+}
+
+// CIR-LABEL: @_Z14test_bswapg_s8a
+// CIR-NOT: cir.byte_swap
+
+// LLVM-LABEL: @_Z14test_bswapg_s8a
+// LLVM-NOT: @llvm.bswap
+
+// OGCG-LABEL: @_Z14test_bswapg_s8a
+// OGCG-NOT: @llvm.bswap
+
unsigned char test_builtin_rotateleft8(unsigned char x, unsigned char y) {
return __builtin_rotateleft8(x, y);
}
diff --git a/clang/test/CIR/IR/invalid-bit.cir b/clang/test/CIR/IR/invalid-bit.cir
new file mode 100644
index 0000000000000..5b918c95996ce
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-bit.cir
@@ -0,0 +1,12 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+// cir.byte_swap requires an unsigned integer whose width is a multiple of 16
+// bits.
+
+module {
+ cir.func @bad_byte_swap_width(%x : !cir.int<u, 24>) {
+ // expected-error at +1 {{'cir.byte_swap' op operand #0 must be unsigned integer type with a width that is a multiple of 16 bits, but got '!cir.int<u, 24>'}}
+ %0 = cir.byte_swap %x : !cir.int<u, 24>
+ cir.return
+ }
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/203618
More information about the cfe-commits
mailing list