#include "mlir/Dialect/Index/IR/IndexOps.h"
#include "mlir/Dialect/Index/IR/IndexAttrs.h"
#include "mlir/Dialect/Index/IR/IndexDialect.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Interfaces/Utils/InferIntRangeCommon.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/TypeSwitch.h"
using namespace mlir;
using namespace mlir::index;
void IndexDialect::registerOperations() {
addOperations<
#define GET_OP_LIST
#include "mlir/Dialect/Index/IR/IndexOps.cpp.inc"
>();
}
Operation *IndexDialect::materializeConstant(OpBuilder &b, Attribute value,
Type type, Location loc) {
if (auto boolValue = dyn_cast<BoolAttr>(value)) {
if (!type.isSignlessInteger(1))
return nullptr;
return BoolConstantOp::create(b, loc, type, boolValue);
}
if (auto indexValue = dyn_cast<IntegerAttr>(value)) {
if (!llvm::isa<IndexType>(indexValue.getType()) ||
!llvm::isa<IndexType>(type))
return nullptr;
assert(indexValue.getValue().getBitWidth() ==
IndexType::kInternalStorageBitWidth);
return ConstantOp::create(b, loc, indexValue);
}
return nullptr;
}
static OpFoldResult foldBinaryOpUnchecked(
ArrayRef<Attribute> operands,
function_ref<std::optional<APInt>(const APInt &, const APInt &)>
calculate) {
assert(operands.size() == 2 && "binary operation expected 2 operands");
auto lhs = dyn_cast_if_present<IntegerAttr>(operands[0]);
auto rhs = dyn_cast_if_present<IntegerAttr>(operands[1]);
if (!lhs || !rhs)
return {};
std::optional<APInt> result = calculate(lhs.getValue(), rhs.getValue());
if (!result)
return {};
assert(result->trunc(32) ==
calculate(lhs.getValue().trunc(32), rhs.getValue().trunc(32)));
return IntegerAttr::get(IndexType::get(lhs.getContext()), *result);
}
static OpFoldResult foldBinaryOpChecked(
ArrayRef<Attribute> operands,
function_ref<std::optional<APInt>(const APInt &, const APInt &lhs)>
calculate) {
assert(operands.size() == 2 && "binary operation expected 2 operands");
auto lhs = dyn_cast_if_present<IntegerAttr>(operands[0]);
auto rhs = dyn_cast_if_present<IntegerAttr>(operands[1]);
if (!lhs || !rhs)
return {};
std::optional<APInt> result64 = calculate(lhs.getValue(), rhs.getValue());
if (!result64)
return {};
std::optional<APInt> result32 =
calculate(lhs.getValue().trunc(32), rhs.getValue().trunc(32));
if (!result32)
return {};
if (result64->trunc(32) != *result32)
return {};
return IntegerAttr::get(IndexType::get(lhs.getContext()), *result64);
}
template <typename BinaryOp>
LogicalResult
canonicalizeAssociativeCommutativeBinaryOp(BinaryOp op,
PatternRewriter &rewriter) {
if (!mlir::matchPattern(op.getRhs(), mlir::m_Constant()))
return rewriter.notifyMatchFailure(op.getLoc(), "RHS is not a constant");
auto lhsOp = op.getLhs().template getDefiningOp<BinaryOp>();
if (!lhsOp)
return rewriter.notifyMatchFailure(op.getLoc(), "LHS is not the same BinaryOp");
if (!mlir::matchPattern(lhsOp.getRhs(), mlir::m_Constant()))
return rewriter.notifyMatchFailure(op.getLoc(), "RHS of LHS op is not a constant");
Value c = rewriter.createOrFold<BinaryOp>(op->getLoc(), op.getRhs(),
lhsOp.getRhs());
if (c.getDefiningOp<BinaryOp>())
return rewriter.notifyMatchFailure(op.getLoc(), "new BinaryOp was not folded");
rewriter.replaceOpWithNewOp<BinaryOp>(op, lhsOp.getLhs(), c);
return success();
}
OpFoldResult AddOp::fold(FoldAdaptor adaptor) {
if (OpFoldResult result = foldBinaryOpUnchecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) { return lhs + rhs; }))
return result;
if (auto rhs = dyn_cast_or_null<IntegerAttr>(adaptor.getRhs())) {
if (rhs.getValue().isZero())
return getLhs();
}
return {};
}
LogicalResult AddOp::canonicalize(AddOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
OpFoldResult SubOp::fold(FoldAdaptor adaptor) {
if (OpFoldResult result = foldBinaryOpUnchecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) { return lhs - rhs; }))
return result;
if (auto rhs = dyn_cast_or_null<IntegerAttr>(adaptor.getRhs())) {
if (rhs.getValue().isZero())
return getLhs();
}
return {};
}
OpFoldResult MulOp::fold(FoldAdaptor adaptor) {
if (OpFoldResult result = foldBinaryOpUnchecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) { return lhs * rhs; }))
return result;
if (auto rhs = dyn_cast_or_null<IntegerAttr>(adaptor.getRhs())) {
if (rhs.getValue().isOne())
return getLhs();
if (rhs.getValue().isZero())
return rhs;
}
return {};
}
LogicalResult MulOp::canonicalize(MulOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
OpFoldResult DivSOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) -> std::optional<APInt> {
if (rhs.isZero())
return std::nullopt;
return lhs.sdiv(rhs);
});
}
OpFoldResult DivUOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) -> std::optional<APInt> {
if (rhs.isZero())
return std::nullopt;
return lhs.udiv(rhs);
});
}
static std::optional<APInt> calculateCeilDivS(const APInt &n, const APInt &m) {
if (m.isZero())
return std::nullopt;
if (n.isZero())
return n;
bool mGtZ = m.sgt(0);
if (n.sgt(0) != mGtZ) {
return -(-n).sdiv(m);
}
int64_t x = mGtZ ? -1 : 1;
return (n + x).sdiv(m) + 1;
}
OpFoldResult CeilDivSOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(adaptor.getOperands(), calculateCeilDivS);
}
OpFoldResult CeilDivUOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(
adaptor.getOperands(),
[](const APInt &n, const APInt &m) -> std::optional<APInt> {
if (m.isZero())
return std::nullopt;
if (n.isZero())
return n;
return (n - 1).udiv(m) + 1;
});
}
static std::optional<APInt> calculateFloorDivS(const APInt &n, const APInt &m) {
if (m.isZero())
return std::nullopt;
if (n.isZero())
return n;
bool mLtZ = m.slt(0);
if (n.slt(0) == mLtZ) {
return n.sdiv(m);
}
int64_t x = mLtZ ? 1 : -1;
return -1 - (x - n).sdiv(m);
}
OpFoldResult FloorDivSOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(adaptor.getOperands(), calculateFloorDivS);
}
OpFoldResult RemSOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) -> std::optional<APInt> {
if (rhs.isZero())
return std::nullopt;
return lhs.srem(rhs);
});
}
OpFoldResult RemUOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) -> std::optional<APInt> {
if (rhs.isZero())
return std::nullopt;
return lhs.urem(rhs);
});
}
OpFoldResult MaxSOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) {
return lhs.sgt(rhs) ? lhs : rhs;
});
}
LogicalResult MaxSOp::canonicalize(MaxSOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
OpFoldResult MaxUOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) {
return lhs.ugt(rhs) ? lhs : rhs;
});
}
LogicalResult MaxUOp::canonicalize(MaxUOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
OpFoldResult MinSOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) {
return lhs.slt(rhs) ? lhs : rhs;
});
}
LogicalResult MinSOp::canonicalize(MinSOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
OpFoldResult MinUOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) {
return lhs.ult(rhs) ? lhs : rhs;
});
}
LogicalResult MinUOp::canonicalize(MinUOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
OpFoldResult ShlOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpUnchecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) -> std::optional<APInt> {
if (rhs.uge(32))
return {};
return lhs << rhs;
});
}
OpFoldResult ShrSOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) -> std::optional<APInt> {
if (rhs.uge(32))
return {};
return lhs.ashr(rhs);
});
}
OpFoldResult ShrUOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpChecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) -> std::optional<APInt> {
if (rhs.uge(32))
return {};
return lhs.lshr(rhs);
});
}
OpFoldResult AndOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpUnchecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) { return lhs & rhs; });
}
LogicalResult AndOp::canonicalize(AndOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
OpFoldResult OrOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpUnchecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) { return lhs | rhs; });
}
LogicalResult OrOp::canonicalize(OrOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
OpFoldResult XOrOp::fold(FoldAdaptor adaptor) {
return foldBinaryOpUnchecked(
adaptor.getOperands(),
[](const APInt &lhs, const APInt &rhs) { return lhs ^ rhs; });
}
LogicalResult XOrOp::canonicalize(XOrOp op, PatternRewriter &rewriter) {
return canonicalizeAssociativeCommutativeBinaryOp(op, rewriter);
}
static OpFoldResult
foldCastOp(Attribute input, Type type,
function_ref<APInt(const APInt &, unsigned)> extFn,
function_ref<APInt(const APInt &, unsigned)> extOrTruncFn) {
auto attr = dyn_cast_if_present<IntegerAttr>(input);
if (!attr)
return {};
const APInt &value = attr.getValue();
if (isa<IndexType>(type)) {
APInt result = extOrTruncFn(value, 64);
return IntegerAttr::get(type, result);
}
auto intType = cast<IntegerType>(type);
unsigned width = intType.getWidth();
if (width <= 32) {
APInt result = value.trunc(width);
return IntegerAttr::get(type, result);
}
if (width >= 64) {
if (extFn(value.trunc(32), 64) != value)
return {};
APInt result = extFn(value, width);
return IntegerAttr::get(type, result);
}
APInt result = value.trunc(width);
if (result != extFn(value.trunc(32), width))
return {};
return IntegerAttr::get(type, result);
}
bool CastSOp::areCastCompatible(TypeRange lhsTypes, TypeRange rhsTypes) {
return llvm::isa<IndexType>(lhsTypes.front()) !=
llvm::isa<IndexType>(rhsTypes.front());
}
OpFoldResult CastSOp::fold(FoldAdaptor adaptor) {
return foldCastOp(
adaptor.getInput(), getType(),
[](const APInt &x, unsigned width) { return x.sext(width); },
[](const APInt &x, unsigned width) { return x.sextOrTrunc(width); });
}
bool CastUOp::areCastCompatible(TypeRange lhsTypes, TypeRange rhsTypes) {
return llvm::isa<IndexType>(lhsTypes.front()) !=
llvm::isa<IndexType>(rhsTypes.front());
}
OpFoldResult CastUOp::fold(FoldAdaptor adaptor) {
return foldCastOp(
adaptor.getInput(), getType(),
[](const APInt &x, unsigned width) { return x.zext(width); },
[](const APInt &x, unsigned width) { return x.zextOrTrunc(width); });
}
bool compareIndices(const APInt &lhs, const APInt &rhs,
IndexCmpPredicate pred) {
switch (pred) {
case IndexCmpPredicate::EQ:
return lhs.eq(rhs);
case IndexCmpPredicate::NE:
return lhs.ne(rhs);
case IndexCmpPredicate::SGE:
return lhs.sge(rhs);
case IndexCmpPredicate::SGT:
return lhs.sgt(rhs);
case IndexCmpPredicate::SLE:
return lhs.sle(rhs);
case IndexCmpPredicate::SLT:
return lhs.slt(rhs);
case IndexCmpPredicate::UGE:
return lhs.uge(rhs);
case IndexCmpPredicate::UGT:
return lhs.ugt(rhs);
case IndexCmpPredicate::ULE:
return lhs.ule(rhs);
case IndexCmpPredicate::ULT:
return lhs.ult(rhs);
}
llvm_unreachable("unhandled IndexCmpPredicate predicate");
}
static std::optional<bool> foldCmpOfMaxOrMin(Operation *lhsOp,
const APInt &cstA,
const APInt &cstB, unsigned width,
IndexCmpPredicate pred) {
ConstantIntRanges lhsRange = TypeSwitch<Operation *, ConstantIntRanges>(lhsOp)
.Case([&](MinSOp op) {
return ConstantIntRanges::fromSigned(
APInt::getSignedMinValue(width), cstA);
})
.Case([&](MinUOp op) {
return ConstantIntRanges::fromUnsigned(
APInt::getMinValue(width), cstA);
})
.Case([&](MaxSOp op) {
return ConstantIntRanges::fromSigned(
cstA, APInt::getSignedMaxValue(width));
})
.Case([&](MaxUOp op) {
return ConstantIntRanges::fromUnsigned(
cstA, APInt::getMaxValue(width));
});
return intrange::evaluatePred(static_cast<intrange::CmpPredicate>(pred),
lhsRange, ConstantIntRanges::constant(cstB));
}
static bool compareSameArgs(IndexCmpPredicate pred) {
switch (pred) {
case IndexCmpPredicate::EQ:
case IndexCmpPredicate::SGE:
case IndexCmpPredicate::SLE:
case IndexCmpPredicate::UGE:
case IndexCmpPredicate::ULE:
return true;
case IndexCmpPredicate::NE:
case IndexCmpPredicate::SGT:
case IndexCmpPredicate::SLT:
case IndexCmpPredicate::UGT:
case IndexCmpPredicate::ULT:
return false;
}
llvm_unreachable("unknown predicate in compareSameArgs");
}
OpFoldResult CmpOp::fold(FoldAdaptor adaptor) {
auto lhs = dyn_cast_if_present<IntegerAttr>(adaptor.getLhs());
auto rhs = dyn_cast_if_present<IntegerAttr>(adaptor.getRhs());
if (lhs && rhs) {
bool result64 = compareIndices(lhs.getValue(), rhs.getValue(), getPred());
bool result32 = compareIndices(lhs.getValue().trunc(32),
rhs.getValue().trunc(32), getPred());
if (result64 == result32)
return BoolAttr::get(getContext(), result64);
}
Operation *lhsOp = getLhs().getDefiningOp();
IntegerAttr cstA;
if (isa_and_nonnull<MinSOp, MinUOp, MaxSOp, MaxUOp>(lhsOp) &&
matchPattern(lhsOp->getOperand(1), m_Constant(&cstA)) && rhs) {
std::optional<bool> result64 = foldCmpOfMaxOrMin(
lhsOp, cstA.getValue(), rhs.getValue(), 64, getPred());
std::optional<bool> result32 =
foldCmpOfMaxOrMin(lhsOp, cstA.getValue().trunc(32),
rhs.getValue().trunc(32), 32, getPred());
if (result64 && result32 && *result64 == *result32)
return BoolAttr::get(getContext(), *result64);
}
if (getLhs() == getRhs())
return BoolAttr::get(getContext(), compareSameArgs(getPred()));
return {};
}
LogicalResult CmpOp::canonicalize(CmpOp op, PatternRewriter &rewriter) {
IntegerAttr cmpRhs;
IntegerAttr cmpLhs;
bool rhsIsZero = matchPattern(op.getRhs(), m_Constant(&cmpRhs)) &&
cmpRhs.getValue().isZero();
bool lhsIsZero = matchPattern(op.getLhs(), m_Constant(&cmpLhs)) &&
cmpLhs.getValue().isZero();
if (!rhsIsZero && !lhsIsZero)
return rewriter.notifyMatchFailure(op.getLoc(),
"cmp is not comparing something with 0");
SubOp subOp = rhsIsZero ? op.getLhs().getDefiningOp<index::SubOp>()
: op.getRhs().getDefiningOp<index::SubOp>();
if (!subOp)
return rewriter.notifyMatchFailure(
op.getLoc(), "non-zero operand is not a result of subtraction");
index::CmpOp newCmp;
if (rhsIsZero)
newCmp = index::CmpOp::create(rewriter, op.getLoc(), op.getPred(),
subOp.getLhs(), subOp.getRhs());
else
newCmp = index::CmpOp::create(rewriter, op.getLoc(), op.getPred(),
subOp.getRhs(), subOp.getLhs());
rewriter.replaceOp(op, newCmp);
return success();
}
void ConstantOp::getAsmResultNames(
function_ref<void(Value, StringRef)> setNameFn) {
SmallString<32> specialNameBuffer;
llvm::raw_svector_ostream specialName(specialNameBuffer);
specialName << "idx" << getValueAttr().getValue();
setNameFn(getResult(), specialName.str());
}
OpFoldResult ConstantOp::fold(FoldAdaptor adaptor) { return getValueAttr(); }
void ConstantOp::build(OpBuilder &b, OperationState &state, int64_t value) {
build(b, state, b.getIndexType(), b.getIndexAttr(value));
}
OpFoldResult BoolConstantOp::fold(FoldAdaptor adaptor) {
return getValueAttr();
}
void BoolConstantOp::getAsmResultNames(
function_ref<void(Value, StringRef)> setNameFn) {
setNameFn(getResult(), getValue() ? "true" : "false");
}
#define GET_OP_CLASSES
#include "mlir/Dialect/Index/IR/IndexOps.cpp.inc"