1 //===- VectorizerTestPass.cpp - VectorizerTestPass Pass Impl --------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file implements a simple testing pass for vectorization functionality. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "mlir/Analysis/AffineAnalysis.h" 14 #include "mlir/Analysis/NestedMatcher.h" 15 #include "mlir/Analysis/SliceAnalysis.h" 16 #include "mlir/Dialect/Affine/IR/AffineOps.h" 17 #include "mlir/Dialect/Vector/VectorOps.h" 18 #include "mlir/Dialect/Vector/VectorUtils.h" 19 #include "mlir/IR/Builders.h" 20 #include "mlir/IR/Diagnostics.h" 21 #include "mlir/IR/StandardTypes.h" 22 #include "mlir/Pass/Pass.h" 23 #include "mlir/Transforms/Passes.h" 24 25 #include "llvm/ADT/STLExtras.h" 26 #include "llvm/Support/CommandLine.h" 27 #include "llvm/Support/Debug.h" 28 29 #define DEBUG_TYPE "affine-super-vectorizer-test" 30 31 using namespace mlir; 32 33 using llvm::SetVector; 34 35 static llvm::cl::OptionCategory clOptionsCategory(DEBUG_TYPE " options"); 36 37 static llvm::cl::list<int> clTestVectorShapeRatio( 38 "vector-shape-ratio", 39 llvm::cl::desc("Specify the HW vector size for vectorization"), 40 llvm::cl::ZeroOrMore, llvm::cl::cat(clOptionsCategory)); 41 static llvm::cl::opt<bool> clTestForwardSlicingAnalysis( 42 "forward-slicing", 43 llvm::cl::desc("Enable testing forward static slicing and topological sort " 44 "functionalities"), 45 llvm::cl::cat(clOptionsCategory)); 46 static llvm::cl::opt<bool> clTestBackwardSlicingAnalysis( 47 "backward-slicing", 48 llvm::cl::desc("Enable testing backward static slicing and " 49 "topological sort functionalities"), 50 llvm::cl::cat(clOptionsCategory)); 51 static llvm::cl::opt<bool> clTestSlicingAnalysis( 52 "slicing", 53 llvm::cl::desc("Enable testing static slicing and topological sort " 54 "functionalities"), 55 llvm::cl::cat(clOptionsCategory)); 56 static llvm::cl::opt<bool> clTestComposeMaps( 57 "compose-maps", 58 llvm::cl::desc( 59 "Enable testing the composition of AffineMap where each " 60 "AffineMap in the composition is specified as the affine_map attribute " 61 "in a constant op."), 62 llvm::cl::cat(clOptionsCategory)); 63 static llvm::cl::opt<bool> clTestNormalizeMaps( 64 "normalize-maps", 65 llvm::cl::desc( 66 "Enable testing the normalization of AffineAffineApplyOp " 67 "where each AffineAffineApplyOp in the composition is a single output " 68 "operation."), 69 llvm::cl::cat(clOptionsCategory)); 70 71 namespace { 72 struct VectorizerTestPass 73 : public PassWrapper<VectorizerTestPass, FunctionPass> { 74 static constexpr auto kTestAffineMapOpName = "test_affine_map"; 75 static constexpr auto kTestAffineMapAttrName = "affine_map"; 76 void getDependentDialects(DialectRegistry ®istry) const override { 77 registry.insert<vector::VectorDialect>(); 78 } 79 80 void runOnFunction() override; 81 void testVectorShapeRatio(llvm::raw_ostream &outs); 82 void testForwardSlicing(llvm::raw_ostream &outs); 83 void testBackwardSlicing(llvm::raw_ostream &outs); 84 void testSlicing(llvm::raw_ostream &outs); 85 void testComposeMaps(llvm::raw_ostream &outs); 86 void testNormalizeMaps(); 87 }; 88 89 } // end anonymous namespace 90 91 void VectorizerTestPass::testVectorShapeRatio(llvm::raw_ostream &outs) { 92 auto f = getFunction(); 93 using matcher::Op; 94 SmallVector<int64_t, 8> shape(clTestVectorShapeRatio.begin(), 95 clTestVectorShapeRatio.end()); 96 auto subVectorType = 97 VectorType::get(shape, FloatType::getF32(f.getContext())); 98 // Only filter operations that operate on a strict super-vector and have one 99 // return. This makes testing easier. 100 auto filter = [&](Operation &op) { 101 assert(subVectorType.getElementType().isF32() && 102 "Only f32 supported for now"); 103 if (!matcher::operatesOnSuperVectorsOf(op, subVectorType)) { 104 return false; 105 } 106 if (op.getNumResults() != 1) { 107 return false; 108 } 109 return true; 110 }; 111 auto pat = Op(filter); 112 SmallVector<NestedMatch, 8> matches; 113 pat.match(f, &matches); 114 for (auto m : matches) { 115 auto *opInst = m.getMatchedOperation(); 116 // This is a unit test that only checks and prints shape ratio. 117 // As a consequence we write only Ops with a single return type for the 118 // purpose of this test. If we need to test more intricate behavior in the 119 // future we can always extend. 120 auto superVectorType = opInst->getResult(0).getType().cast<VectorType>(); 121 auto ratio = shapeRatio(superVectorType, subVectorType); 122 if (!ratio.hasValue()) { 123 opInst->emitRemark("NOT MATCHED"); 124 } else { 125 outs << "\nmatched: " << *opInst << " with shape ratio: "; 126 llvm::interleaveComma(MutableArrayRef<int64_t>(*ratio), outs); 127 } 128 } 129 } 130 131 static NestedPattern patternTestSlicingOps() { 132 using matcher::Op; 133 // Match all operations with the kTestSlicingOpName name. 134 auto filter = [](Operation &op) { 135 // Just use a custom op name for this test, it makes life easier. 136 return op.getName().getStringRef() == "slicing-test-op"; 137 }; 138 return Op(filter); 139 } 140 141 void VectorizerTestPass::testBackwardSlicing(llvm::raw_ostream &outs) { 142 auto f = getFunction(); 143 outs << "\n" << f.getName(); 144 145 SmallVector<NestedMatch, 8> matches; 146 patternTestSlicingOps().match(f, &matches); 147 for (auto m : matches) { 148 SetVector<Operation *> backwardSlice; 149 getBackwardSlice(m.getMatchedOperation(), &backwardSlice); 150 outs << "\nmatched: " << *m.getMatchedOperation() 151 << " backward static slice: "; 152 for (auto *op : backwardSlice) 153 outs << "\n" << *op; 154 } 155 } 156 157 void VectorizerTestPass::testForwardSlicing(llvm::raw_ostream &outs) { 158 auto f = getFunction(); 159 outs << "\n" << f.getName(); 160 161 SmallVector<NestedMatch, 8> matches; 162 patternTestSlicingOps().match(f, &matches); 163 for (auto m : matches) { 164 SetVector<Operation *> forwardSlice; 165 getForwardSlice(m.getMatchedOperation(), &forwardSlice); 166 outs << "\nmatched: " << *m.getMatchedOperation() 167 << " forward static slice: "; 168 for (auto *op : forwardSlice) 169 outs << "\n" << *op; 170 } 171 } 172 173 void VectorizerTestPass::testSlicing(llvm::raw_ostream &outs) { 174 auto f = getFunction(); 175 outs << "\n" << f.getName(); 176 177 SmallVector<NestedMatch, 8> matches; 178 patternTestSlicingOps().match(f, &matches); 179 for (auto m : matches) { 180 SetVector<Operation *> staticSlice = getSlice(m.getMatchedOperation()); 181 outs << "\nmatched: " << *m.getMatchedOperation() << " static slice: "; 182 for (auto *op : staticSlice) 183 outs << "\n" << *op; 184 } 185 } 186 187 static bool customOpWithAffineMapAttribute(Operation &op) { 188 return op.getName().getStringRef() == 189 VectorizerTestPass::kTestAffineMapOpName; 190 } 191 192 void VectorizerTestPass::testComposeMaps(llvm::raw_ostream &outs) { 193 auto f = getFunction(); 194 195 using matcher::Op; 196 auto pattern = Op(customOpWithAffineMapAttribute); 197 SmallVector<NestedMatch, 8> matches; 198 pattern.match(f, &matches); 199 SmallVector<AffineMap, 4> maps; 200 maps.reserve(matches.size()); 201 for (auto m : llvm::reverse(matches)) { 202 auto *opInst = m.getMatchedOperation(); 203 auto map = opInst->getAttr(VectorizerTestPass::kTestAffineMapAttrName) 204 .cast<AffineMapAttr>() 205 .getValue(); 206 maps.push_back(map); 207 } 208 AffineMap res; 209 for (auto m : maps) { 210 res = res ? res.compose(m) : m; 211 } 212 simplifyAffineMap(res).print(outs << "\nComposed map: "); 213 } 214 215 static bool affineApplyOp(Operation &op) { return isa<AffineApplyOp>(op); } 216 217 static bool singleResultAffineApplyOpWithoutUses(Operation &op) { 218 auto app = dyn_cast<AffineApplyOp>(op); 219 return app && app.use_empty(); 220 } 221 222 void VectorizerTestPass::testNormalizeMaps() { 223 using matcher::Op; 224 225 auto f = getFunction(); 226 227 // Save matched AffineApplyOp that all need to be erased in the end. 228 auto pattern = Op(affineApplyOp); 229 SmallVector<NestedMatch, 8> toErase; 230 pattern.match(f, &toErase); 231 { 232 // Compose maps. 233 auto pattern = Op(singleResultAffineApplyOpWithoutUses); 234 SmallVector<NestedMatch, 8> matches; 235 pattern.match(f, &matches); 236 for (auto m : matches) { 237 auto app = cast<AffineApplyOp>(m.getMatchedOperation()); 238 OpBuilder b(m.getMatchedOperation()); 239 SmallVector<Value, 8> operands(app.getOperands()); 240 makeComposedAffineApply(b, app.getLoc(), app.getAffineMap(), operands); 241 } 242 } 243 // We should now be able to erase everything in reverse order in this test. 244 for (auto m : llvm::reverse(toErase)) { 245 m.getMatchedOperation()->erase(); 246 } 247 } 248 249 void VectorizerTestPass::runOnFunction() { 250 // Thread-safe RAII local context, BumpPtrAllocator freed on exit. 251 NestedPatternContext mlContext; 252 253 // Only support single block functions at this point. 254 FuncOp f = getFunction(); 255 if (!llvm::hasSingleElement(f)) 256 return; 257 258 std::string str; 259 llvm::raw_string_ostream outs(str); 260 261 if (!clTestVectorShapeRatio.empty()) 262 testVectorShapeRatio(outs); 263 264 if (clTestForwardSlicingAnalysis) 265 testForwardSlicing(outs); 266 267 if (clTestBackwardSlicingAnalysis) 268 testBackwardSlicing(outs); 269 270 if (clTestSlicingAnalysis) 271 testSlicing(outs); 272 273 if (clTestComposeMaps) 274 testComposeMaps(outs); 275 276 if (clTestNormalizeMaps) 277 testNormalizeMaps(); 278 279 if (!outs.str().empty()) { 280 emitRemark(UnknownLoc::get(&getContext()), outs.str()); 281 } 282 } 283 284 namespace mlir { 285 void registerVectorizerTestPass() { 286 PassRegistration<VectorizerTestPass> pass( 287 "affine-super-vectorizer-test", 288 "Tests vectorizer standalone functionality."); 289 } 290 } // namespace mlir 291