#include "mlir/Conversion/ArithToSPIRV/ArithToSPIRV.h"
#include "../SPIRVCommon/Pattern.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVAttributes.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
#include "mlir/Dialect/SPIRV/IR/SPIRVTypes.h"
#include "mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/DialectResourceBlobManager.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/MathExtras.h"
#include <cassert>
#include <memory>
namespace mlir {
#define GEN_PASS_DEF_CONVERTARITHTOSPIRV
#include "mlir/Conversion/Passes.h.inc"
}
#define DEBUG_TYPE "arith-to-spirv-pattern"
using namespace mlir;
static BoolAttr convertBoolAttr(Attribute srcAttr, Builder builder) {
if (auto boolAttr = dyn_cast<BoolAttr>(srcAttr))
return boolAttr;
if (auto intAttr = dyn_cast<IntegerAttr>(srcAttr))
return builder.getBoolAttr(intAttr.getValue().getBoolValue());
return {};
}
static IntegerAttr convertIntegerAttr(IntegerAttr srcAttr, IntegerType dstType,
Builder builder) {
if (srcAttr.getValue().isIntN(dstType.getWidth()))
return builder.getIntegerAttr(dstType, srcAttr.getInt());
if (srcAttr.getValue().isSignedIntN(dstType.getWidth())) {
auto dstAttr = builder.getIntegerAttr(dstType, srcAttr.getInt());
LLVM_DEBUG(llvm::dbgs() << "attribute '" << srcAttr << "' converted to '"
<< dstAttr << "' for type '" << dstType << "'\n");
return dstAttr;
}
LLVM_DEBUG(llvm::dbgs() << "attribute '" << srcAttr
<< "' illegal: cannot fit into target type '"
<< dstType << "'\n");
return {};
}
static FloatAttr convertFloatAttr(FloatAttr srcAttr, FloatType dstType,
Builder builder) {
if (!dstType.isF32())
return FloatAttr();
APFloat dstVal = srcAttr.getValue();
bool losesInfo = false;
APFloat::opStatus status =
dstVal.convert(APFloat::IEEEsingle(), APFloat::rmTowardZero, &losesInfo);
if (status != APFloat::opOK || losesInfo) {
LLVM_DEBUG(llvm::dbgs()
<< srcAttr << " illegal: cannot fit into converted type '"
<< dstType << "'\n");
return FloatAttr();
}
return builder.getF32FloatAttr(dstVal.convertToFloat());
}
static bool isBoolScalarOrVector(Type type) {
assert(type && "Not a valid type");
if (type.isInteger(1))
return true;
if (auto vecType = dyn_cast<VectorType>(type))
return vecType.getElementType().isInteger(1);
return false;
}
static Value getScalarOrVectorConstInt(Type type, uint64_t value,
OpBuilder &builder, Location loc) {
if (auto vectorType = dyn_cast<VectorType>(type)) {
Attribute element = IntegerAttr::get(vectorType.getElementType(), value);
auto attr = SplatElementsAttr::get(vectorType, element);
return builder.create<spirv::ConstantOp>(loc, vectorType, attr);
}
if (auto intType = dyn_cast<IntegerType>(type))
return builder.create<spirv::ConstantOp>(
loc, type, builder.getIntegerAttr(type, value));
return nullptr;
}
static bool hasSameBitwidth(Type a, Type b) {
auto getNumBitwidth = [](Type type) {
unsigned bw = 0;
if (type.isIntOrFloat())
bw = type.getIntOrFloatBitWidth();
else if (auto vecType = dyn_cast<VectorType>(type))
bw = vecType.getElementTypeBitWidth() * vecType.getNumElements();
return bw;
};
unsigned aBW = getNumBitwidth(a);
unsigned bBW = getNumBitwidth(b);
return aBW != 0 && bBW != 0 && aBW == bBW;
}
static LogicalResult
getTypeConversionFailure(ConversionPatternRewriter &rewriter, Operation *op,
Type srcType) {
return rewriter.notifyMatchFailure(
op->getLoc(),
llvm::formatv("failed to convert source type '{0}'", srcType));
}
static LogicalResult
getTypeConversionFailure(ConversionPatternRewriter &rewriter, Operation *op) {
assert(op->getNumResults() == 1);
return getTypeConversionFailure(rewriter, op, op->getResultTypes().front());
}
static std::string getDecorationString(spirv::Decoration decor) {
return llvm::convertToSnakeFromCamelCase(stringifyDecoration(decor));
}
namespace {
template <typename Op, typename SPIRVOp>
struct ElementwiseArithOpPattern final : OpConversionPattern<Op> {
using OpConversionPattern<Op>::OpConversionPattern;
LogicalResult
matchAndRewrite(Op op, typename Op::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
assert(adaptor.getOperands().size() <= 3);
auto converter = this->template getTypeConverter<SPIRVTypeConverter>();
Type dstType = converter->convertType(op.getType());
if (!dstType) {
return rewriter.notifyMatchFailure(
op->getLoc(),
llvm::formatv("failed to convert type {0} for SPIR-V", op.getType()));
}
if (SPIRVOp::template hasTrait<OpTrait::spirv::UnsignedOp>() &&
!getElementTypeOrSelf(op.getType()).isIndex() &&
dstType != op.getType()) {
return op.emitError("bitwidth emulation is not implemented yet on "
"unsigned op pattern version");
}
auto overflowFlags = arith::IntegerOverflowFlags::none;
if (auto overflowIface =
dyn_cast<arith::ArithIntegerOverflowFlagsInterface>(*op)) {
if (converter->getTargetEnv().allows(
spirv::Extension::SPV_KHR_no_integer_wrap_decoration))
overflowFlags = overflowIface.getOverflowAttr().getValue();
}
auto newOp = rewriter.template replaceOpWithNewOp<SPIRVOp>(
op, dstType, adaptor.getOperands());
if (bitEnumContainsAny(overflowFlags, arith::IntegerOverflowFlags::nsw))
newOp->setAttr(getDecorationString(spirv::Decoration::NoSignedWrap),
rewriter.getUnitAttr());
if (bitEnumContainsAny(overflowFlags, arith::IntegerOverflowFlags::nuw))
newOp->setAttr(getDecorationString(spirv::Decoration::NoUnsignedWrap),
rewriter.getUnitAttr());
return success();
}
};
struct ConstantCompositeOpPattern final
: public OpConversionPattern<arith::ConstantOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::ConstantOp constOp, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
auto srcType = dyn_cast<ShapedType>(constOp.getType());
if (!srcType || srcType.getNumElements() == 1)
return failure();
if (!isa<VectorType, RankedTensorType>(srcType))
return rewriter.notifyMatchFailure(constOp, "unsupported ShapedType");
Type dstType = getTypeConverter()->convertType(srcType);
if (!dstType)
return failure();
mlir::DenseElementsAttr dstElementsAttr;
if (auto denseElementsAttr =
dyn_cast<DenseElementsAttr>(constOp.getValue())) {
dstElementsAttr = denseElementsAttr;
} else if (auto resourceAttr =
dyn_cast<DenseResourceElementsAttr>(constOp.getValue())) {
AsmResourceBlob *blob = resourceAttr.getRawHandle().getBlob();
if (!blob)
return constOp->emitError("could not find resource blob");
ArrayRef<char> ptr = blob->getData();
bool detectedSplat = false;
if (!DenseElementsAttr::isValidRawBuffer(srcType, ptr, detectedSplat))
return constOp->emitError("resource is not a valid buffer");
dstElementsAttr =
DenseElementsAttr::getFromRawBuffer(resourceAttr.getType(), ptr);
} else {
return constOp->emitError("unsupported elements attribute");
}
ShapedType dstAttrType = dstElementsAttr.getType();
if (srcType.getRank() > 1) {
if (isa<RankedTensorType>(srcType)) {
dstAttrType = RankedTensorType::get(srcType.getNumElements(),
srcType.getElementType());
dstElementsAttr = dstElementsAttr.reshape(dstAttrType);
} else {
return failure();
}
}
Type srcElemType = srcType.getElementType();
Type dstElemType;
if (auto arrayType = dyn_cast<spirv::ArrayType>(dstType))
dstElemType = arrayType.getElementType();
else
dstElemType = cast<VectorType>(dstType).getElementType();
if (srcElemType != dstElemType) {
SmallVector<Attribute, 8> elements;
if (isa<FloatType>(srcElemType)) {
for (FloatAttr srcAttr : dstElementsAttr.getValues<FloatAttr>()) {
FloatAttr dstAttr =
convertFloatAttr(srcAttr, cast<FloatType>(dstElemType), rewriter);
if (!dstAttr)
return failure();
elements.push_back(dstAttr);
}
} else if (srcElemType.isInteger(1)) {
return failure();
} else {
for (IntegerAttr srcAttr : dstElementsAttr.getValues<IntegerAttr>()) {
IntegerAttr dstAttr = convertIntegerAttr(
srcAttr, cast<IntegerType>(dstElemType), rewriter);
if (!dstAttr)
return failure();
elements.push_back(dstAttr);
}
}
if (isa<RankedTensorType>(dstAttrType))
dstAttrType =
RankedTensorType::get(dstAttrType.getShape(), dstElemType);
else
dstAttrType = VectorType::get(dstAttrType.getShape(), dstElemType);
dstElementsAttr = DenseElementsAttr::get(dstAttrType, elements);
}
rewriter.replaceOpWithNewOp<spirv::ConstantOp>(constOp, dstType,
dstElementsAttr);
return success();
}
};
struct ConstantScalarOpPattern final
: public OpConversionPattern<arith::ConstantOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::ConstantOp constOp, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type srcType = constOp.getType();
if (auto shapedType = dyn_cast<ShapedType>(srcType)) {
if (shapedType.getNumElements() != 1)
return failure();
srcType = shapedType.getElementType();
}
if (!srcType.isIntOrIndexOrFloat())
return failure();
Attribute cstAttr = constOp.getValue();
if (auto elementsAttr = dyn_cast<DenseElementsAttr>(cstAttr))
cstAttr = elementsAttr.getSplatValue<Attribute>();
Type dstType = getTypeConverter()->convertType(srcType);
if (!dstType)
return failure();
if (isa<FloatType>(srcType)) {
auto srcAttr = cast<FloatAttr>(cstAttr);
auto dstAttr = srcAttr;
if (srcType != dstType) {
dstAttr = convertFloatAttr(srcAttr, cast<FloatType>(dstType), rewriter);
if (!dstAttr)
return failure();
}
rewriter.replaceOpWithNewOp<spirv::ConstantOp>(constOp, dstType, dstAttr);
return success();
}
if (srcType.isInteger(1)) {
auto dstAttr = convertBoolAttr(cstAttr, rewriter);
if (!dstAttr)
return failure();
rewriter.replaceOpWithNewOp<spirv::ConstantOp>(constOp, dstType, dstAttr);
return success();
}
auto srcAttr = cast<IntegerAttr>(cstAttr);
IntegerAttr dstAttr =
convertIntegerAttr(srcAttr, cast<IntegerType>(dstType), rewriter);
if (!dstAttr)
return failure();
rewriter.replaceOpWithNewOp<spirv::ConstantOp>(constOp, dstType, dstAttr);
return success();
}
};
template <typename SignedAbsOp>
static Value emulateSignedRemainder(Location loc, Value lhs, Value rhs,
Value signOperand, OpBuilder &builder) {
assert(lhs.getType() == rhs.getType());
assert(lhs == signOperand || rhs == signOperand);
Type type = lhs.getType();
Value lhsAbs = builder.create<SignedAbsOp>(loc, type, lhs);
Value rhsAbs = builder.create<SignedAbsOp>(loc, type, rhs);
Value abs = builder.create<spirv::UModOp>(loc, lhsAbs, rhsAbs);
Value isPositive;
if (lhs == signOperand)
isPositive = builder.create<spirv::IEqualOp>(loc, lhs, lhsAbs);
else
isPositive = builder.create<spirv::IEqualOp>(loc, rhs, rhsAbs);
Value absNegate = builder.create<spirv::SNegateOp>(loc, type, abs);
return builder.create<spirv::SelectOp>(loc, type, isPositive, abs, absNegate);
}
struct RemSIOpGLPattern final : public OpConversionPattern<arith::RemSIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::RemSIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Value result = emulateSignedRemainder<spirv::CLSAbsOp>(
op.getLoc(), adaptor.getOperands()[0], adaptor.getOperands()[1],
adaptor.getOperands()[0], rewriter);
rewriter.replaceOp(op, result);
return success();
}
};
struct RemSIOpCLPattern final : public OpConversionPattern<arith::RemSIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::RemSIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Value result = emulateSignedRemainder<spirv::GLSAbsOp>(
op.getLoc(), adaptor.getOperands()[0], adaptor.getOperands()[1],
adaptor.getOperands()[0], rewriter);
rewriter.replaceOp(op, result);
return success();
}
};
template <typename Op, typename SPIRVLogicalOp, typename SPIRVBitwiseOp>
struct BitwiseOpPattern final : public OpConversionPattern<Op> {
using OpConversionPattern<Op>::OpConversionPattern;
LogicalResult
matchAndRewrite(Op op, typename Op::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
assert(adaptor.getOperands().size() == 2);
Type dstType = this->getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
if (isBoolScalarOrVector(adaptor.getOperands().front().getType())) {
rewriter.template replaceOpWithNewOp<SPIRVLogicalOp>(
op, dstType, adaptor.getOperands());
} else {
rewriter.template replaceOpWithNewOp<SPIRVBitwiseOp>(
op, dstType, adaptor.getOperands());
}
return success();
}
};
struct XOrIOpLogicalPattern final : public OpConversionPattern<arith::XOrIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::XOrIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
assert(adaptor.getOperands().size() == 2);
if (isBoolScalarOrVector(adaptor.getOperands().front().getType()))
return failure();
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
rewriter.replaceOpWithNewOp<spirv::BitwiseXorOp>(op, dstType,
adaptor.getOperands());
return success();
}
};
struct XOrIOpBooleanPattern final : public OpConversionPattern<arith::XOrIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::XOrIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
assert(adaptor.getOperands().size() == 2);
if (!isBoolScalarOrVector(adaptor.getOperands().front().getType()))
return failure();
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
rewriter.replaceOpWithNewOp<spirv::LogicalNotEqualOp>(
op, dstType, adaptor.getOperands());
return success();
}
};
struct UIToFPI1Pattern final : public OpConversionPattern<arith::UIToFPOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::UIToFPOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type srcType = adaptor.getOperands().front().getType();
if (!isBoolScalarOrVector(srcType))
return failure();
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
Location loc = op.getLoc();
Value zero = spirv::ConstantOp::getZero(dstType, loc, rewriter);
Value one = spirv::ConstantOp::getOne(dstType, loc, rewriter);
rewriter.replaceOpWithNewOp<spirv::SelectOp>(
op, dstType, adaptor.getOperands().front(), one, zero);
return success();
}
};
struct ExtSII1Pattern final : public OpConversionPattern<arith::ExtSIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::ExtSIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Value operand = adaptor.getIn();
if (!isBoolScalarOrVector(operand.getType()))
return failure();
Location loc = op.getLoc();
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
Value allOnes;
if (auto intTy = dyn_cast<IntegerType>(dstType)) {
unsigned componentBitwidth = intTy.getWidth();
allOnes = rewriter.create<spirv::ConstantOp>(
loc, intTy,
rewriter.getIntegerAttr(intTy, APInt::getAllOnes(componentBitwidth)));
} else if (auto vectorTy = dyn_cast<VectorType>(dstType)) {
unsigned componentBitwidth = vectorTy.getElementTypeBitWidth();
allOnes = rewriter.create<spirv::ConstantOp>(
loc, vectorTy,
SplatElementsAttr::get(vectorTy,
APInt::getAllOnes(componentBitwidth)));
} else {
return rewriter.notifyMatchFailure(
loc, llvm::formatv("unhandled type: {0}", dstType));
}
Value zero = spirv::ConstantOp::getZero(dstType, loc, rewriter);
rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, dstType, operand, allOnes,
zero);
return success();
}
};
struct ExtSIPattern final : public OpConversionPattern<arith::ExtSIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::ExtSIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type srcType = adaptor.getIn().getType();
if (isBoolScalarOrVector(srcType))
return failure();
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
if (dstType == srcType) {
unsigned srcBW =
getElementTypeOrSelf(op.getIn().getType()).getIntOrFloatBitWidth();
unsigned dstBW =
getElementTypeOrSelf(op.getType()).getIntOrFloatBitWidth();
assert(srcBW < dstBW);
Value shiftSize = getScalarOrVectorConstInt(dstType, dstBW - srcBW,
rewriter, op.getLoc());
auto shiftLOp = rewriter.create<spirv::ShiftLeftLogicalOp>(
op.getLoc(), dstType, adaptor.getIn(), shiftSize);
rewriter.replaceOpWithNewOp<spirv::ShiftRightArithmeticOp>(
op, dstType, shiftLOp, shiftSize);
} else {
rewriter.replaceOpWithNewOp<spirv::SConvertOp>(op, dstType,
adaptor.getOperands());
}
return success();
}
};
struct ExtUII1Pattern final : public OpConversionPattern<arith::ExtUIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::ExtUIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type srcType = adaptor.getOperands().front().getType();
if (!isBoolScalarOrVector(srcType))
return failure();
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
Location loc = op.getLoc();
Value zero = spirv::ConstantOp::getZero(dstType, loc, rewriter);
Value one = spirv::ConstantOp::getOne(dstType, loc, rewriter);
rewriter.replaceOpWithNewOp<spirv::SelectOp>(
op, dstType, adaptor.getOperands().front(), one, zero);
return success();
}
};
struct ExtUIPattern final : public OpConversionPattern<arith::ExtUIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::ExtUIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type srcType = adaptor.getIn().getType();
if (isBoolScalarOrVector(srcType))
return failure();
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
if (dstType == srcType) {
unsigned bitwidth =
getElementTypeOrSelf(op.getIn().getType()).getIntOrFloatBitWidth();
Value mask = getScalarOrVectorConstInt(
dstType, llvm::maskTrailingOnes<uint64_t>(bitwidth), rewriter,
op.getLoc());
rewriter.replaceOpWithNewOp<spirv::BitwiseAndOp>(op, dstType,
adaptor.getIn(), mask);
} else {
rewriter.replaceOpWithNewOp<spirv::UConvertOp>(op, dstType,
adaptor.getOperands());
}
return success();
}
};
struct TruncII1Pattern final : public OpConversionPattern<arith::TruncIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::TruncIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
if (!isBoolScalarOrVector(dstType))
return failure();
Location loc = op.getLoc();
auto srcType = adaptor.getOperands().front().getType();
Value mask = spirv::ConstantOp::getOne(srcType, loc, rewriter);
Value maskedSrc = rewriter.create<spirv::BitwiseAndOp>(
loc, srcType, adaptor.getOperands()[0], mask);
Value isOne = rewriter.create<spirv::IEqualOp>(loc, maskedSrc, mask);
Value zero = spirv::ConstantOp::getZero(dstType, loc, rewriter);
Value one = spirv::ConstantOp::getOne(dstType, loc, rewriter);
rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, dstType, isOne, one, zero);
return success();
}
};
struct TruncIPattern final : public OpConversionPattern<arith::TruncIOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::TruncIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type srcType = adaptor.getIn().getType();
Type dstType = getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
if (isBoolScalarOrVector(dstType))
return failure();
if (dstType == srcType) {
unsigned bw = getElementTypeOrSelf(op.getType()).getIntOrFloatBitWidth();
Value mask = getScalarOrVectorConstInt(
dstType, llvm::maskTrailingOnes<uint64_t>(bw), rewriter, op.getLoc());
rewriter.replaceOpWithNewOp<spirv::BitwiseAndOp>(op, dstType,
adaptor.getIn(), mask);
} else {
rewriter.replaceOpWithNewOp<spirv::SConvertOp>(op, dstType,
adaptor.getOperands());
}
return success();
}
};
template <typename Op, typename SPIRVOp>
struct TypeCastingOpPattern final : public OpConversionPattern<Op> {
using OpConversionPattern<Op>::OpConversionPattern;
LogicalResult
matchAndRewrite(Op op, typename Op::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
assert(adaptor.getOperands().size() == 1);
Type srcType = adaptor.getOperands().front().getType();
Type dstType = this->getTypeConverter()->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
if (isBoolScalarOrVector(srcType) || isBoolScalarOrVector(dstType))
return failure();
if (dstType == srcType) {
rewriter.replaceOp(op, adaptor.getOperands().front());
} else {
rewriter.template replaceOpWithNewOp<SPIRVOp>(op, dstType,
adaptor.getOperands());
if (auto roundingModeOp =
dyn_cast<arith::ArithRoundingModeInterface>(*op)) {
if (arith::RoundingModeAttr roundingMode =
roundingModeOp.getRoundingModeAttr()) {
return failure();
}
}
}
return success();
}
};
class CmpIOpBooleanPattern final : public OpConversionPattern<arith::CmpIOp> {
public:
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::CmpIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type srcType = op.getLhs().getType();
if (!isBoolScalarOrVector(srcType))
return failure();
Type dstType = getTypeConverter()->convertType(srcType);
if (!dstType)
return getTypeConversionFailure(rewriter, op, srcType);
switch (op.getPredicate()) {
case arith::CmpIPredicate::eq: {
rewriter.replaceOpWithNewOp<spirv::LogicalEqualOp>(op, adaptor.getLhs(),
adaptor.getRhs());
return success();
}
case arith::CmpIPredicate::ne: {
rewriter.replaceOpWithNewOp<spirv::LogicalNotEqualOp>(
op, adaptor.getLhs(), adaptor.getRhs());
return success();
}
case arith::CmpIPredicate::uge:
case arith::CmpIPredicate::ugt:
case arith::CmpIPredicate::ule:
case arith::CmpIPredicate::ult: {
Type type = rewriter.getI32Type();
if (auto vectorType = dyn_cast<VectorType>(dstType))
type = VectorType::get(vectorType.getShape(), type);
Value extLhs =
rewriter.create<arith::ExtUIOp>(op.getLoc(), type, adaptor.getLhs());
Value extRhs =
rewriter.create<arith::ExtUIOp>(op.getLoc(), type, adaptor.getRhs());
rewriter.replaceOpWithNewOp<arith::CmpIOp>(op, op.getPredicate(), extLhs,
extRhs);
return success();
}
default:
break;
}
return failure();
}
};
class CmpIOpPattern final : public OpConversionPattern<arith::CmpIOp> {
public:
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::CmpIOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type srcType = op.getLhs().getType();
if (isBoolScalarOrVector(srcType))
return failure();
Type dstType = getTypeConverter()->convertType(srcType);
if (!dstType)
return getTypeConversionFailure(rewriter, op, srcType);
switch (op.getPredicate()) {
#define DISPATCH(cmpPredicate, spirvOp) \
case cmpPredicate: \
if (spirvOp::template hasTrait<OpTrait::spirv::UnsignedOp>() && \
!getElementTypeOrSelf(srcType).isIndex() && srcType != dstType && \
!hasSameBitwidth(srcType, dstType)) { \
return op.emitError( \
"bitwidth emulation is not implemented yet on unsigned op"); \
} \
rewriter.replaceOpWithNewOp<spirvOp>(op, adaptor.getLhs(), \
adaptor.getRhs()); \
return success();
DISPATCH(arith::CmpIPredicate::eq, spirv::IEqualOp);
DISPATCH(arith::CmpIPredicate::ne, spirv::INotEqualOp);
DISPATCH(arith::CmpIPredicate::slt, spirv::SLessThanOp);
DISPATCH(arith::CmpIPredicate::sle, spirv::SLessThanEqualOp);
DISPATCH(arith::CmpIPredicate::sgt, spirv::SGreaterThanOp);
DISPATCH(arith::CmpIPredicate::sge, spirv::SGreaterThanEqualOp);
DISPATCH(arith::CmpIPredicate::ult, spirv::ULessThanOp);
DISPATCH(arith::CmpIPredicate::ule, spirv::ULessThanEqualOp);
DISPATCH(arith::CmpIPredicate::ugt, spirv::UGreaterThanOp);
DISPATCH(arith::CmpIPredicate::uge, spirv::UGreaterThanEqualOp);
#undef DISPATCH
}
return failure();
}
};
class CmpFOpPattern final : public OpConversionPattern<arith::CmpFOp> {
public:
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::CmpFOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
switch (op.getPredicate()) {
#define DISPATCH(cmpPredicate, spirvOp) \
case cmpPredicate: \
rewriter.replaceOpWithNewOp<spirvOp>(op, adaptor.getLhs(), \
adaptor.getRhs()); \
return success();
DISPATCH(arith::CmpFPredicate::OEQ, spirv::FOrdEqualOp);
DISPATCH(arith::CmpFPredicate::OGT, spirv::FOrdGreaterThanOp);
DISPATCH(arith::CmpFPredicate::OGE, spirv::FOrdGreaterThanEqualOp);
DISPATCH(arith::CmpFPredicate::OLT, spirv::FOrdLessThanOp);
DISPATCH(arith::CmpFPredicate::OLE, spirv::FOrdLessThanEqualOp);
DISPATCH(arith::CmpFPredicate::ONE, spirv::FOrdNotEqualOp);
DISPATCH(arith::CmpFPredicate::UEQ, spirv::FUnordEqualOp);
DISPATCH(arith::CmpFPredicate::UGT, spirv::FUnordGreaterThanOp);
DISPATCH(arith::CmpFPredicate::UGE, spirv::FUnordGreaterThanEqualOp);
DISPATCH(arith::CmpFPredicate::ULT, spirv::FUnordLessThanOp);
DISPATCH(arith::CmpFPredicate::ULE, spirv::FUnordLessThanEqualOp);
DISPATCH(arith::CmpFPredicate::UNE, spirv::FUnordNotEqualOp);
#undef DISPATCH
default:
break;
}
return failure();
}
};
class CmpFOpNanKernelPattern final : public OpConversionPattern<arith::CmpFOp> {
public:
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::CmpFOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
if (op.getPredicate() == arith::CmpFPredicate::ORD) {
rewriter.replaceOpWithNewOp<spirv::OrderedOp>(op, adaptor.getLhs(),
adaptor.getRhs());
return success();
}
if (op.getPredicate() == arith::CmpFPredicate::UNO) {
rewriter.replaceOpWithNewOp<spirv::UnorderedOp>(op, adaptor.getLhs(),
adaptor.getRhs());
return success();
}
return failure();
}
};
class CmpFOpNanNonePattern final : public OpConversionPattern<arith::CmpFOp> {
public:
using OpConversionPattern<arith::CmpFOp>::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::CmpFOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
if (op.getPredicate() != arith::CmpFPredicate::ORD &&
op.getPredicate() != arith::CmpFPredicate::UNO)
return failure();
Location loc = op.getLoc();
Value replace;
if (bitEnumContainsAll(op.getFastmath(), arith::FastMathFlags::nnan)) {
if (op.getPredicate() == arith::CmpFPredicate::ORD) {
replace = spirv::ConstantOp::getOne(op.getType(), loc, rewriter);
} else {
replace = spirv::ConstantOp::getZero(op.getType(), loc, rewriter);
}
} else {
Value lhsIsNan = rewriter.create<spirv::IsNanOp>(loc, adaptor.getLhs());
Value rhsIsNan = rewriter.create<spirv::IsNanOp>(loc, adaptor.getRhs());
replace = rewriter.create<spirv::LogicalOrOp>(loc, lhsIsNan, rhsIsNan);
if (op.getPredicate() == arith::CmpFPredicate::ORD)
replace = rewriter.create<spirv::LogicalNotOp>(loc, replace);
}
rewriter.replaceOp(op, replace);
return success();
}
};
class AddUIExtendedOpPattern final
: public OpConversionPattern<arith::AddUIExtendedOp> {
public:
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::AddUIExtendedOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type dstElemTy = adaptor.getLhs().getType();
Location loc = op->getLoc();
Value result = rewriter.create<spirv::IAddCarryOp>(loc, adaptor.getLhs(),
adaptor.getRhs());
Value sumResult = rewriter.create<spirv::CompositeExtractOp>(
loc, result, llvm::ArrayRef(0));
Value carryValue = rewriter.create<spirv::CompositeExtractOp>(
loc, result, llvm::ArrayRef(1));
Value one = spirv::ConstantOp::getOne(dstElemTy, loc, rewriter);
Value carryResult = rewriter.create<spirv::IEqualOp>(loc, carryValue, one);
rewriter.replaceOp(op, {sumResult, carryResult});
return success();
}
};
template <typename ArithMulOp, typename SPIRVMulOp>
class MulIExtendedOpPattern final : public OpConversionPattern<ArithMulOp> {
public:
using OpConversionPattern<ArithMulOp>::OpConversionPattern;
LogicalResult
matchAndRewrite(ArithMulOp op, typename ArithMulOp::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Location loc = op->getLoc();
Value result =
rewriter.create<SPIRVMulOp>(loc, adaptor.getLhs(), adaptor.getRhs());
Value low = rewriter.create<spirv::CompositeExtractOp>(loc, result,
llvm::ArrayRef(0));
Value high = rewriter.create<spirv::CompositeExtractOp>(loc, result,
llvm::ArrayRef(1));
rewriter.replaceOp(op, {low, high});
return success();
}
};
class SelectOpPattern final : public OpConversionPattern<arith::SelectOp> {
public:
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(arith::SelectOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
rewriter.replaceOpWithNewOp<spirv::SelectOp>(op, adaptor.getCondition(),
adaptor.getTrueValue(),
adaptor.getFalseValue());
return success();
}
};
template <typename Op, typename SPIRVOp>
class MinimumMaximumFOpPattern final : public OpConversionPattern<Op> {
public:
using OpConversionPattern<Op>::OpConversionPattern;
LogicalResult
matchAndRewrite(Op op, typename Op::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
auto *converter = this->template getTypeConverter<SPIRVTypeConverter>();
Type dstType = converter->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
Location loc = op.getLoc();
Value spirvOp =
rewriter.create<SPIRVOp>(loc, dstType, adaptor.getOperands());
if (bitEnumContainsAll(op.getFastmath(), arith::FastMathFlags::nnan)) {
rewriter.replaceOp(op, spirvOp);
return success();
}
Value lhsIsNan = rewriter.create<spirv::IsNanOp>(loc, adaptor.getLhs());
Value rhsIsNan = rewriter.create<spirv::IsNanOp>(loc, adaptor.getRhs());
Value select1 = rewriter.create<spirv::SelectOp>(loc, dstType, lhsIsNan,
adaptor.getLhs(), spirvOp);
Value select2 = rewriter.create<spirv::SelectOp>(loc, dstType, rhsIsNan,
adaptor.getRhs(), select1);
rewriter.replaceOp(op, select2);
return success();
}
};
template <typename Op, typename SPIRVOp>
class MinNumMaxNumFOpPattern final : public OpConversionPattern<Op> {
template <typename TargetOp>
constexpr bool shouldInsertNanGuards() const {
return llvm::is_one_of<TargetOp, spirv::GLFMaxOp, spirv::GLFMinOp>::value;
}
public:
using OpConversionPattern<Op>::OpConversionPattern;
LogicalResult
matchAndRewrite(Op op, typename Op::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
auto *converter = this->template getTypeConverter<SPIRVTypeConverter>();
Type dstType = converter->convertType(op.getType());
if (!dstType)
return getTypeConversionFailure(rewriter, op);
Location loc = op.getLoc();
Value spirvOp =
rewriter.create<SPIRVOp>(loc, dstType, adaptor.getOperands());
if (!shouldInsertNanGuards<SPIRVOp>() ||
bitEnumContainsAll(op.getFastmath(), arith::FastMathFlags::nnan)) {
rewriter.replaceOp(op, spirvOp);
return success();
}
Value lhsIsNan = rewriter.create<spirv::IsNanOp>(loc, adaptor.getLhs());
Value rhsIsNan = rewriter.create<spirv::IsNanOp>(loc, adaptor.getRhs());
Value select1 = rewriter.create<spirv::SelectOp>(loc, dstType, lhsIsNan,
adaptor.getRhs(), spirvOp);
Value select2 = rewriter.create<spirv::SelectOp>(loc, dstType, rhsIsNan,
adaptor.getLhs(), select1);
rewriter.replaceOp(op, select2);
return success();
}
};
}
void mlir::arith::populateArithToSPIRVPatterns(
SPIRVTypeConverter &typeConverter, RewritePatternSet &patterns) {
patterns.add<
ConstantCompositeOpPattern,
ConstantScalarOpPattern,
ElementwiseArithOpPattern<arith::AddIOp, spirv::IAddOp>,
ElementwiseArithOpPattern<arith::SubIOp, spirv::ISubOp>,
ElementwiseArithOpPattern<arith::MulIOp, spirv::IMulOp>,
spirv::ElementwiseOpPattern<arith::DivUIOp, spirv::UDivOp>,
spirv::ElementwiseOpPattern<arith::DivSIOp, spirv::SDivOp>,
spirv::ElementwiseOpPattern<arith::RemUIOp, spirv::UModOp>,
RemSIOpGLPattern, RemSIOpCLPattern,
BitwiseOpPattern<arith::AndIOp, spirv::LogicalAndOp, spirv::BitwiseAndOp>,
BitwiseOpPattern<arith::OrIOp, spirv::LogicalOrOp, spirv::BitwiseOrOp>,
XOrIOpLogicalPattern, XOrIOpBooleanPattern,
ElementwiseArithOpPattern<arith::ShLIOp, spirv::ShiftLeftLogicalOp>,
spirv::ElementwiseOpPattern<arith::ShRUIOp, spirv::ShiftRightLogicalOp>,
spirv::ElementwiseOpPattern<arith::ShRSIOp, spirv::ShiftRightArithmeticOp>,
spirv::ElementwiseOpPattern<arith::NegFOp, spirv::FNegateOp>,
spirv::ElementwiseOpPattern<arith::AddFOp, spirv::FAddOp>,
spirv::ElementwiseOpPattern<arith::SubFOp, spirv::FSubOp>,
spirv::ElementwiseOpPattern<arith::MulFOp, spirv::FMulOp>,
spirv::ElementwiseOpPattern<arith::DivFOp, spirv::FDivOp>,
spirv::ElementwiseOpPattern<arith::RemFOp, spirv::FRemOp>,
ExtUIPattern, ExtUII1Pattern,
ExtSIPattern, ExtSII1Pattern,
TypeCastingOpPattern<arith::ExtFOp, spirv::FConvertOp>,
TruncIPattern, TruncII1Pattern,
TypeCastingOpPattern<arith::TruncFOp, spirv::FConvertOp>,
TypeCastingOpPattern<arith::UIToFPOp, spirv::ConvertUToFOp>, UIToFPI1Pattern,
TypeCastingOpPattern<arith::SIToFPOp, spirv::ConvertSToFOp>,
TypeCastingOpPattern<arith::FPToUIOp, spirv::ConvertFToUOp>,
TypeCastingOpPattern<arith::FPToSIOp, spirv::ConvertFToSOp>,
TypeCastingOpPattern<arith::IndexCastOp, spirv::SConvertOp>,
TypeCastingOpPattern<arith::IndexCastUIOp, spirv::UConvertOp>,
TypeCastingOpPattern<arith::BitcastOp, spirv::BitcastOp>,
CmpIOpBooleanPattern, CmpIOpPattern,
CmpFOpNanNonePattern, CmpFOpPattern,
AddUIExtendedOpPattern,
MulIExtendedOpPattern<arith::MulSIExtendedOp, spirv::SMulExtendedOp>,
MulIExtendedOpPattern<arith::MulUIExtendedOp, spirv::UMulExtendedOp>,
SelectOpPattern,
MinimumMaximumFOpPattern<arith::MaximumFOp, spirv::GLFMaxOp>,
MinimumMaximumFOpPattern<arith::MinimumFOp, spirv::GLFMinOp>,
MinNumMaxNumFOpPattern<arith::MaxNumFOp, spirv::GLFMaxOp>,
MinNumMaxNumFOpPattern<arith::MinNumFOp, spirv::GLFMinOp>,
spirv::ElementwiseOpPattern<arith::MaxSIOp, spirv::GLSMaxOp>,
spirv::ElementwiseOpPattern<arith::MaxUIOp, spirv::GLUMaxOp>,
spirv::ElementwiseOpPattern<arith::MinSIOp, spirv::GLSMinOp>,
spirv::ElementwiseOpPattern<arith::MinUIOp, spirv::GLUMinOp>,
MinimumMaximumFOpPattern<arith::MaximumFOp, spirv::CLFMaxOp>,
MinimumMaximumFOpPattern<arith::MinimumFOp, spirv::CLFMinOp>,
MinNumMaxNumFOpPattern<arith::MaxNumFOp, spirv::CLFMaxOp>,
MinNumMaxNumFOpPattern<arith::MinNumFOp, spirv::CLFMinOp>,
spirv::ElementwiseOpPattern<arith::MaxSIOp, spirv::CLSMaxOp>,
spirv::ElementwiseOpPattern<arith::MaxUIOp, spirv::CLUMaxOp>,
spirv::ElementwiseOpPattern<arith::MinSIOp, spirv::CLSMinOp>,
spirv::ElementwiseOpPattern<arith::MinUIOp, spirv::CLUMinOp>
>(typeConverter, patterns.getContext());
patterns.add<CmpFOpNanKernelPattern>(typeConverter, patterns.getContext(),
2);
}
namespace {
struct ConvertArithToSPIRVPass
: public impl::ConvertArithToSPIRVBase<ConvertArithToSPIRVPass> {
void runOnOperation() override {
Operation *op = getOperation();
spirv::TargetEnvAttr targetAttr = spirv::lookupTargetEnvOrDefault(op);
std::unique_ptr<SPIRVConversionTarget> target =
SPIRVConversionTarget::get(targetAttr);
SPIRVConversionOptions options;
options.emulateLT32BitScalarTypes = this->emulateLT32BitScalarTypes;
SPIRVTypeConverter typeConverter(targetAttr, options);
target->addLegalOp<UnrealizedConversionCastOp>();
target->addIllegalDialect<arith::ArithDialect>();
RewritePatternSet patterns(&getContext());
arith::populateArithToSPIRVPatterns(typeConverter, patterns);
if (failed(applyPartialConversion(op, *target, std::move(patterns))))
signalPassFailure();
}
};
}
std::unique_ptr<OperationPass<>> mlir::arith::createConvertArithToSPIRVPass() {
return std::make_unique<ConvertArithToSPIRVPass>();
}