Skip to content

Commit 28fa80a

Browse files
Lancernbcardosolopes
authored andcommitted
[CIR][CIRGen] Add support for builtin bit operations (#474)
This PR adds CIRGen support for the following built-in bit operations: - `__builtin_ffs{,l,ll,g}` - `__builtin_clz{,l,ll,g}` - `__builtin_ctz{,l,ll,g}` - `__builtin_clrsb{,l,ll,g}` - `__builtin_popcount{,l,ll,g}` - `__builtin_parity{,l,ll,g}` This PR adds a new operation, `cir.bits`, to represent such bit operations on the input integers. LLVMIR lowering support is not included in this PR. > [!NOTE] > As a side note, C++20 adds the `<bit>` header which includes some bit operation functions with similar functionalities to the built-in functions mentioned above. However, these standard library functions have slightly different semantics than the built-in ones and this PR does not include support for these standard library functions. Support for these functions may be added later, or amended into this PR if the reviewers request so. Co-authored-by: Bruno Cardoso Lopes <[email protected]>
1 parent 3db807d commit 28fa80a

File tree

8 files changed

+631
-1
lines changed

8 files changed

+631
-1
lines changed

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

+161
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,167 @@ def CmpOp : CIR_Op<"cmp", [Pure, SameTypeOperands]> {
985985
let hasVerifier = 0;
986986
}
987987

988+
//===----------------------------------------------------------------------===//
989+
// BitsOp
990+
//===----------------------------------------------------------------------===//
991+
992+
class CIR_BitOp<string mnemonic, TypeConstraint inputTy>
993+
: CIR_Op<mnemonic, [Pure]> {
994+
let arguments = (ins inputTy:$input);
995+
let results = (outs SInt32:$result);
996+
997+
let assemblyFormat = [{
998+
`(` $input `:` type($input) `)` `:` type($result) attr-dict
999+
}];
1000+
}
1001+
1002+
def BitClrsbOp : CIR_BitOp<"bit.clrsb", SIntOfWidths<[32, 64]>> {
1003+
let summary = "Get the number of leading redundant sign bits in the input";
1004+
let description = [{
1005+
Compute the number of leading redundant sign bits in the input integer.
1006+
1007+
The input integer must be a signed integer. The most significant bit of the
1008+
input integer is the sign bit. The `cir.bit.clrsb` operation returns the
1009+
number of redundant sign bits in the input, that is, the number of bits
1010+
following the most significant bit that are identical to it.
1011+
1012+
The bit width of the input integer must be either 32 or 64.
1013+
1014+
Examples:
1015+
1016+
```mlir
1017+
!s32i = !cir.int<s, 32>
1018+
1019+
// %0 = 0xDEADBEEF, 0b1101_1110_1010_1101_1011_1110_1110_1111
1020+
%0 = cir.const(#cir.int<3735928559> : !s32i) : !s32i
1021+
// %1 will be 1 because there is 1 bit following the most significant bit
1022+
// that is identical to it.
1023+
%1 = cir.bit.clrsb(%0 : !s32i) : !s32i
1024+
1025+
// %2 = 1, 0b0000_0000_0000_0000_0000_0000_0000_0001
1026+
%2 = cir.const(#cir.int<1> : !s32i) : !s32i
1027+
// %3 will be 30
1028+
%3 = cir.bit.clrsb(%2 : !s32i) : !s32i
1029+
```
1030+
}];
1031+
}
1032+
1033+
def BitClzOp : CIR_BitOp<"bit.clz", UIntOfWidths<[16, 32, 64]>> {
1034+
let summary = "Get the number of leading 0-bits in the input";
1035+
let description = [{
1036+
Compute the number of leading 0-bits in the input.
1037+
1038+
The input integer must be an unsigned integer. The `cir.bit.clz` operation
1039+
returns the number of consecutive 0-bits at the most significant bit
1040+
position in the input.
1041+
1042+
This operation invokes undefined behavior if the input value is 0.
1043+
1044+
Example:
1045+
1046+
```mlir
1047+
!s32i = !cir.int<s, 32>
1048+
!u32i = !cir.int<u, 32>
1049+
1050+
// %0 = 0b0000_0000_0000_0000_0000_0000_0000_1000
1051+
%0 = cir.const(#cir.int<8> : !u32i) : !u32i
1052+
// %1 will be 28
1053+
%1 = cir.bit.clz(%0 : !u32i) : !s32i
1054+
```
1055+
}];
1056+
}
1057+
1058+
def BitCtzOp : CIR_BitOp<"bit.ctz", UIntOfWidths<[16, 32, 64]>> {
1059+
let summary = "Get the number of trailing 0-bits in the input";
1060+
let description = [{
1061+
Compute the number of trailing 0-bits in the input.
1062+
1063+
The input integer must be an unsigned integer. The `cir.bit.ctz` operation
1064+
returns the number of consecutive 0-bits at the least significant bit
1065+
position in the input.
1066+
1067+
This operation invokes undefined behavior if the input value is 0.
1068+
1069+
Example:
1070+
1071+
```mlir
1072+
!s32i = !cir.int<s, 32>
1073+
!u32i = !cir.int<u, 32>
1074+
1075+
// %0 = 0b1000
1076+
%0 = cir.const(#cir.int<8> : !u32i) : !u32i
1077+
// %1 will be 3
1078+
%1 = cir.bit.ctz(%0 : !u32i) : !s32i
1079+
```
1080+
}];
1081+
}
1082+
1083+
def BitFfsOp : CIR_BitOp<"bit.ffs", SIntOfWidths<[32, 64]>> {
1084+
let summary = "Get the position of the least significant 1-bit of input";
1085+
let description = [{
1086+
Compute the position of the least significant 1-bit of the input.
1087+
1088+
The input integer must be a signed integer. The `cir.bit.ffs` operation
1089+
returns one plus the index of the least significant 1-bit of the input
1090+
signed integer. As a special case, if the input integer is 0, `cir.bit.ffs`
1091+
returns 0.
1092+
1093+
Example:
1094+
1095+
```mlir
1096+
!s32i = !cir.int<s, 32>
1097+
1098+
// %0 = 0x0010_1000
1099+
%0 = cir.const(#cir.int<40> : !s32i) : !s32i
1100+
// #1 will be 4 since the 4th least significant bit is 1.
1101+
%1 = cir.bit.ffs(%0 : !s32i) : !s32i
1102+
```
1103+
}];
1104+
}
1105+
1106+
def BitParityOp : CIR_BitOp<"bit.parity", UIntOfWidths<[32, 64]>> {
1107+
let summary = "Get the parity of input";
1108+
let description = [{
1109+
Compute the parity of the input. The parity of an integer is the number of
1110+
1-bits in it modulo 2.
1111+
1112+
The input must be an unsigned integer.
1113+
1114+
Example:
1115+
1116+
```mlir
1117+
!s32i = !cir.int<s, 32>
1118+
!u32i = !cir.int<u, 32>
1119+
1120+
// %0 = 0x0110_1000
1121+
%0 = cir.const(#cir.int<104> : !u32i) : !s32i
1122+
// %1 will be 1 since there are 3 1-bits in %0
1123+
%1 = cir.bit.parity(%0 : !u32i) : !s32i
1124+
```
1125+
}];
1126+
}
1127+
1128+
def BitPopcountOp : CIR_BitOp<"bit.popcount", UIntOfWidths<[16, 32, 64]>> {
1129+
let summary = "Get the number of 1-bits in input";
1130+
let description = [{
1131+
Compute the number of 1-bits in the input.
1132+
1133+
The input must be an unsigned integer.
1134+
1135+
Example:
1136+
1137+
```mlir
1138+
!s32i = !cir.int<s, 32>
1139+
!u32i = !cir.int<u, 32>
1140+
1141+
// %0 = 0x0110_1000
1142+
%0 = cir.const(#cir.int<104> : !u32i) : !s32i
1143+
// %1 will be 3 since there are 3 1-bits in %0
1144+
%1 = cir.bit.popcount(%0 : !u32i) : !s32i
1145+
```
1146+
}];
1147+
}
1148+
9881149
//===----------------------------------------------------------------------===//
9891150
// SwitchOp
9901151
//===----------------------------------------------------------------------===//

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

+30
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,36 @@ def SInt16 : SInt<16>;
9797
def SInt32 : SInt<32>;
9898
def SInt64 : SInt<64>;
9999

100+
// A type constraint that allows unsigned integer type whose width is among the
101+
// specified list of possible widths.
102+
class UIntOfWidths<list<int> widths>
103+
: Type<And<[
104+
CPred<"$_self.isa<::mlir::cir::IntType>()">,
105+
CPred<"$_self.cast<::mlir::cir::IntType>().isUnsigned()">,
106+
Or<!foreach(
107+
w, widths,
108+
CPred<"$_self.cast<::mlir::cir::IntType>().getWidth() == " # w>
109+
)>
110+
]>,
111+
!interleave(!foreach(w, widths, w # "-bit"), " or ") # " uint",
112+
"::mlir::cir::IntType"
113+
> {}
114+
115+
// A type constraint that allows unsigned integer type whose width is among the
116+
// specified list of possible widths.
117+
class SIntOfWidths<list<int> widths>
118+
: Type<And<[
119+
CPred<"$_self.isa<::mlir::cir::IntType>()">,
120+
CPred<"$_self.cast<::mlir::cir::IntType>().isSigned()">,
121+
Or<!foreach(
122+
w, widths,
123+
CPred<"$_self.cast<::mlir::cir::IntType>().getWidth() == " # w>
124+
)>
125+
]>,
126+
!interleave(!foreach(w, widths, w # "-bit"), " or ") # " sint",
127+
"::mlir::cir::IntType"
128+
> {}
129+
100130
//===----------------------------------------------------------------------===//
101131
// FloatType
102132
//===----------------------------------------------------------------------===//

clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp

+66-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,22 @@ static RValue buildUnaryFPBuiltin(CIRGenFunction &CGF, const CallExpr &E) {
5555
return RValue::get(Call->getResult(0));
5656
}
5757

58+
template <typename Op>
59+
static RValue
60+
buildBuiltinBitOp(CIRGenFunction &CGF, const CallExpr *E,
61+
std::optional<CIRGenFunction::BuiltinCheckKind> CK) {
62+
mlir::Value arg;
63+
if (CK.has_value())
64+
arg = CGF.buildCheckedArgForBuiltin(E->getArg(0), *CK);
65+
else
66+
arg = CGF.buildScalarExpr(E->getArg(0));
67+
68+
auto resultTy = CGF.ConvertType(E->getType());
69+
auto op =
70+
CGF.getBuilder().create<Op>(CGF.getLoc(E->getExprLoc()), resultTy, arg);
71+
return RValue::get(op);
72+
}
73+
5874
RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
5975
const CallExpr *E,
6076
ReturnValueSlot ReturnValue) {
@@ -462,7 +478,7 @@ RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
462478
case Builtin::BImemcpy:
463479
case Builtin::BI__builtin_memcpy:
464480
case Builtin::BImempcpy:
465-
case Builtin::BI__builtin_mempcpy:
481+
case Builtin::BI__builtin_mempcpy: {
466482
Address Dest = buildPointerWithAlignment(E->getArg(0));
467483
Address Src = buildPointerWithAlignment(E->getArg(1));
468484
mlir::Value SizeVal = buildScalarExpr(E->getArg(2));
@@ -480,6 +496,42 @@ RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
480496
return RValue::get(Dest.getPointer());
481497
}
482498

499+
case Builtin::BI__builtin_clrsb:
500+
case Builtin::BI__builtin_clrsbl:
501+
case Builtin::BI__builtin_clrsbll:
502+
return buildBuiltinBitOp<mlir::cir::BitClrsbOp>(*this, E, std::nullopt);
503+
504+
case Builtin::BI__builtin_ctzs:
505+
case Builtin::BI__builtin_ctz:
506+
case Builtin::BI__builtin_ctzl:
507+
case Builtin::BI__builtin_ctzll:
508+
return buildBuiltinBitOp<mlir::cir::BitCtzOp>(*this, E, BCK_CTZPassedZero);
509+
510+
case Builtin::BI__builtin_clzs:
511+
case Builtin::BI__builtin_clz:
512+
case Builtin::BI__builtin_clzl:
513+
case Builtin::BI__builtin_clzll:
514+
return buildBuiltinBitOp<mlir::cir::BitClzOp>(*this, E, BCK_CLZPassedZero);
515+
516+
case Builtin::BI__builtin_ffs:
517+
case Builtin::BI__builtin_ffsl:
518+
case Builtin::BI__builtin_ffsll:
519+
return buildBuiltinBitOp<mlir::cir::BitFfsOp>(*this, E, std::nullopt);
520+
521+
case Builtin::BI__builtin_parity:
522+
case Builtin::BI__builtin_parityl:
523+
case Builtin::BI__builtin_parityll:
524+
return buildBuiltinBitOp<mlir::cir::BitParityOp>(*this, E, std::nullopt);
525+
526+
case Builtin::BI__popcnt16:
527+
case Builtin::BI__popcnt:
528+
case Builtin::BI__popcnt64:
529+
case Builtin::BI__builtin_popcount:
530+
case Builtin::BI__builtin_popcountl:
531+
case Builtin::BI__builtin_popcountll:
532+
return buildBuiltinBitOp<mlir::cir::BitPopcountOp>(*this, E, std::nullopt);
533+
}
534+
483535
// If this is an alias for a lib function (e.g. __builtin_sin), emit
484536
// the call using the normal call path, but using the unmangled
485537
// version of the function name.
@@ -543,6 +595,19 @@ RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
543595
return GetUndefRValue(E->getType());
544596
}
545597

598+
mlir::Value CIRGenFunction::buildCheckedArgForBuiltin(const Expr *E,
599+
BuiltinCheckKind Kind) {
600+
assert((Kind == BCK_CLZPassedZero || Kind == BCK_CTZPassedZero) &&
601+
"Unsupported builtin check kind");
602+
603+
auto value = buildScalarExpr(E);
604+
if (!SanOpts.has(SanitizerKind::Builtin))
605+
return value;
606+
607+
assert(!UnimplementedFeature::sanitizerBuiltin());
608+
llvm_unreachable("NYI");
609+
}
610+
546611
static mlir::Value buildTargetArchBuiltinExpr(CIRGenFunction *CGF,
547612
unsigned BuiltinID,
548613
const CallExpr *E,

clang/lib/CIR/CodeGen/CIRGenFunction.h

+11
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,17 @@ class CIRGenFunction : public CIRGenTypeCache {
15091509
LValue buildCheckedLValue(const Expr *E, TypeCheckKind TCK);
15101510
LValue buildMemberExpr(const MemberExpr *E);
15111511

1512+
/// Specifies which type of sanitizer check to apply when handling a
1513+
/// particular builtin.
1514+
enum BuiltinCheckKind {
1515+
BCK_CTZPassedZero,
1516+
BCK_CLZPassedZero,
1517+
};
1518+
1519+
/// Emits an argument for a call to a builtin. If the builtin sanitizer is
1520+
/// enabled, a runtime check specified by \p Kind is also emitted.
1521+
mlir::Value buildCheckedArgForBuiltin(const Expr *E, BuiltinCheckKind Kind);
1522+
15121523
/// returns true if aggregate type has a volatile member.
15131524
/// TODO(cir): this could be a common AST helper between LLVM / CIR.
15141525
bool hasVolatileMember(QualType T) {

clang/lib/CIR/CodeGen/UnimplementedFeatureGuarding.h

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ struct UnimplementedFeature {
5858
static bool pointerOverflowSanitizer() { return false; }
5959
static bool sanitizeDtor() { return false; }
6060
static bool sanitizeVLABound() { return false; }
61+
static bool sanitizerBuiltin() { return false; }
6162
static bool sanitizerReturn() { return false; }
6263

6364
// ObjC

0 commit comments

Comments
 (0)