[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 ®istry) {
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