diff --git a/mlir/include/mlir/Dialect/Tensor/Transforms/RuntimeOpVerification.h b/mlir/include/mlir/Dialect/Tensor/Transforms/RuntimeOpVerification.h new file mode 100644 index 0000000000000..812c4dd6d2cb7 --- /dev/null +++ b/mlir/include/mlir/Dialect/Tensor/Transforms/RuntimeOpVerification.h @@ -0,0 +1,21 @@ +//===- RuntimeOpVerification.h - Op Verification ----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_DIALECT_TENSOR_RUNTIMEOPVERIFICATION_H +#define MLIR_DIALECT_TENSOR_RUNTIMEOPVERIFICATION_H + +namespace mlir { +class DialectRegistry; + +namespace tensor { +void registerRuntimeVerifiableOpInterfaceExternalModels( + DialectRegistry ®istry); +} // namespace tensor +} // namespace mlir + +#endif // MLIR_DIALECT_TENSOR_RUNTIMEOPVERIFICATION_H diff --git a/mlir/include/mlir/InitAllDialects.h b/mlir/include/mlir/InitAllDialects.h index ea285ac7f16e3..261b0e00bdf86 100644 --- a/mlir/include/mlir/InitAllDialects.h +++ b/mlir/include/mlir/InitAllDialects.h @@ -84,6 +84,7 @@ #include "mlir/Dialect/Tensor/IR/ValueBoundsOpInterfaceImpl.h" #include "mlir/Dialect/Tensor/TransformOps/TensorTransformOps.h" #include "mlir/Dialect/Tensor/Transforms/BufferizableOpInterfaceImpl.h" +#include "mlir/Dialect/Tensor/Transforms/RuntimeOpVerification.h" #include "mlir/Dialect/Tensor/Transforms/SubsetInsertionOpInterfaceImpl.h" #include "mlir/Dialect/Tosa/IR/ShardingInterfaceImpl.h" #include "mlir/Dialect/Tosa/IR/TosaOps.h" @@ -186,6 +187,7 @@ inline void registerAllDialects(DialectRegistry ®istry) { tensor::registerBufferizableOpInterfaceExternalModels(registry); tensor::registerFindPayloadReplacementOpInterfaceExternalModels(registry); tensor::registerInferTypeOpInterfaceExternalModels(registry); + tensor::registerRuntimeVerifiableOpInterfaceExternalModels(registry); tensor::registerSubsetOpInterfaceExternalModels(registry); tensor::registerTilingInterfaceExternalModels(registry); tensor::registerValueBoundsOpInterfaceExternalModels(registry); diff --git a/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt b/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt index 5425615dac393..66b73f9bead5d 100644 --- a/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt +++ b/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt @@ -30,6 +30,7 @@ add_mlir_dialect_library(MLIRTensorDialect MLIRInferIntRangeInterface MLIRInferTypeOpInterface MLIRParallelCombiningOpInterface + MLIRRuntimeVerifiableOpInterface MLIRShapedOpInterfaces MLIRSideEffectInterfaces MLIRSupport diff --git a/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp b/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp index e7d8f52d309c9..5b65e47bc937b 100644 --- a/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp +++ b/mlir/lib/Dialect/Tensor/IR/TensorDialect.cpp @@ -12,6 +12,7 @@ #include "mlir/Dialect/Complex/IR/Complex.h" #include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.h" +#include "mlir/Interfaces/RuntimeVerifiableOpInterface.h" #include "mlir/Interfaces/SubsetOpInterface.h" #include "mlir/Transforms/InliningUtils.h" @@ -58,6 +59,8 @@ void TensorDialect::initialize() { InsertSliceOp, ReshapeOp>(); declarePromisedInterfaces(); + declarePromisedInterfaces(); declarePromisedInterfaces(); declarePromisedInterfaces( + loc, arith::CmpIPredicate::sge, value, lb); + Value inBounds2 = builder.createOrFold( + loc, arith::CmpIPredicate::slt, value, ub); + Value inBounds = + builder.createOrFold(loc, inBounds1, inBounds2); + return inBounds; +} + +struct CastOpInterface + : public RuntimeVerifiableOpInterface::ExternalModel { + void generateRuntimeVerification(Operation *op, OpBuilder &builder, + Location loc) const { + auto castOp = cast(op); + auto srcType = cast(castOp.getSource().getType()); + + // Nothing to check if the result is an unranked tensor. + auto resultType = dyn_cast(castOp.getType()); + if (!resultType) + return; + + if (isa(srcType)) { + // Check rank. + Value srcRank = builder.create(loc, castOp.getSource()); + Value resultRank = + builder.create(loc, resultType.getRank()); + Value isSameRank = builder.create( + loc, arith::CmpIPredicate::eq, srcRank, resultRank); + builder.create( + loc, isSameRank, + RuntimeVerifiableOpInterface::generateErrorMessage(op, + "rank mismatch")); + } + + // Check dimension sizes. + for (const auto &it : llvm::enumerate(resultType.getShape())) { + // Static dim size -> static/dynamic dim size does not need verification. + if (auto rankedSrcType = dyn_cast(srcType)) + if (!rankedSrcType.isDynamicDim(it.index())) + continue; + + // Static/dynamic dim size -> dynamic dim size does not need verification. + if (resultType.isDynamicDim(it.index())) + continue; + + Value srcDimSz = + builder.create(loc, castOp.getSource(), it.index()); + Value resultDimSz = + builder.create(loc, it.value()); + Value isSameSz = builder.create( + loc, arith::CmpIPredicate::eq, srcDimSz, resultDimSz); + builder.create( + loc, isSameSz, + RuntimeVerifiableOpInterface::generateErrorMessage( + op, "size mismatch of dim " + std::to_string(it.index()))); + } + } +}; + +struct DimOpInterface + : public RuntimeVerifiableOpInterface::ExternalModel { + void generateRuntimeVerification(Operation *op, OpBuilder &builder, + Location loc) const { + auto dimOp = cast(op); + Value rank = builder.create(loc, dimOp.getSource()); + Value zero = builder.create(loc, 0); + builder.create( + loc, generateInBoundsCheck(builder, loc, dimOp.getIndex(), zero, rank), + RuntimeVerifiableOpInterface::generateErrorMessage( + op, "index is out of bounds")); + } +}; + +/// Verifies that the indices on extract/insert ops are in-bounds of the +/// tensor's index space: 0 <= index#i < dim#i +template +struct ExtractInsertOpInterface + : public RuntimeVerifiableOpInterface::ExternalModel< + ExtractInsertOpInterface, OpTy> { + void generateRuntimeVerification(Operation *op, OpBuilder &builder, + Location loc) const { + auto extractInsertOp = cast(op); + + Value tensor; + if constexpr (std::is_same_v) { + tensor = extractInsertOp.getTensor(); + } else if constexpr (std::is_same_v) { + tensor = extractInsertOp.getDest(); + } else { + llvm_unreachable("invalid op"); + } + auto tensorType = cast(tensor.getType()); + auto rank = tensorType.getRank(); + if (rank == 0) { + // Nothing to check for 0-d tensors. + return; + } + + auto indices = extractInsertOp.getIndices(); + auto zero = builder.create(loc, 0); + Value assertCond; + for (auto i : llvm::seq(0, rank)) { + Value dimOp = builder.createOrFold(loc, tensor, i); + Value inBounds = + generateInBoundsCheck(builder, loc, indices[i], zero, dimOp); + assertCond = + i > 0 ? builder.createOrFold(loc, assertCond, inBounds) + : inBounds; + } + builder.create( + loc, assertCond, + RuntimeVerifiableOpInterface::generateErrorMessage( + op, "out-of-bounds access")); + } +}; + +struct ExtractSliceOpInterface + : public RuntimeVerifiableOpInterface::ExternalModel< + ExtractSliceOpInterface, ExtractSliceOp> { + void generateRuntimeVerification(Operation *op, OpBuilder &builder, + Location loc) const { + auto extractSliceOp = cast(op); + RankedTensorType sourceType = extractSliceOp.getSource().getType(); + + // For each dimension, assert that: + // 0 <= offset < dim_size + // 0 <= offset + (size - 1) * stride < dim_size + Value zero = builder.create(loc, 0); + Value one = builder.create(loc, 1); + for (int64_t i = 0, e = sourceType.getRank(); i < e; ++i) { + Value offset = getValueOrCreateConstantIndexOp( + builder, loc, extractSliceOp.getMixedOffsets()[i]); + Value size = getValueOrCreateConstantIndexOp( + builder, loc, extractSliceOp.getMixedSizes()[i]); + Value stride = getValueOrCreateConstantIndexOp( + builder, loc, extractSliceOp.getMixedStrides()[i]); + + // Verify that offset is in-bounds. + Value dimSize = builder.createOrFold( + loc, extractSliceOp.getSource(), i); + Value offsetInBounds = + generateInBoundsCheck(builder, loc, offset, zero, dimSize); + builder.create( + loc, offsetInBounds, + RuntimeVerifiableOpInterface::generateErrorMessage( + op, "offset " + std::to_string(i) + " is out-of-bounds")); + + // Verify that slice does not run out-of-bounds. + Value sizeMinusOne = builder.create(loc, size, one); + Value sizeMinusOneTimesStride = + builder.create(loc, sizeMinusOne, stride); + Value lastPos = + builder.create(loc, offset, sizeMinusOneTimesStride); + Value lastPosInBounds = + generateInBoundsCheck(builder, loc, lastPos, zero, dimSize); + builder.create( + loc, lastPosInBounds, + RuntimeVerifiableOpInterface::generateErrorMessage( + op, "extract_slice runs out-of-bounds along dimension " + + std::to_string(i))); + } + } +}; +} // namespace +} // namespace tensor +} // namespace mlir + +void mlir::tensor::registerRuntimeVerifiableOpInterfaceExternalModels( + DialectRegistry ®istry) { + registry.addExtension(+[](MLIRContext *ctx, tensor::TensorDialect *dialect) { + CastOp::attachInterface(*ctx); + DimOp::attachInterface(*ctx); + ExtractOp::attachInterface>(*ctx); + ExtractSliceOp::attachInterface(*ctx); + InsertOp::attachInterface>(*ctx); + + // Load additional dialects of which ops may get created. + ctx->loadDialect(); + }); +} diff --git a/mlir/test/Integration/Dialect/Tensor/cast-runtime-verification.mlir b/mlir/test/Integration/Dialect/Tensor/cast-runtime-verification.mlir new file mode 100644 index 0000000000000..e4aab32d4a390 --- /dev/null +++ b/mlir/test/Integration/Dialect/Tensor/cast-runtime-verification.mlir @@ -0,0 +1,50 @@ +// 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=%tlir_runner_utils 2>&1 | \ +// RUN: FileCheck %s + +func.func private @cast_to_static_dim(%t: tensor) -> tensor<10xf32> { + %0 = tensor.cast %t : tensor to tensor<10xf32> + return %0 : tensor<10xf32> +} + +func.func private @cast_to_ranked(%t: tensor<*xf32>) -> tensor { + %0 = tensor.cast %t : tensor<*xf32> to tensor + return %0 : tensor +} + +func.func private @valid_cast(%t: tensor<*xf32>) -> tensor { + %0 = tensor.cast %t : tensor<*xf32> to tensor + return %0 : tensor +} + +func.func @main() { + // All casts inside the called functions are invalid at runtime, except for + // the last one. + %alloc = tensor.empty() : tensor<5xf32> + + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.cast"(%{{.*}}) : (tensor) -> tensor<10xf32> + // CHECK-NEXT: ^ size mismatch of dim 0 + // CHECK-NEXT: Location: loc({{.*}}) + %1 = tensor.cast %alloc : tensor<5xf32> to tensor + func.call @cast_to_static_dim(%1) : (tensor) -> (tensor<10xf32>) + + // CHECK-NEXT: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.cast"(%{{.*}}) : (tensor<*xf32>) -> tensor + // CHECK-NEXT: ^ rank mismatch + // CHECK-NEXT: Location: loc({{.*}}) + %3 = tensor.cast %alloc : tensor<5xf32> to tensor<*xf32> + func.call @cast_to_ranked(%3) : (tensor<*xf32>) -> (tensor) + + // A last cast that actually succeeds. + // CHECK-NOT: ERROR: Runtime op verification failed + func.call @valid_cast(%3) : (tensor<*xf32>) -> (tensor) + + return +} diff --git a/mlir/test/Integration/Dialect/Tensor/dim-runtime-verification.mlir b/mlir/test/Integration/Dialect/Tensor/dim-runtime-verification.mlir new file mode 100644 index 0000000000000..c6d8f698b9433 --- /dev/null +++ b/mlir/test/Integration/Dialect/Tensor/dim-runtime-verification.mlir @@ -0,0 +1,21 @@ +// RUN: mlir-opt %s -generate-runtime-verification \ +// RUN: -one-shot-bufferize \ +// RUN: -buffer-deallocation-pipeline \ +// RUN: -test-cf-assert \ +// RUN: -convert-to-llvm | \ +// RUN: mlir-runner -e main -entry-point-result=void \ +// RUN: -shared-libs=%mlir_runner_utils 2>&1 | \ +// RUN: FileCheck %s + +func.func @main() { + %c4 = arith.constant 4 : index + %tensor = tensor.empty() : tensor<1xf32> + + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.dim"(%{{.*}}, %{{.*}}) : (tensor<1xf32>, index) -> index + // CHECK-NEXT: ^ index is out of bounds + // CHECK-NEXT: Location: loc({{.*}}) + %dim = tensor.dim %tensor, %c4 : tensor<1xf32> + + return +} diff --git a/mlir/test/Integration/Dialect/Tensor/extract-runtime-verification.mlir b/mlir/test/Integration/Dialect/Tensor/extract-runtime-verification.mlir new file mode 100644 index 0000000000000..8e3cab7be704d --- /dev/null +++ b/mlir/test/Integration/Dialect/Tensor/extract-runtime-verification.mlir @@ -0,0 +1,64 @@ +// 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=%tlir_runner_utils 2>&1 | \ +// RUN: FileCheck %s + +func.func @extract(%tensor: tensor<1xf32>, %index: index) { + tensor.extract %tensor[%index] : tensor<1xf32> + return +} + +func.func @extract_dynamic(%tensor: tensor, %index: index) { + tensor.extract %tensor[%index] : tensor + return +} + +func.func @extract_nd_dynamic(%tensor: tensor, %index0: index, %index1: index, %index2: index) { + tensor.extract %tensor[%index0, %index1, %index2] : tensor + return +} + +func.func @main() { + %0 = arith.constant 0 : index + %1 = arith.constant 1 : index + %n1 = arith.constant -1 : index + %2 = arith.constant 2 : index + %alloca_1 = tensor.empty() : tensor<1xf32> + %alloc_1 = tensor.empty(%1) : tensor + %alloc_2x2x2 = tensor.empty(%2, %2, %2) : tensor + + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract"(%{{.*}}, %{{.*}}) : (tensor<1xf32>, index) -> f32 + // CHECK-NEXT: ^ out-of-bounds access + // CHECK-NEXT: Location: loc({{.*}}) + func.call @extract(%alloca_1, %1) : (tensor<1xf32>, index) -> () + + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract"(%{{.*}}, %{{.*}}) : (tensor, index) -> f32 + // CHECK-NEXT: ^ out-of-bounds access + // CHECK-NEXT: Location: loc({{.*}}) + func.call @extract_dynamic(%alloc_1, %1) : (tensor, index) -> () + + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract"(%{{.*}}, %{{.*}}) : (tensor, index, index, index) -> f32 + // CHECK-NEXT: ^ out-of-bounds access + // CHECK-NEXT: Location: loc({{.*}}) + func.call @extract_nd_dynamic(%alloc_2x2x2, %1, %n1, %0) : (tensor, index, index, index) -> () + + // CHECK-NOT: ERROR: Runtime op verification failed + func.call @extract(%alloca_1, %0) : (tensor<1xf32>, index) -> () + + // CHECK-NOT: ERROR: Runtime op verification failed + func.call @extract_dynamic(%alloc_1, %0) : (tensor, index) -> () + + // CHECK-NOT: ERROR: Runtime op verification failed + func.call @extract_nd_dynamic(%alloc_2x2x2, %1, %1, %0) : (tensor, index, index, index) -> () + + return +} + diff --git a/mlir/test/Integration/Dialect/Tensor/extract_slice-runtime-verification.mlir b/mlir/test/Integration/Dialect/Tensor/extract_slice-runtime-verification.mlir new file mode 100644 index 0000000000000..28f9be0fffe64 --- /dev/null +++ b/mlir/test/Integration/Dialect/Tensor/extract_slice-runtime-verification.mlir @@ -0,0 +1,95 @@ +// 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=%tlir_runner_utils 2>&1 | \ +// RUN: FileCheck %s + +func.func @extract_slice(%tensor: tensor<1xf32>, %offset: index) { + tensor.extract_slice %tensor[%offset] [1] [1] : tensor<1xf32> to tensor<1xf32> + return +} + +func.func @extract_slice_dynamic(%tensor: tensor, %offset: index, %size: index, %stride: index) { + tensor.extract_slice %tensor[%offset, 0] [%size, 4] [%stride, 1] : tensor to tensor + return +} + +func.func @extract_slice_dynamic_rank_reduce(%tensor: tensor, %offset: index, %size: index, %stride: index) { + tensor.extract_slice %tensor[%offset, 0] [%size, 1] [%stride, 1] : tensor to tensor + return +} + +func.func @main() { + %0 = arith.constant 0 : index + %1 = arith.constant 1 : index + %n1 = arith.constant -1 : index + %4 = arith.constant 4 : index + %5 = arith.constant 5 : index + + %alloca = tensor.empty() : tensor<1xf32> + %alloca_4 = tensor.empty() : tensor<4x4xf32> + %alloca_4_dyn = tensor.cast %alloca_4 : tensor<4x4xf32> to tensor + + // Offset is out-of-bounds and slice runs out-of-bounds + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract_slice"(%arg0, %arg1, %arg2, %arg3) <{operandSegmentSizes = array, static_offsets = array, static_sizes = array, static_strides = array}> : (tensor, index, index, index) -> tensor + // CHECK-NEXT: ^ offset 0 is out-of-bounds + // CHECK-NEXT: Location: loc({{.*}}) + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract_slice"(%arg0, %arg1, %arg2, %arg3) <{operandSegmentSizes = array, static_offsets = array, static_sizes = array, static_strides = array}> : (tensor, index, index, index) -> tensor + // CHECK-NEXT: ^ extract_slice runs out-of-bounds along dimension 0 + // CHECK-NEXT: Location: loc({{.*}}) + func.call @extract_slice_dynamic_rank_reduce(%alloca_4_dyn, %5, %5, %1) : (tensor, index, index, index) -> () + + // Offset is out-of-bounds and slice runs out-of-bounds + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract_slice"(%arg0, %arg1) <{operandSegmentSizes = array, static_offsets = array, static_sizes = array, static_strides = array}> : (tensor<1xf32>, index) -> tensor<1xf32> + // CHECK-NEXT: ^ offset 0 is out-of-bounds + // CHECK-NEXT: Location: loc({{.*}}) + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract_slice"(%arg0, %arg1) <{operandSegmentSizes = array, static_offsets = array, static_sizes = array, static_strides = array}> : (tensor<1xf32>, index) -> tensor<1xf32> + // CHECK-NEXT: ^ extract_slice runs out-of-bounds along dimension 0 + // CHECK-NEXT: Location: loc({{.*}}) + func.call @extract_slice(%alloca, %1) : (tensor<1xf32>, index) -> () + + // Offset is out-of-bounds and slice runs out-of-bounds + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract_slice"(%arg0, %arg1) <{operandSegmentSizes = array, static_offsets = array, static_sizes = array, static_strides = array}> : (tensor<1xf32>, index) -> tensor<1xf32> + // CHECK-NEXT: ^ offset 0 is out-of-bounds + // CHECK-NEXT: Location: loc({{.*}}) + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract_slice"(%arg0, %arg1) <{operandSegmentSizes = array, static_offsets = array, static_sizes = array, static_strides = array}> : (tensor<1xf32>, index) -> tensor<1xf32> + // CHECK-NEXT: ^ extract_slice runs out-of-bounds along dimension 0 + // CHECK-NEXT: Location: loc({{.*}}) + func.call @extract_slice(%alloca, %n1) : (tensor<1xf32>, index) -> () + + // Slice runs out-of-bounds due to size + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract_slice"(%arg0, %arg1, %arg2, %arg3) <{operandSegmentSizes = array, static_offsets = array, static_sizes = array, static_strides = array}> : (tensor, index, index, index) -> tensor + // CHECK-NEXT: ^ extract_slice runs out-of-bounds along dimension 0 + // CHECK-NEXT: Location: loc({{.*}}) + func.call @extract_slice_dynamic(%alloca_4_dyn, %0, %5, %1) : (tensor, index, index, index) -> () + + // Slice runs out-of-bounds due to stride + // CHECK: ERROR: Runtime op verification failed + // CHECK-NEXT: "tensor.extract_slice"(%arg0, %arg1, %arg2, %arg3) <{operandSegmentSizes = array, static_offsets = array, static_sizes = array, static_strides = array}> : (tensor, index, index, index) -> tensor + // CHECK-NEXT: ^ extract_slice runs out-of-bounds along dimension 0 + // CHECK-NEXT: Location: loc({{.*}}) + func.call @extract_slice_dynamic(%alloca_4_dyn, %0, %4, %4) : (tensor, index, index, index) -> () + + // CHECK-NOT: ERROR: Runtime op verification failed + func.call @extract_slice(%alloca, %0) : (tensor<1xf32>, index) -> () + + // CHECK-NOT: ERROR: Runtime op verification failed + func.call @extract_slice_dynamic(%alloca_4_dyn, %0, %4, %1) : (tensor, index, index, index) -> () + + // CHECK-NOT: ERROR: Runtime op verification failed + func.call @extract_slice_dynamic_rank_reduce(%alloca_4_dyn, %0, %1, %0) : (tensor, index, index, index) -> () + + + return +}