1 //===- LinalgInterfaces.cpp - Linalg interfaces implementation ------------===// 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 #include "mlir/Dialect/Linalg/IR/LinalgInterfaces.h" 10 11 #include "mlir/Dialect/Affine/IR/AffineOps.h" 12 #include "mlir/Dialect/MemRef/IR/MemRef.h" 13 #include "mlir/IR/AffineExprVisitor.h" 14 #include "mlir/IR/AffineMap.h" 15 #include "mlir/IR/TypeUtilities.h" 16 #include "llvm/ADT/SmallSet.h" 17 18 using namespace mlir; 19 using namespace mlir::linalg; 20 21 /// Include the definitions of the copy operation interface. 22 #include "mlir/Dialect/Linalg/IR/LinalgInterfaces.cpp.inc" 23 24 //===----------------------------------------------------------------------===// 25 // ContractionOpInterface implementation 26 //===----------------------------------------------------------------------===// 27 28 /// Return true if the use-def chain from `v` to `from` consists of 0 or more 29 /// unary single-operand operations. 30 // TODO: relax to multi-operands with constants, which are technically unary ops 31 // as needed (e.g. add5). 32 static bool isChainOfUnaryOpsFrom(Value v, Value from) { 33 while (true) { 34 if (v == from) 35 return true; 36 Operation *op = v.getDefiningOp(); 37 if (!op || op->getNumOperands() != 1) 38 return false; 39 v = op->getOperand(0); 40 }; 41 } 42 43 /// Return the unique instance of OpType in `block` if it is indeed unique. 44 /// Return null if none or more than 1 instances exist. 45 template <typename OpType> 46 static OpType getSingleOpOfType(Block &block) { 47 OpType res = nullptr; 48 block.walk([&](OpType op) { 49 if (res) { 50 res = nullptr; 51 return WalkResult::interrupt(); 52 } 53 res = op; 54 return WalkResult::advance(); 55 }); 56 return res; 57 } 58 59 /// Detect whether res is any permutation of `u5(u1(c) + u2(u3(a) * u4(b)))` 60 /// on the field (AddOpType, MulOpType), where u1, u2, u3, u4 and u5 represent 61 /// unary operations that may change the type. 62 template <typename AddOpType, typename MulOpType> 63 static bool isAddMul(Block &block) { 64 if (block.getNumArguments() != 3) 65 return false; 66 Operation *yieldOp = block.getTerminator(); 67 if (yieldOp->getNumOperands() != 1) 68 return false; 69 70 AddOpType addOp = getSingleOpOfType<AddOpType>(block); 71 MulOpType mulOp = getSingleOpOfType<MulOpType>(block); 72 if (!addOp || !mulOp) 73 return false; 74 75 Value argA = block.getArgument(0), argB = block.getArgument(1); 76 Value a = mulOp->getOperand(0), b = mulOp->getOperand(1); 77 Value mul = mulOp->getResult(0); 78 Value argC = block.getArgument(2); 79 Value c1 = addOp->getOperand(0), c2 = addOp->getOperand(1); 80 Value add = addOp->getResult(0); 81 Value res = yieldOp->getOperand(0); 82 // Result traces back to add. 83 auto un = isChainOfUnaryOpsFrom; 84 bool success = un(res, add); 85 // One of the operands of add traces back to argC, the other to the mul. 86 success |= (un(c1, argC) && un(c2, mul)) || ((un(c1, mul)) && un(c2, argC)); 87 // One of the operands of mul traces back to argA, the other to argB. 88 success |= (un(a, argA) && un(b, argB)) || ((un(a, argB)) && un(b, argA)); 89 return success; 90 } 91 92 enum class MatchContractionResult { 93 Success = 0, 94 NotLinalgOp, 95 WrongNumOperands, 96 NoReduction, 97 NotProjectedPermutations, 98 NotAddMul 99 }; 100 static MatchContractionResult isContractionInterfaceImpl(Operation *op) { 101 auto linalgOp = dyn_cast<linalg::LinalgOp>(op); 102 if (!linalgOp) 103 return MatchContractionResult::NotLinalgOp; 104 if (linalgOp.getNumInputs() != 2 || linalgOp.getNumOutputs() != 1) 105 return MatchContractionResult::WrongNumOperands; 106 auto mapRange = linalgOp.indexing_maps().getAsValueRange<AffineMapAttr>(); 107 if (linalgOp.getNumReductionLoops() == 0) 108 return MatchContractionResult::NoReduction; 109 if (llvm::any_of(mapRange, 110 [](AffineMap m) { return !m.isProjectedPermutation(); })) 111 return MatchContractionResult::NotProjectedPermutations; 112 // TODO: more fields than add/mul. 113 if (!isAddMul<AddFOp, MulFOp>(linalgOp->getRegion(0).front()) && 114 !isAddMul<AddIOp, MulIOp>(linalgOp->getRegion(0).front())) 115 return MatchContractionResult::NotAddMul; 116 return MatchContractionResult::Success; 117 } 118 119 bool mlir::linalg::isaContractionOpInterface(LinalgOp linalgOp) { 120 if (!linalgOp) 121 return false; 122 Operation *op = linalgOp.getOperation(); 123 return isa<ContractionOpInterface>(op) || 124 (isContractionInterfaceImpl(op) == MatchContractionResult::Success); 125 } 126 127 /// Verify that a LinalgOp `op` is a contraction. 128 /// A Linalg contraction is defined in general terms: 129 /// 1. Has 2 input and 1 output shapes. 130 /// 2. Has at least one reduction dimension. 131 /// 3. Has only projected permutation indexing maps. 132 /// 4. its body computes `u5(u1(c) + u2(u3(a) * u4(b)))` on some field 133 /// (AddOpType, MulOpType), where u1, u2, u3, u4 and u5 represent scalar unary 134 /// operations that may change the type (e.g. for mixed-precision). 135 /// As a consequence, when vectorization of such an op occurs, the only special 136 /// behavior is that the (unique) MulOpType is vectorized into a 137 /// `vector.contract`. All other ops are handled in a generic fashion. 138 /// In the future, we may wish to allow more input arguments and elementwise and 139 /// constant operations that do not involve the reduction dimension(s). 140 LogicalResult mlir::linalg::detail::verifyContractionInterface(Operation *op) { 141 auto res = isContractionInterfaceImpl(op); 142 if (res == MatchContractionResult::NotLinalgOp) 143 return op->emitError("expected a LinalgOp"); 144 if (res == MatchContractionResult::WrongNumOperands) 145 return op->emitError("expected op with 2 inputs and 1 outputs"); 146 if (res == MatchContractionResult::NoReduction) 147 return op->emitError("expected at least a reduction loop"); 148 if (res == MatchContractionResult::NotProjectedPermutations) 149 return op->emitError("expected all indexings to be projected permutations"); 150 if (res == MatchContractionResult::NotAddMul) 151 return op->emitError("(add, mul) operations not found"); 152 return success(); 153 } 154 155 //===----------------------------------------------------------------------===// 156 // ConvolutionOpInterface implementation 157 //===----------------------------------------------------------------------===// 158 159 /// Of the given two expressions returns one that is of type T (`lhs` gets 160 /// preference over `rhs`) 161 template <typename T> 162 static T getAffineExprOfType(AffineExpr lhs, AffineExpr rhs) { 163 return lhs.isa<T>() ? lhs.cast<T>() 164 : (rhs.isa<T>() ? rhs.cast<T>() : nullptr); 165 } 166 167 namespace { 168 /// Walk the indexing expressions for input of a convolution operation to verify 169 /// its of the right form, either 170 /// - AffineDimExpr 171 /// - AffineDimExpr (`*` (AffineSymbolExpr | AffineConstantExpr))? 172 /// (`+` AffineDimExpr (`*` (AffineSymbolExpr | AffineConstantExpr))?)* 173 /// 174 /// classifies the AffineDimExpr as convolved dimensions or unconvolved 175 /// dimensions and verifies each dimension occurs only once. 176 struct ConvAccessExprWalker 177 : public AffineExprVisitor<ConvAccessExprWalker, LogicalResult> { 178 llvm::SmallDenseSet<unsigned> convolvedDims; 179 llvm::SmallDenseSet<unsigned> unConvolvedDims; 180 181 LogicalResult visitDimExpr(AffineDimExpr dimExpr) { 182 unsigned position = dimExpr.getPosition(); 183 if (unConvolvedDims.count(position) || convolvedDims.count(position)) { 184 return failure(); 185 } 186 unConvolvedDims.insert(position); 187 return success(); 188 } 189 190 LogicalResult visitSymbolExpr(AffineSymbolExpr expr) { return failure(); } 191 192 LogicalResult visitConstantExpr(AffineConstantExpr expr) { return failure(); } 193 194 LogicalResult visitAffineBinaryOpExpr(AffineBinaryOpExpr binaryExpr) { 195 // In pre-order visit, top level op has to be an add op. 196 if (binaryExpr.getKind() != AffineExprKind::Add) 197 return failure(); 198 return success(succeeded(isDimExprOrMulExpr(binaryExpr.getLHS())) && 199 succeeded(isDimExprOrMulExpr(binaryExpr.getRHS()))); 200 } 201 202 LogicalResult isDimExprOrMulExpr(AffineExpr expr) { 203 if (auto dimExpr = expr.dyn_cast<AffineDimExpr>()) { 204 unsigned dim = dimExpr.getPosition(); 205 if (convolvedDims.count(dim) || unConvolvedDims.count(dim)) 206 return failure(); 207 convolvedDims.insert(dim); 208 return success(); 209 } 210 if (auto symbolMulExpr = expr.dyn_cast<AffineBinaryOpExpr>()) { 211 if (symbolMulExpr.getKind() != AffineExprKind::Mul) 212 return failure(); 213 auto lhsExpr = symbolMulExpr.getLHS(); 214 auto rhsExpr = symbolMulExpr.getRHS(); 215 // Check for symbol expression. 216 AffineExpr mulExpr = 217 getAffineExprOfType<AffineSymbolExpr>(lhsExpr, rhsExpr); 218 // If there was no symbol expr, check for constant expression. 219 if (!mulExpr) { 220 mulExpr = getAffineExprOfType<AffineConstantExpr>(lhsExpr, rhsExpr); 221 } 222 auto dimExpr = getAffineExprOfType<AffineDimExpr>(lhsExpr, rhsExpr); 223 if (!mulExpr || !dimExpr) 224 return failure(); 225 unsigned dim = dimExpr.getPosition(); 226 if (convolvedDims.count(dim) || unConvolvedDims.count(dim)) 227 return failure(); 228 convolvedDims.insert(dim); 229 return success(); 230 } 231 return failure(); 232 } 233 }; 234 } // namespace 235 236 static llvm::SmallDenseSet<unsigned> getPreservedDims(AffineMap map) { 237 assert(map.isProjectedPermutation() && 238 "expected map to have projected permutations"); 239 llvm::SmallDenseSet<unsigned> preservedDims; 240 for (auto expr : map.getResults()) 241 preservedDims.insert(expr.cast<AffineDimExpr>().getPosition()); 242 return preservedDims; 243 } 244 245 enum class MatchConvolutionResult { 246 Success = 0, 247 NotLinalgOp, 248 WrongNumOperands, 249 WrongInputIndexingMap, 250 NotProjectedPermutations, 251 NonConvolutionLoop, 252 OutputDimsNotParallel, 253 NonOutputDimNotReduction 254 }; 255 256 static MatchConvolutionResult isConvolutionInterfaceImpl(Operation *op) { 257 auto linalgOp = dyn_cast<linalg::LinalgOp>(op); 258 if (!linalgOp) 259 return MatchConvolutionResult::NotLinalgOp; 260 if (linalgOp.getNumInputs() < 2 || linalgOp.getNumOutputs() != 1) 261 return MatchConvolutionResult::WrongNumOperands; 262 263 auto indexingMaps = linalgOp.getIndexingMaps(); 264 265 // Check the input indexing map has the right form. 266 ConvAccessExprWalker inputExprWalker; 267 if (llvm::any_of(indexingMaps[0].getResults(), 268 [&inputExprWalker](AffineExpr expr) { 269 return failed(inputExprWalker.visit(expr)); 270 })) { 271 return MatchConvolutionResult::WrongInputIndexingMap; 272 } 273 274 // Filter and output maps must be projected permutation. 275 if (!indexingMaps[1].isProjectedPermutation() || 276 !indexingMaps.back().isProjectedPermutation()) 277 return MatchConvolutionResult::NotProjectedPermutations; 278 279 auto iteratorTypesRange = 280 linalgOp.iterator_types().getAsValueRange<StringAttr>(); 281 282 llvm::SmallDenseSet<unsigned> outputDims = 283 getPreservedDims(indexingMaps.back()); 284 llvm::SmallDenseSet<unsigned> filterDims = getPreservedDims(indexingMaps[1]); 285 // Make sure all loops are charecterized as one of: 286 // - Batch loop : present in output, as non-convolved in input, not present in 287 // filter. 288 // - Output image dimension : present in output, convolved dims in input, not 289 // present in filter. 290 // - Output channel dimension : present in output, not present in input, 291 // present in filter. 292 // - Filter loop dimension : present in filter, convolved in input, not 293 // present in output. 294 // - Input channel dimension : unconvolved in input, not present in output, 295 // present in filter. 296 // - Depth multiplier : unconvolved in input, present in output, present in 297 // filter. 298 llvm::SmallDenseSet<unsigned> allLoopDims; 299 for (auto outputExpr : indexingMaps.back().getResults()) { 300 unsigned outputDim = outputExpr.cast<AffineDimExpr>().getPosition(); 301 if (inputExprWalker.unConvolvedDims.count(outputDim) && 302 !filterDims.count(outputDim)) { 303 // Batch dimension. 304 if (*std::next(iteratorTypesRange.begin(), outputDim) != 305 getParallelIteratorTypeName()) 306 return MatchConvolutionResult::OutputDimsNotParallel; 307 allLoopDims.insert(outputDim); 308 continue; 309 } 310 if (inputExprWalker.convolvedDims.count(outputDim) && 311 !filterDims.count(outputDim)) { 312 // Output image Loop dimension. 313 if (*std::next(iteratorTypesRange.begin(), outputDim) != 314 getParallelIteratorTypeName()) 315 return MatchConvolutionResult::OutputDimsNotParallel; 316 allLoopDims.insert(outputDim); 317 continue; 318 } 319 if (!inputExprWalker.convolvedDims.count(outputDim) && 320 !inputExprWalker.unConvolvedDims.count(outputDim) && 321 filterDims.count(outputDim)) { 322 // Output channel dimension. 323 if (*std::next(iteratorTypesRange.begin(), outputDim) != 324 getParallelIteratorTypeName()) 325 return MatchConvolutionResult::OutputDimsNotParallel; 326 allLoopDims.insert(outputDim); 327 continue; 328 } 329 if (inputExprWalker.unConvolvedDims.count(outputDim) && 330 filterDims.count(outputDim)) { 331 // Depth multiplier. 332 if (*std::next(iteratorTypesRange.begin(), outputDim) != 333 getParallelIteratorTypeName()) 334 return MatchConvolutionResult::OutputDimsNotParallel; 335 allLoopDims.insert(outputDim); 336 continue; 337 } 338 return MatchConvolutionResult::NonConvolutionLoop; 339 } 340 for (auto filterExpr : indexingMaps[1].getResults()) { 341 unsigned filterDim = filterExpr.cast<AffineDimExpr>().getPosition(); 342 if (outputDims.count(filterDim) && 343 !inputExprWalker.unConvolvedDims.count(filterDim) && 344 !inputExprWalker.convolvedDims.count(filterDim)) { 345 // Output channel dimension. THis is already seen, continue; 346 continue; 347 } 348 if (inputExprWalker.convolvedDims.count(filterDim) && 349 !outputDims.count(filterDim)) { 350 // Filter loop dimension. 351 if (*std::next(iteratorTypesRange.begin(), filterDim) != 352 getReductionIteratorTypeName()) 353 return MatchConvolutionResult::NonOutputDimNotReduction; 354 if (allLoopDims.count(filterDim)) 355 return MatchConvolutionResult::NonConvolutionLoop; 356 allLoopDims.insert(filterDim); 357 continue; 358 } 359 if (inputExprWalker.unConvolvedDims.count(filterDim) && 360 !outputDims.count(filterDim)) { 361 // Input channel dimension. 362 if (*std::next(iteratorTypesRange.begin(), filterDim) != 363 getReductionIteratorTypeName()) 364 return MatchConvolutionResult::NonOutputDimNotReduction; 365 if (allLoopDims.count(filterDim)) 366 return MatchConvolutionResult::NonConvolutionLoop; 367 allLoopDims.insert(filterDim); 368 continue; 369 } 370 if (inputExprWalker.unConvolvedDims.count(filterDim) && 371 outputDims.count(filterDim)) { 372 // Depthwise loop. Already seen. 373 continue; 374 } 375 return MatchConvolutionResult::NonConvolutionLoop; 376 } 377 // All loops must be covered now. 378 if (allLoopDims.size() != linalgOp.getNumLoops()) 379 return MatchConvolutionResult::NonConvolutionLoop; 380 381 return MatchConvolutionResult::Success; 382 } 383 384 LogicalResult mlir::linalg::detail::verifyConvolutionInterface(Operation *op) { 385 auto res = isConvolutionInterfaceImpl(op); 386 if (res == MatchConvolutionResult::NotLinalgOp) 387 return op->emitError("expected a LinalgOp"); 388 if (res == MatchConvolutionResult::WrongNumOperands) 389 return op->emitError("expected op with 2 inputs and 1 output"); 390 if (res == MatchConvolutionResult::WrongInputIndexingMap) 391 return op->emitError("unexpected input index map for convolutions"); 392 if (res == MatchConvolutionResult::NotProjectedPermutations) { 393 return op->emitError( 394 "expected output/filter indexing maps to be projected permutations"); 395 } 396 if (res == MatchConvolutionResult::NonConvolutionLoop) { 397 return op->emitError("unexpected loop dimension for convolution op"); 398 } 399 if (res == MatchConvolutionResult::OutputDimsNotParallel) { 400 return op->emitError( 401 "expected all iterators used to access outputs to be parallel"); 402 } 403 if (res == MatchConvolutionResult::NonOutputDimNotReduction) { 404 return op->emitError( 405 "expected all iterators not used to access outputs to be reduction"); 406 } 407 return success(); 408 } 409 //===----------------------------------------------------------------------===// 410 // StructuredOpInterface implementation 411 //===----------------------------------------------------------------------===// 412 413 OpOperandVector::operator SmallVector<Value>() { 414 SmallVector<Value> result; 415 result.reserve(this->size()); 416 llvm::transform(*this, std::back_inserter(result), 417 [](OpOperand *opOperand) { return opOperand->get(); }); 418 return result; 419 } 420 421 /// Fully compose map with operands and canonicalize the result. 422 /// Return the `createOrFold`'ed AffineApply op. 423 static Value createFoldedComposedAffineApply(OpBuilder &b, Location loc, 424 AffineMap map, 425 ValueRange operandsRef) { 426 SmallVector<Value, 4> operands(operandsRef.begin(), operandsRef.end()); 427 fullyComposeAffineMapAndOperands(&map, &operands); 428 canonicalizeMapAndOperands(&map, &operands); 429 return b.createOrFold<AffineApplyOp>(loc, map, operands); 430 } 431 432 SmallVector<Value, 4> mlir::linalg::applyMapToValues(OpBuilder &b, Location loc, 433 AffineMap map, 434 ValueRange values) { 435 SmallVector<Value, 4> res; 436 res.reserve(map.getNumResults()); 437 unsigned numDims = map.getNumDims(), numSym = map.getNumSymbols(); 438 // For each `expr` in `map`, applies the `expr` to the values extracted from 439 // ranges. If the resulting application can be folded into a Value, the 440 // folding occurs eagerly. 441 for (auto expr : map.getResults()) { 442 AffineMap map = AffineMap::get(numDims, numSym, expr); 443 res.push_back(createFoldedComposedAffineApply(b, loc, map, values)); 444 } 445 return res; 446 } 447 448 /// Helper function that creates a memref::DimOp or tensor::DimOp depending on 449 /// the type of `source`. 450 static Value createOrFoldDimOp(OpBuilder &b, Location loc, Value source, 451 int64_t dim) { 452 if (source.getType().isa<UnrankedMemRefType, MemRefType>()) 453 return b.createOrFold<memref::DimOp>(loc, source, dim); 454 if (source.getType().isa<UnrankedTensorType, RankedTensorType>()) 455 return b.createOrFold<tensor::DimOp>(loc, source, dim); 456 llvm_unreachable("Expected MemRefType or TensorType"); 457 } 458 459 SmallVector<Value, 4> LinalgOp::createFlatListOfOperandDims(OpBuilder &b, 460 Location loc) { 461 SmallVector<Value, 4> res; 462 for (OpOperand *opOperand : getInputAndOutputOperands()) { 463 for (int64_t i = 0, e = getRank(opOperand); i < e; ++i) 464 res.push_back(createOrFoldDimOp(b, loc, opOperand->get(), i)); 465 } 466 return res; 467 } 468 469 SmallVector<int64_t, 4> LinalgOp::createFlatListOfOperandStaticDims() { 470 SmallVector<int64_t, 4> res; 471 assert(!hasDynamicShape() && "expected operands to have static shapes"); 472 for (OpOperand *opOperand : getInputAndOutputOperands()) 473 llvm::append_range(res, getShape(opOperand)); 474 return res; 475 } 476 477 SmallVector<Range, 4> LinalgOp::createLoopRanges(OpBuilder &b, Location loc) { 478 AffineMap map = getLoopsToShapesMap(); 479 unsigned numDims = map.getNumDims(), numRes = map.getNumResults(); 480 auto viewSizes = createFlatListOfOperandDims(b, loc); 481 SmallVector<Range, 4> res(numDims); 482 Value zeroVal = b.create<ConstantIndexOp>(loc, 0); 483 Value oneVal = b.create<ConstantIndexOp>(loc, 1); 484 for (unsigned idx = 0; idx < numRes; ++idx) { 485 auto result = map.getResult(idx); 486 if (auto d = result.dyn_cast<AffineDimExpr>()) { 487 if (res[d.getPosition()].offset) 488 continue; 489 res[d.getPosition()] = Range{zeroVal, viewSizes[idx], oneVal}; 490 } 491 } 492 return res; 493 } 494 495 SmallVector<int64_t, 4> LinalgOp::computeStaticLoopSizes() { 496 AffineMap map = getLoopsToShapesMap(); 497 unsigned numDims = map.getNumDims(), numRes = map.getNumResults(); 498 SmallVector<int64_t, 4> allShapeSizes = createFlatListOfOperandStaticDims(); 499 SmallVector<int64_t, 4> res(numDims, 0); 500 for (unsigned idx = 0; idx < numRes; ++idx) { 501 auto result = map.getResult(idx); 502 if (auto d = result.dyn_cast<AffineDimExpr>()) 503 res[d.getPosition()] = allShapeSizes[idx]; 504 } 505 return res; 506 } 507 508 /// Visitor to check if any of the given set of positions from AffineDimExprs 509 /// are used within an AffineExpr. 510 struct HasAffineDimExprVisitor 511 : public AffineExprVisitor<HasAffineDimExprVisitor, bool> { 512 HasAffineDimExprVisitor(llvm::SmallSet<unsigned, 4> &positions) 513 : positions(positions) {} 514 515 bool visitAffineBinaryOpExpr(AffineBinaryOpExpr binaryOpExpr) { 516 return visit(binaryOpExpr.getLHS()) || visit(binaryOpExpr.getRHS()); 517 } 518 519 bool visitDimExpr(AffineDimExpr dimExpr) { 520 return positions.count(dimExpr.getPosition()); 521 } 522 523 bool visitConstantExpr(AffineConstantExpr constExpr) { return false; } 524 525 bool visitSymbolExpr(AffineSymbolExpr symbolExpr) { return false; } 526 527 private: 528 llvm::SmallSet<unsigned, 4> positions; 529 }; 530 531 LogicalResult 532 LinalgOp::reifyResultShapes(OpBuilder &b, 533 ReifiedRankedShapedTypeDims &reifiedReturnShapes) { 534 // An example that helps understand the logic below. 535 // Consider the following expression O(i+j, j) += A(i,k) * B(k, j) 536 // We want to express the shape of dim 0 of O in terms of shape of the inputs. 537 // This is achieved as follows. 538 // loopsToShapesMap = (d0, d1, d2) -> (d0, d2, d2, d1, d0 + d1, d1) 539 // subMapOfResultShapes = (d0, d1, d2) -> (d0 + d1, d1) 540 // shapesToLoopsMap = (d0, d2, d2, d3, d4, d5) -> (d0, d3, d2) 541 // resultShapesFromInputShapes = subMapOfResultDim.compose(shapesToLoopMap) 542 // = (d0, d1, d2, d3, d4, d5) -> (d0 + d1, d1) 543 AffineMap loopsToShapesMap = getLoopsToShapesMap(); 544 545 // Find the position in the above map that represents the shape of the 546 // result:dim being inferred. 547 auto resultShapesSubMapPos = getResultsPositionInLoopsToShapeMap(); 548 549 /// From loopsToShapesMap extract the submap that represents the shape of the 550 /// (resultIdx, dim) needed. 551 SmallVector<unsigned, 4> resultPosRange = 552 llvm::to_vector<4>(llvm::seq<unsigned>(resultShapesSubMapPos.first, 553 resultShapesSubMapPos.second)); 554 AffineMap loopToResultsShapeMap = loopsToShapesMap.getSubMap(resultPosRange); 555 AffineMap resultShapesFromInputShapesMap = 556 loopToResultsShapeMap.compose(getShapesToLoopsMap()); 557 558 // Check that the result dim map does not contain the positions corresponding 559 // to the outputs. 560 llvm::SmallSet<unsigned, 4> outputDims; 561 llvm::for_each(resultPosRange, 562 [&outputDims](unsigned dim) { outputDims.insert(dim); }); 563 HasAffineDimExprVisitor checkDimExpr(outputDims); 564 Location loc = getOperation()->getLoc(); 565 auto allResultDimValues = 566 applyMapToValues(b, loc, resultShapesFromInputShapesMap, 567 createFlatListOfOperandDims(b, loc)); 568 int64_t pos = 0; 569 ArrayRef<AffineExpr> shapeExprs = resultShapesFromInputShapesMap.getResults(); 570 for (OpOperand *opOperand : getOutputOperands()) { 571 SmallVector<Value> shapes; 572 for (int64_t dim : llvm::seq<int64_t>(0, getRank(opOperand))) { 573 if (checkDimExpr.visit(shapeExprs[pos])) 574 shapes.push_back(createOrFoldDimOp(b, loc, opOperand->get(), dim)); 575 else 576 shapes.push_back(allResultDimValues[pos]); 577 pos++; 578 } 579 reifiedReturnShapes.emplace_back(std::move(shapes)); 580 } 581 return success(); 582 } 583 584 LogicalResult mlir::linalg::detail::verifyStructuredOpInterface(Operation *op) { 585 LinalgOp linalgOp = cast<LinalgOp>(op); 586 // Expect at least one output operand. 587 // This means an op that constructs a tensor out of indices cannot be a 588 // LinalgOp at the moment. For now this will have to be a special op until we 589 // have output shape operands that are not tensors. 590 int64_t numInputs = linalgOp.getNumInputs(); 591 int64_t numOutputs = linalgOp.getNumOutputs(); 592 if (numOutputs == 0) 593 return op->emitOpError("expected at least one output operand"); 594 if (failed(OpTrait::impl::verifyNOperands(op, numInputs + numOutputs))) 595 return failure(); 596 // Verify the number of results matches the number of output tensors. 597 if (op->getNumResults() != linalgOp.getOutputTensorOperands().size()) 598 return op->emitOpError("expected the number of results (") 599 << op->getNumResults() 600 << ") to be equal to the number of output tensors (" 601 << linalgOp.getOutputTensorOperands().size() << ")"; 602 603 // Before checking indexing maps, we need to make sure the attributes 604 // referenced by it are valid. 605 if (linalgOp.hasDynamicIndexingMaps()) 606 if (failed(linalgOp.verifyIndexingMapRequiredAttributes())) 607 return failure(); 608 609 // All input/output operands must be indexed. 610 if (static_cast<int64_t>(linalgOp.indexing_maps().size()) != 611 linalgOp.getNumInputsAndOutputs()) 612 return op->emitOpError("expected the number of indexing_map (") 613 << linalgOp.indexing_maps().size() 614 << ") to be equal to the number of input/output operands (" 615 << linalgOp.getNumInputsAndOutputs() << ")"; 616 617 for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) { 618 AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand); 619 620 // Symbols disallowed. 621 if (indexingMap.getNumSymbols() != 0) 622 return op->emitOpError("unexpected symbols in indexing_map #") 623 << opOperand->getOperandNumber(); 624 625 // Domain must be consistent. 626 unsigned numLoops = linalgOp.getNumLoops(); 627 if (indexingMap.getNumDims() != numLoops) 628 return op->emitOpError("expected indexing_map #") 629 << opOperand->getOperandNumber() << " to have " << numLoops 630 << " dim(s) to match the number of loops"; 631 632 int64_t rank = linalgOp.getRank(opOperand); 633 if (indexingMap.getNumResults() != rank) 634 return op->emitOpError("expected operand rank (") 635 << rank << ") to match the result rank of indexing_map #" 636 << opOperand->getOperandNumber() << " (" 637 << indexingMap.getNumResults() << ")"; 638 } 639 640 SmallVector<AffineExpr> redDims; 641 linalgOp.getReductionDims(redDims); 642 643 // Simplifying assumption: either full tensor or full buffer mode. 644 // This allows simpler verification of output operands vs result types 645 // without premature tracking of which operand is what in mixed-mode. 646 // TODO: relax when mixed-mode needs to pass verification. 647 if (!linalgOp.getOutputBufferOperands().empty() && 648 !linalgOp.getOutputTensorOperands().empty()) 649 return op->emitOpError( 650 "expected output operands to all have tensor type or " 651 "all have buffer type"); 652 653 for (OpOperand *opOperand : linalgOp.getOutputTensorOperands()) { 654 OpResult result = linalgOp.getTiedOpResult(opOperand); 655 if (result.getType() != opOperand->get().getType()) 656 return op->emitOpError("expected type of operand #") 657 << opOperand->getOperandNumber() << " (" 658 << opOperand->get().getType() << ")" 659 << " to match type of corresponding result (" << result.getType() 660 << ")"; 661 } 662 663 // Output tensor indexing map may not depend on reduction indices. 664 for (OpOperand *opOperand : linalgOp.getOutputOperands()) { 665 AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand); 666 for (auto expr : indexingMap.getResults()) { 667 for (auto dim : redDims) { 668 unsigned pos = dim.cast<AffineDimExpr>().getPosition(); 669 if (expr.isFunctionOfDim(pos)) { 670 std::string exprStr; 671 { 672 llvm::raw_string_ostream os(exprStr); 673 os << expr; 674 } 675 return op->emitOpError( 676 "unexpected output tensor expression in indexing map #") 677 << (opOperand->getOperandNumber() - linalgOp.getNumInputs()) 678 << " a.k.a '" << exprStr 679 << "' is function of reduction iterator 'd" << pos << "'"; 680 } 681 } 682 } 683 } 684 685 // Named ops that are defined manually have a region builder but no region at 686 // this time. Assume the region is well-formed by specification. 687 // TODO: use linalg-ods-gen for all ops when we have enough expressive power. 688 if (linalgOp->getNumRegions() == 0) { 689 assert(!linalgOp.getRegionBuilder() && "regionBuilder but no region"); 690 return success(); 691 } 692 693 auto ®ion = linalgOp->getRegion(0); 694 if (linalgOp->getNumRegions() > 1 || !llvm::hasSingleElement(region)) 695 return op->emitOpError("expected 1 region with 1 block"); 696 697 if (!linalgOp.getShapesToLoopsMap()) 698 return op->emitOpError("expected the shape-to-loops map to be non-null"); 699 700 // Simplifying assumption: bbargs match 1-1 with shape operands elemental 701 // types. 702 // TODO: once ranked shape types are plugged in, we may want to drop the 703 // corresponding bbargs, that can never be read from. This will be subject to 704 // consistency discussions (i.e. what to do with output tensors whose bbarg is 705 // not used). 706 Block &block = linalgOp->getRegion(0).front(); 707 708 if (linalgOp.getNumInputsAndOutputs() != block.getNumArguments()) 709 return op->emitOpError("expected as many non-induction variable region " 710 "arguments as the number of input/output operands"); 711 712 for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) { 713 Type elementType = getElementTypeOrSelf(opOperand->get()); 714 Type argType = block.getArgument(opOperand->getOperandNumber()).getType(); 715 if (elementType != argType) 716 return op->emitOpError("expected type of bb argument #") 717 << opOperand->getOperandNumber() << " (" << argType << ")" 718 << " to match element or self type of the corresponding operand (" 719 << elementType << ")"; 720 } 721 722 // Check if given shapes match to inferred shapes. 723 Optional<SmallVector<int64_t, 4>> endLoopRangeValues = 724 linalgOp.getStaticLoopRanges(); 725 if (!endLoopRangeValues) 726 return op->emitOpError("unable to find loop range for operation"); 727 SmallVector<int64_t, 4> startLoopRangeValues((*endLoopRangeValues).size(), 0); 728 729 // Verify only static cases since we can't get exact dimension sizes and loop 730 // ranges for dynamic cases in this stage. 731 if (llvm::none_of(*endLoopRangeValues, ShapedType::isDynamic)) { 732 for (int64_t &range : *endLoopRangeValues) 733 range -= 1; 734 for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) { 735 AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand); 736 SmallVector<int64_t, 4> startIndices = 737 indexingMap.compose(startLoopRangeValues); 738 SmallVector<int64_t, 4> endIndices = 739 indexingMap.compose(*endLoopRangeValues); 740 ArrayRef<int64_t> shape = linalgOp.getShape(opOperand); 741 for (auto dim : llvm::seq<int64_t>(0, shape.size())) { 742 // Ignore dynamic dimension or the case that the dimension size is 0 743 if (ShapedType::isDynamic(shape[dim]) || shape[dim] == 0) 744 continue; 745 746 // The first index or last index should be the maximum or the minimum in 747 // the inferred index ranges since the range is increasing or 748 // decreasing. The size of dimensions of input/output operands and the 749 // maximum value + 1 in the inferred range should be the same. But, for 750 // now we check if the inferred ranges are in boundary of input/output 751 // operands' size or not in case that Affine Expressions are complicated 752 // such as d0 * 3 753 // + d1 since it is not easy to handle the issues. 754 // Found the case that this solution can't check, for example, (d0, d1) 755 // -> (d1 - d0) 756 int64_t inferredDimSize = 757 std::max(startIndices[dim], endIndices[dim]) + 1; 758 if (std::min(startIndices[dim], endIndices[dim]) < 0) { 759 std::string mapStr; 760 { 761 llvm::raw_string_ostream os(mapStr); 762 os << indexingMap; 763 } 764 return op->emitOpError( 765 "unexpected result less than 0 at expression #") 766 << dim << " in " << mapStr; 767 } 768 if (indexingMap.getResult(dim).dyn_cast<AffineDimExpr>()) { 769 if (inferredDimSize != shape[dim]) { 770 return op->emitOpError("inferred input/output operand #") 771 << opOperand->getOperandNumber() 772 << " has shape's dimension #" << dim << " to be " 773 << inferredDimSize << ", but found " << shape[dim]; 774 } 775 } else { 776 if (inferredDimSize > shape[dim]) { 777 return op->emitOpError("inferred input/output operand #") 778 << opOperand->getOperandNumber() 779 << " has shape's dimension #" << dim 780 << " to be greater than or equal to " << inferredDimSize 781 << ", but found " << shape[dim]; 782 } 783 } 784 } 785 } 786 } 787 788 return success(); 789 } 790