Skip to content

[CIR] Add binary operators #132420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 25, 2025
124 changes: 123 additions & 1 deletion clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,59 @@
#define LLVM_CLANG_CIR_DIALECT_BUILDER_CIRBASEBUILDER_H

#include "clang/AST/CharUnits.h"
#include "clang/AST/Type.h"
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "llvm/ADT/STLForwardCompat.h"
#include "llvm/Support/ErrorHandling.h"

#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/Types.h"

namespace cir {

enum class OverflowBehavior {
None = 0,
NoSignedWrap = 1 << 0,
NoUnsignedWrap = 1 << 1,
Saturated = 1 << 2,
};

constexpr OverflowBehavior operator|(OverflowBehavior a, OverflowBehavior b) {
return static_cast<OverflowBehavior>(llvm::to_underlying(a) |
llvm::to_underlying(b));
}

constexpr OverflowBehavior operator&(OverflowBehavior a, OverflowBehavior b) {
return static_cast<OverflowBehavior>(llvm::to_underlying(a) &
llvm::to_underlying(b));
}

constexpr OverflowBehavior &operator|=(OverflowBehavior &a,
OverflowBehavior b) {
a = a | b;
return a;
}

constexpr OverflowBehavior &operator&=(OverflowBehavior &a,
OverflowBehavior b) {
a = a & b;
return a;
}

class CIRBaseBuilderTy : public mlir::OpBuilder {

public:
CIRBaseBuilderTy(mlir::MLIRContext &mlirContext)
: mlir::OpBuilder(&mlirContext) {}

mlir::Value getConstAPInt(mlir::Location loc, mlir::Type typ,
const llvm::APInt &val) {
return create<cir::ConstantOp>(loc, typ, getAttr<cir::IntAttr>(typ, val));
}

cir::ConstantOp getConstant(mlir::Location loc, mlir::TypedAttr attr) {
return create<cir::ConstantOp>(loc, attr.getType(), attr);
}
Expand Down Expand Up @@ -143,6 +178,93 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return createCast(loc, cir::CastKind::bitcast, src, newTy);
}

//===--------------------------------------------------------------------===//
// Binary Operators
//===--------------------------------------------------------------------===//

mlir::Value createBinop(mlir::Location loc, mlir::Value lhs,
cir::BinOpKind kind, mlir::Value rhs) {
return create<cir::BinOp>(loc, lhs.getType(), kind, lhs, rhs);
}

mlir::Value createLowBitsSet(mlir::Location loc, unsigned size,
unsigned bits) {
llvm::APInt val = llvm::APInt::getLowBitsSet(size, bits);
auto type = cir::IntType::get(getContext(), size, /*isSigned=*/false);
return getConstAPInt(loc, type, val);
}

mlir::Value createAnd(mlir::Location loc, mlir::Value lhs, mlir::Value rhs) {
return createBinop(loc, lhs, cir::BinOpKind::And, rhs);
}

mlir::Value createOr(mlir::Location loc, mlir::Value lhs, mlir::Value rhs) {
return createBinop(loc, lhs, cir::BinOpKind::Or, rhs);
}

mlir::Value createMul(mlir::Location loc, mlir::Value lhs, mlir::Value rhs,
OverflowBehavior ob = OverflowBehavior::None) {
auto op =
create<cir::BinOp>(loc, lhs.getType(), cir::BinOpKind::Mul, lhs, rhs);
op.setNoUnsignedWrap(
llvm::to_underlying(ob & OverflowBehavior::NoUnsignedWrap));
op.setNoSignedWrap(
llvm::to_underlying(ob & OverflowBehavior::NoSignedWrap));
return op;
}
mlir::Value createNSWMul(mlir::Location loc, mlir::Value lhs,
mlir::Value rhs) {
return createMul(loc, lhs, rhs, OverflowBehavior::NoSignedWrap);
}
mlir::Value createNUWAMul(mlir::Location loc, mlir::Value lhs,
mlir::Value rhs) {
return createMul(loc, lhs, rhs, OverflowBehavior::NoUnsignedWrap);
}

mlir::Value createSub(mlir::Location loc, mlir::Value lhs, mlir::Value rhs,
OverflowBehavior ob = OverflowBehavior::Saturated) {
auto op =
create<cir::BinOp>(loc, lhs.getType(), cir::BinOpKind::Sub, lhs, rhs);
op.setNoUnsignedWrap(
llvm::to_underlying(ob & OverflowBehavior::NoUnsignedWrap));
op.setNoSignedWrap(
llvm::to_underlying(ob & OverflowBehavior::NoSignedWrap));
op.setSaturated(llvm::to_underlying(ob & OverflowBehavior::Saturated));
return op;
}

mlir::Value createNSWSub(mlir::Location loc, mlir::Value lhs,
mlir::Value rhs) {
return createSub(loc, lhs, rhs, OverflowBehavior::NoSignedWrap);
}

mlir::Value createNUWSub(mlir::Location loc, mlir::Value lhs,
mlir::Value rhs) {
return createSub(loc, lhs, rhs, OverflowBehavior::NoUnsignedWrap);
}

mlir::Value createAdd(mlir::Location loc, mlir::Value lhs, mlir::Value rhs,
OverflowBehavior ob = OverflowBehavior::None) {
auto op =
create<cir::BinOp>(loc, lhs.getType(), cir::BinOpKind::Add, lhs, rhs);
op.setNoUnsignedWrap(
llvm::to_underlying(ob & OverflowBehavior::NoUnsignedWrap));
op.setNoSignedWrap(
llvm::to_underlying(ob & OverflowBehavior::NoSignedWrap));
op.setSaturated(llvm::to_underlying(ob & OverflowBehavior::Saturated));
return op;
}

mlir::Value createNSWAdd(mlir::Location loc, mlir::Value lhs,
mlir::Value rhs) {
return createAdd(loc, lhs, rhs, OverflowBehavior::NoSignedWrap);
}

mlir::Value createNUWAdd(mlir::Location loc, mlir::Value lhs,
mlir::Value rhs) {
return createAdd(loc, lhs, rhs, OverflowBehavior::NoUnsignedWrap);
}

//
// Block handling helpers
// ----------------------
Expand Down
63 changes: 63 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,69 @@ def ForOp : CIR_Op<"for", [LoopOpInterface, NoRegionArguments]> {
}];
}

