[Mlir-commits] [mlir] [mlir][tensor] Add runtime verification for expand_shape/concat/reshape (PR #236783)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Thu Oct 29 06:47:10 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-tensor

@llvm/pr-subscribers-mlir

Author: Wijkqwe

<details>
<summary>Changes</summary>

Fixes #<!-- -->232222.

Add `RuntimeVerifiableOpInterface` implementations for `tensor.expand_shape`,
`tensor.concat` and `tensor.reshape`. With dynamic shapes, ill-formed IR was
not rejected by the verifier and no runtime checks were generated, leading to
silently wrong results and out-of-bounds accesses after bufferization.

* `tensor.expand_shape`: product of the output sizes in each reassociation
  group equals the corresponding source dim size (mirrors memref).
* `tensor.concat`: inputs agree on every non-concatenated dimension; the sum
  of the input sizes along the concatenation dimension equals the static
  result size.
* `tensor.reshape`: product of the shape-operand values equals the number of
  elements of the source; shape-operand values match the static result sizes.

Checks reference only operands and static type info (verification IR is
inserted before the op). Covered by a lit test and integration tests.

Assisted-by: AI.

---

Patch is 26.29 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/236783.diff


6 Files Affected:

- (modified) mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp (+3-2) 
- (modified) mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp (+227) 
- (added) mlir/test/Dialect/Tensor/runtime-verification.mlir (+88) 
- (added) mlir/test/Integration/Dialect/Tensor/concat-runtime-verification.mlir (+66) 
- (added) mlir/test/Integration/Dialect/Tensor/expand-shape-runtime-verification.mlir (+45) 
- (added) mlir/test/Integration/Dialect/Tensor/reshape-runtime-verification.mlir (+68) 


``````````diff
diff --git a/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp b/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp
index 2c9cd87f14af25..d2a3c7c4277c88 100644
--- a/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp
+++ b/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp
@@ -58,8 +58,9 @@ void TensorDialect::initialize() {
                             InsertSliceOp, ReshapeOp>();
   declarePromisedInterfaces<ReifyRankedShapedTypeOpInterface, ExpandShapeOp,
                             CollapseShapeOp, PadOp>();
-  declarePromisedInterfaces<RuntimeVerifiableOpInterface, CastOp, DimOp,
-                            ExtractOp, InsertOp, ExtractSliceOp>();
+  declarePromisedInterfaces<RuntimeVerifiableOpInterface, CastOp, ConcatOp,
+                            DimOp, ExpandShapeOp, ExtractOp, InsertOp,
+                            ExtractSliceOp, ReshapeOp>();
   declarePromisedInterfaces<SubsetOpInterface, ExtractSliceOp, InsertSliceOp,
                             ParallelInsertSliceOp>();
   declarePromisedInterfaces<SubsetInsertionOpInterface, InsertSliceOp,
diff --git a/mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp b/mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp
index d35f458cbdb363..3a0fb352513a8f 100644
--- a/mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp
+++ b/mlir/lib/Dialect/Tensor/Transforms/RuntimeOpVerification.cpp
@@ -237,6 +237,230 @@ struct ExtractSliceOpInterface
     }
   }
 };
