[Mlir-commits] [mlir] [mlir][tosa] Add row_gather operator (PR #202895)

Luke Hutton llvmlistbot at llvm.org
Mon Jun 15 07:16:00 PDT 2026


https://github.com/lhutton1 updated https://github.com/llvm/llvm-project/pull/202895

>From 815450e44f2cc62452a984e4dd702cffa5eb6aa9 Mon Sep 17 00:00:00 2001
From: Luke Hutton <luke.hutton at arm.com>
Date: Mon, 8 Jun 2026 17:59:08 +0100
Subject: [PATCH 1/2] [mlir][tosa] Add row_gather operator

Adds support for the row_gather operator defined by the TOSA
specification, see https://github.com/arm/tosa-specification/pull/60.

This includes:
- Operator definition
- Verification logic for the operator
- Output shape inference for the operator
- Validation checks to ensure compliance with the TOSA specification
  including profile compliance and level checks.
- Canonicalization to replace row_gather with gather when row_count is
  statically known to be 1.

It does not yet cover support for MXFP types. This will be added once
block scaled types are supported.

Change-Id: I9037f813a6c31b7dc03cfe3b5a191f5e3fce02d6
---
 .../Dialect/Tosa/IR/TosaComplianceData.h.inc  |  65 +++++++++++
 mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td  |  36 ++++++
 .../mlir/Dialect/Tosa/Utils/ConversionUtils.h |   5 +
 .../Dialect/Tosa/IR/TosaCanonicalizations.cpp |  21 ++++
 mlir/lib/Dialect/Tosa/IR/TosaOps.cpp          | 106 +++++++++++++++---
 .../Tosa/Transforms/TosaProfileCompliance.cpp |   9 ++
 .../Tosa/Transforms/TosaValidation.cpp        |  11 ++
 .../Dialect/Tosa/Utils/ConversionUtils.cpp    |  16 +++
 mlir/test/Dialect/Tosa/availability.mlir      |  10 ++
 mlir/test/Dialect/Tosa/canonicalize.mlir      |  30 +++++
 mlir/test/Dialect/Tosa/invalid_extension.mlir |   8 ++
 mlir/test/Dialect/Tosa/level_check.mlir       |   9 ++
 mlir/test/Dialect/Tosa/ops.mlir               |  47 ++++++++
 mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir |  39 +++++++
 .../tosa-validation-version-1p0-invalid.mlir  |   9 ++
 ...a-validation-version-1p1-pro-fp-valid.mlir |   9 ++
 .../tosa-validation-version-1p1-valid.mlir    |  27 +++++
 mlir/test/Dialect/Tosa/verifier.mlir          |  45 ++++++++
 18 files changed, 486 insertions(+), 16 deletions(-)

diff --git a/mlir/include/mlir/Dialect/Tosa/IR/TosaComplianceData.h.inc b/mlir/include/mlir/Dialect/Tosa/IR/TosaComplianceData.h.inc
index 50bb9f69c6242..f17475fae563d 100644
--- a/mlir/include/mlir/Dialect/Tosa/IR/TosaComplianceData.h.inc
+++ b/mlir/include/mlir/Dialect/Tosa/IR/TosaComplianceData.h.inc
@@ -396,6 +396,33 @@ profileComplianceMap = {
         {{i16T, i64T, i16T}, SpecificationVersion::V_1_1_DRAFT},
         {{i32T, i64T, i32T}, SpecificationVersion::V_1_1_DRAFT}},
        anyOf}}},
