1 //===- Tiling.cpp - Implementation of linalg Tiling -----------------------===// 2 // 3 // Copyright 2019 The MLIR Authors. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 // ============================================================================= 17 // 18 // This file implements the linalg dialect Tiling pass. 19 // 20 //===----------------------------------------------------------------------===// 21 22 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 23 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 24 #include "mlir/Dialect/Linalg/Passes.h" 25 #include "mlir/Dialect/Linalg/Utils/Intrinsics.h" 26 #include "mlir/Dialect/Linalg/Utils/Utils.h" 27 #include "mlir/Dialect/LoopOps/LoopOps.h" 28 #include "mlir/EDSC/Helpers.h" 29 #include "mlir/IR/AffineExpr.h" 30 #include "mlir/IR/AffineExprVisitor.h" 31 #include "mlir/IR/AffineMap.h" 32 #include "mlir/IR/OpImplementation.h" 33 #include "mlir/Pass/Pass.h" 34 #include "mlir/Support/LLVM.h" 35 #include "mlir/Support/STLExtras.h" 36 #include "mlir/Transforms/FoldUtils.h" 37 38 #include "llvm/Support/CommandLine.h" 39 40 using namespace mlir; 41 using namespace mlir::edsc; 42 using namespace mlir::edsc::intrinsics; 43 using namespace mlir::linalg; 44 using namespace mlir::linalg::intrinsics; 45 using namespace mlir::loop; 46 47 #define DEBUG_TYPE "linalg-tiling" 48 49 static llvm::cl::OptionCategory clOptionsCategory(DEBUG_TYPE " options"); 50 static llvm::cl::list<unsigned> 51 clTileSizes("linalg-tile-sizes", 52 llvm::cl::desc("Tile sizes by which to tile linalg operations"), 53 llvm::cl::ZeroOrMore, llvm::cl::MiscFlags::CommaSeparated, 54 llvm::cl::cat(clOptionsCategory)); 55 56 static bool isZero(Value *v) { 57 return isa_and_nonnull<ConstantIndexOp>(v->getDefiningOp()) && 58 cast<ConstantIndexOp>(v->getDefiningOp()).getValue() == 0; 59 } 60 61 // Creates a number of ranges equal to the number of non-zero in `tileSizes`. 62 // One for each loop of the LinalgOp that is tiled. The `tileSizes` argument has 63 // one entry per surrounding loop. It uses zero as the convention that a 64 // particular loop is not tiled. This convention simplifies implementations by 65 // avoiding affine map manipulations. 66 // The returned ranges correspond to the loop ranges, in the proper order, that 67 // are tiled and for which new loops will be created. 68 static SmallVector<SubViewOp::Range, 4> 69 makeTiledLoopRanges(OpBuilder &b, Location loc, AffineMap map, 70 ArrayRef<Value *> allViewSizes, 71 ArrayRef<Value *> allTileSizes, OperationFolder *folder) { 72 assert(allTileSizes.size() == map.getNumResults()); 73 // Apply `map` to get view sizes in loop order. 74 auto viewSizes = applyMapToValues(b, loc, map, allViewSizes, folder); 75 SmallVector<Value *, 4> tileSizes(allTileSizes.begin(), allTileSizes.end()); 76 77 // Traverse the tile sizes, which are in loop order, erase zeros everywhere. 78 for (int idx = tileSizes.size() - 1; idx >= 0; --idx) { 79 if (isZero(tileSizes[idx])) { 80 viewSizes.erase(viewSizes.begin() + idx); 81 tileSizes.erase(tileSizes.begin() + idx); 82 } 83 } 84 85 // Create a new range with the applied tile sizes. 86 SmallVector<SubViewOp::Range, 4> res; 87 for (unsigned idx = 0, e = tileSizes.size(); idx < e; ++idx) { 88 res.push_back(SubViewOp::Range{constant_index(folder, 0), viewSizes[idx], 89 tileSizes[idx]}); 90 } 91 return res; 92 } 93 94 namespace { 95 // Helper visitor to determine whether an AffineExpr is tiled. 96 // This is achieved by traversing every AffineDimExpr with position `pos` and 97 // checking whether the corresponding `tileSizes[pos]` is non-zero. 98 // This also enforces only positive coefficients occur in multiplications. 99 // 100 // Example: 101 // `d0 + 2 * d1 + d3` is tiled by [0, 0, 0, 2] but not by [0, 0, 2, 0] 102 // 103 struct TileCheck : public AffineExprVisitor<TileCheck> { 104 TileCheck(ArrayRef<Value *> tileSizes) 105 : isTiled(false), tileSizes(tileSizes) {} 106 107 void visitDimExpr(AffineDimExpr expr) { 108 isTiled |= !isZero(tileSizes[expr.getPosition()]); 109 } 110 void visitAffineBinaryOpExpr(AffineBinaryOpExpr expr) { 111 visit(expr.getLHS()); 112 visit(expr.getRHS()); 113 if (expr.getKind() == mlir::AffineExprKind::Mul) 114 assert(expr.getRHS().cast<AffineConstantExpr>().getValue() > 0 && 115 "nonpositive multiplying coefficient"); 116 } 117 bool isTiled; 118 ArrayRef<Value *> tileSizes; 119 }; 120 } // namespace 121 122 static bool isTiled(AffineExpr expr, ArrayRef<Value *> tileSizes) { 123 if (!expr) 124 return false; 125 TileCheck t(tileSizes); 126 t.visit(expr); 127 return t.isTiled; 128 } 129 130 // Checks whether the view with index `viewIndex` within `linalgOp` varies with 131 // respect to a non-zero `tileSize`. 132 static bool isTiled(AffineMap map, ArrayRef<Value *> tileSizes) { 133 if (!map) 134 return false; 135 for (unsigned r = 0; r < map.getNumResults(); ++r) 136 if (isTiled(map.getResult(r), tileSizes)) 137 return true; 138 return false; 139 } 140 141 static SmallVector<Value *, 4> 142 makeTiledViews(OpBuilder &b, Location loc, LinalgOp linalgOp, 143 ArrayRef<Value *> ivs, ArrayRef<Value *> tileSizes, 144 ArrayRef<Value *> viewSizes, OperationFolder *folder) { 145 assert(ivs.size() == static_cast<size_t>(llvm::count_if( 146 llvm::make_range(tileSizes.begin(), tileSizes.end()), 147 [](Value *v) { return !isZero(v); })) && 148 "expected as many ivs as non-zero sizes"); 149 150 using edsc::intrinsics::select; 151 using edsc::op::operator+; 152 using edsc::op::operator<; 153 154 // Construct (potentially temporary) mins and maxes on which to apply maps 155 // that define tile subviews. 156 SmallVector<Value *, 8> lbs, subViewSizes; 157 for (unsigned idx = 0, idxIvs = 0, e = tileSizes.size(); idx < e; ++idx) { 158 bool isTiled = !isZero(tileSizes[idx]); 159 lbs.push_back(isTiled ? ivs[idxIvs++] : (Value *)constant_index(folder, 0)); 160 subViewSizes.push_back(isTiled ? tileSizes[idx] : viewSizes[idx]); 161 } 162 163 auto *op = linalgOp.getOperation(); 164 165 SmallVector<Value *, 4> res; 166 res.reserve(op->getNumOperands()); 167 auto viewIteratorBegin = linalgOp.getInputsAndOutputs().begin(); 168 for (unsigned viewIndex = 0; viewIndex < linalgOp.getNumInputsAndOutputs(); 169 ++viewIndex) { 170 Value *view = *(viewIteratorBegin + viewIndex); 171 unsigned rank = view->getType().cast<MemRefType>().getRank(); 172 auto map = loopToOperandRangesMaps(linalgOp)[viewIndex]; 173 // If the view is not tiled, we can use it as is. 174 if (!isTiled(map, tileSizes)) { 175 res.push_back(view); 176 continue; 177 } 178 179 // Construct a new subview for the tile. 180 SmallVector<Value *, 4> offsets, sizes, strides; 181 offsets.reserve(rank); 182 sizes.reserve(rank); 183 strides.reserve(rank); 184 for (unsigned r = 0; r < rank; ++r) { 185 if (!isTiled(map.getSubMap({r}), tileSizes)) { 186 offsets.push_back(constant_index(folder, 0)); 187 sizes.push_back(dim(view, r)); 188 strides.push_back(constant_index(folder, 1)); 189 continue; 190 } 191 192 // Tiling creates a new slice at the proper index, the slice step is 1 193 // (i.e. the slice view does not subsample, stepping occurs in the loop). 194 auto m = map.getSubMap({r}); 195 auto *offset = applyMapToValues(b, loc, m, lbs, folder).front(); 196 offsets.push_back(offset); 197 auto *size = applyMapToValues(b, loc, m, subViewSizes, folder).front(); 198 sizes.push_back(size); 199 strides.push_back(constant_index(folder, 1)); 200 } 201 // TODO(b/144419024) Atm std.subview is not guaranteed in-bounds. Depending 202 // on the semantics we attach to it, we may need to use min(size, dim) here 203 // and canonicalize later. 204 res.push_back(b.create<SubViewOp>(loc, view, offsets, sizes, strides)); 205 } 206 207 // Traverse the mins/maxes and erase those that don't have uses left. 208 // This is a special type of folding that we only apply when `folder` is 209 // defined. 210 if (folder) 211 for (auto *v : llvm::concat<Value *>(lbs, subViewSizes)) 212 if (v->use_empty()) 213 v->getDefiningOp()->erase(); 214 215 return res; 216 } 217 218 void applyPermutationToLoopRanges(SmallVector<SubViewOp::Range, 4> &loopRanges, 219 ArrayRef<unsigned> permutation) { 220 SmallVector<SubViewOp::Range, 4> auxVec(loopRanges.size()); 221 for (unsigned i = 0; i < permutation.size(); ++i) 222 auxVec[i] = loopRanges[permutation[i]]; 223 loopRanges = auxVec; 224 } 225 226 llvm::Optional<TiledLinalgOp> mlir::linalg::tileLinalgOp( 227 OpBuilder &b, LinalgOp op, ArrayRef<Value *> tileSizes, 228 ArrayRef<unsigned> permutation, OperationFolder *folder) { 229 // 1. Enforce the convention that "tiling by zero" skips tiling a particular 230 // dimension. This convention is significantly simpler to handle instead of 231 // adjusting affine maps to account for missing dimensions. 232 assert(op.getNumParallelLoops() + op.getNumReductionLoops() + 233 op.getNumWindowLoops() == 234 tileSizes.size() && 235 "expected matching number of tile sizes and loops"); 236 237 // If permutation is empty, use the identity. Build the permutation map 238 // otherwise. 239 auto invPermutationMap = AffineMap::getMultiDimIdentityMap( 240 tileSizes.size(), ScopedContext::getContext()); 241 if (!permutation.empty()) 242 invPermutationMap = inversePermutation( 243 AffineMap::getPermutationMap(permutation, ScopedContext::getContext())); 244 245 OpBuilder::InsertionGuard g(b); 246 b.setInsertionPoint(op); 247 ScopedContext scope(b, op.getLoc()); 248 // 2. Build the tiled loop ranges. 249 auto viewSizes = getViewSizes(op); 250 // The flattened loopToOperandRangesMaps is expected to be an invertible 251 // permutation map (asserted in the inverse calculation). 252 auto viewSizesToLoopsMap = 253 inversePermutation(concatAffineMaps(loopToOperandRangesMaps(op))); 254 assert(viewSizesToLoopsMap && "expected invertible map"); 255 auto loopRanges = 256 makeTiledLoopRanges(b, scope.getLocation(), viewSizesToLoopsMap, 257 viewSizes, tileSizes, folder); 258 if (!permutation.empty()) 259 applyPermutationToLoopRanges(loopRanges, permutation); 260 261 // 3. Create the tiled loops. 262 LinalgOp res = op; 263 SmallVector<IndexHandle, 4> ivs(loopRanges.size()); 264 auto pivs = makeIndexHandlePointers(ivs); 265 LoopNestRangeBuilder(pivs, loopRanges)([&] { 266 auto b = ScopedContext::getBuilder(); 267 auto loc = ScopedContext::getLocation(); 268 SmallVector<Value *, 4> ivValues(ivs.begin(), ivs.end()); 269 270 // If we have to apply a permutation to the tiled loop nest, we have to 271 // reorder the induction variables This permutation is the right one 272 // assuming that loopRanges have previously been permuted by 273 // (i,j,k)->(k,i,j) So this permutation should be the inversePermutation of 274 // that one: (d0,d1,d2)->(d2,d0,d1) 275 if (!permutation.empty()) 276 ivValues = applyMapToValues(b, loc, invPermutationMap, ivValues, folder); 277 278 auto views = 279 makeTiledViews(b, loc, op, ivValues, tileSizes, viewSizes, folder); 280 auto operands = getAssumedNonViewOperands(op); 281 views.append(operands.begin(), operands.end()); 282 res = op.clone(b, loc, views); 283 }); 284 285 // 4. Gather the newly created loops and return them with the new op. 286 SmallVector<ForOp, 8> loops; 287 loops.reserve(ivs.size()); 288 for (auto iv : ivs) 289 loops.push_back(loop::getForInductionVarOwner(iv)); 290 291 return TiledLinalgOp{res, loops}; 292 } 293 294 llvm::Optional<TiledLinalgOp> mlir::linalg::tileLinalgOp( 295 OpBuilder &b, LinalgOp op, ArrayRef<int64_t> tileSizes, 296 ArrayRef<unsigned> permutation, OperationFolder *folder) { 297 if (tileSizes.empty()) 298 return llvm::None; 299 300 // The following uses the convention that "tiling by zero" skips tiling a 301 // particular dimension. This convention is significantly simpler to handle 302 // instead of adjusting affine maps to account for missing dimensions. 303 auto nLoops = op.getNumParallelLoops() + op.getNumReductionLoops() + 304 op.getNumWindowLoops(); 305 tileSizes = tileSizes.take_front(nLoops); 306 // If only 0 tilings are left, then return. 307 if (llvm::all_of(tileSizes, [](int64_t v) { return v == 0; })) 308 return llvm::None; 309 310 // Create a builder for tile size constants. 311 OpBuilder::InsertionGuard g(b); 312 b.setInsertionPoint(op); 313 ScopedContext scope(b, op.getLoc()); 314 315 // Materialize concrete tile size values to pass the generic tiling function. 316 SmallVector<Value *, 8> tileSizeValues; 317 tileSizeValues.reserve(tileSizes.size()); 318 for (auto ts : tileSizes) 319 tileSizeValues.push_back(constant_index(folder, ts)); 320 // Pad tile sizes with zero values to enforce our convention. 321 if (tileSizeValues.size() < nLoops) { 322 for (unsigned i = tileSizeValues.size(); i < nLoops; ++i) 323 tileSizeValues.push_back(constant_index(folder, 0)); 324 } 325 326 return tileLinalgOp(b, op, tileSizeValues, permutation, folder); 327 } 328 329 static void tileLinalgOps(FuncOp f, ArrayRef<int64_t> tileSizes) { 330 OpBuilder b(f); 331 OperationFolder folder(f.getContext()); 332 f.walk([tileSizes, &b, &folder](LinalgOp op) { 333 auto opLoopsPair = 334 tileLinalgOp(b, op, tileSizes, /*permutation=*/{}, &folder); 335 // If tiling occurred successfully, erase old op. 336 if (opLoopsPair) 337 op.erase(); 338 }); 339 f.walk([](LinalgOp op) { 340 if (!op.getOperation()->hasNoSideEffect()) 341 return; 342 if (op.getOperation()->use_empty()) 343 op.erase(); 344 }); 345 } 346 347 namespace { 348 struct LinalgTilingPass : public FunctionPass<LinalgTilingPass> { 349 LinalgTilingPass() = default; 350 LinalgTilingPass(ArrayRef<int64_t> sizes); 351 352 void runOnFunction() override { tileLinalgOps(getFunction(), tileSizes); } 353 354 SmallVector<int64_t, 8> tileSizes; 355 }; 356 } // namespace 357 358 LinalgTilingPass::LinalgTilingPass(ArrayRef<int64_t> sizes) { 359 this->tileSizes.assign(sizes.begin(), sizes.end()); 360 } 361 362 std::unique_ptr<OpPassBase<FuncOp>> 363 mlir::linalg::createLinalgTilingPass(ArrayRef<int64_t> tileSizes) { 364 return std::make_unique<LinalgTilingPass>(tileSizes); 365 } 366 367 static PassRegistration<LinalgTilingPass> 368 pass("linalg-tile", "Tile operations in the linalg dialect", [] { 369 auto pass = std::make_unique<LinalgTilingPass>(); 370 pass->tileSizes.assign(clTileSizes.begin(), clTileSizes.end()); 371 return pass; 372 }); 373