+
+struct ExpandShapeOpInterface
+    : public RuntimeVerifiableOpInterface::ExternalModel<ExpandShapeOpInterface,
+                                                         ExpandShapeOp> {
+  void
+  generateRuntimeVerification(Operation *op, OpBuilder &builder, Location loc,
+                              function_ref<std::string(Operation *, StringRef)>
+                                  generateErrorMessage) const {
+    auto expandShapeOp = cast<ExpandShapeOp>(op);
+
+    SmallVector<OpFoldResult> outputShape = expandShapeOp.getMixedOutputShape();
+
+    // Verify that the product of output dim sizes in each reassociation group
+    // equals the corresponding input dim size.
+    for (const auto &it :
+         llvm::enumerate(expandShapeOp.getReassociationIndices())) {
+      Value srcDimSz =
+          DimOp::create(builder, loc, expandShapeOp.getSrc(), it.index());
+      Value groupProduct = getValueOrCreateConstantIndexOp(
+          builder, loc, outputShape[it.value().front()]);
+      for (int64_t resultDim : llvm::drop_begin(it.value())) {
+        Value dimSz = getValueOrCreateConstantIndexOp(builder, loc,
+                                                      outputShape[resultDim]);
+        groupProduct = arith::MulIOp::create(builder, loc, groupProduct, dimSz);
+      }
+      Value isEqual = arith::CmpIOp::create(
+          builder, loc, arith::CmpIPredicate::eq, groupProduct, srcDimSz);
+      cf::AssertOp::create(
+          builder, loc, isEqual,
+          generateErrorMessage(op, "product of output dims in reassoc group "
+                                   "does not equal input dim"));
+    }
+  }
+};
+
+struct ConcatOpInterface
+    : public RuntimeVerifiableOpInterface::ExternalModel<ConcatOpInterface,
+                                                         ConcatOp> {
+  void
+  generateRuntimeVerification(Operation *op, OpBuilder &builder, Location loc,
+                              function_ref<std::string(Operation *, StringRef)>
+                                  generateErrorMessage) const {
+    auto concatOp = cast<ConcatOp>(op);
+    ValueRange inputs = concatOp.getInputs();
+    int64_t concatDim = concatOp.getDim();
+    int64_t rank = concatOp.getRank();
+    RankedTensorType resultType = concatOp.getResultType();
+
+    // Materialize the size of `tensor` along dimension `i`. Static sizes are
+    // materialized as constants.
+    auto getDimSize = [&](Value tensor, int64_t i) -> Value {
+      auto tensorType = cast<RankedTensorType>(tensor.getType());
+      if (!tensorType.isDynamicDim(i))
+        return arith::ConstantIndexOp::create(builder, loc,
+                                              tensorType.getDimSize(i));
+      return DimOp::create(builder, loc, tensor, i);
+    };
+
+    // Check that all inputs have the same size along every non-concatenated
+    // dimension, and that this size matches the static result size (if any).
+    // Dynamic result dims are defined by the op itself and cannot be checked
+    // here (the result is not yet defined at the insertion point).
+    for (int64_t i : llvm::seq<int64_t>(0, rank)) {
+      if (i == concatDim)
+        continue;
+
+      bool hasDynamicInput = llvm::any_of(inputs, [&](Value input) {
+        return cast<RankedTensorType>(input.getType()).isDynamicDim(i);
+      });
+      if (!hasDynamicInput)
+        continue;
+
+      Value expectedSize = getDimSize(inputs.front(), i);
+      bool frontIsDynamic =
+          cast<RankedTensorType>(inputs.front().getType()).isDynamicDim(i);
+      for (Value input : llvm::drop_begin(inputs)) {
+        // Pairs of static sizes are verified statically.
+        if (!frontIsDynamic &&
+            !cast<RankedTensorType>(input.getType()).isDynamicDim(i))
+          continue;
+        Value size = getDimSize(input, i);
+        Value isSameSize = arith::CmpIOp::create(
+            builder, loc, arith::CmpIPredicate::eq, size, expectedSize);
+        cf::AssertOp::create(
+            builder, loc, isSameSize,
+            generateErrorMessage(op, "input size mismatch along "
+                                     "non-concatenated dimension " +
+                                         std::to_string(i)));
+      }
+
+      if (!resultType.isDynamicDim(i)) {
+        Value resultSize = arith::ConstantIndexOp::create(
+            builder, loc, resultType.getDimSize(i));
+        Value isSameSize = arith::CmpIOp::create(
+            builder, loc, arith::CmpIPredicate::eq, expectedSize, resultSize);
+        cf::AssertOp::create(
+            builder, loc, isSameSize,
+            generateErrorMessage(op, "input size does not match result size "
+                                     "along non-concatenated dimension " +
+                                         std::to_string(i)));
+      }
+    }
+
+    // Check that the sum of the input sizes along the concatenation dimension
+    // equals the static result size. Dynamic result sizes are reified as the
+    // sum of the input sizes (ConcatOp::reifyResultShapes), so they cannot
+    // mismatch after lowering.
+    if (resultType.isDynamicDim(concatDim))
+      return;
+
+    bool hasDynamicInput = llvm::any_of(inputs, [&](Value input) {
+      return cast<RankedTensorType>(input.getType()).isDynamicDim(concatDim);
+    });
+    if (!hasDynamicInput)
+      return;
+
+    Value sum = getDimSize(inputs.front(), concatDim);
+    for (Value input : llvm::drop_begin(inputs))
+      sum = arith::AddIOp::create(builder, loc, sum,
+                                  getDimSize(input, concatDim));
+    Value resultSize = arith::ConstantIndexOp::create(
+        builder, loc, resultType.getDimSize(concatDim));
+    Value isEqual = arith::CmpIOp::create(builder, loc,
+                                          arith::CmpIPredicate::eq, sum,
+                                          resultSize);
+    cf::AssertOp::create(
+        builder, loc, isEqual,
+        generateErrorMessage(op, "sum of input sizes along concatenated "
+                                 "dimension does not match result size"));
+  }
+};
+
+struct ReshapeOpInterface
+    : public RuntimeVerifiableOpInterface::ExternalModel<ReshapeOpInterface,
+                                                         ReshapeOp> {
+  void
+  generateRuntimeVerification(Operation *op, OpBuilder &builder, Location loc,
+                              function_ref<std::string(Operation *, StringRef)>
+                                  generateErrorMessage) const {
+    auto reshapeOp = cast<ReshapeOp>(op);
+    auto shapeType = cast<RankedTensorType>(reshapeOp.getShape().getType());
+    int64_t shapeSize = shapeType.getDimSize(0);
+
+    // A dynamic shape-operand length is only valid with an unranked result
+    // (verified statically); there is then nothing to check here.
+    if (ShapedType::isDynamic(shapeSize))
+      return;
+
+    // Extract the shape operand values and normalize them to index.
+    Type indexType = builder.getIndexType();
+    SmallVector<Value> shapeValues;
+    shapeValues.reserve(shapeSize);
+    for (int64_t i : llvm::seq<int64_t>(0, shapeSize)) {
+      Value idx = arith::ConstantIndexOp::create(builder, loc, i);
+      Value shapeValue =
+          ExtractOp::create(builder, loc, reshapeOp.getShape(), idx);
+      if (shapeValue.getType() != indexType)
+        shapeValue = arith::IndexCastOp::create(builder, loc, indexType,
+                                                shapeValue);
+      shapeValues.push_back(shapeValue);
+    }
+
+    // Check that the shape operand values agree with the static result sizes.
+    // The lowering of memref.reshape trusts the static result dimensions over
+    // the shape operand values, so a mismatch yields an inconsistent memref
+    // descriptor and out-of-bounds accesses.
+    if (auto resultType =
+            dyn_cast<RankedTensorType>(reshapeOp.getResult().getType())) {
+      for (int64_t i : llvm::seq<int64_t>(0, resultType.getRank())) {
+        if (resultType.isDynamicDim(i))
+          continue;
+        Value expectedSize = arith::ConstantIndexOp::create(
+            builder, loc, resultType.getDimSize(i));
+        Value isEqual = arith::CmpIOp::create(
+            builder, loc, arith::CmpIPredicate::eq, shapeValues[i],
+            expectedSize);
+        cf::AssertOp::create(
+            builder, loc, isEqual,
+            generateErrorMessage(op, "shape operand value does not match "
+                                     "static result size along dimension " +
+                                         std::to_string(i)));
+      }
+    }
+
+    // Check that the product of the shape operand values equals the number of
+    // elements in the source. This requires a ranked source.
+    auto sourceType =
+        dyn_cast<RankedTensorType>(reshapeOp.getSource().getType());
+    if (!sourceType)
+      return;
+
+    Value sourceNumElements;
+    if (sourceType.hasStaticShape()) {
+      sourceNumElements = arith::ConstantIndexOp::create(
+          builder, loc, sourceType.getNumElements());
+    } else {
+      sourceNumElements = DimOp::create(builder, loc, reshapeOp.getSource(), 0);
+      for (int64_t i : llvm::seq<int64_t>(1, sourceType.getRank())) {
+        Value dimSize = DimOp::create(builder, loc, reshapeOp.getSource(), i);
+        sourceNumElements =
+            arith::MulIOp::create(builder, loc, sourceNumElements, dimSize);
+      }
+    }
+
+    std::optional<Value> shapeProduct;
+    for (Value shapeValue : shapeValues) {
+      shapeProduct =
+          shapeProduct ? arith::MulIOp::create(builder, loc, *shapeProduct,
+                                               shapeValue)
+                       : shapeValue;
+    }
+    if (!shapeProduct)
+      shapeProduct = arith::ConstantIndexOp::create(builder, loc, 1);
+
+    Value isEqual = arith::CmpIOp::create(
+        builder, loc, arith::CmpIPredicate::eq, *shapeProduct,
+        sourceNumElements);
+    cf::AssertOp::create(
+        builder, loc, isEqual,
+        generateErrorMessage(op, "product of shape operand values does not "
+                                 "equal number of elements in source"));
+  }
+};
+
 } // namespace
 } // namespace tensor
 } // namespace mlir
