Skip to content

Commit 1249ab9

Browse files
authored
[CIR] Add bit ffs operation (#150997)
This patch adds the `cir.ffs` operation which corresponds to the `__builtin_ffs` family of builtin functions. This operation was not included in the previous PRs because the call to `__builtin_ffs` would be transformed into a library call to `ffs`. At the time of authoring this patch, this behavior has been changed and now we can properly lower calls to `__builtin_ffs` to `cir.ffs`.
1 parent 586cacd commit 1249ab9

File tree

8 files changed

+167
-1
lines changed

8 files changed

+167
-1
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,6 +2911,28 @@ def CIR_BitCtzOp : CIR_BitZeroCountOpBase<"ctz",
29112911
}];
29122912
}
29132913

2914+
def CIR_BitFfsOp : CIR_BitOpBase<"ffs", CIR_SIntOfWidths<[32, 64]>> {
2915+
let summary = "Get the position of the least significant 1-bit in input";
2916+
let description = [{
2917+
Compute the 1-based position of the least significant 1-bit of the input.
2918+
2919+
The input integer must be a signed integer. The `cir.ffs` operation returns
2920+
one plus the index of the least significant 1-bit of the input signed
2921+
integer. If the input integer is 0, `cir.ffs` yields 0.
2922+
2923+
Example:
2924+
2925+
```mlir
2926+
!s32i = !cir.int<s, 32>
2927+
2928+
// %0 = 0x0010_1000
2929+
%0 = cir.const #cir.int<40> : !s32i
2930+
// #1 will be 4 since the 4th least significant bit is 1.
2931+
%1 = cir.ffs %0 : !s32i
2932+
```
2933+
}];
2934+
}
2935+
29142936
def CIR_BitParityOp : CIR_BitOpBase<"parity", CIR_UIntOfWidths<[32, 64]>> {
29152937
let summary = "Get the parity of input";
29162938
let description = [{

clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
190190
assert(!cir::MissingFeatures::builtinCheckKind());
191191
return emitBuiltinBitOp<cir::BitClzOp>(*this, e, /*poisonZero=*/true);
192192

193+
case Builtin::BI__builtin_ffs:
194+
case Builtin::BI__builtin_ffsl:
195+
case Builtin::BI__builtin_ffsll:
196+
return emitBuiltinBitOp<cir::BitFfsOp>(*this, e);
197+
193198
case Builtin::BI__builtin_parity:
194199
case Builtin::BI__builtin_parityl:
195200
case Builtin::BI__builtin_parityll:

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2295,6 +2295,15 @@ OpFoldResult BitCtzOp::fold(FoldAdaptor adaptor) {
22952295
getPoisonZero());
22962296
}
22972297

2298+
OpFoldResult BitFfsOp::fold(FoldAdaptor adaptor) {
2299+
return foldUnaryBitOp(adaptor.getInput(), [](const llvm::APInt &inputValue) {
2300+
unsigned trailingZeros = inputValue.countTrailingZeros();
2301+
unsigned result =
2302+
trailingZeros == inputValue.getBitWidth() ? 0 : trailingZeros + 1;
2303+
return llvm::APInt(inputValue.getBitWidth(), result);
2304+
});
2305+
}
2306+
22982307
OpFoldResult BitParityOp::fold(FoldAdaptor adaptor) {
22992308
return foldUnaryBitOp(adaptor.getInput(), [](const llvm::APInt &inputValue) {
23002309
return llvm::APInt(inputValue.getBitWidth(), inputValue.popcount() % 2);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ 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, BitClrsbOp, BitClzOp, BitCtzOp, BitParityOp,
146+
VecTernaryOp, BitClrsbOp, BitClzOp, BitCtzOp, BitFfsOp, BitParityOp,
147147
BitPopcountOp, BitReverseOp, ByteSwapOp, RotateOp>(op))
148148
ops.push_back(op);
149149
});

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,32 @@ mlir::LogicalResult CIRToLLVMBitCtzOpLowering::matchAndRewrite(
521521
return mlir::LogicalResult::success();
522522
}
523523

524+
mlir::LogicalResult CIRToLLVMBitFfsOpLowering::matchAndRewrite(
525+
cir::BitFfsOp op, OpAdaptor adaptor,
526+
mlir::ConversionPatternRewriter &rewriter) const {
527+
auto resTy = getTypeConverter()->convertType(op.getType());
528+
auto ctz = rewriter.create<mlir::LLVM::CountTrailingZerosOp>(
529+
op.getLoc(), resTy, adaptor.getInput(), /*is_zero_poison=*/true);
530+
531+
auto one = rewriter.create<mlir::LLVM::ConstantOp>(op.getLoc(), resTy, 1);
532+
auto ctzAddOne = rewriter.create<mlir::LLVM::AddOp>(op.getLoc(), ctz, one);
533+
534+
auto zeroInputTy = rewriter.create<mlir::LLVM::ConstantOp>(
535+
op.getLoc(), adaptor.getInput().getType(), 0);
536+
auto isZero = rewriter.create<mlir::LLVM::ICmpOp>(
537+
op.getLoc(),
538+
mlir::LLVM::ICmpPredicateAttr::get(rewriter.getContext(),
539+
mlir::LLVM::ICmpPredicate::eq),
540+
adaptor.getInput(), zeroInputTy);
541+
542+
auto zero = rewriter.create<mlir::LLVM::ConstantOp>(op.getLoc(), resTy, 0);
543+
auto res = rewriter.create<mlir::LLVM::SelectOp>(op.getLoc(), isZero, zero,
544+
ctzAddOne);
545+
rewriter.replaceOp(op, res);
546+
547+
return mlir::LogicalResult::success();
548+
}
549+
524550
mlir::LogicalResult CIRToLLVMBitParityOpLowering::matchAndRewrite(
525551
cir::BitParityOp op, OpAdaptor adaptor,
526552
mlir::ConversionPatternRewriter &rewriter) const {
@@ -2089,6 +2115,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
20892115
CIRToLLVMBitClrsbOpLowering,
20902116
CIRToLLVMBitClzOpLowering,
20912117
CIRToLLVMBitCtzOpLowering,
2118+
CIRToLLVMBitFfsOpLowering,
20922119
CIRToLLVMBitParityOpLowering,
20932120
CIRToLLVMBitPopcountOpLowering,
20942121
CIRToLLVMBitReverseOpLowering,

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ class CIRToLLVMBitCtzOpLowering
8484
mlir::ConversionPatternRewriter &) const override;
8585
};
8686

87+
class CIRToLLVMBitFfsOpLowering
88+
: public mlir::OpConversionPattern<cir::BitFfsOp> {
89+
public:
90+
using mlir::OpConversionPattern<cir::BitFfsOp>::OpConversionPattern;
91+
92+
mlir::LogicalResult
93+
matchAndRewrite(cir::BitFfsOp op, OpAdaptor,
94+
mlir::ConversionPatternRewriter &) const override;
95+
};
96+
8797
class CIRToLLVMBitParityOpLowering
8898
: public mlir::OpConversionPattern<cir::BitParityOp> {
8999
public:

clang/test/CIR/CodeGen/builtin_bit.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,78 @@ int test_builtin_clzg(unsigned x) {
216216
// OGCG-LABEL: _Z17test_builtin_clzgj
217217
// OGCG: %{{.+}} = call i32 @llvm.ctlz.i32(i32 %{{.+}}, i1 true)
218218

219+
int test_builtin_ffs(int x) {
220+
return __builtin_ffs(x);
221+
}
222+
223+
// CIR-LABEL: _Z16test_builtin_ffsi
224+
// CIR: %{{.+}} = cir.ffs %{{.+}} : !s32i
225+
// CIR: }
226+
227+
// LLVM-LABEL: _Z16test_builtin_ffsi
228+
// LLVM: %[[INPUT:.+]] = load i32, ptr %{{.+}}, align 4
229+
// LLVM-NEXT: %[[CTZ:.+]] = call i32 @llvm.cttz.i32(i32 %[[INPUT]], i1 true)
230+
// LLVM-NEXT: %[[R1:.+]] = add i32 %[[CTZ]], 1
231+
// LLVM-NEXT: %[[IS_ZERO:.+]] = icmp eq i32 %[[INPUT]], 0
232+
// LLVM-NEXT: %{{.+}} = select i1 %[[IS_ZERO]], i32 0, i32 %[[R1]]
233+
// LLVM: }
234+
235+
// OGCG-LABEL: _Z16test_builtin_ffsi
236+
// OGCG: %[[INPUT:.+]] = load i32, ptr %{{.+}}, align 4
237+
// OGCG-NEXT: %[[CTZ:.+]] = call i32 @llvm.cttz.i32(i32 %[[INPUT]], i1 true)
238+
// OGCG-NEXT: %[[R1:.+]] = add i32 %[[CTZ]], 1
239+
// OGCG-NEXT: %[[IS_ZERO:.+]] = icmp eq i32 %[[INPUT]], 0
240+
// OGCG-NEXT: %{{.+}} = select i1 %[[IS_ZERO]], i32 0, i32 %[[R1]]
241+
// OGCG: }
242+
243+
int test_builtin_ffsl(long x) {
244+
return __builtin_ffsl(x);
245+
}
246+
247+
// CIR-LABEL: _Z17test_builtin_ffsll
248+
// CIR: %{{.+}} = cir.ffs %{{.+}} : !s64i
249+
// CIR: }
250+
251+
// LLVM-LABEL: _Z17test_builtin_ffsll
252+
// LLVM: %[[INPUT:.+]] = load i64, ptr %{{.+}}, align 8
253+
// LLVM-NEXT: %[[CTZ:.+]] = call i64 @llvm.cttz.i64(i64 %[[INPUT]], i1 true)
254+
// LLVM-NEXT: %[[R1:.+]] = add i64 %[[CTZ]], 1
255+
// LLVM-NEXT: %[[IS_ZERO:.+]] = icmp eq i64 %[[INPUT]], 0
256+
// LLVM-NEXT: %{{.+}} = select i1 %[[IS_ZERO]], i64 0, i64 %[[R1]]
257+
// LLVM: }
258+
259+
// OGCG-LABEL: _Z17test_builtin_ffsll
260+
// OGCG: %[[INPUT:.+]] = load i64, ptr %{{.+}}, align 8
261+
// OGCG-NEXT: %[[CTZ:.+]] = call i64 @llvm.cttz.i64(i64 %[[INPUT]], i1 true)
262+
// OGCG-NEXT: %[[R1:.+]] = add i64 %[[CTZ]], 1
263+
// OGCG-NEXT: %[[IS_ZERO:.+]] = icmp eq i64 %[[INPUT]], 0
264+
// OGCG-NEXT: %{{.+}} = select i1 %[[IS_ZERO]], i64 0, i64 %[[R1]]
265+
// OGCG: }
266+
267+
int test_builtin_ffsll(long long x) {
268+
return __builtin_ffsll(x);
269+
}
270+
271+
// CIR-LABEL: _Z18test_builtin_ffsllx
272+
// CIR: %{{.+}} = cir.ffs %{{.+}} : !s64i
273+
// CIR: }
274+
275+
// LLVM-LABEL: _Z18test_builtin_ffsllx
276+
// LLVM: %[[INPUT:.+]] = load i64, ptr %{{.+}}, align 8
277+
// LLVM-NEXT: %[[CTZ:.+]] = call i64 @llvm.cttz.i64(i64 %[[INPUT]], i1 true)
278+
// LLVM-NEXT: %[[R1:.+]] = add i64 %[[CTZ]], 1
279+
// LLVM-NEXT: %[[IS_ZERO:.+]] = icmp eq i64 %[[INPUT]], 0
280+
// LLVM-NEXT: %{{.+}} = select i1 %[[IS_ZERO]], i64 0, i64 %[[R1]]
281+
// LLVM: }
282+
283+
// OGCG-LABEL: _Z18test_builtin_ffsllx
284+
// OGCG: %[[INPUT:.+]] = load i64, ptr %{{.+}}, align 8
285+
// OGCG-NEXT: %[[CTZ:.+]] = call i64 @llvm.cttz.i64(i64 %[[INPUT]], i1 true)
286+
// OGCG-NEXT: %[[R1:.+]] = add i64 %[[CTZ]], 1
287+
// OGCG-NEXT: %[[IS_ZERO:.+]] = icmp eq i64 %[[INPUT]], 0
288+
// OGCG-NEXT: %{{.+}} = select i1 %[[IS_ZERO]], i64 0, i64 %[[R1]]
289+
// OGCG: }
290+
219291
int test_builtin_parity(unsigned x) {
220292
return __builtin_parity(x);
221293
}

clang/test/CIR/Transforms/bit.cir

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,27 @@ module {
7575
// CHECK-NEXT: cir.return %[[R]] : !u32i
7676
// CHECK-NEXT: }
7777

78+
cir.func @fold_ffs() -> !s32i {
79+
// 40 is 0b0010_1000
80+
%0 = cir.const #cir.int<40> : !s32i
81+
%1 = cir.ffs %0 : !s32i
82+
cir.return %1 : !s32i
83+
}
84+
// CHECK-LABEL: @fold_ffs
85+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<4> : !s32i
86+
// CHECK-NEXT: cir.return %[[R]] : !s32i
87+
// CHECK-NEXT: }
88+
89+
cir.func @fold_ffs_zero() -> !s32i {
90+
%0 = cir.const #cir.int<0> : !s32i
91+
%1 = cir.ffs %0 : !s32i
92+
cir.return %1 : !s32i
93+
}
94+
// CHECK-LABEL: @fold_ffs_zero
95+
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<0> : !s32i
96+
// CHECK-NEXT: cir.return %[[R]] : !s32i
97+
// CHECK-NEXT: }
98+
7899
cir.func @fold_parity() -> !u32i {
79100
// 0xdeadbeef is 0b1101_1110_1010_1101_1011_1110_1110_1111
80101
// 0xdeadbeef contains 24 ones

0 commit comments

Comments
 (0)