//===----------------------------------------------------------------------===//
// BinOp
//===----------------------------------------------------------------------===//

// FIXME: represent Commutative, Idempotent traits for appropriate binops
def BinOpKind_Mul : I32EnumAttrCase<"Mul", 1, "mul">;
def BinOpKind_Div : I32EnumAttrCase<"Div", 2, "div">;
def BinOpKind_Rem : I32EnumAttrCase<"Rem", 3, "rem">;
def BinOpKind_Add : I32EnumAttrCase<"Add", 4, "add">;
def BinOpKind_Sub : I32EnumAttrCase<"Sub", 5, "sub">;
def BinOpKind_And : I32EnumAttrCase<"And", 8, "and">;
def BinOpKind_Xor : I32EnumAttrCase<"Xor", 9, "xor">;
def BinOpKind_Or : I32EnumAttrCase<"Or", 10, "or">;
// TODO(cir): Do we need a min binop?
def BinOpKind_Max : I32EnumAttrCase<"Max", 11, "max">;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does max represent in the language? Or is this something we transform to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to support __builtin_elementwise_max and at least one corresponding SIMD intrinsic function (see llvm/clangir#1201).

I can't find that TODO in the git history of the incubator for some reason. But since there is a __builtin_elementwise_min I assume there needs to be a min as well.

Both builtins are still NYI though so I could remove them for the time being.


def BinOpKind : I32EnumAttr<
"BinOpKind",
"binary operation (arith and logic) kind",
[BinOpKind_Mul, BinOpKind_Div, BinOpKind_Rem,
BinOpKind_Add, BinOpKind_Sub,
BinOpKind_And, BinOpKind_Xor,
BinOpKind_Or, BinOpKind_Max]> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Max isn't listed on the ClangIR GitHub pages. We should update those soon.

let cppNamespace = "::cir";
}