+    {"tosa.row_gather",
+     {{{Profile::pro_int},
+       {{{boolT, i32T, boolT}, SpecificationVersion::V_1_1_DRAFT},
+        {{i8T, i32T, i8T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i16T, i32T, i16T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i32T, i32T, i32T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i64T, i32T, i64T}, SpecificationVersion::V_1_1_DRAFT},
+        {{boolT, i64T, boolT}, SpecificationVersion::V_1_1_DRAFT},
+        {{i8T, i64T, i8T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i16T, i64T, i16T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i32T, i64T, i32T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i64T, i64T, i64T}, SpecificationVersion::V_1_1_DRAFT}}},
+      {{Profile::pro_fp},
+       {{{boolT, i32T, boolT}, SpecificationVersion::V_1_1_DRAFT},
+        {{i8T, i32T, i8T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i16T, i32T, i16T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i32T, i32T, i32T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i64T, i32T, i64T}, SpecificationVersion::V_1_1_DRAFT},
+        {{fp16T, i32T, fp16T}, SpecificationVersion::V_1_1_DRAFT},
+        {{fp32T, i32T, fp32T}, SpecificationVersion::V_1_1_DRAFT},
+        {{boolT, i64T, boolT}, SpecificationVersion::V_1_1_DRAFT},
+        {{i8T, i64T, i8T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i16T, i64T, i16T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i32T, i64T, i32T}, SpecificationVersion::V_1_1_DRAFT},
+        {{i64T, i64T, i64T}, SpecificationVersion::V_1_1_DRAFT},
+        {{fp16T, i64T, fp16T}, SpecificationVersion::V_1_1_DRAFT},
+        {{fp32T, i64T, fp32T}, SpecificationVersion::V_1_1_DRAFT}}}}},
     {"tosa.row_gather_block_scaled",
      {{{Profile::pro_int},
        {{{i8T, i32T, i32T, i8T}, SpecificationVersion::V_1_1_DRAFT},
@@ -909,6 +936,44 @@ extensionComplianceMap = {
       {{Extension::bf16, Extension::int64},
        {{{bf16T, i64T, bf16T}, SpecificationVersion::V_1_1_DRAFT}},
        allOf}}},
+    {"tosa.row_gather",
+     {
+         {{Extension::int16},
+          {{{i16T, i32T, i16T}, SpecificationVersion::V_1_1_DRAFT},
+           {{i16T, i32T, i16T}, SpecificationVersion::V_1_1_DRAFT}}},
+         {{Extension::int64},
+          {{{i64T, i32T, i64T}, SpecificationVersion::V_1_1_DRAFT},
+           {{boolT, i64T, boolT}, SpecificationVersion::V_1_1_DRAFT},
+           {{i8T, i64T, i8T}, SpecificationVersion::V_1_1_DRAFT},
+           {{i32T, i64T, i32T}, SpecificationVersion::V_1_1_DRAFT},
+           {{i64T, i64T, i64T}, SpecificationVersion::V_1_1_DRAFT},
+           {{i64T, i32T, i64T}, SpecificationVersion::V_1_1_DRAFT},
+           {{boolT, i64T, boolT}, SpecificationVersion::V_1_1_DRAFT},
+           {{i8T, i64T, i8T}, SpecificationVersion::V_1_1_DRAFT},
+           {{i32T, i64T, i32T}, SpecificationVersion::V_1_1_DRAFT},
+           {{i64T, i64T, i64T}, SpecificationVersion::V_1_1_DRAFT},
+           {{fp16T, i64T, fp16T}, SpecificationVersion::V_1_1_DRAFT},
+           {{fp32T, i64T, fp32T}, SpecificationVersion::V_1_1_DRAFT}}},
+         {{Extension::int16, Extension::int64},
+          {{{i16T, i64T, i16T}, SpecificationVersion::V_1_1_DRAFT},
+           {{i16T, i64T, i16T}, SpecificationVersion::V_1_1_DRAFT}},
+          allOf},
+         {{Extension::fp8e4m3},
+          {{{fp8e4m3T, i32T, fp8e4m3T}, SpecificationVersion::V_1_1_DRAFT}}},
+         {{Extension::fp8e5m2},
+          {{{fp8e5m2T, i32T, fp8e5m2T}, SpecificationVersion::V_1_1_DRAFT}}},
+         {{Extension::bf16},
+          {{{bf16T, i32T, bf16T}, SpecificationVersion::V_1_1_DRAFT}}},
+         {{Extension::fp8e4m3, Extension::int64},
+          {{{fp8e4m3T, i64T, fp8e4m3T}, SpecificationVersion::V_1_1_DRAFT}},
+          allOf},
+         {{Extension::fp8e5m2, Extension::int64},
+          {{{fp8e5m2T, i64T, fp8e5m2T}, SpecificationVersion::V_1_1_DRAFT}},
+          allOf},
+         {{Extension::bf16, Extension::int64},
+          {{{bf16T, i64T, bf16T}, SpecificationVersion::V_1_1_DRAFT}},
+          allOf},
+     }},
     {"tosa.row_gather_block_scaled",
      {{{Extension::fp8e4m3},
        {{{fp8e4m3T, i32T, i32T, fp8e4m3T}, SpecificationVersion::V_1_1_DRAFT}}},
diff --git a/mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td b/mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td
index a99fb2fcae547..ac98354be9f5e 100644
--- a/mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td
+++ b/mlir/include/mlir/Dialect/Tosa/IR/TosaOps.td
@@ -2531,6 +2531,42 @@ def Tosa_GatherOp : Tosa_InferShapedTypeOp<"gather", [NoMemoryEffect]> {
       "operands attr-dict `:` functional-type(operands, results)";
 }
 
+//===----------------------------------------------------------------------===//
+// Operator: row_gather
+//===----------------------------------------------------------------------===//
+def Tosa_RowGatherOp : Tosa_InferShapedTypeOp<"row_gather", [NoMemoryEffect]> {
+  let summary = "Row gather operation.";
+
+  let description = [{
+    Generate a tensor based on the input indices and row_count. The number of
+    consecutive rows gathered for each index is specified in row_count. N is 
+    the number of batches, W is the number of indices in each batch, K is
+    the range of each index, and C is the number of data channels for each
+    index. The values tensor has shape [N, K, C] and the output tensor has shape
+    [N, W * row_count, C].
+
+    This operation is not pure. Undefined behaviour may occur if the specified
+    indices are out of range.
+  }];
+
+  let arguments = (ins Tosa_Tensor3D:$values, Tosa_IndexTensor2D:$indices,
+      Tosa_ScalarInt32Tensor:$row_count);
+
+  let results = (outs Tosa_Tensor3D:$output);
+
+  list<Availability> availability = [Profile<[Tosa_PRO_INT, Tosa_PRO_FP]>,
+                                     Extension<[Tosa_EXT_FP8E4M3,
+                                                Tosa_EXT_FP8E5M2, Tosa_EXT_BF16,
+                                                Tosa_EXT_INT64]>,
+  ];
+
+  let hasCanonicalizer = 1;
+  let hasVerifier = 1;
+
+  let assemblyFormat =
+      "operands attr-dict `:` functional-type(operands, results)";
+}
+
 //===----------------------------------------------------------------------===//
 // Operator: row_gather_block_scaled
 //===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h b/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h
index 3a48608e7fd94..df4dabc2afda8 100644
--- a/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h
+++ b/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h
@@ -271,6 +271,11 @@ std::optional<ArrayRef<T>> tryGetDenseResourceValues(ElementsAttr attr) {
   return std::nullopt;
 }
 
+// returns the value of a constant scalar int tensor, or failure if
+// the value cannot be extracted
+template <typename T>
+FailureOr<T> getConstantScalarIntValue(Value val);
+
 } // namespace tosa
 } // namespace mlir
 
diff --git a/mlir/lib/Dialect/Tosa/IR/TosaCanonicalizations.cpp b/mlir/lib/Dialect/Tosa/IR/TosaCanonicalizations.cpp
index 63d40ed4a95a5..20f9d002f5482 100644
--- a/mlir/lib/Dialect/Tosa/IR/TosaCanonicalizations.cpp
+++ b/mlir/lib/Dialect/Tosa/IR/TosaCanonicalizations.cpp
@@ -1238,6 +1238,27 @@ void CastToBlockScaledOp::getCanonicalizationPatterns(
   results.add<CancellingBlockScaledCastsOptimization>(context);
 }
 
+struct RowGatherToGather : public OpRewritePattern<tosa::RowGatherOp> {
+  using OpRewritePattern<tosa::RowGatherOp>::OpRewritePattern;
+
+  LogicalResult matchAndRewrite(tosa::RowGatherOp op,
+                                PatternRewriter &rewriter) const override {
+    const FailureOr<int32_t> rowCount =
+        mlir::tosa::getConstantScalarIntValue<int32_t>(op.getRowCount());
+    if (failed(rowCount) || rowCount.value() != 1)
+      return failure();
+
+    rewriter.replaceOpWithNewOp<tosa::GatherOp>(
+        op, op.getOutput().getType(), op.getValues(), op.getIndices());
+    return success();
+  }
+};
+
+void RowGatherOp::getCanonicalizationPatterns(RewritePatternSet &results,
+                                              MLIRContext *context) {
+  results.add<RowGatherToGather>(context);
+}
+
 //===----------------------------------------------------------------------===//
 // Operator Folders.
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
index f05399cf6b00b..c10fa69e3a175 100644
--- a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
+++ b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
@@ -3127,18 +3127,6 @@ static FailureOr<int64_t> getZeroPoint(Value val, bool signExtend) {
   return -1;
 }
 
-static FailureOr<int64_t> getConstantScalarIntValue(Value val) {
-  ElementsAttr attr;
-  if (!matchPattern(val, m_Constant(&attr)))
-    return failure();
-
-  if (!llvm::isa<IntegerType>(attr.getElementType()) ||
-      attr.getNumElements() != 1)
-    return failure();
-
-  return attr.getValues<APInt>()[0].getSExtValue();
-}
-
 template <typename T>
 static LogicalResult verifyZeroPoint(T op, Value val, const int64_t &zp,
                                      const std::string &operand) {
@@ -3387,6 +3375,37 @@ LogicalResult tosa::GatherOp::inferReturnTypeComponents(
   return success();
 }
 
+LogicalResult tosa::RowGatherOp::inferReturnTypeComponents(
+    MLIRContext *context, ::std::optional<Location> location,
+    RowGatherOp::Adaptor adaptor,
+    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
+  llvm::SmallVector<int64_t> outputShape;
+  outputShape.resize(3, ShapedType::kDynamic);
+
+  const ShapeAdaptor valuesShape(adaptor.getValues().getType());
+  if (valuesShape.hasRank()) {
+    outputShape[0] = valuesShape.getDimSize(0);
+    outputShape[2] = valuesShape.getDimSize(2);
+  }
+
+  const ShapeAdaptor indicesShape(adaptor.getIndices().getType());
+  if (indicesShape.hasRank()) {
+    if (outputShape[0] == ShapedType::kDynamic)
+      outputShape[0] = indicesShape.getDimSize(0);
+
+    const FailureOr<int32_t> maybeRowCount =
+        getConstantScalarIntValue<int32_t>(adaptor.getRowCount());
+    if (succeeded(maybeRowCount) && maybeRowCount.value() > 0) {
+      const int64_t indicesW = indicesShape.getDimSize(1);
+      if (ShapedType::isStatic(indicesW))
+        outputShape[1] = indicesW * maybeRowCount.value();
+    }
+  }
+
+  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
+  return success();
+}
+
 LogicalResult tosa::RowGatherBlockScaledOp::inferReturnTypeComponents(
     MLIRContext *context, ::std::optional<Location> location,
     RowGatherBlockScaledOp::Adaptor adaptor,
@@ -3407,7 +3426,8 @@ LogicalResult tosa::RowGatherBlockScaledOp::inferReturnTypeComponents(
     if (dataShape[0] == ShapedType::kDynamic)
       dataShape[0] = indicesShape.getDimSize(0);
 
-    if (auto rowCount = getConstantScalarIntValue(adaptor.getRowCount());
+    if (auto rowCount =
+            getConstantScalarIntValue<int32_t>(adaptor.getRowCount());
         succeeded(rowCount) && rowCount.value() > 0) {
       const int64_t indicesW = indicesShape.getDimSize(1);
       if (ShapedType::isStatic(indicesW))
@@ -3478,6 +3498,60 @@ LogicalResult tosa::GatherOp::verify() {
   return success();
 }
 
+LogicalResult tosa::RowGatherOp::verify() {
+  if (failed(verifySameElementTypes(*this, /* inType = */ getValues().getType(),
+                                    /* outType = */ getOutput().getType())))
+    return failure();
+
+  const FailureOr<int32_t> maybeRowCount =
+      getConstantScalarIntValue<int32_t>(getRowCount());
+  if (succeeded(maybeRowCount) && maybeRowCount.value() <= 0)
+    return emitOpError() << "requires row_count to be > 0, got "
+                         << maybeRowCount.value();
+
+  int64_t n = ShapedType::kDynamic;
+  int64_t c = ShapedType::kDynamic;
+  int64_t w = ShapedType::kDynamic;
+
+  const ShapeAdaptor valuesShape(getValues().getType());
+  if (valuesShape.hasRank()) {
+    n = valuesShape.getDimSize(0);
+    c = valuesShape.getDimSize(2);
+  }
+
+  const ShapeAdaptor indicesShape(getIndices().getType());
+  if (indicesShape.hasRank()) {
+    if (failed(tryUpdateDimOrFailure(*this, n, indicesShape.getDimSize(0),
+                                     "indices", "batch")))
+      return failure();
+    w = indicesShape.getDimSize(1);
+  }
+
+  const ShapeAdaptor outputShape(getOutput().getType());
+  if (outputShape.hasRank()) {
+    if (failed(tryUpdateDimOrFailure(*this, n, outputShape.getDimSize(0),
+                                     "output", "batch")) ||
+        failed(tryUpdateDimOrFailure(*this, c, outputShape.getDimSize(2),
+                                     "output", "channels")))
+      return failure();
+
+    const FailureOr<int32_t> maybeRowCount =
+        getConstantScalarIntValue<int32_t>(getRowCount());
+    if (succeeded(maybeRowCount) && maybeRowCount.value() > 0 &&
+        ShapedType::isStatic(w)) {
+      const int64_t expectedOutputRows = w * maybeRowCount.value();
+      if (ShapedType::isStatic(outputShape.getDimSize(1)) &&
+          outputShape.getDimSize(1) != expectedOutputRows)
+        return emitOpError()
+               << "requires output dimension to be equal to "
+                  "indices[1]*row_count ("
+               << expectedOutputRows << "), got " << outputShape.getDimSize(1);
+    }
+  }
+
+  return success();
+}
+
 LogicalResult tosa::RowGatherBlockScaledOp::verify() {
   const OperandRange values = getValues();
   const ResultRange output = getOutput();
@@ -3511,7 +3585,7 @@ LogicalResult tosa::RowGatherBlockScaledOp::verify() {
                                 "values[1]", "output[1]")))
     return failure();
 
-  if (auto rowCount = getConstantScalarIntValue(getRowCount());
+  if (auto rowCount = getConstantScalarIntValue<int32_t>(getRowCount());
       succeeded(rowCount) && rowCount.value() <= 0)
     return emitOpError() << "requires row_count to be > 0, got "
                          << rowCount.value();
@@ -3550,7 +3624,7 @@ LogicalResult tosa::RowGatherBlockScaledOp::verify() {
                                      "output[0]", "channels")))
       return failure();
 
-    if (auto rowCount = getConstantScalarIntValue(getRowCount());
+    if (auto rowCount = getConstantScalarIntValue<int32_t>(getRowCount());
         succeeded(rowCount) && rowCount.value() > 0 &&
         ShapedType::isStatic(w)) {
       const int64_t expectedOutputRows = w * rowCount.value();
@@ -3579,7 +3653,7 @@ LogicalResult tosa::RowGatherBlockScaledOp::verify() {
                                        "output[1]", "batch")))
         return failure();
 
-      if (auto rowCount = getConstantScalarIntValue(getRowCount());
+      if (auto rowCount = getConstantScalarIntValue<int32_t>(getRowCount());
           succeeded(rowCount) && rowCount.value() > 0 &&
           ShapedType::isStatic(w)) {
         const int64_t expectedOutputRows = w * rowCount.value();
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaProfileCompliance.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaProfileCompliance.cpp
index 9511d4da89dbd..1bf92ff562a84 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaProfileCompliance.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaProfileCompliance.cpp
@@ -185,6 +185,14 @@ LogicalResult ProfileInfoDepot::populateProfileInfo(tosa::GatherOp op) {
   return success();
 }
 
+template <>
+LogicalResult ProfileInfoDepot::populateProfileInfo(tosa::RowGatherOp op) {
+  addValue(op.getValues());
+  addValue(op.getIndices());
+  addValue(op.getOutput());
+  return success();
+}
+
 template <>
 LogicalResult
 ProfileInfoDepot::populateProfileInfo(tosa::RowGatherBlockScaledOp op) {
@@ -300,6 +308,7 @@ LogicalResult ProfileInfoDepot::populatationDispatch(Operation *op) {
   POPULATE_PROFILE_INFO_CUSTOM(Tile)
   POPULATE_PROFILE_INFO_CUSTOM(Transpose)
   POPULATE_PROFILE_INFO_CUSTOM(Gather)
+  POPULATE_PROFILE_INFO_CUSTOM(RowGather)
   POPULATE_PROFILE_INFO_CUSTOM(RowGatherBlockScaled)
   POPULATE_PROFILE_INFO_CUSTOM(Scatter)
   POPULATE_PROFILE_INFO_CUSTOM(Resize)
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp
index 34ac84d237f45..4a29e5327cc55 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp
@@ -125,6 +125,15 @@ checkConstantOperandRowGatherBlockScaled(Operation *op, const TargetEnv &env) {
   return success();
 }
 
+static LogicalResult checkConstantOperandRowGather(Operation *op,
+                                                   const TargetEnv &env) {
+  if (!env.allows(Extension::dynamic) && isa<tosa::RowGatherOp>(op)) {
+    // Check 'row_count'
+    return checkConstantOperands(op, {2});
+  }
+  return success();
+}
+
 static LogicalResult checkConstantOperandAvgPool2d(Operation *op,
                                                    const TargetEnv &env) {
   if (!env.allows(Extension::dynamic) && isa<tosa::AvgPool2dOp>(op)) {
@@ -210,6 +219,7 @@ struct TosaValidation : public tosa::impl::TosaValidationBase<TosaValidation> {
     constCheckers.emplace_back(
         checkConstantOperandConvOps<tosa::TransposeConv2DOp>);
     constCheckers.emplace_back(checkConstantOperandMatMul);
+    constCheckers.emplace_back(checkConstantOperandRowGather);
     constCheckers.emplace_back(checkConstantOperandRowGatherBlockScaled);
     constCheckers.emplace_back(checkConstantOperandAvgPool2d);
     constCheckers.emplace_back(checkConstantOperandAvgPool2dAdaptive);
@@ -834,6 +844,7 @@ LogicalResult TosaValidation::levelCheckRanksAndSizes(Operation *op) {
   CHECK_SIZES(RFFT2d);
   // Scatter/Gather Operators
   CHECK_SIZES(Gather);
+  CHECK_SIZES(RowGather);
   CHECK_SIZES(Scatter);
   // Image Operators
   CHECK_SIZES(Resize);
diff --git a/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp b/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp
index dd36083e910b4..e0d4b3470c981 100644
--- a/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp
+++ b/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp
@@ -248,3 +248,19 @@ bool mlir::tosa::hasUniqueConstantScatterIndices(
 
   return true;
 }
+
+template <typename T>
+FailureOr<T> mlir::tosa::getConstantScalarIntValue(Value val) {
+  ElementsAttr attr;
+  if (!matchPattern(val, m_Constant(&attr)))
+    return failure();
+
+  if (!llvm::isa<IntegerType>(attr.getElementType()) ||
+      attr.getNumElements() != 1)
+    return failure();
+
+  return attr.getValues<T>()[0];
+}
+
+template FailureOr<int32_t>
+mlir::tosa::getConstantScalarIntValue<int32_t>(Value val);
diff --git a/mlir/test/Dialect/Tosa/availability.mlir b/mlir/test/Dialect/Tosa/availability.mlir
index 81294f2c0c308..d2a156e028fc8 100644
--- a/mlir/test/Dialect/Tosa/availability.mlir
+++ b/mlir/test/Dialect/Tosa/availability.mlir
@@ -595,6 +595,16 @@ func.func @test_gather(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) ->
   return %0 : tensor<13x26x3xf32>
 }
 
+// -----
+// CHECK-LABEL: test_row_gather
+func.func @test_row_gather(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // CHECK: profiles: [ [pro_int, pro_fp] ]
+  // CHECK: extensions: [ [fp8e4m3, fp8e5m2, bf16, int64] ]
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xf32>
+  return %0 : tensor<13x52x3xf32>
+}
+
 // -----
 // CHECK-LABEL: row_gather_block_scaled
 func.func @test_row_gather_block_scaled(%arg0: tensor<13x21x32xf4E2M1FN>, %arg1: tensor<13x21x1xf8E8M0FNU>, %arg2: tensor<13x26xi32>) -> (tensor<13x52x32xf4E2M1FN>, tensor<13x52x1xf8E8M0FNU>) {
diff --git a/mlir/test/Dialect/Tosa/canonicalize.mlir b/mlir/test/Dialect/Tosa/canonicalize.mlir
index 2cd040f056db8..3333e562e313b 100644
--- a/mlir/test/Dialect/Tosa/canonicalize.mlir
+++ b/mlir/test/Dialect/Tosa/canonicalize.mlir
@@ -18,6 +18,36 @@ func.func @test_argmax_fold_i64_index(%arg0: tensor<1xi8>) -> tensor<i64> {
 
 // -----
 
+// CHECK-LABEL: @row_gather_row_count_one_to_gather
+func.func @row_gather_row_count_one_to_gather(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x26x3xf32> {
+  %row_count = "tosa.const"() {values = dense<1> : tensor<1xi32>} : () -> tensor<1xi32>
+  // CHECK-NOT: tosa.row_gather
+  // CHECK: tosa.gather %arg0, %arg1 : (tensor<13x21x3xf32>, tensor<13x26xi32>) -> tensor<13x26x3xf32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x26x3xf32>
+  return %0 : tensor<13x26x3xf32>
+}
+
+// -----
+
+// CHECK-LABEL: @row_gather_row_count_two_no_fold
+func.func @row_gather_row_count_two_no_fold(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // CHECK: tosa.row_gather %arg0, %arg1, %{{.*}} : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xf32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xf32>
+  return %0 : tensor<13x52x3xf32>
+}
+
+// -----
+
+// CHECK-LABEL: @row_gather_non_const_row_count_no_fold
+func.func @row_gather_non_const_row_count_no_fold(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>, %row_count: tensor<1xi32>) -> tensor<13x?x3xf32> {
+  // CHECK: tosa.row_gather %arg0, %arg1, %arg2 : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x?x3xf32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x?x3xf32>
+  return %0 : tensor<13x?x3xf32>
+}
+
+// -----
+
 // CHECK-LABEL: @pad_wh_avg_pool2d_nofold
 func.func @pad_wh_avg_pool2d_nofold(%input: tensor<1x10x8x3xf32>) -> tensor<1x6x5x3xf32> {
   // CHECK: tosa.pad
diff --git a/mlir/test/Dialect/Tosa/invalid_extension.mlir b/mlir/test/Dialect/Tosa/invalid_extension.mlir
index 295d7172bc2c4..cd82e314e3b6e 100644
--- a/mlir/test/Dialect/Tosa/invalid_extension.mlir
+++ b/mlir/test/Dialect/Tosa/invalid_extension.mlir
@@ -249,6 +249,14 @@ func.func @test_gather(%arg0: tensor<13x21x3xbf16>, %arg1: tensor<13x26xi32>) ->
   return %0 : tensor<13x26x3xbf16>
 }
 
+// -----
+func.func @test_row_gather(%arg0: tensor<13x21x3xbf16>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xbf16> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op illegal: requires any of [bf16] profiles/extensions to be specified in the target environment}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xbf16>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xbf16>
+  return %0 : tensor<13x52x3xbf16>
+}
+
 // -----
 func.func @test_row_gather_block_scaled(%arg0: tensor<13x21x32xf4E2M1FN>, %arg1: tensor<13x21x1xf8E8M0FNU>, %arg2: tensor<13x26xi32>) -> (tensor<13x52x32xf4E2M1FN>, tensor<13x52x1xf8E8M0FNU>) {
   %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
diff --git a/mlir/test/Dialect/Tosa/level_check.mlir b/mlir/test/Dialect/Tosa/level_check.mlir
index 3042f5a23e9e6..85b5d002434bc 100644
--- a/mlir/test/Dialect/Tosa/level_check.mlir
+++ b/mlir/test/Dialect/Tosa/level_check.mlir
@@ -1286,6 +1286,15 @@ func.func @test_gather_tensor_size_invalid(%arg0: tensor<536870912x21x3xf32>, %a
 
 // -----
 
+func.func @test_row_gather_tensor_size_invalid(%arg0: tensor<536870912x21x3xf32>, %arg1: tensor<536870912x26xi32>) -> tensor<536870912x52x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op failed level check: operand tensor size (in bytes) <= (1 << MAX_LOG2_SIZE - 1)}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<536870912x21x3xf32>, tensor<536870912x26xi32>, tensor<1xi32>) -> tensor<536870912x52x3xf32>
+  return %0 : tensor<536870912x52x3xf32>
+}
+
+// -----
+
 func.func @test_custom_tensor_size_invalid(%arg0: tensor<536870912xi32>) -> tensor<536870912xi32> {
   // expected-error at +1 {{'tosa.custom' op failed level check: operand tensor size (in bytes) <= (1 << MAX_LOG2_SIZE - 1)}}
   %0 = tosa.custom %arg0 {operator_name="custom_test", domain_name="tosa.mlir_test", implementation_attrs="" } : (tensor<536870912xi32>) -> (tensor<536870912xi32>)
diff --git a/mlir/test/Dialect/Tosa/ops.mlir b/mlir/test/Dialect/Tosa/ops.mlir
index c7f4c67072d06..50f28df37a169 100644
--- a/mlir/test/Dialect/Tosa/ops.mlir
+++ b/mlir/test/Dialect/Tosa/ops.mlir
@@ -961,6 +961,45 @@ func.func @test_gather_unranked_indices(%arg0: tensor<13x21x3xf32>, %arg1: tenso
   return %0 : tensor<13x26x3xf32>
 }
 
+// -----
+// CHECK-LABEL: test_row_gather
+func.func @test_row_gather(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xf32>
+  return %0 : tensor<13x52x3xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_int64
+func.func @test_row_gather_int64(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi64>) -> tensor<13x52x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi64>, tensor<1xi32>) -> tensor<13x52x3xf32>
+  return %0 : tensor<13x52x3xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_non_const_row_count
+func.func @test_row_gather_non_const_row_count(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>, %row_count: tensor<1xi32>) -> tensor<13x?x3xf32> {
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x?x3xf32>
+  return %0 : tensor<13x?x3xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_unranked_indices
+func.func @test_row_gather_unranked_indices(%arg0: tensor<13x21x3xf32>, %arg1: tensor<*xi32>) -> tensor<13x?x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<*xi32>, tensor<1xi32>) -> tensor<13x?x3xf32>
+  return %0 : tensor<13x?x3xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_fully_unranked
+func.func @test_row_gather_fully_unranked(%arg0: tensor<*xf32>, %arg1: tensor<*xi32>) -> tensor<*xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<*xf32>, tensor<*xi32>, tensor<1xi32>) -> tensor<*xf32>
+  return %0 : tensor<*xf32>
+}
+
 // -----
 // CHECK-LABEL: test_row_gather_block_scaled
 func.func @test_row_gather_block_scaled(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xf32> {
@@ -1284,6 +1323,14 @@ func.func @test_concat_f8E5M2(%arg0: tensor<13x21x3xf8E5M2>, %arg1: tensor<13x21
   return %0 : tensor<26x21x3xf8E5M2>
 }
 
+// -----
+// CHECK-LABEL: row_gather_f8E5M2
+func.func @test_row_gather_f8E5M2(%arg0: tensor<13x21x3xf8E5M2>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xf8E5M2> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf8E5M2>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xf8E5M2>
+  return %0 : tensor<13x52x3xf8E5M2>
+}
+
 // -----
 // CHECK-LABEL: pad_f8E5M2
 func.func @test_pad_f8E5M2(%arg0: tensor<13x21x3xf8E5M2>) -> tensor<13x21x3xf8E5M2> {
diff --git a/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir b/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir
index db488baef3571..5499da8c6e135 100644
--- a/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir
@@ -771,6 +771,45 @@ func.func @gather_minimum_info(%arg0 : tensor<3x?x5xi32>, %arg1 : tensor<?x6xi32
 
 // -----
 
+// CHECK-LABEL: @row_gather_static
+func.func @row_gather_static(%arg0 : tensor<3x4x5xi32>, %arg1 : tensor<3x6xi32>) {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // CHECK: tosa.row_gather %arg0, %arg1, %[[ROW_COUNT:.+]] : (tensor<3x4x5xi32>, tensor<3x6xi32>, tensor<1xi32>) -> tensor<3x12x5xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<3x4x5xi32>, tensor<3x6xi32>, tensor<1xi32>) -> tensor<?x?x?xi32>
+  return
+}
+
+// -----
+
+// CHECK-LABEL: @row_gather_minimum_info
+func.func @row_gather_minimum_info(%arg0 : tensor<3x?x5xi32>, %arg1 : tensor<?x6xi32>) {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // CHECK: tosa.row_gather %arg0, %arg1, %[[ROW_COUNT:.+]] : (tensor<3x?x5xi32>, tensor<?x6xi32>, tensor<1xi32>) -> tensor<3x12x5xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<3x?x5xi32>, tensor<?x6xi32>, tensor<1xi32>) -> tensor<?x?x?xi32>
+  return
+}
+
+// -----
+
+// CHECK-LABEL: @row_gather_nonconstant_row_count
+func.func @row_gather_nonconstant_row_count(%arg0 : tensor<3x4x5xi32>, %arg1 : tensor<3x6xi32>, %row_count : tensor<1xi32>) {
+  // CHECK: tosa.row_gather %arg0, %arg1, %arg2 : (tensor<3x4x5xi32>, tensor<3x6xi32>, tensor<1xi32>) -> tensor<3x?x5xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<3x4x5xi32>, tensor<3x6xi32>, tensor<1xi32>) -> tensor<?x?x?xi32>
+  return
+}
+
+// -----
+
+// CHECK-LABEL: @row_gather_row_count_one
+func.func @row_gather_row_count_one(%arg0 : tensor<3x4x5xi32>, %arg1 : tensor<3x6xi32>) {
+  %row_count = "tosa.const"() {values = dense<1> : tensor<1xi32>} : () -> tensor<1xi32>
+  // CHECK: tosa.row_gather %arg0, %arg1, %[[ROW_COUNT:.+]] : (tensor<3x4x5xi32>, tensor<3x6xi32>, tensor<1xi32>) -> tensor<3x6x5xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<3x4x5xi32>, tensor<3x6xi32>, tensor<1xi32>) -> tensor<?x?x?xi32>
+  return
+}
+
+// -----
+
 // CHECK-LABEL: @row_gather_block_scaled_static
 func.func @row_gather_block_scaled_static(%arg0 : tensor<3x4x5xi32>, %arg1 : tensor<3x6xi32>) {
   %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
diff --git a/mlir/test/Dialect/Tosa/tosa-validation-version-1p0-invalid.mlir b/mlir/test/Dialect/Tosa/tosa-validation-version-1p0-invalid.mlir
index 7ff883e8e5431..3dc10eff15a62 100644
--- a/mlir/test/Dialect/Tosa/tosa-validation-version-1p0-invalid.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-validation-version-1p0-invalid.mlir
@@ -78,6 +78,15 @@ func.func @test_gather_bool_i32(%arg0: tensor<13x21x3xi1>, %arg1: tensor<13x26xi
 
 // -----
 
+func.func @test_row_gather_i8_i32(%arg0: tensor<13x21x3xi8>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xi8> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op illegal: requires specification version compatible with 1.1.draft (got 1.0) OR requires specification version compatible with 1.1.draft (got 1.0) to be specified in the target environment}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xi8>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xi8>
+  return %0 : tensor<13x52x3xi8>
+}
+
+// -----
+
 func.func @test_row_gather_block_scaled_i8_i32(%arg0: tensor<13x21x3xi8>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xi8> {
   %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
   // expected-error at +1 {{'tosa.row_gather_block_scaled' op illegal: requires specification version compatible with 1.1.draft (got 1.0) OR requires specification version compatible with 1.1.draft (got 1.0) to be specified in the target environment}}
diff --git a/mlir/test/Dialect/Tosa/tosa-validation-version-1p1-pro-fp-valid.mlir b/mlir/test/Dialect/Tosa/tosa-validation-version-1p1-pro-fp-valid.mlir
index 57802fd147f23..408ee86690154 100644
--- a/mlir/test/Dialect/Tosa/tosa-validation-version-1p1-pro-fp-valid.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-validation-version-1p1-pro-fp-valid.mlir
@@ -16,6 +16,15 @@ func.func @test_scatter_i8_i32(%input: tensor<13x27x3xi8>, %indices: tensor<13x2
 
 // -----
 
+// CHECK-LABEL: test_row_gather_i8_i32
+func.func @test_row_gather_i8_i32(%input: tensor<13x27x3xi8>, %indices: tensor<13x26xi32>) -> tensor<13x52x3xi8> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %gather = tosa.row_gather %input, %indices, %row_count : (tensor<13x27x3xi8>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xi8>
+  return %gather : tensor<13x52x3xi8>
+}
+
+// -----
+
 // CHECK-LABEL: test_row_gather_block_scaled_i8_i32
 func.func @test_row_gather_block_scaled_i8_i32(%input: tensor<13x27x3xi8>, %indices: tensor<13x26xi32>) -> tensor<13x52x3xi8> {
   %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
diff --git a/mlir/test/Dialect/Tosa/tosa-validation-version-1p1-valid.mlir b/mlir/test/Dialect/Tosa/tosa-validation-version-1p1-valid.mlir
index 1bac8bacbaf40..584dc4e9aa9ed 100644
--- a/mlir/test/Dialect/Tosa/tosa-validation-version-1p1-valid.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-validation-version-1p1-valid.mlir
@@ -223,6 +223,33 @@ func.func @test_gather_i8_i32_indices(%arg0: tensor<13x21x3xi8>, %arg1: tensor<1
 
 // -----
 
+// CHECK-LABEL: test_row_gather_i8_i32_indices
+func.func @test_row_gather_i8_i32_indices(%arg0: tensor<13x21x3xi8>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xi8> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xi8>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xi8>
+  return %0 : tensor<13x52x3xi8>
+}
+
+// -----
+
+// CHECK-LABEL: test_row_gather_i8_i64_indices
+func.func @test_row_gather_i8_i64_indices(%arg0: tensor<13x21x3xi8>, %arg1: tensor<13x26xi64>) -> tensor<13x52x3xi8> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xi8>, tensor<13x26xi64>, tensor<1xi32>) -> tensor<13x52x3xi8>
+  return %0 : tensor<13x52x3xi8>
+}
+
+// -----
+
+// CHECK-LABEL: test_row_gather_f8e5m2_i32_indices
+func.func @test_row_gather_f8e5m2_i32_indices(%arg0: tensor<13x21x3xf8E5M2>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xf8E5M2> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf8E5M2>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x3xf8E5M2>
+  return %0 : tensor<13x52x3xf8E5M2>
+}
+
+// -----
+
 // CHECK-LABEL: test_row_gather_block_scaled_i8_i32_indices
 func.func @test_row_gather_block_scaled_i8_i32_indices(%arg0: tensor<13x21x3xi8>, %arg1: tensor<13x26xi32>) -> tensor<13x52x3xi8> {
   %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
diff --git a/mlir/test/Dialect/Tosa/verifier.mlir b/mlir/test/Dialect/Tosa/verifier.mlir
index 9b5faf575971b..c9a881845a06a 100644
--- a/mlir/test/Dialect/Tosa/verifier.mlir
+++ b/mlir/test/Dialect/Tosa/verifier.mlir
@@ -499,6 +499,51 @@ func.func @test_gather_invalid_out_C(%arg0: tensor<13x21x3xf32>, %arg1: tensor<1
   return %0 : tensor<13x26x8xf32>
 }
 
+// -----
+// CHECK-LABEL: @test_row_gather_invalid_indices_N
+func.func @test_row_gather_invalid_indices_N(%arg0: tensor<13x21x3xf32>, %arg1: tensor<12x26xi32>) -> tensor<13x52x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op expected batch of indices to match size 13, got 12}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<12x26xi32>, tensor<1xi32>) -> tensor<13x52x3xf32>
+  return %0 : tensor<13x52x3xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_invalid_out_N
+func.func @test_row_gather_invalid_out_N(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<12x52x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op expected batch of output to match size 13, got 12}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<12x52x3xf32>
+  return %0 : tensor<12x52x3xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_invalid_out_W
+func.func @test_row_gather_invalid_out_W(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x53x3xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op requires output dimension to be equal to indices[1]*row_count (52), got 53}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x53x3xf32>
+  return %0 : tensor<13x53x3xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_invalid_out_C
+func.func @test_row_gather_invalid_out_C(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x52x8xf32> {
+  %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op expected channels of output to match size 3, got 8}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x52x8xf32>
+  return %0 : tensor<13x52x8xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_invalid_row_count
+func.func @test_row_gather_invalid_row_count(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x26x3xf32> {
+  %row_count = "tosa.const"() {values = dense<0> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op requires row_count to be > 0, got 0}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x26x3xf32>
+  return %0 : tensor<13x26x3xf32>
+}
+
 // -----
 // CHECK-LABEL: test_row_gather_block_scaled_output_count_mismatch
 func.func @test_row_gather_block_scaled_output_count_mismatch(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> (tensor<13x52x3xf32>, tensor<13x52x3xf32>) {

>From f6a2e1cba87d016a630523859cba723adede5537 Mon Sep 17 00:00:00 2001
From: Luke Hutton <luke.hutton at arm.com>
Date: Mon, 15 Jun 2026 15:04:53 +0100
Subject: [PATCH 2/2] address review comments

Change-Id: I882fdb7536698c8e42656a5d285fd20ff8cd779a
---
 .../Dialect/Tosa/IR/TosaComplianceData.h.inc   | 10 ++--------
 mlir/lib/Dialect/Tosa/IR/TosaOps.cpp           |  4 +---
 mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir  | 10 ++++++++++
 mlir/test/Dialect/Tosa/verifier.mlir           | 18 ++++++++++++++++++
 4 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/mlir/include/mlir/Dialect/Tosa/IR/TosaComplianceData.h.inc b/mlir/include/mlir/Dialect/Tosa/IR/TosaComplianceData.h.inc
index f17475fae563d..7db696cc935ea 100644
--- a/mlir/include/mlir/Dialect/Tosa/IR/TosaComplianceData.h.inc
+++ b/mlir/include/mlir/Dialect/Tosa/IR/TosaComplianceData.h.inc
@@ -939,8 +939,7 @@ extensionComplianceMap = {
     {"tosa.row_gather",
      {
          {{Extension::int16},
-          {{{i16T, i32T, i16T}, SpecificationVersion::V_1_1_DRAFT},
-           {{i16T, i32T, i16T}, SpecificationVersion::V_1_1_DRAFT}}},
+          {{{i16T, i32T, i16T}, SpecificationVersion::V_1_1_DRAFT}}},
          {{Extension::int64},
           {{{i64T, i32T, i64T}, SpecificationVersion::V_1_1_DRAFT},
            {{boolT, i64T, boolT}, SpecificationVersion::V_1_1_DRAFT},
@@ -948,15 +947,10 @@ extensionComplianceMap = {
            {{i32T, i64T, i32T}, SpecificationVersion::V_1_1_DRAFT},
            {{i64T, i64T, i64T}, SpecificationVersion::V_1_1_DRAFT},
            {{i64T, i32T, i64T}, SpecificationVersion::V_1_1_DRAFT},
-           {{boolT, i64T, boolT}, SpecificationVersion::V_1_1_DRAFT},
-           {{i8T, i64T, i8T}, SpecificationVersion::V_1_1_DRAFT},
-           {{i32T, i64T, i32T}, SpecificationVersion::V_1_1_DRAFT},
-           {{i64T, i64T, i64T}, SpecificationVersion::V_1_1_DRAFT},
            {{fp16T, i64T, fp16T}, SpecificationVersion::V_1_1_DRAFT},
            {{fp32T, i64T, fp32T}, SpecificationVersion::V_1_1_DRAFT}}},
          {{Extension::int16, Extension::int64},
-          {{{i16T, i64T, i16T}, SpecificationVersion::V_1_1_DRAFT},
-           {{i16T, i64T, i16T}, SpecificationVersion::V_1_1_DRAFT}},
+          {{{i16T, i64T, i16T}, SpecificationVersion::V_1_1_DRAFT}},
           allOf},
          {{Extension::fp8e4m3},
           {{{fp8e4m3T, i32T, fp8e4m3T}, SpecificationVersion::V_1_1_DRAFT}}},
diff --git a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
index c10fa69e3a175..c2407729d3bd7 100644
--- a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
+++ b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
@@ -3395,7 +3395,7 @@ LogicalResult tosa::RowGatherOp::inferReturnTypeComponents(
 
     const FailureOr<int32_t> maybeRowCount =
         getConstantScalarIntValue<int32_t>(adaptor.getRowCount());
-    if (succeeded(maybeRowCount) && maybeRowCount.value() > 0) {
+    if (succeeded(maybeRowCount)) {
       const int64_t indicesW = indicesShape.getDimSize(1);
       if (ShapedType::isStatic(indicesW))
         outputShape[1] = indicesW * maybeRowCount.value();
@@ -3535,8 +3535,6 @@ LogicalResult tosa::RowGatherOp::verify() {
                                      "output", "channels")))
       return failure();
 
-    const FailureOr<int32_t> maybeRowCount =
-        getConstantScalarIntValue<int32_t>(getRowCount());
     if (succeeded(maybeRowCount) && maybeRowCount.value() > 0 &&
         ShapedType::isStatic(w)) {
       const int64_t expectedOutputRows = w * maybeRowCount.value();
diff --git a/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir b/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir
index 5499da8c6e135..cfe4ea4782037 100644
--- a/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir
+++ b/mlir/test/Dialect/Tosa/tosa-infer-shapes.mlir
@@ -810,6 +810,16 @@ func.func @row_gather_row_count_one(%arg0 : tensor<3x4x5xi32>, %arg1 : tensor<3x
 
 // -----
 
+// CHECK-LABEL: @row_gather_unranked
+func.func @row_gather_unranked(%arg0 : tensor<*xi32>, %arg1 : tensor<*xi32>) {
+  %row_count = "tosa.const"() {values = dense<1> : tensor<1xi32>} : () -> tensor<1xi32>
+  // CHECK: tosa.row_gather %arg0, %arg1, %[[ROW_COUNT:.+]] : (tensor<*xi32>, tensor<*xi32>, tensor<1xi32>) -> tensor<?x?x?xi32>
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<*xi32>, tensor<*xi32>, tensor<1xi32>) -> tensor<*xi32>
+  return
+}
+
+// -----
+
 // CHECK-LABEL: @row_gather_block_scaled_static
 func.func @row_gather_block_scaled_static(%arg0 : tensor<3x4x5xi32>, %arg1 : tensor<3x6xi32>) {
   %row_count = "tosa.const"() {values = dense<2> : tensor<1xi32>} : () -> tensor<1xi32>
diff --git a/mlir/test/Dialect/Tosa/verifier.mlir b/mlir/test/Dialect/Tosa/verifier.mlir
index c9a881845a06a..00fd07304cf6f 100644
--- a/mlir/test/Dialect/Tosa/verifier.mlir
+++ b/mlir/test/Dialect/Tosa/verifier.mlir
@@ -544,6 +544,24 @@ func.func @test_row_gather_invalid_row_count(%arg0: tensor<13x21x3xf32>, %arg1:
   return %0 : tensor<13x26x3xf32>
 }
 
+// -----
+// CHECK-LABEL: test_row_gather_invalid_row_count_negative
+func.func @test_row_gather_invalid_row_count_negative(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x26x3xf32> {
+  %row_count = "tosa.const"() {values = dense<-1> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op requires row_count to be > 0, got -1}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x26x3xf32>
+  return %0 : tensor<13x26x3xf32>
+}
+
+// -----
+// CHECK-LABEL: test_row_gather_input_output_mismatch
+func.func @test_row_gather_input_output_mismatch(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> tensor<13x26x3xf16> {
+  %row_count = "tosa.const"() {values = dense<0> : tensor<1xi32>} : () -> tensor<1xi32>
+  // expected-error at +1 {{'tosa.row_gather' op expect input and output to have same element type, got 'f32' and 'f16'}}
+  %0 = tosa.row_gather %arg0, %arg1, %row_count : (tensor<13x21x3xf32>, tensor<13x26xi32>, tensor<1xi32>) -> tensor<13x26x3xf16>
+  return %0 : tensor<13x26x3xf16>
+}
+
 // -----
 // CHECK-LABEL: test_row_gather_block_scaled_output_count_mismatch
 func.func @test_row_gather_block_scaled_output_count_mismatch(%arg0: tensor<13x21x3xf32>, %arg1: tensor<13x26xi32>) -> (tensor<13x52x3xf32>, tensor<13x52x3xf32>) {



More information about the Mlir-commits mailing list