Skip to content

Commit 4b9f622

Browse files
authored
[CIR] Bit manipulation builtin functions (llvm#146529)
This patch adds CIR support for the following families of bit manipulation builtin functions: - `__builtin_clrsb`, represented by the `cir.bit.clrsb` operation - `__builtin_ctz`, represented by the `cir.bit.clz` operation - `__builtin_clz`, represented by the `cir.bit.ctz` operation - `__builtin_parity`, represented by the `cir.bit.parity` operation - `__builtin_popcount`, represented by the `cir.bit.popcnt` operation The `__builtin_ffs` builtin function is not included in this patch because the current CIRGen would emit it as a library call to `@ffs`.
1 parent f019c89 commit 4b9f622

File tree

6 files changed

+662
-0
lines changed

6 files changed

+662
-0
lines changed

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

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2521,6 +2521,146 @@ def ComplexImagOp : CIR_Op<"complex.imag", [Pure]> {
25212521
let hasFolder = 1;
25222522
}
25232523

2524+
//===----------------------------------------------------------------------===//
2525+
// Bit Manipulation Operations
2526+
//===----------------------------------------------------------------------===//
2527+
2528+
class CIR_BitOpBase<string mnemonic, TypeConstraint operandTy>
2529+
: CIR_Op<mnemonic, [Pure, SameOperandsAndResultType]> {
2530+
let arguments = (ins operandTy:$input);
2531+
let results = (outs operandTy:$result);
2532+
2533+
let assemblyFormat = [{
2534+
`(` $input `:` type($input) `)` `:` type($result) attr-dict
2535+
}];
2536+
}
2537+
2538+
class CIR_BitZeroCountOpBase<string mnemonic, TypeConstraint operandTy>
2539+
: CIR_BitOpBase<mnemonic, operandTy> {
2540+
let arguments = (ins operandTy:$input, UnitAttr:$poison_zero);
2541+
2542+
let assemblyFormat = [{
2543+
`(` $input `:` type($input) `)` (`poison_zero` $poison_zero^)?
2544+
`:` type($result) attr-dict
2545+
}];
2546+
}
2547+
2548+
def BitClrsbOp : CIR_BitOpBase<"bit.clrsb", CIR_SIntOfWidths<[32, 64]>> {
2549+
let summary = "Get the number of leading redundant sign bits in the input";
2550+
let description = [{
2551+
Compute the number of leading redundant sign bits in the input integer.
2552+
2553+
The input integer must be a signed integer. The most significant bit of the
2554+
input integer is the sign bit. The `cir.bit.clrsb` operation returns the
2555+
number of consecutive bits following the sign bit that are identical to the
2556+
sign bit.
2557+
2558+
The bit width of the input integer must be either 32 or 64.
2559+
2560+
Examples:
2561+
2562+
```mlir
2563+
// %0 = 0b1101_1110_1010_1101_1011_1110_1110_1111
2564+
%0 = cir.const #cir.int<3735928559> : !s32i
2565+
// %1 will be 1 because there is 1 bit following the most significant bit
2566+
// that is identical to it.
2567+
%1 = cir.bit.clrsb(%0 : !s32i) : !s32i
2568+
2569+
// %2 = 1, 0b0000_0000_0000_0000_0000_0000_0000_0001
2570+
%2 = cir.const #cir.int<1> : !s32i
2571+
// %3 will be 30 because there are 30 consecutive bits following the sign
2572+
// bit that are identical to the sign bit.
2573+
%3 = cir.bit.clrsb(%2 : !s32i) : !s32i
2574+
```
2575+
}];
2576+
}
2577+
2578+
def BitClzOp : CIR_BitZeroCountOpBase<"bit.clz",
2579+
CIR_UIntOfWidths<[16, 32, 64]>> {
2580+
let summary = "Get the number of leading 0-bits in the input";
2581+
let description = [{
2582+
Compute the number of leading 0-bits in the input.
2583+
2584+
The input integer must be an unsigned integer. The `cir.bit.clz` operation
2585+
returns the number of consecutive 0-bits at the most significant bit
2586+
position in the input.
2587+
2588+
If the `poison_zero` attribute is present, this operation will have
2589+
undefined behavior if the input value is 0.
2590+
2591+
Example:
2592+
2593+
```mlir
2594+
// %0 = 0b0000_0000_0000_0000_0000_0000_0000_1000
2595+
%0 = cir.const #cir.int<8> : !u32i
2596+
// %1 will be 28
2597+
%1 = cir.bit.clz(%0 : !u32i) poison_zero : !u32i
2598+
```
2599+
}];
2600+
}
2601+
2602+
def BitCtzOp : CIR_BitZeroCountOpBase<"bit.ctz",
2603+
CIR_UIntOfWidths<[16, 32, 64]>> {
2604+
let summary = "Get the number of trailing 0-bits in the input";
2605+
let description = [{
2606+
Compute the number of trailing 0-bits in the input.
2607+
2608+
The input integer must be an unsigned integer. The `cir.bit.ctz` operation
2609+
counts the number of consecutive 0-bits starting from the least significant
2610+
bit.
2611+
2612+
If the `poison_zero` attribute is present, this operation will have
2613+
undefined behavior if the input value is 0.
2614+
2615+
Example:
2616+
2617+
```mlir
2618+
// %0 = 0b1000
2619+
%0 = cir.const #cir.int<8> : !u32i
2620+
// %1 will be 3
2621+
%1 = cir.bit.ctz(%0 : !u32i) poison_zero : !u32i
2622+
```
2623+
}];
2624+
}
2625+
2626+
def BitParityOp : CIR_BitOpBase<"bit.parity", CIR_UIntOfWidths<[32, 64]>> {
2627+
let summary = "Get the parity of input";
2628+
let description = [{
2629+
Compute the parity of the input. The parity of an integer is the number of
2630+
1-bits in it modulo 2.
2631+
2632+
The input must be an unsigned integer.
2633+
2634+
Example:
2635+
2636+
```mlir
2637+
// %0 = 0x0110_1000
2638+
%0 = cir.const #cir.int<104> : !u32i
2639+
// %1 will be 1 since there are three 1-bits in %0
2640+
%1 = cir.bit.parity(%0 : !u32i) : !u32i
2641+
```
2642+
}];
2643+
}
2644+
2645+
def BitPopcountOp : CIR_BitOpBase<"bit.popcnt",
2646+
CIR_UIntOfWidths<[16, 32, 64]>> {
2647+
let summary = "Get the number of 1-bits in input";
2648+
let description = [{
2649+
Compute the number of 1-bits in the input.
2650+
2651+
The input must be an unsigned integer.
2652+
2653+
Example:
2654+
2655+
```mlir
2656+
// %0 = 0x0110_1000
2657+
%0 = cir.const #cir.int<104> : !u32i
2658+
// %1 will be 3 since there are 3 1-bits in %0
2659+
%1 = cir.bit.popcnt(%0 : !u32i) : !u32i
2660+
```
2661+
}];
2662+
}
2663+
25242664
//===----------------------------------------------------------------------===//
25252665
// Assume Operations
25262666
//===----------------------------------------------------------------------===//

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ struct MissingFeatures {
180180
static bool builtinCall() { return false; }
181181
static bool builtinCallF128() { return false; }
182182
static bool builtinCallMathErrno() { return false; }
183+
static bool builtinCheckKind() { return false; }
183184
static bool cgFPOptionsRAII() { return false; }
184185
static bool cirgenABIInfo() { return false; }
185186
static bool cleanupAfterErrorDiags() { return false; }

clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,29 @@ static RValue emitLibraryCall(CIRGenFunction &cgf, const FunctionDecl *fd,
3434
return cgf.emitCall(e->getCallee()->getType(), callee, e, ReturnValueSlot());
3535
}
3636

37+
template <typename Op>
38+
static RValue emitBuiltinBitOp(CIRGenFunction &cgf, const CallExpr *e,
39+
bool poisonZero = false) {
40+
assert(!cir::MissingFeatures::builtinCheckKind());
41+
42+
mlir::Value arg = cgf.emitScalarExpr(e->getArg(0));
43+
CIRGenBuilderTy &builder = cgf.getBuilder();
44+
45+
Op op;
46+
if constexpr (std::is_same_v<Op, cir::BitClzOp> ||
47+
std::is_same_v<Op, cir::BitCtzOp>)
48+
op = builder.create<Op>(cgf.getLoc(e->getSourceRange()), arg, poisonZero);
49+
else
50+
op = builder.create<Op>(cgf.getLoc(e->getSourceRange()), arg);
51+
52+
mlir::Value result = op.getResult();
53+
mlir::Type exprTy = cgf.convertType(e->getType());
54+
if (exprTy != result.getType())
55+
result = builder.createIntCast(result, exprTy);
56+
57+
return RValue::get(result);
58+
}
59+
3760
RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
3861
const CallExpr *e,
3962
ReturnValueSlot returnValue) {
@@ -101,6 +124,47 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
101124
return RValue::get(complex);
102125
}
103126

127+
case Builtin::BI__builtin_clrsb:
128+
case Builtin::BI__builtin_clrsbl:
129+
case Builtin::BI__builtin_clrsbll:
130+
return emitBuiltinBitOp<cir::BitClrsbOp>(*this, e);
131+
132+
case Builtin::BI__builtin_ctzs:
133+
case Builtin::BI__builtin_ctz:
134+
case Builtin::BI__builtin_ctzl:
135+
case Builtin::BI__builtin_ctzll:
136+
case Builtin::BI__builtin_ctzg:
137+
assert(!cir::MissingFeatures::builtinCheckKind());
138+
return emitBuiltinBitOp<cir::BitCtzOp>(*this, e, /*poisonZero=*/true);
139+
140+
case Builtin::BI__builtin_clzs:
141+
case Builtin::BI__builtin_clz:
142+
case Builtin::BI__builtin_clzl:
143+
case Builtin::BI__builtin_clzll:
144+
case Builtin::BI__builtin_clzg:
145+
assert(!cir::MissingFeatures::builtinCheckKind());
146+
return emitBuiltinBitOp<cir::BitClzOp>(*this, e, /*poisonZero=*/true);
147+
148+
case Builtin::BI__builtin_parity:
149+
case Builtin::BI__builtin_parityl:
150+
case Builtin::BI__builtin_parityll:
151+
return emitBuiltinBitOp<cir::BitParityOp>(*this, e);
152+
153+
case Builtin::BI__lzcnt16:
154+
case Builtin::BI__lzcnt:
155+
case Builtin::BI__lzcnt64:
156+
assert(!cir::MissingFeatures::builtinCheckKind());
157+
return emitBuiltinBitOp<cir::BitClzOp>(*this, e, /*poisonZero=*/false);
158+
159+
case Builtin::BI__popcnt16:
160+
case Builtin::BI__popcnt:
161+
case Builtin::BI__popcnt64:
162+
case Builtin::BI__builtin_popcount:
163+
case Builtin::BI__builtin_popcountl:
164+
case Builtin::BI__builtin_popcountll:
165+
case Builtin::BI__builtin_popcountg:
166+
return emitBuiltinBitOp<cir::BitPopcountOp>(*this, e);
167+
104168
case Builtin::BI__builtin_expect:
105169
case Builtin::BI__builtin_expect_with_probability: {
106170
mlir::Value argValue = emitScalarExpr(e->getArg(0));

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,81 @@ mlir::LogicalResult CIRToLLVMAssumeOpLowering::matchAndRewrite(
460460
return mlir::success();
461461
}
462462

463+
mlir::LogicalResult CIRToLLVMBitClrsbOpLowering::matchAndRewrite(
464+
cir::BitClrsbOp op, OpAdaptor adaptor,
465+
mlir::ConversionPatternRewriter &rewriter) const {
466+
auto zero = rewriter.create<mlir::LLVM::ConstantOp>(
467+
op.getLoc(), adaptor.getInput().getType(), 0);
468+
auto isNeg = rewriter.create<mlir::LLVM::ICmpOp>(
469+
op.getLoc(),
470+
mlir::LLVM::ICmpPredicateAttr::get(rewriter.getContext(),
471+
mlir::LLVM::ICmpPredicate::slt),
472+
adaptor.getInput(), zero);
473+
474+
auto negOne = rewriter.create<mlir::LLVM::ConstantOp>(
475+
op.getLoc(), adaptor.getInput().getType(), -1);
476+
auto flipped = rewriter.create<mlir::LLVM::XOrOp>(op.getLoc(),
477+
adaptor.getInput(), negOne);
478+
479+
auto select = rewriter.create<mlir::LLVM::SelectOp>(
480+
op.getLoc(), isNeg, flipped, adaptor.getInput());
481+
482+
auto resTy = getTypeConverter()->convertType(op.getType());
483+
auto clz = rewriter.create<mlir::LLVM::CountLeadingZerosOp>(
484+
op.getLoc(), resTy, select, /*is_zero_poison=*/false);
485+
486+
auto one = rewriter.create<mlir::LLVM::ConstantOp>(op.getLoc(), resTy, 1);
487+
auto res = rewriter.create<mlir::LLVM::SubOp>(op.getLoc(), clz, one);
488+
rewriter.replaceOp(op, res);
489+
490+
return mlir::LogicalResult::success();
491+
}
492+
493+
mlir::LogicalResult CIRToLLVMBitClzOpLowering::matchAndRewrite(
494+
cir::BitClzOp op, OpAdaptor adaptor,
495+
mlir::ConversionPatternRewriter &rewriter) const {
496+
auto resTy = getTypeConverter()->convertType(op.getType());
497+
auto llvmOp = rewriter.create<mlir::LLVM::CountLeadingZerosOp>(
498+
op.getLoc(), resTy, adaptor.getInput(), op.getPoisonZero());
499+
rewriter.replaceOp(op, llvmOp);
500+
return mlir::LogicalResult::success();
501+
}
502+
503+
mlir::LogicalResult CIRToLLVMBitCtzOpLowering::matchAndRewrite(
504+
cir::BitCtzOp op, OpAdaptor adaptor,
505+
mlir::ConversionPatternRewriter &rewriter) const {
506+
auto resTy = getTypeConverter()->convertType(op.getType());
507+
auto llvmOp = rewriter.create<mlir::LLVM::CountTrailingZerosOp>(
508+
op.getLoc(), resTy, adaptor.getInput(), op.getPoisonZero());
509+
rewriter.replaceOp(op, llvmOp);
510+
return mlir::LogicalResult::success();
511+
}
512+
513+
mlir::LogicalResult CIRToLLVMBitParityOpLowering::matchAndRewrite(
514+
cir::BitParityOp op, OpAdaptor adaptor,
515+
mlir::ConversionPatternRewriter &rewriter) const {
516+
auto resTy = getTypeConverter()->convertType(op.getType());
517+
auto popcnt = rewriter.create<mlir::LLVM::CtPopOp>(op.getLoc(), resTy,
518+
adaptor.getInput());
519+
520+
auto one = rewriter.create<mlir::LLVM::ConstantOp>(op.getLoc(), resTy, 1);
521+
auto popcntMod2 =
522+
rewriter.create<mlir::LLVM::AndOp>(op.getLoc(), popcnt, one);
523+
rewriter.replaceOp(op, popcntMod2);
524+
525+
return mlir::LogicalResult::success();
526+
}
527+
528+
mlir::LogicalResult CIRToLLVMBitPopcountOpLowering::matchAndRewrite(
529+
cir::BitPopcountOp op, OpAdaptor adaptor,
530+
mlir::ConversionPatternRewriter &rewriter) const {
531+
auto resTy = getTypeConverter()->convertType(op.getType());
532+
auto llvmOp = rewriter.create<mlir::LLVM::CtPopOp>(op.getLoc(), resTy,
533+
adaptor.getInput());
534+
rewriter.replaceOp(op, llvmOp);
535+
return mlir::LogicalResult::success();
536+
}
537+
463538
mlir::LogicalResult CIRToLLVMBrCondOpLowering::matchAndRewrite(
464539
cir::BrCondOp brOp, OpAdaptor adaptor,
465540
mlir::ConversionPatternRewriter &rewriter) const {
@@ -1955,6 +2030,11 @@ void ConvertCIRToLLVMPass::runOnOperation() {
19552030
CIRToLLVMAssumeOpLowering,
19562031
CIRToLLVMBaseClassAddrOpLowering,
19572032
CIRToLLVMBinOpLowering,
2033+
CIRToLLVMBitClrsbOpLowering,
2034+
CIRToLLVMBitClzOpLowering,
2035+
CIRToLLVMBitCtzOpLowering,
2036+
CIRToLLVMBitParityOpLowering,
2037+
CIRToLLVMBitPopcountOpLowering,
19582038
CIRToLLVMBrCondOpLowering,
19592039
CIRToLLVMBrOpLowering,
19602040
CIRToLLVMCallOpLowering,

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,56 @@ class CIRToLLVMAssumeOpLowering
4444
mlir::ConversionPatternRewriter &) const override;
4545
};
4646

47+
class CIRToLLVMBitClrsbOpLowering
48+
: public mlir::OpConversionPattern<cir::BitClrsbOp> {
49+
public:
50+
using mlir::OpConversionPattern<cir::BitClrsbOp>::OpConversionPattern;
51+
52+
mlir::LogicalResult
53+
matchAndRewrite(cir::BitClrsbOp op, OpAdaptor,
54+
mlir::ConversionPatternRewriter &) const override;
55+
};
56+
57+
class CIRToLLVMBitClzOpLowering
58+
: public mlir::OpConversionPattern<cir::BitClzOp> {
59+
public:
60+
using mlir::OpConversionPattern<cir::BitClzOp>::OpConversionPattern;
61+
62+
mlir::LogicalResult
63+
matchAndRewrite(cir::BitClzOp op, OpAdaptor,
64+
mlir::ConversionPatternRewriter &) const override;
65+
};
66+
67+
class CIRToLLVMBitCtzOpLowering
68+
: public mlir::OpConversionPattern<cir::BitCtzOp> {
69+
public:
70+
using mlir::OpConversionPattern<cir::BitCtzOp>::OpConversionPattern;
71+
72+
mlir::LogicalResult
73+
matchAndRewrite(cir::BitCtzOp op, OpAdaptor,
74+
mlir::ConversionPatternRewriter &) const override;
75+
};
76+
77+
class CIRToLLVMBitParityOpLowering
78+
: public mlir::OpConversionPattern<cir::BitParityOp> {
79+
public:
80+
using mlir::OpConversionPattern<cir::BitParityOp>::OpConversionPattern;
81+
82+
mlir::LogicalResult
83+
matchAndRewrite(cir::BitParityOp op, OpAdaptor,
84+
mlir::ConversionPatternRewriter &) const override;
85+
};
86+
87+
class CIRToLLVMBitPopcountOpLowering
88+
: public mlir::OpConversionPattern<cir::BitPopcountOp> {
89+
public:
90+
using mlir::OpConversionPattern<cir::BitPopcountOp>::OpConversionPattern;
91+
92+
mlir::LogicalResult
93+
matchAndRewrite(cir::BitPopcountOp op, OpAdaptor,
94+
mlir::ConversionPatternRewriter &) const override;
95+
};
96+
4797
class CIRToLLVMBrCondOpLowering
4898
: public mlir::OpConversionPattern<cir::BrCondOp> {
4999
public:

0 commit comments

Comments
 (0)