def BinOp : CIR_Op<"binop", [Pure,
SameTypeOperands, SameOperandsAndResultType]> {

let summary = "Binary operations (arith and logic)";
let description = [{
cir.binop performs the binary operation according to
the specified opcode kind: [mul, div, rem, add, sub,
and, xor, or, max].

It requires two input operands and has one result, all types
should be the same.

```mlir
%7 = cir.binop(add, %1, %2) : !s32i
%7 = cir.binop(mul, %1, %2) : !u8i
```
}];

// TODO: get more accurate than CIR_AnyType
let results = (outs CIR_AnyType:$result);
let arguments = (ins Arg<BinOpKind, "binop kind">:$kind,
CIR_AnyType:$lhs, CIR_AnyType:$rhs,
UnitAttr:$no_unsigned_wrap,
UnitAttr:$no_signed_wrap,
UnitAttr:$saturated);

let assemblyFormat = [{
`(` $kind `,` $lhs `,` $rhs `)`
(`nsw` $no_signed_wrap^)?
(`nuw` $no_unsigned_wrap^)?
(`sat` $saturated^)?
`:` type($lhs) attr-dict
}];

let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// GlobalOp
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
namespace cir {

bool isAnyFloatingPointType(mlir::Type t);
bool isFPOrFPVectorTy(mlir::Type);

} // namespace cir

Expand Down
12 changes: 11 additions & 1 deletion clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ struct MissingFeatures {
static bool opUnarySignedOverflow() { return false; }
static bool opUnaryPromotionType() { return false; }

// Clang early optimizations or things defered to LLVM lowering.
static bool mayHaveIntegerOverflow() { return false; }

// Misc
static bool cxxABI() { return false; }
static bool tryEmitAsConstant() { return false; }
Expand All @@ -93,16 +96,19 @@ struct MissingFeatures {
static bool stackSaveOp() { return false; }
static bool aggValueSlot() { return false; }
static bool generateDebugInfo() { return false; }
static bool pointerOverflowSanitizer() { return false; }
static bool fpConstraints() { return false; }
static bool sanitizers() { return false; }
static bool addHeapAllocSiteMetadata() { return false; }
static bool targetCodeGenInfoGetNullPointer() { return false; }
static bool CGFPOptionsRAII() { return false; }
static bool loopInfoStack() { return false; }
static bool requiresCleanups() { return false; }
static bool createProfileWeightsForLoop() { return false; }
static bool emitCondLikelihoodViaExpectIntrinsic() { return false; }
static bool pgoUse() { return false; }
static bool cgFPOptionsRAII() { return false; }
static bool metaDataNode() { return false; }
static bool fastMathFlags() { return false; }

// Missing types
static bool dataMemberType() { return false; }
Expand All @@ -111,6 +117,8 @@ struct MissingFeatures {
static bool scalableVectors() { return false; }
static bool unsizedTypes() { return false; }
static bool vectorType() { return false; }
static bool complexType() { return false; }
static bool fixedPointType() { return false; }

// Future CIR operations
static bool awaitOp() { return false; }
Expand All @@ -127,6 +135,8 @@ struct MissingFeatures {
static bool ternaryOp() { return false; }
static bool tryOp() { return false; }
static bool zextOp() { return false; }
static bool ptrStrideOp() { return false; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AmrDeveloper Be sure to look for the places where this is used when you add support for cir.ptr_stride.

static bool ptrDiffOp() { return false; }
};

} // namespace cir
Expand Down
Loading