diff options
| author | Nicolas Vasilache <ntv@google.com> | 2018-11-20 08:36:07 -0800 |
|---|---|---|
| committer | jpienaar <jpienaar@google.com> | 2019-03-29 14:02:17 -0700 |
| commit | 89d9913a2071c3b50cb43f6bfda70cf58fe09b09 (patch) | |
| tree | 5d216444d93ca23f86ba039e110d8ab8ae72085d | |
| parent | f10f48ee6339bcd4af13fba98f89fac307d56530 (diff) | |
| download | bcm5719-llvm-89d9913a2071c3b50cb43f6bfda70cf58fe09b09.tar.gz bcm5719-llvm-89d9913a2071c3b50cb43f6bfda70cf58fe09b09.zip | |
[MLIR][VectorAnalysis] Add a VectorAnalysis and standalone tests
This CL adds some vector support in prevision of the upcoming vector
materialization pass. In particular this CL adds 2 functions to:
1. compute the multiplicity of a subvector shape in a supervector shape;
2. help match operations on strict super-vectors. This is defined for a given
subvector shape as an operation that manipulates a vector type that is an
integral multiple of the subtype, with multiplicity at least 2.
This CL also adds a TestUtil pass where we can dump arbitrary testing of
functions and analysis that operate at a much smaller granularity than a pass
(e.g. an analysis for which it is convenient to write a bit of artificial MLIR
and write some custom test). This is in order to keep using Filecheck for
things that essentially look and feel like C++ unit tests.
PiperOrigin-RevId: 222250910
| -rw-r--r-- | mlir/include/mlir/Analysis/LoopAnalysis.h | 4 | ||||
| -rw-r--r-- | mlir/include/mlir/Analysis/VectorAnalysis.h | 68 | ||||
| -rw-r--r-- | mlir/include/mlir/Support/Functional.h | 19 | ||||
| -rw-r--r-- | mlir/include/mlir/Transforms/Passes.h | 6 | ||||
| -rw-r--r-- | mlir/lib/Analysis/LoopAnalysis.cpp | 5 | ||||
| -rw-r--r-- | mlir/lib/Analysis/VectorAnalysis.cpp | 166 | ||||
| -rw-r--r-- | mlir/lib/Transforms/Vectorization/VectorizerTestPass.cpp | 116 | ||||
| -rw-r--r-- | mlir/lib/Transforms/Vectorize.cpp | 1 | ||||
| -rw-r--r-- | mlir/test/Transforms/vector_utils.mlir | 37 |
9 files changed, 409 insertions, 13 deletions
diff --git a/mlir/include/mlir/Analysis/LoopAnalysis.h b/mlir/include/mlir/Analysis/LoopAnalysis.h index 6ee38d28441..91c8e747836 100644 --- a/mlir/include/mlir/Analysis/LoopAnalysis.h +++ b/mlir/include/mlir/Analysis/LoopAnalysis.h @@ -32,10 +32,6 @@ class ForStmt; class MemRefType; class MLValue; -// TODO(ntv): Drop this once we have proper Ops. -static constexpr auto kVectorTransferReadOpName = "vector_transfer_read"; -static constexpr auto kVectorTransferWriteOpName = "vector_transfer_write"; - /// Returns the trip count of the loop as an affine expression if the latter is /// expressible as an affine expression, and nullptr otherwise. The trip count /// expression is simplified before returning. diff --git a/mlir/include/mlir/Analysis/VectorAnalysis.h b/mlir/include/mlir/Analysis/VectorAnalysis.h new file mode 100644 index 00000000000..82bffb8fa7d --- /dev/null +++ b/mlir/include/mlir/Analysis/VectorAnalysis.h @@ -0,0 +1,68 @@ +//===- VectorAnalysis.h - Analysis for Vectorization -------*- C++ -*-=======// +// +// Copyright 2019 The MLIR Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +#ifndef MLIR_ANALYSIS_VECTORANALYSIS_H_ +#define MLIR_ANALYSIS_VECTORANALYSIS_H_ + +#include "mlir/Support/LLVM.h" + +namespace mlir { + +class OperationStmt; +class VectorType; + +// TODO(ntv): Drop this once we have proper Ops. +static constexpr auto kVectorTransferReadOpName = "vector_transfer_read"; +static constexpr auto kVectorTransferWriteOpName = "vector_transfer_write"; +bool isaVectorTransferRead(const OperationStmt &stmt); +bool isaVectorTransferWrite(const OperationStmt &stmt); + +/// Computes and returns the multi-dimensional ratio of `superShape` to +/// `subShape`. This is calculated by performing a traversal from minor to major +/// dimensions (i.e. in reverse shape order). If integral division is not +/// possible, returns None. +/// +/// Examples: +/// - shapeRatio({3, 4, 5, 8}, {2, 5, 2}) returns {3, 2, 1, 4} +/// - shapeRatio({3, 4, 4, 8}, {2, 5, 2}) returns None +/// - shapeRatio({1, 2, 10, 32}, {2, 5, 2}) returns {1, 1, 2, 16} +llvm::Optional<llvm::SmallVector<unsigned, 4>> +shapeRatio(ArrayRef<int> superShape, ArrayRef<int> subShape); + +/// Computes and returns the multi-dimensional ratio of the shapes of +/// `superVecto` to `subVector`. If integral division is not possible, returns +/// None. +llvm::Optional<llvm::SmallVector<unsigned, 4>> +shapeRatio(VectorType superVectorType, VectorType subVectorType); + +namespace matcher { + +/// Matches vector_transfer_read, vector_transfer_write and ops that return a +/// vector type that is at least a 2-multiple of the sub-vector type. This +/// allows passing over other smaller vector types in the function and avoids +/// interfering with operations on those. +/// This is a first approximation, it can easily be extended in the future. +/// TODO(ntv): this could all be much simpler if we added a bit that a vector +/// type to mark that a vector is a strict super-vector but it still does not +/// warrant adding even 1 extra bit in the IR for now. +bool operatesOnStrictSuperVectors(const OperationStmt &stmt, + VectorType subVectorType); + +} // end namespace matcher +} // end namespace mlir + +#endif // MLIR_ANALYSIS_VECTORANALYSIS_H_ diff --git a/mlir/include/mlir/Support/Functional.h b/mlir/include/mlir/Support/Functional.h index e108e32c126..7740c5d27c9 100644 --- a/mlir/include/mlir/Support/Functional.h +++ b/mlir/include/mlir/Support/Functional.h @@ -18,10 +18,9 @@ #ifndef MLIR_SUPPORT_FUNCTIONAL_H_ #define MLIR_SUPPORT_FUNCTIONAL_H_ -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/iterator_range.h" -#include <functional> -#include <type_traits> +#include "llvm/ADT/STLExtras.h" +//#include <functional> +//#include <type_traits> /// This file provides some simple template functional-style sugar to operate /// on **value** types. Make sure when using that the stored type is cheap to @@ -64,12 +63,22 @@ void apply(Fun fun, IterType begin, IterType end) { } } -/// Map with templated container. +/// Apply with templated container. template <typename Fun, typename ContainerType> void apply(Fun fun, ContainerType input) { return apply(fun, std::begin(input), std::end(input)); } +/// Zip with 2 templated container. +/// TODO(ntv): make variadic. +template <typename Fun, typename ContainerType1, typename ContainerType2> +void zip(Fun fun, ContainerType1 input1, ContainerType2 input2) { + auto zipIter = llvm::zip(input1, input2); + for (auto it : zipIter) { + fun(std::get<0>(it), std::get<1>(it)); + } +} + /// Simple ScopeGuard. struct ScopeGuard { explicit ScopeGuard(std::function<void(void)> destruct) diff --git a/mlir/include/mlir/Transforms/Passes.h b/mlir/include/mlir/Transforms/Passes.h index 05aa04fc70b..fa509ac6337 100644 --- a/mlir/include/mlir/Transforms/Passes.h +++ b/mlir/include/mlir/Transforms/Passes.h @@ -37,9 +37,13 @@ FunctionPass *createConstantFoldPass(); FunctionPass *createCanonicalizerPass(); /// Creates a pass to vectorize loops, operations and data types using a -/// target-independent, n-D virtual vector abstraction. +/// target-independent, n-D super-vector abstraction. FunctionPass *createVectorizePass(); +/// Creates a pass to allow independent testing of vectorizer functionality with +/// FileCheck. +FunctionPass *createVectorizerTestPass(); + /// Creates a loop unrolling pass. Default option or command-line options take /// effect if -1 is passed as parameter. FunctionPass *createLoopUnrollPass(int unrollFactor = -1, int unrollFull = -1); diff --git a/mlir/lib/Analysis/LoopAnalysis.cpp b/mlir/lib/Analysis/LoopAnalysis.cpp index 78a8e2d7a46..8406a37d793 100644 --- a/mlir/lib/Analysis/LoopAnalysis.cpp +++ b/mlir/lib/Analysis/LoopAnalysis.cpp @@ -24,6 +24,7 @@ #include "mlir/Analysis/AffineAnalysis.h" #include "mlir/Analysis/AffineStructures.h" #include "mlir/Analysis/MLFunctionMatcher.h" +#include "mlir/Analysis/VectorAnalysis.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/Statements.h" @@ -193,9 +194,7 @@ static bool isVectorElement(LoadOrStoreOpPointer memoryOp) { // TODO(ntv): make the following into MLIR instructions, then use isa<>. static bool isVectorTransferReadOrWrite(const Statement &stmt) { const auto *opStmt = cast<OperationStmt>(&stmt); - llvm::SmallString<16> name(opStmt->getName().getStringRef()); - return name == kVectorTransferReadOpName || - name == kVectorTransferWriteOpName; + return isaVectorTransferRead(*opStmt) || isaVectorTransferWrite(*opStmt); } using VectorizableStmtFun = diff --git a/mlir/lib/Analysis/VectorAnalysis.cpp b/mlir/lib/Analysis/VectorAnalysis.cpp new file mode 100644 index 00000000000..f6bb9d4f516 --- /dev/null +++ b/mlir/lib/Analysis/VectorAnalysis.cpp @@ -0,0 +1,166 @@ +//===- VectorAnalysis.cpp - Analysis for Vectorization --------------------===// +// +// Copyright 2019 The MLIR Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= + +#include "mlir/Analysis/VectorAnalysis.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Statements.h" +#include "mlir/Support/Functional.h" +#include "mlir/Support/STLExtras.h" + +/// +/// Implements Analysis functions specific to vectors which support +/// the vectorization and vectorization materialization passes. +/// + +using namespace mlir; + +bool mlir::isaVectorTransferRead(const OperationStmt &stmt) { + return stmt.getName().getStringRef().str() == kVectorTransferReadOpName; +} + +bool mlir::isaVectorTransferWrite(const OperationStmt &stmt) { + return stmt.getName().getStringRef().str() == kVectorTransferWriteOpName; +} + +Optional<SmallVector<unsigned, 4>> mlir::shapeRatio(ArrayRef<int> superShape, + ArrayRef<int> subShape) { + if (superShape.size() < subShape.size()) { + return Optional<SmallVector<unsigned, 4>>(); + } + + // Starting from the end, compute the integer divisors. + // Set the boolean `divides` if integral division is not possible. + std::vector<unsigned> result; + result.reserve(superShape.size()); + bool divides = true; + auto divide = [÷s, &result](int superSize, int subSize) { + assert(superSize > 0 && "superSize must be > 0"); + assert(subSize > 0 && "subSize must be > 0"); + divides &= (superSize % subSize == 0); + result.push_back(superSize / subSize); + }; + functional::zip(divide, + SmallVector<int, 8>{superShape.rbegin(), superShape.rend()}, + SmallVector<int, 8>{subShape.rbegin(), subShape.rend()}); + + // If integral division does not occur, return and let the caller decide. + if (!divides) { + return Optional<SmallVector<unsigned, 4>>(); + } + + // At this point we computed the multiplicity (in reverse) for the common + // size. Fill with the remaining entries from the super-vector shape (still in + // reverse). + int commonSize = subShape.size(); + std::copy(superShape.rbegin() + commonSize, superShape.rend(), + std::back_inserter(result)); + + assert(result.size() == superShape.size() && + "multiplicity must be of the same size as the super-vector rank"); + + // Reverse again to get it back in the proper order and return. + return SmallVector<unsigned, 4>{result.rbegin(), result.rend()}; +} + +Optional<SmallVector<unsigned, 4>> mlir::shapeRatio(VectorType superVectorType, + VectorType subVectorType) { + assert(superVectorType.getElementType() == subVectorType.getElementType() && + "NYI: vector types must be of the same elemental type"); + assert(superVectorType.getElementType() == + Type::getF32(superVectorType.getContext()) && + "Only f32 supported for now"); + return shapeRatio(superVectorType.getShape(), subVectorType.getShape()); +} + +/// Matches vector_transfer_read, vector_transfer_write and ops that return a +/// vector type that is at least a 2-multiple of the sub-vector type size. +/// This allows leaving other vector types in the function untouched and avoids +/// interfering with operations on those. +/// This is a first approximation, it can easily be extended in the future. +/// TODO(ntv): this could all be much simpler if we added a bit that a vector +/// type to mark that a vector is a strict super-vector but it is not strictly +/// needed so let's avoid adding even 1 extra bit in the IR for now. +bool mlir::matcher::operatesOnStrictSuperVectors(const OperationStmt &opStmt, + VectorType subVectorType) { + // First, extract the vector type and ditinguish between: + // a. ops that *must* lower a super-vector (i.e. vector_transfer_read, + // vector_transfer_write); and + // b. ops that *may* lower a super-vector (all other ops). + // The ops that *may* lower a super-vector only do so if the vector size is + // an integer multiple of the HW vector size, with multiplicity 1. + // The ops that *must* lower a super-vector are explicitly checked for this + // property. + /// TODO(ntv): there should be a single function for all ops to do this so we + /// do not have to special case. Maybe a trait, or just a method, unclear atm. + bool mustDivide = false; + VectorType superVectorType; + if (isaVectorTransferRead(opStmt)) { + superVectorType = opStmt.getResult(0)->getType().cast<VectorType>(); + mustDivide = true; + } else if (isaVectorTransferWrite(opStmt)) { + // TODO(ntv): if vector_transfer_write had store-like semantics we could + // have written something similar to: + // auto store = storeOp->cast<StoreOp>(); + // auto *value = store->getValueToStore(); + superVectorType = opStmt.getOperand(0)->getType().cast<VectorType>(); + mustDivide = true; + } else if (opStmt.getNumResults() == 0) { + assert(opStmt.dyn_cast<ReturnOp>() && + "NYI: assuming only return statements can have 0 results at this " + "point"); + return false; + } else if (opStmt.getNumResults() == 1) { + if (auto v = opStmt.getResult(0)->getType().dyn_cast<VectorType>()) { + superVectorType = v; + } else { + // Not a vector type. + return false; + } + } else { + // Not a vector_transfer and has more than 1 result, fail hard for now to + // wake us up when something changes. + assert(false && "NYI: statement has more than 1 result"); + return false; + } + + // Get the multiplicity. + auto multiplicity = shapeRatio(superVectorType, subVectorType); + + // Sanity check. + assert((multiplicity.hasValue() || !mustDivide) && + "NYI: vector_transfer instruction in which super-vector size is not an" + " integer multiple of sub-vector size"); + + // This catches cases that are not strictly necessary to have multiplicity but + // still aren't divisible by the sub-vector shape. + // This could be useful information if we wanted to reshape at the level of + // the vector type (but we would have to look at the compute and distinguish + // between parallel, reduction and possibly other cases. + if (!multiplicity.hasValue()) { + return false; + } + + // A strict super-vector is at least 2 sub-vectors. + for (auto m : *multiplicity) { + if (m > 1) { + return true; + } + } + + // Not a strict super-vector. + return false; +} diff --git a/mlir/lib/Transforms/Vectorization/VectorizerTestPass.cpp b/mlir/lib/Transforms/Vectorization/VectorizerTestPass.cpp new file mode 100644 index 00000000000..dcd0a072ed3 --- /dev/null +++ b/mlir/lib/Transforms/Vectorization/VectorizerTestPass.cpp @@ -0,0 +1,116 @@ +//===- VectorizerTestPass.cpp - VectorizerTestPass Pass Impl ----*- C++ +//-*-====================// +// +// Copyright 2019 The MLIR Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================= +// +// This file implements a simple testing pass for vectorization functionality. +// +//===----------------------------------------------------------------------===// + +#include "mlir/Analysis/MLFunctionMatcher.h" +#include "mlir/Analysis/VectorAnalysis.h" +#include "mlir/Pass.h" +#include "mlir/Support/LLVM.h" +#include "mlir/Support/STLExtras.h" +#include "mlir/Transforms/Passes.h" + +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Debug.h" + +using namespace mlir; + +using llvm::outs; +using llvm::cl::desc; +using llvm::cl::list; +using llvm::cl::ZeroOrMore; + +static list<int> clTestVectorMultiplicity( + "vector-multiplicity", desc("Specify the HW vector size for vectorization"), + ZeroOrMore); + +#define DEBUG_TYPE "vectorizer-test" + +namespace { + +struct VectorizerTestPass : public FunctionPass { + VectorizerTestPass() : FunctionPass(&VectorizerTestPass::passID) {} + + PassResult runOnMLFunction(MLFunction *f) override; + void testVectorMultiplicity(MLFunction *f); + + // Thread-safe RAII contexts local to pass, BumpPtrAllocator freed on exit. + MLFunctionMatcherContext MLContext; + + static char passID; +}; + +} // end anonymous namespace + +char VectorizerTestPass::passID = 0; + +void VectorizerTestPass::testVectorMultiplicity(MLFunction *f) { + using matcher::Op; + SmallVector<int, 8> shape(clTestVectorMultiplicity.begin(), + clTestVectorMultiplicity.end()); + auto subVectorType = VectorType::get(shape, Type::getF32(f->getContext())); + // Only filter statements that operate on a strict super-vector and have one + // return. This makes testing easier. + auto filter = [subVectorType](const Statement &stmt) { + outs() << "\ntest: " << stmt << " "; + auto *opStmt = dyn_cast<OperationStmt>(&stmt); + if (!opStmt) { + return false; + } + if (!matcher::operatesOnStrictSuperVectors(*opStmt, subVectorType)) { + return false; + } + if (opStmt->getNumResults() != 1) { + return false; + } + return true; + }; + auto pat = Op(filter); + auto matches = pat.match(f); + for (auto m : matches) { + auto *opStmt = cast<OperationStmt>(m.first); + // This is a unit test that only checks and prints shape ratio. + // As a consequence we write only Ops with a single return type for the + // purpose of this test. If we need to test more intricate behavior in the + // future we can always extend. + auto superVectorType = opStmt->getResult(0)->getType().cast<VectorType>(); + auto multiplicity = shapeRatio(superVectorType, subVectorType); + assert(multiplicity.hasValue() && "Expected multiplicity"); + outs() << "\nmatched: " << *opStmt << " with multiplicity: "; + interleaveComma(MutableArrayRef<unsigned>(*multiplicity), outs()); + } +} + +PassResult VectorizerTestPass::runOnMLFunction(MLFunction *f) { + if (!clTestVectorMultiplicity.empty()) { + testVectorMultiplicity(f); + } + + return PassResult::Success; +} + +FunctionPass *mlir::createVectorizerTestPass() { + return new VectorizerTestPass(); +} + +static PassRegistration<VectorizerTestPass> + pass("vectorizer-test", "Tests vectorizer standalone functionality."); + +#undef DEBUG_TYPE diff --git a/mlir/lib/Transforms/Vectorize.cpp b/mlir/lib/Transforms/Vectorize.cpp index d72353e9a87..7763c2d9471 100644 --- a/mlir/lib/Transforms/Vectorize.cpp +++ b/mlir/lib/Transforms/Vectorize.cpp @@ -22,6 +22,7 @@ #include "mlir/Analysis/LoopAnalysis.h" #include "mlir/Analysis/MLFunctionMatcher.h" +#include "mlir/Analysis/VectorAnalysis.h" #include "mlir/IR/AffineExpr.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" diff --git a/mlir/test/Transforms/vector_utils.mlir b/mlir/test/Transforms/vector_utils.mlir new file mode 100644 index 00000000000..08810935334 --- /dev/null +++ b/mlir/test/Transforms/vector_utils.mlir @@ -0,0 +1,37 @@ +// RUN: mlir-opt %s -vectorizer-test -vector-multiplicity 4 -vector-multiplicity 8 | FileCheck %s +// RUN: mlir-opt %s -vectorizer-test -vector-multiplicity 2 -vector-multiplicity 5 -vector-multiplicity 2 | FileCheck %s -check-prefix=TEST-3x4x5x8 + +mlfunc @vector_add_2d(%arg0 : index, %arg1 : index) -> f32 { + // Nothing should be matched in this first block. + // CHECK-NOT:matched: {{.*}} = alloc{{.*}} + // CHECK-NOT:matched: {{.*}} = constant 0{{.*}} + // CHECK-NOT:matched: {{.*}} = constant 1{{.*}} + %0 = alloc(%arg0, %arg1) : memref<?x?xf32> + %1 = alloc(%arg0, %arg1) : memref<?x?xf32> + %2 = alloc(%arg0, %arg1) : memref<?x?xf32> + %c0 = constant 0 : index + %cst = constant 1.000000e+00 : f32 + + // CHECK:matched: {{.*}} constant splat{{.*}} with multiplicity: 2, 32 + %cst_1 = constant splat<vector<8x256xf32>, 1.000000e+00> : vector<8x256xf32> + // CHECK:matched: {{.*}} constant splat{{.*}} with multiplicity: 1, 3, 7, 2, 1 + %cst_a = constant splat<vector<1x3x7x8x8xf32>, 1.000000e+00> : vector<1x3x7x8x8xf32> + // CHECK-NOT:matched: {{.*}} constant splat{{.*}} with multiplicity: 1, 3, 7, 1{{.*}} + %cst_b = constant splat<vector<1x3x7x4x4xf32>, 1.000000e+00> : vector<1x3x7x8x8xf32> + // TEST-3x4x5x8:matched: {{.*}} constant splat{{.*}} with multiplicity: 3, 2, 1, 4 + %cst_c = constant splat<vector<3x4x5x8xf32>, 1.000000e+00> : vector<3x4x5x8xf32> + // TEST-3x4x4x8-NOT:matched: {{.*}} constant splat{{.*}} with multiplicity{{.*}} + %cst_d = constant splat<vector<3x4x4x8xf32>, 1.000000e+00> : vector<3x4x4x8xf32> + // TEST-3x4x4x8:matched: {{.*}} constant splat{{.*}} with multiplicity: 1, 1, 2, 16 + %cst_e = constant splat<vector<1x2x10x32xf32>, 1.000000e+00> : vector<1x2x10x32xf32> + + // Nothing should be matched in this last block. + // CHECK-NOT:matched: {{.*}} = constant 7{{.*}} + // CHECK-NOT:matched: {{.*}} = constant 42{{.*}} + // CHECK-NOT:matched: {{.*}} = load{{.*}} + // CHECK-NOT:matched: return {{.*}} + %c7 = constant 7 : index + %c42 = constant 42 : index + %9 = load %2[%c7, %c42] : memref<?x?xf32> + return %9 : f32 +}
\ No newline at end of file |

