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