Skip to content

Commit b22e90b

Browse files
committed
[CIR] Add folders for bit manipulation operations
1 parent 75346e3 commit b22e90b

File tree

5 files changed

+269
-1
lines changed

5 files changed

+269
-1
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2817,6 +2817,8 @@ class CIR_BitOpBase<string mnemonic, TypeConstraint operandTy>
28172817
let assemblyFormat = [{
28182818
$input `:` type($result) attr-dict
28192819
}];
2820+
2821+
let hasFolder = 1;
28202822
}
28212823

28222824
class CIR_BitZeroCountOpBase<string mnemonic, TypeConstraint operandTy>
@@ -3024,6 +3026,8 @@ def CIR_RotateOp : CIR_Op<"rotate", [Pure, SameOperandsAndResultType]> {
30243026
bool isRotateLeft() { return getRotateLeft(); }
30253027
bool isRotateRight() { return !getRotateLeft(); }
30263028
}];
3029+
3030+
let hasFolder = 1;
30273031
}
30283032

30293033
//===----------------------------------------------------------------------===//

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ struct MissingFeatures {
287287

288288
// Future CIR attributes
289289
static bool optInfoAttr() { return false; }
290+
static bool poisonAttr() { return false; }
290291
};
291292

292293
} // namespace cir

clang/lib/CIR/Dialect/IR/CIRDialect.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "mlir/Interfaces/ControlFlowInterfaces.h"
1919
#include "mlir/Interfaces/FunctionImplementation.h"
20+
#include "mlir/Support/LLVM.h"
2021

2122
#include "clang/CIR/Dialect/IR/CIROpsDialect.cpp.inc"
2223
#include "clang/CIR/Dialect/IR/CIROpsEnums.cpp.inc"
@@ -2230,6 +2231,126 @@ LogicalResult cir::ComplexImagPtrOp::verify() {
22302231
return success();
22312232
}
22322233

