//===- ArithmeticOps.cpp - MLIR Arithmetic dialect ops implementation -----===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include #include "mlir/Dialect/Arithmetic/IR/Arithmetic.h" #include "mlir/Dialect/CommonFolders.h" #include "mlir/IR/Builders.h" #include "mlir/IR/Matchers.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/PatternMatch.h" #include "mlir/IR/TypeUtilities.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/APSInt.h" using namespace mlir; using namespace mlir::arith; //===----------------------------------------------------------------------===// // Pattern helpers //===----------------------------------------------------------------------===// static IntegerAttr addIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs) { return builder.getIntegerAttr(res.getType(), lhs.cast().getInt() + rhs.cast().getInt()); } static IntegerAttr subIntegerAttrs(PatternRewriter &builder, Value res, Attribute lhs, Attribute rhs) { return builder.getIntegerAttr(res.getType(), lhs.cast().getInt() - rhs.cast().getInt()); } /// Invert an integer comparison predicate. arith::CmpIPredicate arith::invertPredicate(arith::CmpIPredicate pred) { switch (pred) { case arith::CmpIPredicate::eq: return arith::CmpIPredicate::ne; case arith::CmpIPredicate::ne: return arith::CmpIPredicate::eq; case arith::CmpIPredicate::slt: return arith::CmpIPredicate::sge; case arith::CmpIPredicate::sle: return arith::CmpIPredicate::sgt; case arith::CmpIPredicate::sgt: return arith::CmpIPredicate::sle; case arith::CmpIPredicate::sge: return arith::CmpIPredicate::slt; case arith::CmpIPredicate::ult: return arith::CmpIPredicate::uge; case arith::CmpIPredicate::ule: return arith::CmpIPredicate::ugt; case arith::CmpIPredicate::ugt: return arith::CmpIPredicate::ule; case arith::CmpIPredicate::uge: return arith::CmpIPredicate::ult; } llvm_unreachable("unknown cmpi predicate kind"); } static arith::CmpIPredicateAttr invertPredicate(arith::CmpIPredicateAttr pred) { return arith::CmpIPredicateAttr::get(pred.getContext(), invertPredicate(pred.getValue())); } //===----------------------------------------------------------------------===// // TableGen'd canonicalization patterns //===----------------------------------------------------------------------===// namespace { #include "ArithmeticCanonicalization.inc" } // namespace //===----------------------------------------------------------------------===// // ConstantOp //===----------------------------------------------------------------------===// void arith::ConstantOp::getAsmResultNames( function_ref setNameFn) { auto type = getType(); if (auto intCst = getValue().dyn_cast()) { auto intType = type.dyn_cast(); // Sugar i1 constants with 'true' and 'false'. if (intType && intType.getWidth() == 1) return setNameFn(getResult(), (intCst.getInt() ? "true" : "false")); // Otherwise, build a complex name with the value and type. SmallString<32> specialNameBuffer; llvm::raw_svector_ostream specialName(specialNameBuffer); specialName << 'c' << intCst.getValue(); if (intType) specialName << '_' << type; setNameFn(getResult(), specialName.str()); } else { setNameFn(getResult(), "cst"); } } /// TODO: disallow arith.constant to return anything other than signless integer /// or float like. LogicalResult arith::ConstantOp::verify() { auto type = getType(); // The value's type must match the return type. if (getValue().getType() != type) { return emitOpError() << "value type " << getValue().getType() << " must match return type: " << type; } // Integer values must be signless. if (type.isa() && !type.cast().isSignless()) return emitOpError("integer return type must be signless"); // Any float or elements attribute are acceptable. if (!getValue().isa()) { return emitOpError( "value must be an integer, float, or elements attribute"); } return success(); } bool arith::ConstantOp::isBuildableWith(Attribute value, Type type) { // The value's type must be the same as the provided type. if (value.getType() != type) return false; // Integer values must be signless. if (type.isa() && !type.cast().isSignless()) return false; // Integer, float, and element attributes are buildable. return value.isa(); } OpFoldResult arith::ConstantOp::fold(ArrayRef operands) { return getValue(); } void arith::ConstantIntOp::build(OpBuilder &builder, OperationState &result, int64_t value, unsigned width) { auto type = builder.getIntegerType(width); arith::ConstantOp::build(builder, result, type, builder.getIntegerAttr(type, value)); } void arith::ConstantIntOp::build(OpBuilder &builder, OperationState &result, int64_t value, Type type) { assert(type.isSignlessInteger() && "ConstantIntOp can only have signless integer type values"); arith::ConstantOp::build(builder, result, type, builder.getIntegerAttr(type, value)); } bool arith::ConstantIntOp::classof(Operation *op) { if (auto constOp = dyn_cast_or_null(op)) return constOp.getType().isSignlessInteger(); return false; } void arith::ConstantFloatOp::build(OpBuilder &builder, OperationState &result, const APFloat &value, FloatType type) { arith::ConstantOp::build(builder, result, type, builder.getFloatAttr(type, value)); } bool arith::ConstantFloatOp::classof(Operation *op) { if (auto constOp = dyn_cast_or_null(op)) return constOp.getType().isa(); return false; } void arith::ConstantIndexOp::build(OpBuilder &builder, OperationState &result, int64_t value) { arith::ConstantOp::build(builder, result, builder.getIndexType(), builder.getIndexAttr(value)); } bool arith::ConstantIndexOp::classof(Operation *op) { if (auto constOp = dyn_cast_or_null(op)) return constOp.getType().isIndex(); return false; } //===----------------------------------------------------------------------===// // AddIOp //===----------------------------------------------------------------------===// OpFoldResult arith::AddIOp::fold(ArrayRef operands) { // addi(x, 0) -> x if (matchPattern(getRhs(), m_Zero())) return getLhs(); // addi(subi(a, b), b) -> a if (auto sub = getLhs().getDefiningOp()) if (getRhs() == sub.getRhs()) return sub.getLhs(); // addi(b, subi(a, b)) -> a if (auto sub = getRhs().getDefiningOp()) if (getLhs() == sub.getRhs()) return sub.getLhs(); return constFoldBinaryOp( operands, [](APInt a, const APInt &b) { return std::move(a) + b; }); } void arith::AddIOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add( context); } //===----------------------------------------------------------------------===// // SubIOp //===----------------------------------------------------------------------===// OpFoldResult arith::SubIOp::fold(ArrayRef operands) { // subi(x,x) -> 0 if (getOperand(0) == getOperand(1)) return Builder(getContext()).getZeroAttr(getType()); // subi(x,0) -> x if (matchPattern(getRhs(), m_Zero())) return getLhs(); return constFoldBinaryOp( operands, [](APInt a, const APInt &b) { return std::move(a) - b; }); } void arith::SubIOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns .add( context); } //===----------------------------------------------------------------------===// // MulIOp //===----------------------------------------------------------------------===// OpFoldResult arith::MulIOp::fold(ArrayRef operands) { // muli(x, 0) -> 0 if (matchPattern(getRhs(), m_Zero())) return getRhs(); // muli(x, 1) -> x if (matchPattern(getRhs(), m_One())) return getOperand(0); // TODO: Handle the overflow case. // default folder return constFoldBinaryOp( operands, [](const APInt &a, const APInt &b) { return a * b; }); } //===----------------------------------------------------------------------===// // DivUIOp //===----------------------------------------------------------------------===// OpFoldResult arith::DivUIOp::fold(ArrayRef operands) { // divui (x, 1) -> x. if (matchPattern(getRhs(), m_One())) return getLhs(); // Don't fold if it would require a division by zero. bool div0 = false; auto result = constFoldBinaryOp(operands, [&](APInt a, const APInt &b) { if (div0 || !b) { div0 = true; return a; } return a.udiv(b); }); return div0 ? Attribute() : result; } //===----------------------------------------------------------------------===// // DivSIOp //===----------------------------------------------------------------------===// OpFoldResult arith::DivSIOp::fold(ArrayRef operands) { // divsi (x, 1) -> x. if (matchPattern(getRhs(), m_One())) return getLhs(); // Don't fold if it would overflow or if it requires a division by zero. bool overflowOrDiv0 = false; auto result = constFoldBinaryOp(operands, [&](APInt a, const APInt &b) { if (overflowOrDiv0 || !b) { overflowOrDiv0 = true; return a; } return a.sdiv_ov(b, overflowOrDiv0); }); return overflowOrDiv0 ? Attribute() : result; } //===----------------------------------------------------------------------===// // Ceil and floor division folding helpers //===----------------------------------------------------------------------===// static APInt signedCeilNonnegInputs(const APInt &a, const APInt &b, bool &overflow) { // Returns (a-1)/b + 1 APInt one(a.getBitWidth(), 1, true); // Signed value 1. APInt val = a.ssub_ov(one, overflow).sdiv_ov(b, overflow); return val.sadd_ov(one, overflow); } //===----------------------------------------------------------------------===// // CeilDivUIOp //===----------------------------------------------------------------------===// OpFoldResult arith::CeilDivUIOp::fold(ArrayRef operands) { // ceildivui (x, 1) -> x. if (matchPattern(getRhs(), m_One())) return getLhs(); bool overflowOrDiv0 = false; auto result = constFoldBinaryOp(operands, [&](APInt a, const APInt &b) { if (overflowOrDiv0 || !b) { overflowOrDiv0 = true; return a; } APInt quotient = a.udiv(b); if (!a.urem(b)) return quotient; APInt one(a.getBitWidth(), 1, true); return quotient.uadd_ov(one, overflowOrDiv0); }); return overflowOrDiv0 ? Attribute() : result; } //===----------------------------------------------------------------------===// // CeilDivSIOp //===----------------------------------------------------------------------===// OpFoldResult arith::CeilDivSIOp::fold(ArrayRef operands) { // ceildivsi (x, 1) -> x. if (matchPattern(getRhs(), m_One())) return getLhs(); // Don't fold if it would overflow or if it requires a division by zero. bool overflowOrDiv0 = false; auto result = constFoldBinaryOp(operands, [&](APInt a, const APInt &b) { if (overflowOrDiv0 || !b) { overflowOrDiv0 = true; return a; } if (!a) return a; // After this point we know that neither a or b are zero. unsigned bits = a.getBitWidth(); APInt zero = APInt::getZero(bits); bool aGtZero = a.sgt(zero); bool bGtZero = b.sgt(zero); if (aGtZero && bGtZero) { // Both positive, return ceil(a, b). return signedCeilNonnegInputs(a, b, overflowOrDiv0); } if (!aGtZero && !bGtZero) { // Both negative, return ceil(-a, -b). APInt posA = zero.ssub_ov(a, overflowOrDiv0); APInt posB = zero.ssub_ov(b, overflowOrDiv0); return signedCeilNonnegInputs(posA, posB, overflowOrDiv0); } if (!aGtZero && bGtZero) { // A is negative, b is positive, return - ( -a / b). APInt posA = zero.ssub_ov(a, overflowOrDiv0); APInt div = posA.sdiv_ov(b, overflowOrDiv0); return zero.ssub_ov(div, overflowOrDiv0); } // A is positive, b is negative, return - (a / -b). APInt posB = zero.ssub_ov(b, overflowOrDiv0); APInt div = a.sdiv_ov(posB, overflowOrDiv0); return zero.ssub_ov(div, overflowOrDiv0); }); return overflowOrDiv0 ? Attribute() : result; } //===----------------------------------------------------------------------===// // FloorDivSIOp //===----------------------------------------------------------------------===// OpFoldResult arith::FloorDivSIOp::fold(ArrayRef operands) { // floordivsi (x, 1) -> x. if (matchPattern(getRhs(), m_One())) return getLhs(); // Don't fold if it would overflow or if it requires a division by zero. bool overflowOrDiv0 = false; auto result = constFoldBinaryOp(operands, [&](APInt a, const APInt &b) { if (overflowOrDiv0 || !b) { overflowOrDiv0 = true; return a; } if (!a) return a; // After this point we know that neither a or b are zero. unsigned bits = a.getBitWidth(); APInt zero = APInt::getZero(bits); bool aGtZero = a.sgt(zero); bool bGtZero = b.sgt(zero); if (aGtZero && bGtZero) { // Both positive, return a / b. return a.sdiv_ov(b, overflowOrDiv0); } if (!aGtZero && !bGtZero) { // Both negative, return -a / -b. APInt posA = zero.ssub_ov(a, overflowOrDiv0); APInt posB = zero.ssub_ov(b, overflowOrDiv0); return posA.sdiv_ov(posB, overflowOrDiv0); } if (!aGtZero && bGtZero) { // A is negative, b is positive, return - ceil(-a, b). APInt posA = zero.ssub_ov(a, overflowOrDiv0); APInt ceil = signedCeilNonnegInputs(posA, b, overflowOrDiv0); return zero.ssub_ov(ceil, overflowOrDiv0); } // A is positive, b is negative, return - ceil(a, -b). APInt posB = zero.ssub_ov(b, overflowOrDiv0); APInt ceil = signedCeilNonnegInputs(a, posB, overflowOrDiv0); return zero.ssub_ov(ceil, overflowOrDiv0); }); return overflowOrDiv0 ? Attribute() : result; } //===----------------------------------------------------------------------===// // RemUIOp //===----------------------------------------------------------------------===// OpFoldResult arith::RemUIOp::fold(ArrayRef operands) { // remui (x, 1) -> 0. if (matchPattern(getRhs(), m_One())) return Builder(getContext()).getZeroAttr(getType()); // Don't fold if it would require a division by zero. bool div0 = false; auto result = constFoldBinaryOp(operands, [&](APInt a, const APInt &b) { if (div0 || b.isNullValue()) { div0 = true; return a; } return a.urem(b); }); return div0 ? Attribute() : result; } //===----------------------------------------------------------------------===// // RemSIOp //===----------------------------------------------------------------------===// OpFoldResult arith::RemSIOp::fold(ArrayRef operands) { // remsi (x, 1) -> 0. if (matchPattern(getRhs(), m_One())) return Builder(getContext()).getZeroAttr(getType()); // Don't fold if it would require a division by zero. bool div0 = false; auto result = constFoldBinaryOp(operands, [&](APInt a, const APInt &b) { if (div0 || b.isNullValue()) { div0 = true; return a; } return a.srem(b); }); return div0 ? Attribute() : result; } //===----------------------------------------------------------------------===// // AndIOp //===----------------------------------------------------------------------===// OpFoldResult arith::AndIOp::fold(ArrayRef operands) { /// and(x, 0) -> 0 if (matchPattern(getRhs(), m_Zero())) return getRhs(); /// and(x, allOnes) -> x APInt intValue; if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isAllOnes()) return getLhs(); return constFoldBinaryOp( operands, [](APInt a, const APInt &b) { return std::move(a) & b; }); } //===----------------------------------------------------------------------===// // OrIOp //===----------------------------------------------------------------------===// OpFoldResult arith::OrIOp::fold(ArrayRef operands) { /// or(x, 0) -> x if (matchPattern(getRhs(), m_Zero())) return getLhs(); /// or(x, ) -> if (auto rhsAttr = operands[1].dyn_cast_or_null()) if (rhsAttr.getValue().isAllOnes()) return rhsAttr; return constFoldBinaryOp( operands, [](APInt a, const APInt &b) { return std::move(a) | b; }); } //===----------------------------------------------------------------------===// // XOrIOp //===----------------------------------------------------------------------===// OpFoldResult arith::XOrIOp::fold(ArrayRef operands) { /// xor(x, 0) -> x if (matchPattern(getRhs(), m_Zero())) return getLhs(); /// xor(x, x) -> 0 if (getLhs() == getRhs()) return Builder(getContext()).getZeroAttr(getType()); /// xor(xor(x, a), a) -> x if (arith::XOrIOp prev = getLhs().getDefiningOp()) if (prev.getRhs() == getRhs()) return prev.getLhs(); return constFoldBinaryOp( operands, [](APInt a, const APInt &b) { return std::move(a) ^ b; }); } void arith::XOrIOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add(context); } //===----------------------------------------------------------------------===// // NegFOp //===----------------------------------------------------------------------===// OpFoldResult arith::NegFOp::fold(ArrayRef operands) { /// negf(negf(x)) -> x if (auto op = this->getOperand().getDefiningOp()) return op.getOperand(); return constFoldUnaryOp(operands, [](const APFloat &a) { return -a; }); } //===----------------------------------------------------------------------===// // AddFOp //===----------------------------------------------------------------------===// OpFoldResult arith::AddFOp::fold(ArrayRef operands) { // addf(x, -0) -> x if (matchPattern(getRhs(), m_NegZeroFloat())) return getLhs(); return constFoldBinaryOp( operands, [](const APFloat &a, const APFloat &b) { return a + b; }); } //===----------------------------------------------------------------------===// // SubFOp //===----------------------------------------------------------------------===// OpFoldResult arith::SubFOp::fold(ArrayRef operands) { // subf(x, +0) -> x if (matchPattern(getRhs(), m_PosZeroFloat())) return getLhs(); return constFoldBinaryOp( operands, [](const APFloat &a, const APFloat &b) { return a - b; }); } //===----------------------------------------------------------------------===// // MaxFOp //===----------------------------------------------------------------------===// OpFoldResult arith::MaxFOp::fold(ArrayRef operands) { assert(operands.size() == 2 && "maxf takes two operands"); // maxf(x,x) -> x if (getLhs() == getRhs()) return getRhs(); // maxf(x, -inf) -> x if (matchPattern(getRhs(), m_NegInfFloat())) return getLhs(); return constFoldBinaryOp( operands, [](const APFloat &a, const APFloat &b) { return llvm::maximum(a, b); }); } //===----------------------------------------------------------------------===// // MaxSIOp //===----------------------------------------------------------------------===// OpFoldResult MaxSIOp::fold(ArrayRef operands) { assert(operands.size() == 2 && "binary operation takes two operands"); // maxsi(x,x) -> x if (getLhs() == getRhs()) return getRhs(); APInt intValue; // maxsi(x,MAX_INT) -> MAX_INT if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isMaxSignedValue()) return getRhs(); // maxsi(x, MIN_INT) -> x if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isMinSignedValue()) return getLhs(); return constFoldBinaryOp(operands, [](const APInt &a, const APInt &b) { return llvm::APIntOps::smax(a, b); }); } //===----------------------------------------------------------------------===// // MaxUIOp //===----------------------------------------------------------------------===// OpFoldResult MaxUIOp::fold(ArrayRef operands) { assert(operands.size() == 2 && "binary operation takes two operands"); // maxui(x,x) -> x if (getLhs() == getRhs()) return getRhs(); APInt intValue; // maxui(x,MAX_INT) -> MAX_INT if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isMaxValue()) return getRhs(); // maxui(x, MIN_INT) -> x if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isMinValue()) return getLhs(); return constFoldBinaryOp(operands, [](const APInt &a, const APInt &b) { return llvm::APIntOps::umax(a, b); }); } //===----------------------------------------------------------------------===// // MinFOp //===----------------------------------------------------------------------===// OpFoldResult arith::MinFOp::fold(ArrayRef operands) { assert(operands.size() == 2 && "minf takes two operands"); // minf(x,x) -> x if (getLhs() == getRhs()) return getRhs(); // minf(x, +inf) -> x if (matchPattern(getRhs(), m_PosInfFloat())) return getLhs(); return constFoldBinaryOp( operands, [](const APFloat &a, const APFloat &b) { return llvm::minimum(a, b); }); } //===----------------------------------------------------------------------===// // MinSIOp //===----------------------------------------------------------------------===// OpFoldResult MinSIOp::fold(ArrayRef operands) { assert(operands.size() == 2 && "binary operation takes two operands"); // minsi(x,x) -> x if (getLhs() == getRhs()) return getRhs(); APInt intValue; // minsi(x,MIN_INT) -> MIN_INT if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isMinSignedValue()) return getRhs(); // minsi(x, MAX_INT) -> x if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isMaxSignedValue()) return getLhs(); return constFoldBinaryOp(operands, [](const APInt &a, const APInt &b) { return llvm::APIntOps::smin(a, b); }); } //===----------------------------------------------------------------------===// // MinUIOp //===----------------------------------------------------------------------===// OpFoldResult MinUIOp::fold(ArrayRef operands) { assert(operands.size() == 2 && "binary operation takes two operands"); // minui(x,x) -> x if (getLhs() == getRhs()) return getRhs(); APInt intValue; // minui(x,MIN_INT) -> MIN_INT if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isMinValue()) return getRhs(); // minui(x, MAX_INT) -> x if (matchPattern(getRhs(), m_ConstantInt(&intValue)) && intValue.isMaxValue()) return getLhs(); return constFoldBinaryOp(operands, [](const APInt &a, const APInt &b) { return llvm::APIntOps::umin(a, b); }); } //===----------------------------------------------------------------------===// // MulFOp //===----------------------------------------------------------------------===// OpFoldResult arith::MulFOp::fold(ArrayRef operands) { // mulf(x, 1) -> x if (matchPattern(getRhs(), m_OneFloat())) return getLhs(); return constFoldBinaryOp( operands, [](const APFloat &a, const APFloat &b) { return a * b; }); } void arith::MulFOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add(context); } //===----------------------------------------------------------------------===// // DivFOp //===----------------------------------------------------------------------===// OpFoldResult arith::DivFOp::fold(ArrayRef operands) { // divf(x, 1) -> x if (matchPattern(getRhs(), m_OneFloat())) return getLhs(); return constFoldBinaryOp( operands, [](const APFloat &a, const APFloat &b) { return a / b; }); } void arith::DivFOp::getCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context) { patterns.add(context); } //===----------------------------------------------------------------------===// // RemFOp //===----------------------------------------------------------------------===// OpFoldResult arith::RemFOp::fold(ArrayRef operands) { return constFoldBinaryOp(operands, [](const APFloat &a, const APFloat &b) { APFloat result(a); (void)result.remainder(b); return result; }); } //===----------------------------------------------------------------------===// // Utility functions for verifying cast ops //===----------------------------------------------------------------------===// template using type_list = std::tuple *; /// Returns a non-null type only if the provided type is one of the allowed /// types or one of the allowed shaped types of the allowed types. Returns the /// element type if a valid shaped type is provided. template static Type getUnderlyingType(Type type, type_list, type_list) { if (type.isa() && !type.isa()) return {}; auto underlyingType = getElementTypeOrSelf(type); if (!underlyingType.isa()) return {}; return underlyingType; } /// Get allowed underlying types for vectors and tensors. template static Type getTypeIfLike(Type type) { return getUnderlyingType(type, type_list(), type_list()); } /// Get allowed underlying types for vectors, tensors, and memrefs. template static Type getTypeIfLikeOrMemRef(Type type) { return getUnderlyingType(type, type_list(), type_list()); } static bool areValidCastInputsAndOutputs(TypeRange inputs, TypeRange outputs) { return inputs.size() == 1 && outputs.size() == 1 && succeeded(verifyCompatibleShapes(inputs.front(), outputs.front())); } //===----------------------------------------------------------------------===// // Verifiers for integer and floating point extension/truncation ops //===----------------------------------------------------------------------===// // Extend ops can only extend to a wider type. template static LogicalResult verifyExtOp(Op op) { Type srcType = getElementTypeOrSelf(op.getIn().getType()); Type dstType = getElementTypeOrSelf(op.getType()); if (srcType.cast().getWidth() >= dstType.cast().getWidth()) return op.emitError("result type ") << dstType << " must be wider than operand type " << srcType; return success(); } // Truncate ops can only truncate to a shorter type. template static LogicalResult verifyTruncateOp(Op op) { Type srcType = getElementTypeOrSelf(op.getIn().getType()); Type dstType = getElementTypeOrSelf(op.getType()); if (srcType.cast().getWidth() <= dstType.cast().getWidth()) return op.emitError("result type ") << dstType << " must be shorter than operand type " << srcType; return success(); } /// Validate a cast that changes the width of a type. template