1 //===- Utils.cpp - Utilities to support the Linalg dialect ----------------===// 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 utilities for the Linalg dialect. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "mlir/Dialect/Linalg/Utils/Utils.h" 14 15 #include "mlir/Dialect/Affine/EDSC/Intrinsics.h" 16 #include "mlir/Dialect/Affine/IR/AffineOps.h" 17 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 18 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 19 #include "mlir/Dialect/SCF/EDSC/Builders.h" 20 #include "mlir/Dialect/SCF/SCF.h" 21 #include "mlir/Dialect/StandardOps/IR/Ops.h" 22 #include "mlir/IR/AffineExpr.h" 23 #include "mlir/IR/AffineMap.h" 24 #include "mlir/IR/Matchers.h" 25 #include "mlir/IR/OpImplementation.h" 26 #include "mlir/Pass/Pass.h" 27 #include "mlir/Transforms/LoopUtils.h" 28 29 using namespace mlir; 30 using namespace mlir::linalg; 31 using namespace mlir::scf; 32 33 Optional<RegionMatcher::BinaryOpKind> 34 RegionMatcher::matchAsScalarBinaryOp(GenericOp op) { 35 auto ®ion = op.region(); 36 if (!llvm::hasSingleElement(region)) 37 return llvm::None; 38 39 Block &block = region.front(); 40 if (block.getNumArguments() != 2 || 41 !block.getArgument(0).getType().isSignlessIntOrFloat() || 42 !block.getArgument(1).getType().isSignlessIntOrFloat()) 43 return llvm::None; 44 45 auto &ops = block.getOperations(); 46 if (!llvm::hasSingleElement(block.without_terminator())) 47 return llvm::None; 48 49 using mlir::matchers::m_Val; 50 auto a = m_Val(block.getArgument(0)); 51 auto b = m_Val(block.getArgument(1)); 52 53 auto addPattern = m_Op<linalg::YieldOp>(m_Op<AddIOp>(a, b)); 54 if (addPattern.match(&ops.back())) 55 return BinaryOpKind::IAdd; 56 57 return llvm::None; 58 } 59 60 bool mlir::linalg::isParallelIteratorType(Attribute attr) { 61 if (auto strAttr = attr.dyn_cast<StringAttr>()) { 62 return strAttr.getValue() == getParallelIteratorTypeName(); 63 } 64 return false; 65 } 66 67 bool mlir::linalg::isReductionIteratorType(Attribute attr) { 68 if (auto strAttr = attr.dyn_cast<StringAttr>()) { 69 return strAttr.getValue() == getReductionIteratorTypeName(); 70 } 71 return false; 72 } 73 74 bool mlir::linalg::isWindowIteratorType(Attribute attr) { 75 if (auto strAttr = attr.dyn_cast<StringAttr>()) { 76 return strAttr.getValue() == getWindowIteratorTypeName(); 77 } 78 return false; 79 } 80 81 /// Explicit instantiation of loop nest generator for different loop types. 82 template struct mlir::linalg::GenerateLoopNest<scf::ForOp>; 83 template struct mlir::linalg::GenerateLoopNest<scf::ParallelOp>; 84 template struct mlir::linalg::GenerateLoopNest<AffineForOp>; 85 86 /// Given a list of subview ranges, extract individual values for lower, upper 87 /// bounds and steps and put them into the corresponding vectors. 88 static void unpackRanges(ArrayRef<Range> ranges, SmallVectorImpl<Value> &lbs, 89 SmallVectorImpl<Value> &ubs, 90 SmallVectorImpl<Value> &steps) { 91 for (Range range : ranges) { 92 lbs.emplace_back(range.offset); 93 ubs.emplace_back(range.size); 94 steps.emplace_back(range.stride); 95 } 96 } 97 98 namespace mlir { 99 namespace linalg { 100 101 SmallVector<int64_t, 8> getStaticShape(LinalgOp linalgOp) { 102 SmallVector<int64_t, 8> res; 103 for (Value v : linalgOp.getShapedOperands()) { 104 auto shape = v.getType().cast<ShapedType>().getShape(); 105 res.append(shape.begin(), shape.end()); 106 } 107 return res; 108 } 109 110 Optional<SmallVector<int64_t, 4>> getStaticLoopRanges(LinalgOp linalgOp) { 111 SmallVector<int64_t, 8> viewSizes = getStaticShape(linalgOp); 112 AffineMap invertedMap = 113 inversePermutation(concatAffineMaps(linalgOp.getIndexingMaps())); 114 if (!invertedMap) 115 return {}; 116 return invertedMap.compose(viewSizes); 117 } 118 119 /// Specialization to build an scf "for" nest. 120 template <> 121 void GenerateLoopNest<scf::ForOp>::doit( 122 ArrayRef<Range> loopRanges, ValueRange iterArgInitValues, 123 ArrayRef<Attribute> iteratorTypes, 124 function_ref<scf::ValueVector(ValueRange, ValueRange)> bodyBuilderFn, 125 Optional<LinalgLoopDistributionOptions> distributionOptions) { 126 // Create procInfo so it dominate loops, if appropriate. 127 OpBuilder &builder = edsc::ScopedContext::getBuilderRef(); 128 Location loc = edsc::ScopedContext::getLocation(); 129 SmallVector<ProcInfo, 2> procInfo; 130 if (distributionOptions.hasValue()) 131 procInfo = distributionOptions->procInfo(builder, loc, ArrayRef<Range>{}); 132 133 SmallVector<Value, 4> lbs, ubs, steps; 134 unpackRanges(loopRanges, lbs, ubs, steps); 135 LoopNest loopNest = 136 edsc::loopNestBuilder(lbs, ubs, steps, iterArgInitValues, bodyBuilderFn); 137 138 if (!distributionOptions.hasValue() || loopNest.loops.empty()) 139 return; 140 141 // TODO: support distributionMethod, which is currently ignored. 142 for (auto it : llvm::zip(loopNest.loops, procInfo, 143 distributionOptions->distributionMethod)) 144 mapLoopToProcessorIds(std::get<0>(it), std::get<1>(it).procId, 145 std::get<1>(it).nprocs); 146 } 147 148 /// Specialization to build affine "for" nest. 149 template <> 150 void GenerateLoopNest<AffineForOp>::doit( 151 ArrayRef<Range> loopRanges, ValueRange iterArgInitValues, 152 ArrayRef<Attribute> iteratorTypes, 153 function_ref<scf::ValueVector(ValueRange, ValueRange)> bodyBuilderFn, 154 Optional<LinalgLoopDistributionOptions>) { 155 assert(iterArgInitValues.empty() && "unexpected AffineForOp init values"); 156 SmallVector<Value, 4> lbs, ubs, steps; 157 unpackRanges(loopRanges, lbs, ubs, steps); 158 159 // Affine loops require constant steps. 160 SmallVector<int64_t, 4> constantSteps; 161 constantSteps.reserve(steps.size()); 162 for (Value v : steps) { 163 auto op = v.getDefiningOp<ConstantIndexOp>(); 164 assert(op && "Affine loops require constant steps"); 165 constantSteps.push_back(op.getValue()); 166 } 167 168 auto bodyBuilderWithoutIterArgsFn = [&](ValueRange ivs) { 169 bodyBuilderFn(ivs, {}); 170 }; 171 edsc::affineLoopNestBuilder(lbs, ubs, constantSteps, 172 bodyBuilderWithoutIterArgsFn); 173 } 174 175 /// Update the `lb`, `ub` and `step` to get per processor `lb`, `ub` and `step`. 176 static void updateBoundsForCyclicDistribution(OpBuilder &builder, Location loc, 177 Value procId, Value nprocs, 178 Value &lb, Value &ub, 179 Value &step) { 180 using edsc::op::operator+; 181 using edsc::op::operator*; 182 lb = lb + (procId * step); 183 step = nprocs * step; 184 } 185 186 /// Generates a loop nest consisting of scf.parallel and scf.for, depending 187 /// on the `iteratorTypes.` Consecutive parallel loops create a single 188 /// scf.parallel operation; each sequential loop creates a new scf.for 189 /// operation. The body of the innermost loop is populated by 190 /// `bodyBuilderFn` that accepts a range of induction variables for all 191 /// loops. `ivStorage` is used to store the partial list of induction 192 /// variables. 193 // TODO: this function can be made iterative instead. However, it 194 // will have at most as many recursive calls as nested loops, which rarely 195 // exceeds 10. 196 static void 197 generateParallelLoopNest(ValueRange lbs, ValueRange ubs, ValueRange steps, 198 ArrayRef<Attribute> iteratorTypes, 199 function_ref<void(ValueRange)> bodyBuilderFn, 200 SmallVectorImpl<Value> &ivStorage, 201 ArrayRef<DistributionMethod> distributionMethod = {}) { 202 assert(lbs.size() == ubs.size()); 203 assert(lbs.size() == steps.size()); 204 assert(lbs.size() == iteratorTypes.size()); 205 206 // If there are no (more) loops to be generated, generate the body and be 207 // done with it. 208 if (iteratorTypes.empty()) 209 return bodyBuilderFn(ivStorage); 210 211 // Find the outermost parallel loops and drop their types from the list. 212 unsigned nLoops = iteratorTypes.size(); 213 unsigned nOuterPar = 214 nLoops - iteratorTypes.drop_while(isParallelIteratorType).size(); 215 216 // If there are no outer parallel loops, generate one sequential loop and 217 // recurse. Note that we wouldn't have dropped anything from `iteratorTypes` 218 // in this case. 219 if (nOuterPar == 0) { 220 edsc::loopNestBuilder(lbs[0], ubs[0], steps[0], [&](Value iv) { 221 ivStorage.push_back(iv); 222 generateParallelLoopNest(lbs.drop_front(), ubs.drop_front(), 223 steps.drop_front(), iteratorTypes.drop_front(), 224 bodyBuilderFn, ivStorage, distributionMethod); 225 }); 226 return; 227 } 228 if (distributionMethod.empty()) { 229 // Generate a single parallel loop-nest operation for all outermost 230 // parallel loops and recurse. 231 edsc::OperationBuilder<scf::ParallelOp>( 232 lbs.take_front(nOuterPar), ubs.take_front(nOuterPar), 233 steps.take_front(nOuterPar), 234 [&](OpBuilder &nestedBuilder, Location nestedLoc, ValueRange localIvs) { 235 edsc::ScopedContext context(nestedBuilder, nestedLoc); 236 ivStorage.append(localIvs.begin(), localIvs.end()); 237 generateParallelLoopNest( 238 lbs.drop_front(nOuterPar), ubs.drop_front(nOuterPar), 239 steps.drop_front(nOuterPar), iteratorTypes.drop_front(nOuterPar), 240 bodyBuilderFn, ivStorage, 241 (distributionMethod.size() < nOuterPar) 242 ? ArrayRef<DistributionMethod>() 243 : distributionMethod.drop_front(nOuterPar)); 244 }); 245 return; 246 } 247 248 // Process all consecutive similarly distributed loops simultaneously. 249 DistributionMethod methodToUse = distributionMethod[0]; 250 unsigned numProcessed = 1; 251 for (unsigned i = 1; i < nOuterPar && i < distributionMethod.size(); ++i) { 252 if (distributionMethod[i] != methodToUse) 253 break; 254 numProcessed++; 255 } 256 257 switch (methodToUse) { 258 case DistributionMethod::Cyclic: { 259 // Generate a single parallel loop-nest operation for all outermost 260 // parallel loops and recurse. 261 edsc::OperationBuilder<scf::ParallelOp>( 262 lbs.take_front(numProcessed), ubs.take_front(numProcessed), 263 steps.take_front(numProcessed), 264 [&](OpBuilder &nestedBuilder, Location nestedLoc, ValueRange localIvs) { 265 edsc::ScopedContext context(nestedBuilder, nestedLoc); 266 ivStorage.append(localIvs.begin(), localIvs.end()); 267 generateParallelLoopNest( 268 lbs.drop_front(numProcessed), ubs.drop_front(numProcessed), 269 steps.drop_front(numProcessed), 270 iteratorTypes.drop_front(numProcessed), bodyBuilderFn, ivStorage, 271 (distributionMethod.size() < numProcessed) 272 ? ArrayRef<DistributionMethod>() 273 : distributionMethod.drop_front(numProcessed)); 274 }); 275 return; 276 } 277 case DistributionMethod::CyclicNumProcsGeNumIters: { 278 // Check (for the processed loops) that the iteration is in-bounds. 279 using edsc::op::slt; 280 using edsc::op::operator&&; 281 Value cond = slt(lbs[0], ubs[0]); 282 for (unsigned i = 1; i < numProcessed; ++i) 283 cond = cond && slt(lbs[i], ubs[i]); 284 ivStorage.append(lbs.begin(), std::next(lbs.begin(), numProcessed)); 285 edsc::conditionBuilder(cond, [&]() { 286 generateParallelLoopNest( 287 lbs.drop_front(numProcessed), ubs.drop_front(numProcessed), 288 steps.drop_front(numProcessed), 289 iteratorTypes.drop_front(numProcessed), bodyBuilderFn, ivStorage, 290 distributionMethod.drop_front(numProcessed)); 291 }); 292 return; 293 } 294 case DistributionMethod::CyclicNumProcsEqNumIters: 295 // No check/loops needed here. Set the `%iv` to be the `%lb` and proceed 296 // with inner loop generation. 297 ivStorage.append(lbs.begin(), std::next(lbs.begin(), numProcessed)); 298 generateParallelLoopNest( 299 lbs.drop_front(numProcessed), ubs.drop_front(numProcessed), 300 steps.drop_front(numProcessed), iteratorTypes.drop_front(numProcessed), 301 bodyBuilderFn, ivStorage, distributionMethod.drop_front(numProcessed)); 302 return; 303 } 304 } 305 306 /// Specialization for generating a mix of parallel and sequential scf loops. 307 template <> 308 void GenerateLoopNest<scf::ParallelOp>::doit( 309 ArrayRef<Range> loopRanges, ValueRange iterArgInitValues, 310 ArrayRef<Attribute> iteratorTypes, 311 function_ref<scf::ValueVector(ValueRange, ValueRange)> bodyBuilderFn, 312 Optional<LinalgLoopDistributionOptions> distributionOptions) { 313 assert(iterArgInitValues.empty() && "unexpected ParallelOp init values"); 314 // This function may be passed more iterator types than ranges. 315 assert(iteratorTypes.size() >= loopRanges.size() && 316 "expected iterator type for all ranges"); 317 iteratorTypes = iteratorTypes.take_front(loopRanges.size()); 318 SmallVector<Value, 8> lbsStorage, ubsStorage, stepsStorage, ivs; 319 unsigned numLoops = iteratorTypes.size(); 320 ivs.reserve(numLoops); 321 lbsStorage.reserve(numLoops); 322 ubsStorage.reserve(numLoops); 323 stepsStorage.reserve(numLoops); 324 325 // Get the loop lb, ub, and step. 326 unpackRanges(loopRanges, lbsStorage, ubsStorage, stepsStorage); 327 328 // Modify the lb, ub, and step based on the distribution options. 329 SmallVector<DistributionMethod, 0> distributionMethod; 330 if (distributionOptions) { 331 auto &options = distributionOptions.getValue(); 332 OpBuilder &builder = edsc::ScopedContext::getBuilderRef(); 333 Location loc = edsc::ScopedContext::getLocation(); 334 distributionMethod.assign(distributionOptions->distributionMethod.begin(), 335 distributionOptions->distributionMethod.end()); 336 SmallVector<Range, 2> parallelLoopRanges; 337 for (auto iteratorType : enumerate(iteratorTypes)) { 338 if (isParallelIteratorType(iteratorType.value())) 339 parallelLoopRanges.push_back(loopRanges[iteratorType.index()]); 340 } 341 if (distributionMethod.size() < parallelLoopRanges.size()) 342 parallelLoopRanges.resize(distributionMethod.size()); 343 SmallVector<ProcInfo, 2> procInfo = 344 options.procInfo(builder, loc, parallelLoopRanges); 345 unsigned index = 0; 346 for (auto iteratorType : enumerate(iteratorTypes)) { 347 if (index >= procInfo.size()) 348 break; 349 if (isParallelIteratorType(iteratorType.value())) { 350 unsigned i = iteratorType.index(); 351 updateBoundsForCyclicDistribution(builder, loc, procInfo[index].procId, 352 procInfo[index].nprocs, lbsStorage[i], 353 ubsStorage[i], stepsStorage[i]); 354 index++; 355 } 356 } 357 } 358 ValueRange lbs(lbsStorage), ubs(ubsStorage), steps(stepsStorage); 359 auto bodyBuilderWithoutIterArgsFn = [&](ValueRange ivs) { 360 bodyBuilderFn(ivs, {}); 361 }; 362 generateParallelLoopNest(lbs, ubs, steps, iteratorTypes, 363 bodyBuilderWithoutIterArgsFn, ivs, 364 distributionMethod); 365 366 assert(ivs.size() == iteratorTypes.size() && "did not generate enough loops"); 367 } 368 369 } // namespace linalg 370 } // namespace mlir 371