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