@@ -245,10 +469,13 @@ void mlir::tensor::registerRuntimeVerifiableOpInterfaceExternalModels(
     DialectRegistry &registry) {
   registry.addExtension(+[](MLIRContext *ctx, tensor::TensorDialect *dialect) {
     CastOp::attachInterface<CastOpInterface>(*ctx);
+    ConcatOp::attachInterface<ConcatOpInterface>(*ctx);
     DimOp::attachInterface<DimOpInterface>(*ctx);
+    ExpandShapeOp::attachInterface<ExpandShapeOpInterface>(*ctx);
     ExtractOp::attachInterface<ExtractInsertOpInterface<ExtractOp>>(*ctx);
     ExtractSliceOp::attachInterface<ExtractSliceOpInterface>(*ctx);
     InsertOp::attachInterface<ExtractInsertOpInterface<InsertOp>>(*ctx);
+    ReshapeOp::attachInterface<ReshapeOpInterface>(*ctx);
 
     // Load additional dialects of which ops may get created.
     ctx->loadDialect<arith::ArithDialect, cf::ControlFlowDialect>();
diff --git a/mlir/test/Dialect/Tensor/runtime-verification.mlir b/mlir/test/Dialect/Tensor/runtime-verification.mlir
new file mode 100644
index 00000000000000..952d10e7dc8303
--- /dev/null
+++ b/mlir/test/Dialect/Tensor/runtime-verification.mlir
@@ -0,0 +1,88 @@
+// RUN: mlir-opt %s -generate-runtime-verification -cse -split-input-file | FileCheck %s
+
+// CHECK-LABEL: func @expand_shape(
+//  CHECK-SAME:     %[[m:.*]]: tensor<?xf32>
+//  CHECK-SAME:     %[[sz0:.*]]: index
+//       CHECK:   %[[c0:.*]] = arith.constant 0 : index
+//       CHECK:   %[[dim:.*]] = tensor.dim %[[m]], %[[c0]]
+//       CHECK:   %[[c2:.*]] = arith.constant 2 : index
+//       CHECK:   %[[prod:.*]] = arith.muli %[[sz0]], %[[c2]]
+//       CHECK:   %[[cmpi:.*]] = arith.cmpi eq, %[[prod]], %[[dim]]
+//       CHECK:   cf.assert %[[cmpi]], "ERROR: Runtime op verification failed
+func.func @expand_shape(%m: tensor<?xf32>, %sz0: index) -> tensor<?x2xf32> {
+  %0 = tensor.expand_shape %m [[0, 1]] output_shape [%sz0, 2] : tensor<?xf32> into tensor<?x2xf32>
+  return %0 : tensor<?x2xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @concat(
+//  CHECK-SAME:     %[[a:.*]]: tensor<?x?xf32>,
+//  CHECK-SAME:     %[[b:.*]]: tensor<?x?xf32>
+//       CHECK:   %[[c0:.*]] = arith.constant 0 : index
+//       CHECK:   %[[da:.*]] = tensor.dim %[[a]], %[[c0]]
+//       CHECK:   %[[db:.*]] = tensor.dim %[[b]], %[[c0]]
+//       CHECK:   %[[cmp:.*]] = arith.cmpi eq, %[[db]], %[[da]]
+//       CHECK:   cf.assert %[[cmp]], "ERROR: Runtime op verification failed
+func.func @concat(%a: tensor<?x?xf32>, %b: tensor<?x?xf32>) -> tensor<?x?xf32> {
+  %0 = tensor.concat dim(1) %a, %b : (tensor<?x?xf32>, tensor<?x?xf32>) -> tensor<?x?xf32>
+  return %0 : tensor<?x?xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @concat_sum(
+//  CHECK-SAME:     %[[a:.*]]: tensor<?x3xf32>,
+//  CHECK-SAME:     %[[b:.*]]: tensor<?x3xf32>
+//       CHECK:   %[[c0:.*]] = arith.constant 0 : index
+//       CHECK:   %[[da:.*]] = tensor.dim %[[a]], %[[c0]]
+//       CHECK:   %[[db:.*]] = tensor.dim %[[b]], %[[c0]]
+//       CHECK:   %[[sum:.*]] = arith.addi %[[da]], %[[db]]
+//       CHECK:   %[[c5:.*]] = arith.constant 5 : index
+//       CHECK:   %[[cmp:.*]] = arith.cmpi eq, %[[sum]], %[[c5]]
+//       CHECK:   cf.assert %[[cmp]], "ERROR: Runtime op verification failed
+func.func @concat_sum(%a: tensor<?x3xf32>, %b: tensor<?x3xf32>) -> tensor<5x3xf32> {
+  %0 = tensor.concat dim(0) %a, %b : (tensor<?x3xf32>, tensor<?x3xf32>) -> tensor<5x3xf32>
+  return %0 : tensor<5x3xf32>
+}
+
+// -----
+
+// CHECK-LABEL: func @reshape(
+//  CHECK-SAME:     %[[x:.*]]: tensor<?xf32>
+//  CHECK-SAME:     %[[shape:.*]]: tensor<2xindex>
+//       CHECK:   %[[c0:.*]] = arith.constant 0 : index
+//       CHECK:   %[[e0:.*]] = tensor.extract %[[shape]][%[[c0]]]
+//       CHECK:   %[[c1:.*]] = arith.constant 1 : index
+//       CHECK:   %[[e1:.*]] = tensor.extract %[[shape]][%[[c1]]]
+//       CHECK:   %[[dim:.*]] = tensor.dim %[[x]], %[[c0]]
+//       CHECK:   %[[prod:.*]] = arith.muli %[[e0]], %[[e1]]
+//       CHECK:   %[[cmp:.*]] = arith.cmpi eq, %[[prod]], %[[dim]]
+//       CHECK:   cf.assert %[[cmp]], "ERROR: Runtime op verification failed
+func.func @reshape(%x: tensor<?xf32>, %shape: tensor<2xindex>) -> tensor<?x?xf32> {
+  %0 = tensor.reshape %x(%shape) : (tensor<?xf32>, tensor<2xindex>) -> tensor<?x?xf32>
+  return %0 : tensor<?x?xf32>
+}
+
+// -----
+
+// Check that shape operand values are compared against static result sizes.
+
+// CHECK-LABEL: func @reshape_static_result(
+//  CHECK-SAME:     %[[x:.*]]: tensor<?xf32>
+//  CHECK-SAME:     %[[shape:.*]]: tensor<2xindex>
+//       CHECK:   %[[c0:.*]] = arith.constant 0 : index
+//       CHECK:   %[[e0:.*]] = tensor.extract %[[shape]][%[[c0]]]
+//       CHECK:   %[[c1:.*]] = arith.constant 1 : index
+//       CHECK:   %[[e1:.*]] = tensor.extract %[[shape]][%[[c1]]]
+//       CHECK:   %[[c6:.*]] = arith.constant 6 : index
+//       CHECK:   %[[cmp0:.*]] = arith.cmpi eq, %[[e0]], %[[c6]]
+//       CHECK:   cf.assert %[[cmp0]], "ERROR: Runtime op verification failed
+//       CHECK:   %[[dim:.*]] = tensor.dim %[[x]], %[[c0]]
+//       CHECK:   %[[prod:.*]] = arith.muli %[[e0]], %[[e1]]
+//       CHECK:   %[[cmp1:.*]] = arith.cmpi eq, %[[prod]], %[[dim]]
+//       CHECK:   cf.assert %[[cmp1]], "ERROR: Runtime op verification failed
+func.func @reshape_static_result(%x: tensor<?xf32>, %shape: tensor<2xindex>) -> tensor<6x?xf32> {
+  %0 = tensor.reshape %x(%shape) : (tensor<?xf32>, tensor<2xindex>) -> tensor<6x?xf32>
+  return %0 : tensor<6x?xf32>
+}
\ No newline at end of file
diff --git a/mlir/test/Integration/Dialect/Tensor/concat-runtime-verification.mlir b/mlir/test/Integration/Dialect/Tensor/concat-runtime-verification.mlir
new file mode 100644
index 00000000000000..7221e8e07e2d72
--- /dev/null
+++ b/mlir/test/Integration/Dialect/Tensor/concat-runtime-verification.mlir
@@ -0,0 +1,66 @@
+// RUN: mlir-opt %s -generate-runtime-verification \
+// RUN:     -one-shot-bufferize="bufferize-function-boundaries" \
+// RUN:     -buffer-deallocation-pipeline=private-function-dynamic-ownership \
+// RUN:     -test-cf-assert \
+// RUN:     -convert-scf-to-cf \
+// RUN:     -convert-to-llvm | \
+// RUN: mlir-runner -e main -entry-point-result=void \
+// RUN:     -shared-libs=%mlir_runner_utils 2>&1 | \
+// RUN: FileCheck %s
+
+// RUN: mlir-opt %s -generate-runtime-verification \
+// RUN:     -one-shot-bufferize="bufferize-function-boundaries" \
+// RUN:     -buffer-deallocation-pipeline=private-function-dynamic-ownership \
+// RUN:     -test-cf-assert \
+// RUN:     -convert-scf-to-cf \
+// RUN:     -convert-to-llvm="allow-pattern-rollback=0" \
+// RUN:     -reconcile-unrealized-casts | \
+// RUN: mlir-runner -e main -entry-point-result=void \
+// RUN:     -shared-libs=%mlir_runner_utils 2>&1 | \
+// RUN: FileCheck %s
+
+func.func private @concat(%a: tensor<?x?xf32>, %b: tensor<?x?xf32>) -> tensor<?x?xf32> {
+  %c = tensor.concat dim(1) %a, %b : (tensor<?x?xf32>, tensor<?x?xf32>) -> tensor<?x?xf32>
+  return %c : tensor<?x?xf32>
+}
+
+func.func private @concat_sum(%a: tensor<?x3xf32>, %b: tensor<?x3xf32>) -> tensor<5x3xf32> {
+  %c = tensor.concat dim(0) %a, %b : (tensor<?x3xf32>, tensor<?x3xf32>) -> tensor<5x3xf32>
+  return %c : tensor<5x3xf32>
+}
+
+func.func @main() {
+  %x = tensor.empty() : tensor<2x3xf32>
+  %y = tensor.empty() : tensor<4x3xf32>
+  %z = tensor.empty() : tensor<3x3xf32>
+  %a = tensor.cast %x : tensor<2x3xf32> to tensor<?x?xf32>
+  %b = tensor.cast %y : tensor<4x3xf32> to tensor<?x?xf32>
+  %c = tensor.cast %z : tensor<3x3xf32> to tensor<?x?xf32>
+  %a3 = tensor.cast %x : tensor<2x3xf32> to tensor<?x3xf32>
+  %b3 = tensor.cast %z : tensor<3x3xf32> to tensor<?x3xf32>
+  %y3 = tensor.cast %y : tensor<4x3xf32> to tensor<?x3xf32>
+
+  // Non-concatenated dimension 0 mismatch: 2 vs 4.
+  //      CHECK: ERROR: Runtime op verification failed
+  // CHECK-NEXT: tensor.concat dim(1) %{{.*}}, %{{.*}} : (tensor<?x?xf32>, tensor<?x?xf32>) -> tensor<?x?xf32>
+  // CHECK-NEXT: ^ input size mismatch along non-concatenated dimension 0
+  // CHECK-NEXT: Location: loc({{.*}})
+  %r1 = func.call @concat(%a, %b) : (tensor<?x?xf32>, tensor<?x?xf32>) -> tensor<?x?xf32>
+
+  // Sum 2+4=6 does not equal static result size 5.
+  //      CHECK: ERROR: Runtime op verification failed
+  // CHECK-NEXT: tensor.concat dim(0) %{{.*}}, %{{.*}} : (tensor<?x3xf32>, tensor<?x3xf32>) -> tensor<5x3xf32>
+  // CHECK-NEXT: ^ sum of input sizes along concatenated dimension does not match result size
+  // CHECK-NEXT: Location: loc({{.*}})
+  %r2 = func.call @concat_sum(%a3, %y3) : (tensor<?x3xf32>, tensor<?x3xf32>) -> tensor<5x3xf32>
+
+  // Matching inputs. No error.
+  // CHECK-NOT: ERROR: Runtime op verifica...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/236783


More information about the Mlir-commits mailing list