1 //===- LinalgOps.cpp - Implementation of the linalg operations ------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file implements the Linalg operations. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 14 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 15 #include "mlir/Dialect/StandardOps/IR/Ops.h" 16 #include "mlir/IR/AffineExpr.h" 17 #include "mlir/IR/AffineMap.h" 18 #include "mlir/IR/Builders.h" 19 #include "mlir/IR/Function.h" 20 #include "mlir/IR/Module.h" 21 #include "mlir/IR/OpImplementation.h" 22 #include "mlir/IR/PatternMatch.h" 23 #include "mlir/IR/StandardTypes.h" 24 #include "mlir/Support/Functional.h" 25 #include "mlir/Support/LLVM.h" 26 #include "mlir/Support/STLExtras.h" 27 28 #include "llvm/ADT/StringSet.h" 29 #include "llvm/Support/MathExtras.h" 30 #include "llvm/Support/raw_ostream.h" 31 32 using namespace mlir; 33 using namespace mlir::linalg; 34 35 /// Determines whether it is possible to fold it away in the parent Linalg op: 36 /// 37 /// ```mlir 38 /// %1 = memref_cast %0 : memref<8x16xf32> to memref<?x?xf32> 39 /// %2 = linalg.slice %1 ... : memref<?x?xf32> ... 40 /// // or 41 /// %1 = memref_cast %0 : memref<8x16xf32, affine_map<(i, j)->(16 * i + j)>> 42 /// to memref<?x?xf32> 43 /// linalg.generic(%1 ...) : memref<?x?xf32> ... 44 /// ``` 45 /// 46 /// into 47 /// 48 /// ```mlir 49 /// %2 = linalg.slice %0 ... : memref<8x16xf32> ... 50 /// // or 51 /// linalg.generic(%0 ... : memref<8x16xf32, affine_map<(i, j)->(16 * i + j)>> 52 /// ``` 53 /// 54 static bool canFold(MemRefCastOp castOp) { 55 MemRefType sourceType = castOp.source().getType().dyn_cast<MemRefType>(); 56 MemRefType resultType = castOp.getType().dyn_cast<MemRefType>(); 57 58 // If we don't have MemRefType as source and destination, bail out. 59 if (!sourceType || !resultType) 60 return false; 61 62 // If resultType has a map, it needs to be the same as the source type to 63 // canonicalize. 64 if (!resultType.getAffineMaps().empty() && 65 sourceType.getAffineMaps() != resultType.getAffineMaps()) 66 return false; 67 68 // Ensure that: 69 // 1. source is static 70 // 2. source and target have the same rank (will be extended when needed) 71 // 3. if result is partially static, ensure sizes match. 72 if (!sourceType.hasStaticShape() || 73 sourceType.getRank() != resultType.getRank()) 74 return false; 75 76 for (auto it : llvm::zip(sourceType.getShape(), resultType.getShape())) { 77 auto sourceSize = std::get<0>(it); 78 auto resultSize = std::get<1>(it); 79 if (ShapedType::isDynamic(resultSize)) 80 continue; 81 if (sourceSize != resultSize) 82 return false; 83 } 84 85 // If source has a map, it can only canonicalize if it is the canonical 86 // strided layout map. 87 if (sourceType.getAffineMaps().empty()) 88 return true; 89 90 int64_t offset; 91 SmallVector<int64_t, 4> strides; 92 auto res = getStridesAndOffset(sourceType, strides, offset); 93 (void)res; 94 assert(succeeded(res)); 95 auto stridedMap = 96 makeStridedLinearLayoutMap(strides, offset, castOp.getContext()); 97 AffineMap sourceMap = sourceType.getAffineMaps().front(); 98 return sourceMap == stridedMap; 99 } 100 101 /// This is a common class used for patterns of the form 102 /// ``` 103 /// someop(memrefcast) -> someop 104 /// ``` 105 /// It folds the source of any memref_cast into the root operation directly. 106 static LogicalResult foldMemRefCast(Operation *op) { 107 bool folded = false; 108 for (OpOperand &operand : op->getOpOperands()) { 109 auto castOp = dyn_cast_or_null<MemRefCastOp>(operand.get().getDefiningOp()); 110 if (castOp && canFold(castOp)) { 111 operand.set(castOp.getOperand()); 112 folded = true; 113 } 114 } 115 return success(folded); 116 } 117 118 ///////////////////// Operations defined with Tablegen ///////////////////////// 119 // For such operations that do not correspond to library calls (i.e. defined in 120 // LinalgOps.td), we define an overloaded `print` function and a 121 // parse`className` function. 122 123 //===----------------------------------------------------------------------===// 124 // GenericOps 125 //===----------------------------------------------------------------------===// 126 127 template <typename GenericOpType> 128 static void printGenericOp(OpAsmPrinter &p, GenericOpType op) { 129 auto attrNames = op.linalgTraitAttrNames(); 130 llvm::StringSet<> linalgTraitAttrsSet; 131 linalgTraitAttrsSet.insert(attrNames.begin(), attrNames.end()); 132 SmallVector<NamedAttribute, 8> attrs; 133 for (auto attr : op.getAttrs()) 134 if (linalgTraitAttrsSet.count(attr.first.strref()) > 0) 135 attrs.push_back(attr); 136 137 auto dictAttr = DictionaryAttr::get(attrs, op.getContext()); 138 p << op.getOperationName() << " " << dictAttr << " " << op.getOperands(); 139 if (!op.region().empty()) 140 p.printRegion(op.region()); 141 p.printOptionalAttrDict(op.getAttrs(), attrNames); 142 p << ": " << op.getOperandTypes(); 143 auto outputTensorTypes = op.getResultTypes(); 144 if (!outputTensorTypes.empty()) 145 p << " -> " << outputTensorTypes; 146 } 147 148 static void print(OpAsmPrinter &p, GenericOp op) { printGenericOp(p, op); } 149 150 static void print(OpAsmPrinter &p, IndexedGenericOp op) { 151 printGenericOp(p, op); 152 } 153 154 static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) { 155 SmallVector<OpAsmParser::OperandType, 8> operandsInfo, regionOperandsInfo; 156 DictionaryAttr dictAttr; 157 // Parse the core linalg traits that must check into a dictAttr. 158 // The name is unimportant as we will overwrite result.attributes. 159 // The core linalg traits must contain the information necessary to pass the 160 // verifier. 161 if (parser.parseAttribute(dictAttr, "_", result.attributes) || 162 parser.parseOperandList(operandsInfo)) 163 return failure(); 164 result.attributes.assign(dictAttr.getValue().begin(), 165 dictAttr.getValue().end()); 166 167 Region ®ion = *result.addRegion(); 168 SmallVector<Type, 8> operandTypes, regionTypes; 169 // Optional attributes may be added. 170 // Either Optional getFunAttrName() attribute or region must be specified. 171 if (!dictAttr.get(getFunAttrName()) && 172 parser.parseOptionalRegion(region, regionOperandsInfo, regionTypes)) 173 return failure(); 174 if (parser.parseOptionalAttrDict(result.attributes) || 175 parser.parseColonTypeList(operandTypes)) 176 return failure(); 177 // Generic ops may specify that a subset of its outputs are tensors. Such 178 // outputs are specified in the result type. 179 SmallVector<Type, 8> tensorResultTypes; 180 if (parser.parseOptionalArrowTypeList(tensorResultTypes)) 181 return failure(); 182 if (!tensorResultTypes.empty()) 183 result.addTypes(tensorResultTypes); 184 return parser.resolveOperands(operandsInfo, operandTypes, 185 parser.getCurrentLocation(), result.operands); 186 } 187 188 template <typename GenericOpType> 189 static LogicalResult verifyBlockArgs(GenericOpType op, Block &block); 190 191 template <> LogicalResult verifyBlockArgs(GenericOp op, Block &block) { 192 auto nOperands = op.getNumOperands(); 193 if (block.getNumArguments() != nOperands) 194 return op.emitOpError("expected number of block arguments to match number " 195 "of operands"); 196 197 // Note: the number and type of yield values are checked in the YieldOp. 198 auto nInputViews = op.getNumInputs(); 199 for (unsigned i = 0; i < nOperands; ++i) { 200 auto viewType = op.getShapedType(i); 201 if (viewType.getElementType() != block.getArgument(i).getType()) 202 return op.emitOpError("expected block argument ") 203 << (i + 1) << " of the same type as elemental type of " 204 << ((i < nInputViews) ? "input " : "output ") 205 << "operand: " << viewType; 206 } 207 return success(); 208 } 209 210 template <> LogicalResult verifyBlockArgs(IndexedGenericOp op, Block &block) { 211 auto nInputViews = op.getNumInputs(); 212 auto nLoops = op.getNumLoops(); 213 auto nOperands = op.getNumOperands(); 214 if (block.getNumArguments() != nOperands + nLoops) 215 return op.emitOpError( 216 "expected number of block arguments to match number of operands + " 217 "number of loops"); 218 219 // Note: the number and type of yield values are checked in the YieldOp. 220 for (unsigned i = 0; i < nLoops; ++i) 221 if (!block.getArgument(i).getType().isIndex()) 222 return op.emitOpError("expected block argument ") 223 << (i + 1) << " to be an index"; 224 225 for (unsigned i = 0; i < nOperands; ++i) { 226 unsigned memrefArgIndex = i + nLoops; 227 auto viewType = op.getShapedType(i); 228 if (viewType.getElementType() != 229 block.getArgument(memrefArgIndex).getType()) 230 return op.emitOpError("expected block argument ") 231 << (memrefArgIndex + 1) 232 << " of the same type as elemental type of " 233 << ((i < nInputViews) ? "input " : "output ") 234 << "operand: " << viewType; 235 } 236 return success(); 237 } 238 239 template <typename GenericOpType> 240 static LogicalResult verifyFuncArgs(GenericOpType op, FunctionType funType); 241 242 template <typename GenericOpType> 243 static LogicalResult verifyFuncArgsGeneric(GenericOpType op, 244 FunctionType funType) { 245 auto res = verifyFuncArgs(op, funType); 246 if (failed(res)) 247 return res; 248 249 auto nInputs = op.getNumInputs(); 250 auto nOutputs = op.getNumOutputs(); 251 // linalg.generic output element types are exactly the function results. 252 for (unsigned idx = 0; idx < nOutputs; ++idx) { 253 ShapedType shapedType = op.getShapedType(nInputs + idx); 254 if (funType.getResult(idx) != shapedType.getElementType()) 255 return op.emitOpError("expected function result ") 256 << (idx + 1) << " of the same type as elemental type " 257 << shapedType.getElementType() << " of output " << (idx + 1); 258 } 259 return success(); 260 } 261 262 template <> LogicalResult verifyFuncArgs(GenericOp op, FunctionType funType) { 263 auto nOperands = op.getNumOperands(); 264 if (funType.getNumInputs() != nOperands) 265 return op.emitOpError( 266 "expected function arguments to match number of operands"); 267 if (funType.getNumResults() != op.getNumOutputs()) 268 return op.emitOpError("expected function results(") 269 << funType.getNumResults() << ") to match number of outputs(" 270 << op.getNumOutputs() << ")"; 271 272 // linalg.generic operands element types are exactly the first function 273 // arguments. 274 for (unsigned idx = 0; idx < nOperands; ++idx) { 275 ShapedType shapedType = op.getShapedType(idx); 276 if (funType.getInput(idx) != shapedType.getElementType()) 277 return op.emitOpError("expected function argument ") 278 << (idx + 1) << " of the same type as elemental type " 279 << shapedType.getElementType() << " of operand " << (idx + 1); 280 } 281 282 return success(); 283 } 284 285 template <> 286 LogicalResult verifyFuncArgs(IndexedGenericOp op, FunctionType funType) { 287 auto nLoops = op.getNumLoops(); 288 auto nOutputs = op.getNumOutputs(); 289 auto nOperands = op.getNumOperands(); 290 if (funType.getNumInputs() != nOperands + nLoops) 291 return op.emitOpError("expected function arguments to match number of " 292 "loops + number of operands"); 293 if (funType.getNumResults() != nOutputs) 294 return op.emitOpError( 295 "expected function results to match number of outputs"); 296 for (unsigned i = 0; i < nLoops; ++i) 297 if (!funType.getInput(i).isIndex()) 298 return op.emitOpError("expected function argument ") 299 << (i + 1) << " to be an index"; 300 301 // linalg.generic operands element types are exactly the first function 302 // arguments. 303 for (unsigned idx = 0; idx < nOperands; ++idx) { 304 ShapedType shapedType = op.getShapedType(idx); 305 if (funType.getInput(idx + nLoops) != shapedType.getElementType()) 306 return op.emitOpError("expected function argument ") 307 << (idx + nLoops + 1) << " of the same type as elemental type " 308 << shapedType.getElementType() << " of input " << (idx + 1); 309 } 310 311 return success(); 312 } 313 314 template <typename GenericOpType> 315 static LogicalResult verifyGenericOp(GenericOpType op) { 316 auto nInputViews = op.getNumInputs(); 317 auto nLoops = op.getNumLoops(); 318 auto nInputsAndOutputBuffers = op.getNumInputsAndOutputBuffers(); 319 if (nInputsAndOutputBuffers != llvm::size(op.views())) 320 return op.emitOpError("expected exactly ") 321 << nInputsAndOutputBuffers 322 << " inputs (tensor or buffer) and output buffer operands"; 323 324 auto ®ion = op.region(); 325 auto funOp = op.getFunction(); 326 auto funType = funOp ? funOp.getType() : FunctionType(); 327 if (!region.empty()) { 328 if (region.getBlocks().size() != 1) 329 return op.emitOpError("expected region with 1 block"); 330 if (failed(verifyBlockArgs(op, region.getBlocks().front()))) 331 return failure(); 332 } else { 333 if (!funOp || !funOp.getType()) 334 return op.emitOpError( 335 "expected function attribute to refer to a defined symbol"); 336 if (failed(verifyFuncArgsGeneric(op, funType))) 337 return failure(); 338 } 339 340 SmallVector<AffineMap, 4> indexingMaps; 341 indexingMaps.reserve(op.indexing_maps().size()); 342 for (auto en : llvm::enumerate(op.indexing_maps())) { 343 auto idx = en.index(); 344 auto m = en.value().template cast<AffineMapAttr>().getValue(); 345 indexingMaps.push_back(m); // Save reference to map for further checks. 346 auto view = (idx < nInputViews) ? op.getInputShapedType(idx) 347 : op.getOutputShapedType(idx - nInputViews); 348 349 if (m.getNumSymbols() != 0) 350 return op.emitOpError("expected indexing_map #") 351 << idx << " to have no symbols"; 352 353 if (m.getNumDims() != nLoops) 354 return op.emitOpError("expected indexing_map #") 355 << idx << " to have " << nLoops 356 << " dim(s) to match the number of loops"; 357 358 if (m.getNumResults() != view.getRank()) 359 return op.emitOpError("expected indexing_map #") 360 << idx << " results to match view rank: " << view; 361 } 362 363 auto concatMap = concatAffineMaps(indexingMaps); 364 auto aggregateMap = inversePermutation(concatMap); 365 if (!aggregateMap) 366 return op.emitOpError("expected the concatenation of maps in indexing_map " 367 "to be invertible"); 368 369 return success(); 370 } 371 372 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); } 373 static LogicalResult verify(IndexedGenericOp op) { return verifyGenericOp(op); } 374 375 //===----------------------------------------------------------------------===// 376 // ReshapeOp 377 //===----------------------------------------------------------------------===// 378 379 /// Return true if the reassociation specification is valid, false otherwise. 380 /// When false, the `invalidIndex` integer pointer is optionally filled with the 381 /// index of the offending reassociation map. 382 static bool isReassociationValid(ArrayRef<AffineMap> reassociation, 383 int *invalidIndex = nullptr) { 384 if (reassociation.empty()) 385 return true; 386 unsigned nDims = reassociation[0].getNumDims(); 387 unsigned nextExpectedDim = 0; 388 for (auto it : llvm::enumerate(reassociation)) { 389 auto m = it.value(); 390 if (m.getNumDims() != nDims || m.getNumSymbols() != 0) { 391 if (invalidIndex) 392 *invalidIndex = it.index(); 393 return false; 394 } 395 for (auto e : m.getResults()) { 396 auto d = e.dyn_cast<AffineDimExpr>(); 397 if (!d || d.getPosition() != nextExpectedDim++) { 398 if (invalidIndex) 399 *invalidIndex = it.index(); 400 return false; 401 } 402 } 403 } 404 if (nextExpectedDim != nDims) { 405 if (invalidIndex) 406 *invalidIndex = reassociation.size() - 1; 407 return false; 408 } 409 return true; 410 } 411 412 /// Detect whether memref dims [dim, dim + extent) can be reshaped without 413 /// copies. 414 static bool isReshapableDimBand(unsigned dim, unsigned extent, 415 ArrayRef<int64_t> sizes, 416 ArrayRef<AffineExpr> strides) { 417 assert(sizes.size() == strides.size() && "mismatched ranks"); 418 // off by 1 indexing to avoid out of bounds 419 // V 420 for (auto idx = dim, e = dim + extent; idx + 1 < e; ++idx) { 421 // Only bands of static shapes are reshapable. This is due to the fact that 422 // there is no relation between dynamic sizes and dynamic strides: we do not 423 // have enough information to know whether a "-1" size corresponds to the 424 // proper symbol in the AffineExpr of a stride. 425 if (ShapedType::isDynamic(sizes[dim + 1])) 426 return false; 427 // TODO(ntv) Refine this by passing the proper nDims and nSymbols so we can 428 // simplify on the fly and catch more reshapable cases. 429 if (strides[idx] != strides[idx + 1] * sizes[idx + 1]) 430 return false; 431 } 432 return true; 433 } 434 435 /// Compute the MemRefType obtained by applying the `reassociation` (which is 436 /// expected to be valid) to `type`. 437 /// If `type` is Contiguous MemRefType, this always produce a contiguous 438 /// MemRefType. 439 static MemRefType 440 computeReshapeCollapsedType(MemRefType type, 441 ArrayRef<AffineMap> reassociation) { 442 auto sizes = type.getShape(); 443 AffineExpr offset; 444 SmallVector<AffineExpr, 4> strides; 445 auto status = getStridesAndOffset(type, strides, offset); 446 (void)status; 447 assert(succeeded(status) && "expected strided memref"); 448 449 SmallVector<int64_t, 4> newSizes; 450 newSizes.reserve(reassociation.size()); 451 SmallVector<AffineExpr, 4> newStrides; 452 newStrides.reserve(reassociation.size()); 453 454 // Use the fact that reassociation is valid to simplify the logic: only use 455 // each map's rank. 456 assert(isReassociationValid(reassociation) && "invalid reassociation"); 457 unsigned currentDim = 0; 458 for (AffineMap m : reassociation) { 459 unsigned dim = m.getNumResults(); 460 int64_t size = 1; 461 AffineExpr stride = strides[currentDim + dim - 1]; 462 if (!isReshapableDimBand(currentDim, dim, sizes, strides)) { 463 size = ShapedType::kDynamicSize; 464 stride = AffineExpr(); 465 } else { 466 for (unsigned d = 0; d < dim; ++d) 467 size *= sizes[currentDim + d]; 468 } 469 newSizes.push_back(size); 470 newStrides.push_back(stride); 471 currentDim += dim; 472 } 473 474 // Early-exit: if `type` is contiguous, the result must be contiguous. 475 if (canonicalizeStridedLayout(type).getAffineMaps().empty()) 476 return MemRefType::Builder(type).setShape(newSizes).setAffineMaps({}); 477 478 // Convert back to int64_t because we don't have enough information to create 479 // new strided layouts from AffineExpr only. This corresponds to a case where 480 // copies may be necessary. 481 int64_t intOffset = ShapedType::kDynamicStrideOrOffset; 482 if (auto o = offset.dyn_cast<AffineConstantExpr>()) 483 intOffset = o.getValue(); 484 SmallVector<int64_t, 4> intStrides; 485 intStrides.reserve(strides.size()); 486 for (auto stride : newStrides) { 487 if (auto cst = stride.dyn_cast_or_null<AffineConstantExpr>()) 488 intStrides.push_back(cst.getValue()); 489 else 490 intStrides.push_back(ShapedType::kDynamicStrideOrOffset); 491 } 492 auto layout = 493 makeStridedLinearLayoutMap(intStrides, intOffset, type.getContext()); 494 return canonicalizeStridedLayout( 495 MemRefType::Builder(type).setShape(newSizes).setAffineMaps({layout})); 496 } 497 498 /// Helper functions assert Attribute of the proper type in attr and returns the 499 /// corresponding vector. 500 /// TODO(rridle,ntv) this should be evolved into a generic 501 /// `getRangeOfType<AffineMap>(ArrayAttr attrs)` that does not copy. 502 static SmallVector<AffineMap, 4> getAffineMaps(ArrayAttr attrs) { 503 return functional::map( 504 [](Attribute a) { return a.cast<AffineMapAttr>().getValue(); }, attrs); 505 } 506 507 template <typename AffineExprTy> 508 unsigned getMaxPosOfType(ArrayRef<ArrayRef<AffineExpr>> exprArrays) { 509 unsigned pos = 0; 510 for (auto exprs : exprArrays) { 511 for (auto expr : exprs) { 512 expr.walk([&pos](AffineExpr e) { 513 if (auto d = e.dyn_cast<AffineExprTy>()) 514 pos = std::max(pos, d.getPosition()); 515 }); 516 } 517 } 518 return pos; 519 } 520 521 static SmallVector<AffineMap, 4> 522 getSymbolLessAffineMaps(ArrayRef<ArrayRef<AffineExpr>> reassociation) { 523 unsigned maxDim = getMaxPosOfType<AffineDimExpr>(reassociation); 524 assert(getMaxPosOfType<AffineSymbolExpr>(reassociation) == 0 && 525 "Expected symbol-less expressions"); 526 SmallVector<AffineMap, 4> maps; 527 maps.reserve(reassociation.size()); 528 for (auto exprs : reassociation) 529 maps.push_back(AffineMap::get(maxDim + 1, 0, exprs)); 530 return maps; 531 } 532 533 void mlir::linalg::ReshapeOp::build( 534 Builder *b, OperationState &result, Value src, 535 ArrayRef<ArrayRef<AffineExpr>> reassociation, 536 ArrayRef<NamedAttribute> attrs) { 537 auto maps = getSymbolLessAffineMaps(reassociation); 538 auto memRefType = src.getType().cast<MemRefType>(); 539 auto resultType = computeReshapeCollapsedType(memRefType, maps); 540 build(b, result, resultType, src, attrs); 541 result.addAttribute(ReshapeOp::getReassociationAttrName(), 542 b->getAffineMapArrayAttr(maps)); 543 } 544 545 void mlir::linalg::ReshapeOp::build( 546 Builder *b, OperationState &result, Type resultType, Value src, 547 ArrayRef<ArrayRef<AffineExpr>> reassociation, 548 ArrayRef<NamedAttribute> attrs) { 549 auto maps = getSymbolLessAffineMaps(reassociation); 550 build(b, result, resultType, src, attrs); 551 result.addAttribute(ReshapeOp::getReassociationAttrName(), 552 b->getAffineMapArrayAttr(maps)); 553 } 554 555 // Common verifier for reshape-like types. Fills `expandedType` and 556 // `collapsedType` with the proper `src` or `result` type. 557 template <typename Op, typename T> 558 LogicalResult verifyReshapeLikeTypes(Op op, T &expandedType, T &collapsedType) { 559 expandedType = op.getSrcType(); 560 collapsedType = op.getResultType(); 561 unsigned expandedRank = expandedType.getRank(); 562 unsigned collapsedRank = collapsedType.getRank(); 563 bool isCollapse = expandedRank > collapsedRank; 564 if (!isCollapse) { 565 std::swap(expandedRank, collapsedRank); 566 std::swap(expandedType, collapsedType); 567 } 568 if (expandedRank == 0 || collapsedRank == 0) 569 return op.emitOpError("expected non-zero memref ranks"); 570 if (expandedRank == collapsedRank) 571 return op.emitOpError("expected to collapse or expand dims"); 572 573 if (collapsedRank != op.reassociation().size()) 574 return op.emitOpError("expected rank of the collapsed type(") 575 << collapsedRank << ") to be the number of reassociation maps(" 576 << op.reassociation().size() << ")"; 577 auto maps = getAffineMaps(op.reassociation()); 578 for (auto it : llvm::enumerate(maps)) 579 if (it.value().getNumDims() != expandedRank) 580 return op.emitOpError("expected reassociation map #") 581 << it.index() << " of same rank as expanded memref(" 582 << expandedRank << "), but got " << it.value().getNumDims(); 583 int invalidIdx = 0; 584 if (!isReassociationValid(maps, &invalidIdx)) 585 return op.emitOpError("expected reassociation map #") 586 << invalidIdx << " to be valid and contiguous"; 587 return success(); 588 } 589 590 static LogicalResult verify(ReshapeOp op) { 591 MemRefType expandedType, collapsedType; 592 if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType))) 593 return failure(); 594 auto maps = getAffineMaps(op.reassociation()); 595 MemRefType expectedType = computeReshapeCollapsedType(expandedType, maps); 596 if (collapsedType != expectedType) 597 return op.emitOpError("expected collapsed type to be ") 598 << expectedType << ", but got " << collapsedType; 599 return success(); 600 } 601 602 //===----------------------------------------------------------------------===// 603 // TensorReshapeOp 604 //===----------------------------------------------------------------------===// 605 606 /// Compute the RankedTensorType obtained by applying `reassociation` to `type`. 607 static RankedTensorType 608 computeTensorReshapeCollapsedType(RankedTensorType type, 609 ArrayRef<AffineMap> reassociation) { 610 auto shape = type.getShape(); 611 SmallVector<int64_t, 4> newShape; 612 newShape.reserve(reassociation.size()); 613 614 // Use the fact that reassociation is valid to simplify the logic: only use 615 // each map's rank. 616 assert(isReassociationValid(reassociation) && "invalid reassociation"); 617 unsigned currentDim = 0; 618 for (AffineMap m : reassociation) { 619 unsigned dim = m.getNumResults(); 620 auto band = shape.drop_front(currentDim).take_front(dim); 621 int64_t size = 1; 622 if (llvm::is_contained(band, ShapedType::kDynamicSize)) 623 size = ShapedType::kDynamicSize; 624 else 625 for (unsigned d = 0; d < dim; ++d) 626 size *= shape[currentDim + d]; 627 newShape.push_back(size); 628 currentDim += dim; 629 } 630 631 return RankedTensorType::get(newShape, type.getElementType()); 632 } 633 634 void mlir::linalg::TensorReshapeOp::build( 635 Builder *b, OperationState &result, Value src, 636 ArrayRef<ArrayRef<AffineExpr>> reassociation, 637 ArrayRef<NamedAttribute> attrs) { 638 auto maps = getSymbolLessAffineMaps(reassociation); 639 auto resultType = computeTensorReshapeCollapsedType( 640 src.getType().cast<RankedTensorType>(), maps); 641 build(b, result, resultType, src, attrs); 642 result.addAttribute(TensorReshapeOp::getReassociationAttrName(), 643 b->getAffineMapArrayAttr(maps)); 644 } 645 646 void mlir::linalg::TensorReshapeOp::build( 647 Builder *b, OperationState &result, Type resultType, Value src, 648 ArrayRef<ArrayRef<AffineExpr>> reassociation, 649 ArrayRef<NamedAttribute> attrs) { 650 auto maps = getSymbolLessAffineMaps(reassociation); 651 build(b, result, resultType, src, attrs); 652 result.addAttribute(TensorReshapeOp::getReassociationAttrName(), 653 b->getAffineMapArrayAttr(maps)); 654 } 655 656 static LogicalResult verify(TensorReshapeOp op) { 657 RankedTensorType expandedType, collapsedType; 658 if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType))) 659 return failure(); 660 auto maps = getAffineMaps(op.reassociation()); 661 // TODO(ntv): expanding a ? with a non-constant is under-specified. Error 662 // out. 663 RankedTensorType expectedType = 664 computeTensorReshapeCollapsedType(expandedType, maps); 665 if (collapsedType != expectedType) 666 return op.emitOpError("expected collapsed type to be ") 667 << expectedType << ", but got " << collapsedType; 668 return success(); 669 } 670 671 //===----------------------------------------------------------------------===// 672 // SliceOp 673 //===----------------------------------------------------------------------===// 674 void mlir::linalg::SliceOp::build(Builder *b, OperationState &result, 675 Value base, ValueRange indexings) { 676 result.addOperands(base); 677 result.addOperands(indexings); 678 679 auto memRefType = base.getType().cast<MemRefType>(); 680 int64_t offset; 681 SmallVector<int64_t, 4> strides; 682 auto res = getStridesAndOffset(memRefType, strides, offset); 683 assert(succeeded(res) && strides.size() == indexings.size()); 684 (void)res; 685 686 unsigned rank = memRefType.getRank(); 687 // TODO(ntv): propagate static size and stride information when available. 688 SmallVector<int64_t, 4> sizes(rank, -1); // -1 encodes dynamic size. 689 result.addTypes({MemRefType::Builder(memRefType) 690 .setShape(sizes) 691 .setAffineMaps(makeStridedLinearLayoutMap( 692 strides, offset, b->getContext()))}); 693 } 694 695 static void print(OpAsmPrinter &p, SliceOp op) { 696 auto indexings = op.indexings(); 697 p << SliceOp::getOperationName() << " " << op.view() << "[" << indexings 698 << "] "; 699 p.printOptionalAttrDict(op.getAttrs()); 700 p << " : " << op.getBaseViewType(); 701 if (!indexings.empty()) 702 p << ", " << op.indexings().getTypes(); 703 p << ", " << op.getType(); 704 } 705 706 static ParseResult parseSliceOp(OpAsmParser &parser, OperationState &result) { 707 OpAsmParser::OperandType baseInfo; 708 SmallVector<OpAsmParser::OperandType, 8> operands; 709 SmallVector<Type, 8> types; 710 if (parser.parseOperand(baseInfo) || 711 parser.parseOperandList(operands, OpAsmParser::Delimiter::Square) || 712 parser.parseOptionalAttrDict(result.attributes) || 713 parser.parseColonTypeList(types)) 714 return failure(); 715 716 if (types.size() < 2) 717 return parser.emitError(parser.getCurrentLocation(), 718 "expected at least input and result view types"); 719 720 ArrayRef<Type> indexingTypes = ArrayRef<Type>(types).drop_front().drop_back(); 721 return failure( 722 parser.resolveOperand(baseInfo, types.front(), result.operands) || 723 (!operands.empty() && 724 parser.resolveOperands(operands, indexingTypes, 725 operands.front().location, result.operands)) || 726 parser.addTypeToList(types.back(), result.types)); 727 } 728 729 static LogicalResult verify(SliceOp op) { 730 unsigned rank = op.getBaseViewRank(); 731 if (rank != llvm::size(op.indexings())) 732 return op.emitOpError("expected ") 733 << rank << " indexings, got " << llvm::size(op.indexings()); 734 unsigned index = 0; 735 for (auto indexing : op.indexings()) { 736 if (indexing.getType().isa<IndexType>()) 737 --rank; 738 ++index; 739 } 740 if (op.getRank() != rank) 741 return op.emitOpError() << "expected rank of the view(" << op.getRank() 742 << ") to be the number of ranges(" << rank << ")"; 743 return success(); 744 } 745 746 //===----------------------------------------------------------------------===// 747 // TransposeOp 748 //===----------------------------------------------------------------------===// 749 void mlir::linalg::TransposeOp::build(Builder *b, OperationState &result, 750 Value view, AffineMapAttr permutation, 751 ArrayRef<NamedAttribute> attrs) { 752 auto permutationMap = permutation.getValue(); 753 assert(permutationMap); 754 755 auto memRefType = view.getType().cast<MemRefType>(); 756 auto rank = memRefType.getRank(); 757 auto originalSizes = memRefType.getShape(); 758 // Compute permuted sizes. 759 SmallVector<int64_t, 4> sizes(rank, 0); 760 for (auto en : llvm::enumerate(permutationMap.getResults())) 761 sizes[en.index()] = 762 originalSizes[en.value().cast<AffineDimExpr>().getPosition()]; 763 764 // Compute permuted strides. 765 int64_t offset; 766 SmallVector<int64_t, 4> strides; 767 auto res = getStridesAndOffset(memRefType, strides, offset); 768 assert(succeeded(res) && strides.size() == static_cast<unsigned>(rank)); 769 (void)res; 770 auto map = makeStridedLinearLayoutMap(strides, offset, b->getContext()); 771 map = permutationMap ? map.compose(permutationMap) : map; 772 // Compute result type. 773 MemRefType resultType = 774 MemRefType::Builder(memRefType).setShape(sizes).setAffineMaps(map); 775 776 build(b, result, resultType, view, attrs); 777 result.addAttribute(TransposeOp::getPermutationAttrName(), permutation); 778 } 779 780 static void print(OpAsmPrinter &p, TransposeOp op) { 781 p << op.getOperationName() << " " << op.view() << " " << op.permutation(); 782 p.printOptionalAttrDict(op.getAttrs(), 783 {TransposeOp::getPermutationAttrName()}); 784 p << " : " << op.view().getType(); 785 } 786 787 static ParseResult parseTransposeOp(OpAsmParser &parser, 788 OperationState &result) { 789 OpAsmParser::OperandType view; 790 AffineMap permutation; 791 MemRefType type; 792 if (parser.parseOperand(view) || parser.parseAffineMap(permutation) || 793 parser.parseOptionalAttrDict(result.attributes) || 794 parser.parseColonType(type) || 795 parser.resolveOperand(view, type, result.operands) || 796 parser.addTypeToList(type, result.types)) 797 return failure(); 798 799 result.addAttribute(TransposeOp::getPermutationAttrName(), 800 AffineMapAttr::get(permutation)); 801 return success(); 802 } 803 804 //===----------------------------------------------------------------------===// 805 // YieldOp 806 //===----------------------------------------------------------------------===// 807 808 static void print(OpAsmPrinter &p, YieldOp op) { 809 p << op.getOperationName(); 810 if (op.getNumOperands() > 0) 811 p << ' ' << op.getOperands(); 812 p.printOptionalAttrDict(op.getAttrs()); 813 if (op.getNumOperands() > 0) 814 p << " : " << op.getOperandTypes(); 815 } 816 817 static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) { 818 SmallVector<OpAsmParser::OperandType, 2> opInfo; 819 SmallVector<Type, 2> types; 820 llvm::SMLoc loc = parser.getCurrentLocation(); 821 return failure(parser.parseOperandList(opInfo) || 822 parser.parseOptionalAttrDict(result.attributes) || 823 (!opInfo.empty() && parser.parseColonTypeList(types)) || 824 parser.resolveOperands(opInfo, types, loc, result.operands)); 825 } 826 827 template <typename GenericOpType> 828 static LogicalResult verifyYield(YieldOp op, GenericOpType genericOp) { 829 // The operand number and types must match the view element types. 830 auto nOutputs = genericOp.getNumOutputs(); 831 if (op.getNumOperands() != nOutputs) 832 return op.emitOpError("expected number of yield values (") 833 << nOutputs << ") to match the number of operands of the enclosing " 834 << "linalg.generic op (" << op.getNumOperands() << ")"; 835 836 for (unsigned i = 0; i != nOutputs; ++i) { 837 auto elementType = genericOp.getOutputShapedType(i).getElementType(); 838 if (op.getOperand(i).getType() != elementType) 839 return op.emitOpError("type of yield operand ") 840 << (i + 1) << " (" << op.getOperand(i).getType() 841 << ") doesn't match " 842 << "the element type of the enclosing linalg.generic op (" 843 << elementType << ")"; 844 } 845 return success(); 846 } 847 848 static LogicalResult verify(YieldOp op) { 849 auto *parentOp = op.getParentOp(); 850 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty()) 851 return op.emitOpError("expected single non-empty parent region"); 852 853 auto genericOp = dyn_cast<GenericOp>(parentOp); 854 if (genericOp) 855 return verifyYield(op, genericOp); 856 857 auto indexedGenericOp = dyn_cast<IndexedGenericOp>(parentOp); 858 if (indexedGenericOp) 859 return verifyYield(op, indexedGenericOp); 860 861 return op.emitOpError("expected '") 862 << GenericOp::getOperationName() << "' or '" 863 << IndexedGenericOp::getOperationName() << "' parent op"; 864 } 865 866 /////// Operations corresponding to library calls defined with Tablegen //////// 867 868 static LogicalResult verify(FillOp op) { 869 auto viewType = op.getOutputShapedType(0); 870 auto fillType = op.value().getType(); 871 if (viewType.getElementType() != fillType) 872 return op.emitOpError("expects fill type to match view elemental type"); 873 return success(); 874 } 875 876 static LogicalResult verify(CopyOp op) { 877 auto outputViewType = op.getOutputShapedType(0); 878 auto inputViewType = op.getInputShapedType(0); 879 if (inputViewType.getElementType() != outputViewType.getElementType()) 880 return op.emitOpError("expects views of the same type"); 881 if (inputViewType.getRank() != outputViewType.getRank()) 882 return op.emitOpError("expects views of the same rank"); 883 auto rank = op.getNumParallelLoops(); 884 auto inputPermutationMap = op.inputPermutation(); 885 if (inputPermutationMap) { 886 if (inputPermutationMap->getNumInputs() != rank) 887 return op.emitOpError("expects optional input_permutation map of rank ") 888 << rank; 889 if (!inputPermutationMap->isPermutation()) 890 return op.emitOpError( 891 "expects optional input_permutation map to be a permutation"); 892 } 893 auto outputPermutationMap = op.outputPermutation(); 894 if (outputPermutationMap) { 895 if (outputPermutationMap->getNumInputs() != rank) 896 return op.emitOpError("expects optional output_permutation map of rank ") 897 << rank; 898 if (!outputPermutationMap->isPermutation()) 899 return op.emitOpError( 900 "expects optional output_permutation map to be a permutation"); 901 } 902 if (rank == 0 && inputPermutationMap) 903 return op.emitOpError("expected no input permutation when rank == 0"); 904 if (rank == 0 && outputPermutationMap) 905 return op.emitOpError("expected no output permutation when rank == 0"); 906 return success(); 907 } 908 909 template <typename LinalgPoolingOp> 910 static LogicalResult verifyStrideOrDilation(LinalgPoolingOp op, 911 ArrayRef<Attribute> attrs, 912 bool isStride) { 913 auto strideOrDilation = isStride ? "stride" : "dilation"; 914 if (attrs.size() != op.getNumWindowLoops()) 915 return op.emitOpError("expects num ") 916 << strideOrDilation 917 << "s equal to number of window dimensions: " << attrs.size() 918 << " vs " << op.getNumWindowLoops(); 919 return success(); 920 } 921 922 static LogicalResult verify(ConvOp op) { 923 auto oType = op.output().getType().cast<MemRefType>(); 924 auto fType = op.filter().getType().cast<MemRefType>(); 925 auto iType = op.input().getType().cast<MemRefType>(); 926 if (oType.getElementType() != iType.getElementType() || 927 oType.getElementType() != fType.getElementType()) 928 return op.emitOpError("expects memref elemental types to match"); 929 if (oType.getRank() != iType.getRank() || oType.getRank() != fType.getRank()) 930 return op.emitOpError("expects memref ranks to match"); 931 if (auto strides = op.strides()) { 932 if (failed( 933 verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true))) 934 return failure(); 935 } 936 if (auto dilations = op.dilations()) { 937 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 938 /*isStride=*/false))) 939 return failure(); 940 } 941 return success(); 942 } 943 944 template <typename PoolingOp> 945 static LogicalResult verifySingleInputPoolingOp(PoolingOp op) { 946 auto inputType = op.input().getType().template cast<MemRefType>(); 947 auto outputType = op.output().getType().template cast<MemRefType>(); 948 if (outputType.getElementType() != inputType.getElementType()) 949 return op.emitOpError("expects memref elemental types to match"); 950 951 auto windowDimsType = op.windowDims().getType().template cast<MemRefType>(); 952 if (outputType.getRank() != inputType.getRank() || 953 outputType.getRank() != windowDimsType.getRank()) 954 return op.emitOpError("expects memref ranks to match"); 955 956 if (auto strides = op.strides()) { 957 if (failed( 958 verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true))) 959 return failure(); 960 } 961 if (auto dilations = op.dilations()) { 962 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 963 /*isStride=*/false))) 964 return failure(); 965 } 966 return success(); 967 } 968 969 static LogicalResult verify(PoolingMaxOp op) { 970 return verifySingleInputPoolingOp(op); 971 } 972 static LogicalResult verify(PoolingMinOp op) { 973 return verifySingleInputPoolingOp(op); 974 } 975 static LogicalResult verify(PoolingSumOp op) { 976 return verifySingleInputPoolingOp(op); 977 } 978 979 namespace mlir { 980 namespace linalg { 981 982 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOpsInterfaces.cpp.inc" 983 984 #define GET_OP_CLASSES 985 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 986 987 #define GET_OP_CLASSES 988 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 989 990 } // namespace linalg 991 } // namespace mlir 992 993 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 994 unsigned rank, 995 MLIRContext *context) { 996 if (maybeMap) 997 return maybeMap.getValue(); 998 if (rank == 0) 999 return AffineMap::get(context); 1000 return AffineMap::getMultiDimIdentityMap(rank, context); 1001 } 1002 1003 SmallVector<AffineExpr, 4> 1004 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 1005 MLIRContext *context) { 1006 SmallVector<AffineExpr, 4> res; 1007 res.reserve(num); 1008 for (unsigned i = 0; i < num; ++i) 1009 res.push_back(getAffineDimExpr(startIdx++, context)); 1010 return res; 1011 } 1012 1013 template <typename PoolingOp> 1014 SmallVector<AffineExpr, 4> 1015 mlir::linalg::weightedPoolingInputIndex(PoolingOp op, 1016 ArrayRef<AffineExpr> outputDims, 1017 ArrayRef<AffineExpr> windowDims) { 1018 assert(outputDims.size() == windowDims.size()); 1019 SmallVector<AffineExpr, 4> res; 1020 res.reserve(outputDims.size()); 1021 for (unsigned i = 0, e = outputDims.size(); i < e; ++i) { 1022 // TODO(ntv): add a level of indirection to linalg.generic. 1023 auto expr = op.getStride(i) * outputDims[i] + 1024 op.getDilation(i) * windowDims[i] - op.getLowPad(i); 1025 res.push_back(expr); 1026 } 1027 return res; 1028 } 1029 1030 #define INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(OP_TYPE) \ 1031 template SmallVector<AffineExpr, 4> \ 1032 mlir::linalg::weightedPoolingInputIndex<OP_TYPE>( \ 1033 OP_TYPE op, ArrayRef<AffineExpr> outputDims, \ 1034 ArrayRef<AffineExpr> windowDims); 1035 1036 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(ConvOp) 1037 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMaxOp) 1038 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMinOp) 1039 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingSumOp) 1040 1041 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 1042 ArrayRef<AffineExpr> b) { 1043 auto rangeA = llvm::make_range(a.begin(), a.end()); 1044 auto rangeB = llvm::make_range(b.begin(), b.end()); 1045 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 1046 return llvm::to_vector<4>(concatRanges); 1047 } 1048 1049 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 1050 if (auto memref = t.dyn_cast<MemRefType>()) { 1051 ss << "view"; 1052 for (auto size : memref.getShape()) 1053 if (size < 0) 1054 ss << "sx"; 1055 else 1056 ss << size << "x"; 1057 appendMangledType(ss, memref.getElementType()); 1058 } else if (auto vec = t.dyn_cast<VectorType>()) { 1059 ss << "vector"; 1060 interleave( 1061 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 1062 appendMangledType(ss, vec.getElementType()); 1063 } else if (t.isSignlessIntOrIndexOrFloat()) { 1064 ss << t; 1065 } else { 1066 llvm_unreachable("Invalid type for linalg library name mangling"); 1067 } 1068 } 1069 1070 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 1071 assert(isa<LinalgOp>(op)); 1072 std::string name(op->getName().getStringRef().str()); 1073 name.reserve(128); 1074 std::replace(name.begin(), name.end(), '.', '_'); 1075 llvm::raw_string_ostream ss(name); 1076 ss << "_"; 1077 auto types = op->getOperandTypes(); 1078 interleave( 1079 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 1080 [&]() { ss << "_"; }); 1081 return ss.str(); 1082 } 1083 1084 // TODO(ntv, rriddle): Consider making all this boilerplate easy to autogenerate 1085 // with Tablegen. This seems a desirable property in the context of OpInterfaces 1086 // where a Linalg "named" op **isa** LinalgOp. 1087 LogicalResult ConvOp::fold(ArrayRef<Attribute>, 1088 SmallVectorImpl<OpFoldResult> &) { 1089 return foldMemRefCast(*this); 1090 } 1091 LogicalResult PoolingMaxOp::fold(ArrayRef<Attribute>, 1092 SmallVectorImpl<OpFoldResult> &) { 1093 return foldMemRefCast(*this); 1094 } 1095 LogicalResult PoolingMinOp::fold(ArrayRef<Attribute>, 1096 SmallVectorImpl<OpFoldResult> &) { 1097 return foldMemRefCast(*this); 1098 } 1099 LogicalResult PoolingSumOp::fold(ArrayRef<Attribute>, 1100 SmallVectorImpl<OpFoldResult> &) { 1101 return foldMemRefCast(*this); 1102 } 1103 LogicalResult CopyOp::fold(ArrayRef<Attribute>, 1104 SmallVectorImpl<OpFoldResult> &) { 1105 return foldMemRefCast(*this); 1106 } 1107 LogicalResult DotOp::fold(ArrayRef<Attribute>, 1108 SmallVectorImpl<OpFoldResult> &) { 1109 return foldMemRefCast(*this); 1110 } 1111 LogicalResult FillOp::fold(ArrayRef<Attribute>, 1112 SmallVectorImpl<OpFoldResult> &) { 1113 return foldMemRefCast(*this); 1114 } 1115 LogicalResult GenericOp::fold(ArrayRef<Attribute>, 1116 SmallVectorImpl<OpFoldResult> &) { 1117 return foldMemRefCast(*this); 1118 } 1119 LogicalResult IndexedGenericOp::fold(ArrayRef<Attribute>, 1120 SmallVectorImpl<OpFoldResult> &) { 1121 return foldMemRefCast(*this); 1122 } 1123 LogicalResult MatvecOp::fold(ArrayRef<Attribute>, 1124 SmallVectorImpl<OpFoldResult> &) { 1125 return foldMemRefCast(*this); 1126 } 1127 LogicalResult MatmulOp::fold(ArrayRef<Attribute>, 1128 SmallVectorImpl<OpFoldResult> &) { 1129 return foldMemRefCast(*this); 1130 } 1131 OpFoldResult ReshapeOp::fold(ArrayRef<Attribute>) { 1132 if (succeeded(foldMemRefCast(*this))) 1133 return getResult(); 1134 return {}; 1135 } 1136 OpFoldResult SliceOp::fold(ArrayRef<Attribute>) { 1137 if (succeeded(foldMemRefCast(*this))) 1138 return getResult(); 1139 return {}; 1140 } 1141 OpFoldResult TransposeOp::fold(ArrayRef<Attribute>) { 1142 if (succeeded(foldMemRefCast(*this))) 1143 return getResult(); 1144 return {}; 1145 } 1146