[Mlir-commits] [mlir] [TOSA] Convert tosa.elementwise to linalg.elementwise (PR #224344)
Sayan Saha
llvmlistbot at llvm.org
Sat Sep 26 10:43:19 PDT 2026
================
@@ -12,21 +12,146 @@
#include "mlir/Conversion/TosaToLinalg/TosaToLinalg.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/Arith/Utils/Utils.h"
#include "mlir/Dialect/Linalg/IR/Linalg.h"
#include "mlir/Dialect/Math/IR/Math.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Dialect/Tosa/IR/TosaOps.h"
#include "mlir/Dialect/Tosa/Utils/ConversionUtils.h"
#include "mlir/Dialect/Utils/ReshapeOpsUtils.h"
+#include "mlir/IR/Matchers.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/SmallVectorExtras.h"
+#include "llvm/ADT/TypeSwitch.h"
+#include <optional>
#include <type_traits>
using namespace mlir;
using namespace mlir::tosa;
+// Return the linalg.elementwise kind that is semantically equivalent to the
+// given TOSA elementwise op for the provided element type, or std::nullopt if
+// the op has no direct linalg.elementwise counterpart. Only ops whose TOSA
+// lowering maps to a single linalg.elementwise function are handled; the more
+// involved lowerings (e.g. quantized negate, mul with shift, casts) are left to
+// the generic TosaToLinalg conversion.
+static std::optional<linalg::ElementwiseKind>
+getElementwiseKind(Operation *op, Type elementTy) {
+ using linalg::ElementwiseKind;
+ const bool isFloat = isa<FloatType>(elementTy);
+ const bool isSignlessInt = elementTy.isSignlessInteger();
+ const bool isBoolean = elementTy.isInteger(1);
+
+ // Unary ops. linalg.elementwise only defines these on floating point types.
+ if (isFloat) {
+ if (isa<tosa::AbsOp>(op))
+ return ElementwiseKind::abs;
+ if (isa<tosa::CeilOp>(op))
+ return ElementwiseKind::ceil;
+ if (isa<tosa::FloorOp>(op))
+ return ElementwiseKind::floor;
+ if (isa<tosa::ExpOp>(op))
+ return ElementwiseKind::exp;
+ if (isa<tosa::LogOp>(op))
+ return ElementwiseKind::log;
+ if (isa<tosa::RsqrtOp>(op))
+ return ElementwiseKind::rsqrt;
+ if (isa<tosa::SinOp>(op))
+ return ElementwiseKind::sin;
+ if (isa<tosa::CosOp>(op))
+ return ElementwiseKind::cos;
+ if (isa<tosa::TanhOp>(op))
+ return ElementwiseKind::tanh;
+ if (isa<tosa::ErfOp>(op))
+ return ElementwiseKind::erf;
+ if (isa<tosa::ReciprocalOp>(op))
+ return ElementwiseKind::reciprocal;
+ if (isa<tosa::PowOp>(op))
+ return ElementwiseKind::powf;
+ }
+
+ // Binary ops defined on both floating point and (non-boolean) integers.
+ if (isFloat || (isSignlessInt && !isBoolean)) {
+ if (isa<tosa::AddOp>(op))
+ return ElementwiseKind::add;
+ if (isa<tosa::SubOp>(op))
+ return ElementwiseKind::sub;
+ }
+
+ // Signed integer division.
+ if (isSignlessInt && !isBoolean && isa<tosa::IntDivOp>(op))
+ return ElementwiseKind::div;
+
+ // Multiply maps to a plain elementwise multiply only when no rescaling shift
+ // is requested (the shift must be a constant zero) and the inputs already
+ // share the result element type. The shifted/rescaled integer variant is left
+ // to the generic conversion.
+ if (auto mulOp = dyn_cast<tosa::MulOp>(op)) {
+ DenseElementsAttr shiftAttr;
+ const bool zeroShift =
+ matchPattern(mulOp.getShift(), m_Constant(&shiftAttr)) &&
+ shiftAttr.getValues<APInt>()[0].isZero();
+ const bool matchingTypes =
+ cast<ShapedType>(mulOp.getInput1().getType()).getElementType() ==
+ elementTy &&
+ cast<ShapedType>(mulOp.getInput2().getType()).getElementType() ==
+ elementTy;
+ if ((isFloat || (isSignlessInt && !isBoolean)) && zeroShift &&
+ matchingTypes)
+ return ElementwiseKind::mul;
+ }
+
+ // Negate maps to floating-point negation only when the zero points are zero.
+ // The quantized integer variant is left to the generic conversion.
+ if (auto negateOp = dyn_cast<tosa::NegateOp>(op)) {
+ if (isFloat) {
+ FailureOr<int64_t> inZp = negateOp.getInput1ZeroPoint();
+ FailureOr<int64_t> outZp = negateOp.getOutputZeroPoint();
+ if (succeeded(inZp) && succeeded(outZp) && *inZp == 0 && *outZp == 0)
+ return ElementwiseKind::negf;
+ }
+ }
+
+ // Maximum/minimum. The NaN "IGNORE" mode requires an additional compare and
+ // select that linalg.elementwise cannot express, so only the default
+ // "PROPAGATE" mode is handled for floating point types.
+ auto nanModeIsPropagate = [](auto tosaOp) {
+ return tosaOp.getNanMode() == NanPropagationMode::PROPAGATE;
+ };
+ if (isFloat || (isSignlessInt && !isBoolean)) {
+ if (auto maxOp = dyn_cast<tosa::MaximumOp>(op)) {
+ if (!isFloat || nanModeIsPropagate(maxOp))
+ return ElementwiseKind::max_signed;
+ }
+ if (auto minOp = dyn_cast<tosa::MinimumOp>(op)) {
+ if (!isFloat || nanModeIsPropagate(minOp))
+ return ElementwiseKind::min_signed;
+ }
+ }
+
+ // Select (ternary) is valid for any element type.
+ if (isa<tosa::SelectOp>(op))
+ return ElementwiseKind::select;
+
+ return std::nullopt;
+}
+
+bool mlir::tosa::isConvertibleToLinalgElementwise(Operation *op) {
+ if (op->getNumResults() != 1)
+ return false;
+
+ // TOSA has more strict requirements on operand shapes and broadcasting
+ // compared to generic linalg.elementwise operations. All shapes must
+ // have the same rank, so we don't need to check the operands types.
+ auto resultTy = dyn_cast<ShapedType>(op->getResult(0).getType());
+ if (!resultTy || !resultTy.hasRank())
----------------
sahas3 wrote:
```suggestion
auto resultTy = dyn_cast<RankedTensorType>(op->getResult(0).getType());
if (!resultTy)
```
https://github.com/llvm/llvm-project/pull/224344
More information about the Mlir-commits
mailing list