2234+
//===----------------------------------------------------------------------===//
2235+
// Bit manipulation operations
2236+
//===----------------------------------------------------------------------===//
2237+
2238+
static OpFoldResult
2239+
foldUnaryBitOp(mlir::Attribute inputAttr,
2240+
llvm::function_ref<llvm::APInt(const llvm::APInt &)> func,
2241+
bool poisonZero = false) {
2242+
auto input = mlir::dyn_cast_if_present<IntAttr>(inputAttr);
2243+
if (!input)
2244+
return nullptr;
2245+
2246+
llvm::APInt inputValue = input.getValue();
2247+
if (poisonZero && inputValue.isZero()) {
2248+
// TODO(cir): maybe we should return a poison value here?
2249+
assert(!MissingFeatures::poisonAttr());
2250+
return nullptr;
2251+
}
2252+
2253+
llvm::APInt resultValue = func(inputValue);
2254+
return IntAttr::get(input.getType(), resultValue);
2255+
}
2256+
2257+
OpFoldResult BitClrsbOp::fold(FoldAdaptor adaptor) {
2258+
return foldUnaryBitOp(adaptor.getInput(), [](const llvm::APInt &inputValue) {
2259+
unsigned resultValue =
2260+
inputValue.getBitWidth() - inputValue.getSignificantBits();
2261+
return llvm::APInt(inputValue.getBitWidth(), resultValue);
2262+
});
2263+
}
2264+
2265+
OpFoldResult BitClzOp::fold(FoldAdaptor adaptor) {
2266+
return foldUnaryBitOp(
2267+
adaptor.getInput(),
2268+
[](const llvm::APInt &inputValue) {
2269+
unsigned resultValue = inputValue.countLeadingZeros();
2270+
return llvm::APInt(inputValue.getBitWidth(), resultValue);
2271+
},
2272+
getPoisonZero());
2273+
}
2274+
2275+
OpFoldResult BitCtzOp::fold(FoldAdaptor adaptor) {
2276+
return foldUnaryBitOp(
2277+
adaptor.getInput(),
2278+
[](const llvm::APInt &inputValue) {
2279+
return llvm::APInt(inputValue.getBitWidth(),
2280+
inputValue.countTrailingZeros());
2281+
},
2282+
getPoisonZero());
2283+
}
2284+
2285+
OpFoldResult BitParityOp::fold(FoldAdaptor adaptor) {
2286+
return foldUnaryBitOp(adaptor.getInput(), [](const llvm::APInt &inputValue) {
2287+
return llvm::APInt(inputValue.getBitWidth(), inputValue.popcount() % 2);
2288+
});
2289+
}
2290+
2291+
OpFoldResult BitPopcountOp::fold(FoldAdaptor adaptor) {
2292+
return foldUnaryBitOp(adaptor.getInput(), [](const llvm::APInt &inputValue) {
2293+
return llvm::APInt(inputValue.getBitWidth(), inputValue.popcount());
2294+
});
2295+
}
2296+
2297+
OpFoldResult BitReverseOp::fold(FoldAdaptor adaptor) {
2298+
return foldUnaryBitOp(adaptor.getInput(), [](const llvm::APInt &inputValue) {
2299+
return inputValue.reverseBits();
2300+
});
2301+
}
2302+
2303+
OpFoldResult ByteSwapOp::fold(FoldAdaptor adaptor) {
2304+
return foldUnaryBitOp(adaptor.getInput(), [](const llvm::APInt &inputValue) {
2305+
return inputValue.byteSwap();
2306+
});
2307+
}
2308+
2309+
OpFoldResult RotateOp::fold(FoldAdaptor adaptor) {
2310+
auto input = mlir::dyn_cast_if_present<IntAttr>(adaptor.getInput());
2311+
auto amount = mlir::dyn_cast_if_present<IntAttr>(adaptor.getAmount());
2312+
if (!input && !amount)
2313+
return nullptr;
2314+
2315+
// We could fold cir.rotate even if one of its two operands is not a constant:
2316+
// - `cir.rotate left/right %0, 0` could be folded into just %0 even if %0
2317+
// is not a constant.
2318+
// - `cir.rotate left/right 0/0b111...111, %0` could be folded into 0 or
2319+
// 0b111...111 even if %0 is not a constant.
2320+
2321+
llvm::APInt inputValue;
2322+
if (input) {
2323+
inputValue = input.getValue();
2324+
if (inputValue.isZero() || inputValue.isAllOnes()) {
2325+
// An input value of all 0s or all 1s will not change after rotation
2326+
return input;
2327+
}
2328+
}
2329+
2330+
uint64_t amountValue;
2331+
if (amount) {
2332+
amountValue = amount.getValue().urem(getInput().getType().getWidth());
2333+
if (amountValue == 0) {
2334+
// A shift amount of 0 will not change the input value
2335+
return getInput();
2336+
}
2337+
}
2338+
2339+
if (!input || !amount)
2340+
return nullptr;
2341+
2342+
assert(inputValue.getBitWidth() == getInput().getType().getWidth() &&
2343+
"input value must have the same bit width as the input type");
2344+
2345+
llvm::APInt resultValue;
2346+
if (isRotateLeft())
2347+
resultValue = inputValue.rotl(amountValue);
2348+
else
2349+
resultValue = inputValue.rotr(amountValue);
2350+
2351+
return IntAttr::get(input.getContext(), input.getType(), resultValue);
2352+
}
2353+
22332354
//===----------------------------------------------------------------------===//
22342355
// TableGen'd op method definitions
22352356
//===----------------------------------------------------------------------===//

clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ void CIRCanonicalizePass::runOnOperation() {
143143
if (isa<BrOp, BrCondOp, CastOp, ScopeOp, SwitchOp, SelectOp, UnaryOp,
144144
ComplexCreateOp, ComplexImagOp, ComplexRealOp, VecCmpOp,
145145
VecCreateOp, VecExtractOp, VecShuffleOp, VecShuffleDynamicOp,
146-
VecTernaryOp>(op))
146+
VecTernaryOp, BitClrsbOp, BitClzOp, BitCtzOp, BitParityOp,
147+
BitPopcountOp, BitReverseOp, ByteSwapOp, RotateOp>(op))
147148
ops.push_back(op);
148149
});
149150

clang/test/CIR/Transforms/bit.cir

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// RUN: cir-opt -cir-canonicalize -cir-simplify -o %t.cir %s
2+
// RUN: FileCheck --input-file=%t.cir %s
3+
4+
!s32i = !cir.int<s, 32>
5+
!u32i = !cir.int<u, 32>
6+
7+
module {
8+
cir.func @fold_clrsb() -> !s32i {
9+
%0 = cir.const #cir.int<1> : !s32i
10+
%1 = cir.clrsb %0 : !s32i
11+
cir.return %1 : !s32i
12+
}
13+
// CHECK-LABEL: @fold_clrsb
14+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<30> : !s32i
15+
// CHECK-NEXT: cir.return %[[R]] : !s32i
16+
// CHECK-NEXT: }
17+
18+
cir.func @fold_clz() -> !u32i {
19+
%0 = cir.const #cir.int<1> : !u32i
20+
%1 = cir.clz %0 : !u32i
21+
cir.return %1 : !u32i
22+
}
23+
// CHECK-LABEL: @fold_clz
24+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<31> : !u32i
25+
// CHECK-NEXT: cir.return %[[R]] : !u32i
26+
// CHECK-NEXT: }
27+
28+
cir.func @fold_ctz() -> !u32i {
29+
%0 = cir.const #cir.int<2> : !u32i
30+
%1 = cir.ctz %0 : !u32i
31+
cir.return %1 : !u32i
32+
}
33+
// CHECK-LABEL: @fold_ctz
34+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<1> : !u32i
35+
// CHECK-NEXT: cir.return %[[R]] : !u32i
36+
// CHECK-NEXT: }
37+
38+
cir.func @fold_parity() -> !u32i {
39+
// 0xdeadbeef is 0b1101_1110_1010_1101_1011_1110_1110_1111
40+
// 0xdeadbeef contains 24 ones
41+
%0 = cir.const #cir.int<0xdeadbeef> : !u32i
42+
%1 = cir.parity %0 : !u32i
43+
cir.return %1 : !u32i
44+
}
45+
// CHECK-LABEL: @fold_parity
46+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<0> : !u32i
47+
// CHECK-NEXT: cir.return %[[R]] : !u32i
48+
// CHECK-NEXT: }
49+
50+
cir.func @fold_popcount() -> !u32i {
51+
// 0xdeadbeef is 0b1101_1110_1010_1101_1011_1110_1110_1111
52+
// 0xdeadbeef contains 24 ones
53+
%0 = cir.const #cir.int<0xdeadbeef> : !u32i
54+
%1 = cir.popcount %0 : !u32i
55+
cir.return %1 : !u32i
56+
}
57+
// CHECK-LABEL: @fold_popcount
58+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<24> : !u32i
59+
// CHECK-NEXT: cir.return %[[R]] : !u32i
60+
// CHECK-NEXT: }
61+
62+
cir.func @fold_bitreverse() -> !u32i {
63+
// 0xdeadbeef is 0b1101_1110_1010_1101_1011_1110_1110_1111
64+
%0 = cir.const #cir.int<0xdeadbeef> : !u32i
65+
%1 = cir.bitreverse %0 : !u32i
66+
cir.return %1 : !u32i
67+
}
68+
// CHECK-LABEL: @fold_bitreverse
69+
// 4152210811 is 0b1111_0111_0111_1101_1011_0101_0111_1011
70+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<4152210811> : !u32i
71+
// CHECK-NEXT: cir.return %[[R]] : !u32i
72+
// CHECK-NEXT: }
73+
74+
cir.func @fold_byte_swap() -> !u32i {
75+
%0 = cir.const #cir.int<0xdeadbeef> : !u32i
76+
%1 = cir.byte_swap %0 : !u32i
77+
cir.return %1 : !u32i
78+
}
79+
// CHECK-LABEL: @fold_byte_swap
80+
// 4022250974 is 0xefbeadde
81+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<4022250974> : !u32i
82+
// CHECK-NEXT: cir.return %[[R]] : !u32i
83+
// CHECK-NEXT: }
84+
85+
cir.func @fold_rotate_input_all_zeros(%arg0 : !u32i) -> !u32i {
86+
%0 = cir.const #cir.int<0> : !u32i
87+
%1 = cir.rotate left %0, %arg0 : !u32i
88+
cir.return %1 : !u32i
89+
}
90+
// CHECK-LABEL: @fold_rotate_input_all_zeros
91+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<0> : !u32i
92+
// CHECK-NEXT: cir.return %[[R]] : !u32i
93+
// CHECK-NEXT: }
94+
95+
cir.func @fold_rotate_input_all_ones(%arg0 : !u32i) -> !u32i {
96+
// 4294967295 is 0b1111_1111_1111_1111_1111_1111_1111_1111
97+
%0 = cir.const #cir.int<4294967295> : !u32i
98+
%1 = cir.rotate left %0, %arg0 : !u32i
99+
cir.return %1 : !u32i
100+
}
101+
// CHECK-LABEL: @fold_rotate_input_all_ones
102+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<4294967295> : !u32i
103+
// CHECK-NEXT: cir.return %[[R]] : !u32i
104+
// CHECK-NEXT: }
105+
106+
cir.func @fold_rotate_zero_amount(%arg0 : !u32i) -> !u32i {
107+
%0 = cir.const #cir.int<32> : !u32i
108+
%1 = cir.rotate left %arg0, %0 : !u32i
109+
cir.return %1 : !u32i
110+
}
111+
// CHECK-LABEL: @fold_rotate_zero_amount
112+
// CHECK-SAME: (%[[R:.+]]: !u32i)
113+
// CHECK-NEXT: cir.return %[[R]] : !u32i
114+
// CHECK-NEXT: }
115+
116+
cir.func @fold_rotate_left() -> !u32i {
117+
// 0xdeadbeef is 0b1101_1110_1010_1101_1011_1110_1110_1111
118+
%0 = cir.const #cir.int<0xdeadbeef> : !u32i
119+
%1 = cir.const #cir.int<8> : !u32i
120+
%2 = cir.rotate left %0, %1 : !u32i
121+
cir.return %2 : !u32i
122+
}
123+
// CHECK-LABEL: @fold_rotate_left
124+
// 2914971614 is 0b1010_1101_1011_1110_1110_1111_1101_1110
125+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<2914971614> : !u32i
126+
// CHECK-NEXT: cir.return %[[R]] : !u32i
127+
// CHECK-NEXT: }
128+
129+
cir.func @fold_rotate_right() -> !u32i {
130+
// 0xdeadbeef is 0b1101_1110_1010_1101_1011_1110_1110_1111
131+
%0 = cir.const #cir.int<0xdeadbeef> : !u32i
132+
%1 = cir.const #cir.int<8> : !u32i
133+
%2 = cir.rotate right %0, %1 : !u32i
134+
cir.return %2 : !u32i
135+
}
136+
// CHECK-LABEL: @fold_rotate_right
137+
// 4260027374 is 0b1110_1111_1101_1110_1010_1101_1011_1110
138+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<4024348094> : !u32i
139+
// CHECK-NEXT: cir.return %[[R]] : !u32i
140+
// CHECK-NEXT: }
141+
}

0 commit comments

Comments
 (0)