Skip to content

Commit fe0289c

Browse files
committed
[CIR][CIRGen] Add support for builtin bit operations
This patch 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 patch adds a new operation, `cir.bits`, to represent such bit operations on the input integers. LLVMIR lowering support is not included in this patch. Rename CIR bit operations to cir.bit.* Move all verification code into TableGen
1 parent b3d0d3c commit fe0289c

File tree

8 files changed

+631
-1
lines changed

8 files changed

+631
-1
lines changed

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

Lines changed: 161 additions & 0 deletions
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

Lines changed: 30 additions & 0 deletions
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

Lines changed: 66 additions & 1 deletion
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

Lines changed: 11 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

6263
// ObjC
6364
static bool setObjCGCLValueClass() { return false; }

0 commit comments

Comments
 (0)