MLIR 24.0.0git
AffineOps.cpp
Go to the documentation of this file.
1//===- AffineOps.cpp - MLIR Affine 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
14#include "mlir/IR/AffineExpr.h"
16#include "mlir/IR/IRMapping.h"
17#include "mlir/IR/IntegerSet.h"
18#include "mlir/IR/Matchers.h"
21#include "mlir/IR/Value.h"
25#include "llvm/ADT/STLExtras.h"
26#include "llvm/ADT/SmallBitVector.h"
27#include "llvm/ADT/SmallVectorExtras.h"
28#include "llvm/ADT/TypeSwitch.h"
29#include "llvm/Support/DebugLog.h"
30#include "llvm/Support/LogicalResult.h"
31#include "llvm/Support/MathExtras.h"
32#include <numeric>
33#include <optional>
34
35using namespace mlir;
36using namespace mlir::affine;
37
38using llvm::divideCeilSigned;
39using llvm::divideFloorSigned;
40using llvm::mod;
41
42#define DEBUG_TYPE "affine-ops"
43
44#include "mlir/Dialect/Affine/IR/AffineOpsDialect.cpp.inc"
45
46/// A utility function to check if a value is defined at the top level of
47/// `region` or is an argument of `region`. A value of index type defined at the
48/// top level of a `AffineScope` region is always a valid symbol for all
49/// uses in that region.
51 if (auto arg = dyn_cast<BlockArgument>(value))
52 return arg.getParentRegion() == region;
53 return value.getDefiningOp()->getParentRegion() == region;
54}
55
56/// Checks if `value` known to be a legal affine dimension or symbol in `src`
57/// region remains legal if the operation that uses it is inlined into `dest`
58/// with the given value mapping. `legalityCheck` is either `isValidDim` or
59/// `isValidSymbol`, depending on the value being required to remain a valid
60/// dimension or symbol.
61static bool
63 const IRMapping &mapping,
64 function_ref<bool(Value, Region *)> legalityCheck) {
65 // If the value is a valid dimension for any other reason than being
66 // a top-level value, it will remain valid: constants get inlined
67 // with the function, transitive affine applies also get inlined and
68 // will be checked themselves, etc.
69 if (!isTopLevelValue(value, src))
70 return true;
71
72 // If it's a top-level value because it's a block operand, i.e. a
73 // function argument, check whether the value replacing it after
74 // inlining is a valid dimension in the new region.
75 if (llvm::isa<BlockArgument>(value))
76 return legalityCheck(mapping.lookup(value), dest);
77
78 // If it's a top-level value because it's defined in the region,
79 // it can only be inlined if the defining op is a constant or a
80 // `dim`, which can appear anywhere and be valid, since the defining
81 // op won't be top-level anymore after inlining.
82 Attribute operandCst;
83 bool isDimLikeOp = isa<ShapedDimOpInterface>(value.getDefiningOp());
84 return matchPattern(value.getDefiningOp(), m_Constant(&operandCst)) ||
85 isDimLikeOp;
86}
87
88/// Checks if all values known to be legal affine dimensions or symbols in `src`
89/// remain so if their respective users are inlined into `dest`.
90static bool
92 const IRMapping &mapping,
93 function_ref<bool(Value, Region *)> legalityCheck) {
94 return llvm::all_of(values, [&](Value v) {
95 return remainsLegalAfterInline(v, src, dest, mapping, legalityCheck);
96 });
97}
98
99/// Checks if an affine read or write operation remains legal after inlining
100/// from `src` to `dest`.
101template <typename OpTy>
102static bool remainsLegalAfterInline(OpTy op, Region *src, Region *dest,
103 const IRMapping &mapping) {
104 static_assert(llvm::is_one_of<OpTy, AffineReadOpInterface,
105 AffineWriteOpInterface>::value,
106 "only ops with affine read/write interface are supported");
107
108 AffineMap map = op.getAffineMap();
109 ValueRange dimOperands = op.getMapOperands().take_front(map.getNumDims());
110 ValueRange symbolOperands =
111 op.getMapOperands().take_back(map.getNumSymbols());
113 dimOperands, src, dest, mapping,
114 static_cast<bool (*)(Value, Region *)>(isValidDim)))
115 return false;
117 symbolOperands, src, dest, mapping,
118 static_cast<bool (*)(Value, Region *)>(isValidSymbol)))
119 return false;
120 return true;
121}
122
123/// Checks if an affine apply operation remains legal after inlining from `src`
124/// to `dest`.
125// Use "unused attribute" marker to silence clang-tidy warning stemming from
126// the inability to see through "llvm::TypeSwitch".
127template <>
128[[maybe_unused]] bool remainsLegalAfterInline(AffineApplyOp op, Region *src,
129 Region *dest,
130 const IRMapping &mapping) {
131 // If it's a valid dimension, we need to check that it remains so.
132 if (isValidDim(op.getResult(), src))
134 op.getMapOperands(), src, dest, mapping,
135 static_cast<bool (*)(Value, Region *)>(isValidDim));
136
137 // Otherwise it must be a valid symbol, check that it remains so.
139 op.getMapOperands(), src, dest, mapping,
140 static_cast<bool (*)(Value, Region *)>(isValidSymbol));
141}
142
143//===----------------------------------------------------------------------===//
144// AffineDialect Interfaces
145//===----------------------------------------------------------------------===//
146
147namespace {
148/// This class defines the interface for handling inlining with affine
149/// operations.
150struct AffineInlinerInterface : public DialectInlinerInterface {
151 using DialectInlinerInterface::DialectInlinerInterface;
152
153 //===--------------------------------------------------------------------===//
154 // Analysis Hooks
155 //===--------------------------------------------------------------------===//
156
157 /// Returns true if the given region 'src' can be inlined into the region
158 /// 'dest' that is attached to an operation registered to the current dialect.
159 /// 'wouldBeCloned' is set if the region is cloned into its new location
160 /// rather than moved, indicating there may be other users.
161 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
162 IRMapping &valueMapping) const final {
163 // We can inline into affine loops and conditionals if this doesn't break
164 // affine value categorization rules.
165 Operation *destOp = dest->getParentOp();
166 if (!isa<AffineParallelOp, AffineForOp, AffineIfOp>(destOp))
167 return false;
168
169 // Multi-block regions cannot be inlined into affine constructs, all of
170 // which require single-block regions.
171 if (!src->hasOneBlock())
172 return false;
173
174 // Side-effecting operations that the affine dialect cannot understand
175 // should not be inlined.
176 Block &srcBlock = src->front();
177 for (Operation &op : srcBlock) {
178 // Ops with no side effects are fine,
179 if (auto iface = dyn_cast<MemoryEffectOpInterface>(op)) {
180 if (iface.hasNoEffect())
181 continue;
182 }
183
184 // Assuming the inlined region is valid, we only need to check if the
185 // inlining would change it.
186 bool remainsValid =
187 llvm::TypeSwitch<Operation *, bool>(&op)
188 .Case<AffineApplyOp, AffineReadOpInterface,
189 AffineWriteOpInterface>([&](auto op) {
190 return remainsLegalAfterInline(op, src, dest, valueMapping);
191 })
192 .Default([](Operation *) {
193 // Conservatively disallow inlining ops we cannot reason about.
194 return false;
195 });
196
197 if (!remainsValid)
198 return false;
199 }
200
201 return true;
202 }
203
204 /// Returns true if the given operation 'op', that is registered to this
205 /// dialect, can be inlined into the given region, false otherwise.
206 bool isLegalToInline(Operation *op, Region *region, bool wouldBeCloned,
207 IRMapping &valueMapping) const final {
208 // Always allow inlining affine operations into a region that is marked as
209 // affine scope, or into affine loops and conditionals. There are some edge
210 // cases when inlining *into* affine structures, but that is handled in the
211 // other 'isLegalToInline' hook above.
212 Operation *parentOp = region->getParentOp();
213 return parentOp->hasTrait<OpTrait::AffineScope>() ||
214 isa<AffineForOp, AffineParallelOp, AffineIfOp>(parentOp);
215 }
216
217 /// Affine regions should be analyzed recursively.
218 bool shouldAnalyzeRecursively(Operation *op) const final { return true; }
219};
220} // namespace
221
222//===----------------------------------------------------------------------===//
223// AffineDialect
224//===----------------------------------------------------------------------===//
225
226void AffineDialect::initialize() {
227 addOperations<
228#define GET_OP_LIST
229#include "mlir/Dialect/Affine/IR/AffineOps.cpp.inc"
230 >();
231 addInterfaces<AffineInlinerInterface>();
232 declarePromisedInterfaces<ValueBoundsOpInterface, AffineApplyOp, AffineMaxOp,
233 AffineMinOp>();
234}
235
236/// Materialize a single constant operation from a given attribute value with
237/// the desired resultant type.
238Operation *AffineDialect::materializeConstant(OpBuilder &builder,
239 Attribute value, Type type,
240 Location loc) {
241 if (auto poison = dyn_cast<ub::PoisonAttr>(value))
242 return ub::PoisonOp::create(builder, loc, type, poison);
243 return arith::ConstantOp::materialize(builder, value, type, loc);
244}
245
246/// A utility function to check if a value is defined at the top level of an
247/// op with trait `AffineScope`. If the value is defined in an unlinked region,
248/// conservatively assume it is not top-level. A value of index type defined at
249/// the top level is always a valid symbol.
251 if (auto arg = dyn_cast<BlockArgument>(value)) {
252 // The block owning the argument may be unlinked, e.g. when the surrounding
253 // region has not yet been attached to an Op, at which point the parent Op
254 // is null.
255 Operation *parentOp = arg.getOwner()->getParentOp();
256 return parentOp && parentOp->hasTrait<OpTrait::AffineScope>();
257 }
258 // The defining Op may live in an unlinked block so its parent Op may be null.
259 Operation *parentOp = value.getDefiningOp()->getParentOp();
260 return parentOp && parentOp->hasTrait<OpTrait::AffineScope>();
261}
262
263/// Returns the closest region enclosing `op` that is held by an operation with
264/// trait `AffineScope`; `nullptr` if there is no such region.
266 auto *curOp = op;
267 while (auto *parentOp = curOp->getParentOp()) {
268 if (parentOp->hasTrait<OpTrait::AffineScope>())
269 return curOp->getParentRegion();
270 curOp = parentOp;
271 }
272 return nullptr;
273}
274
276 Operation *curOp = op;
277 while (auto *parentOp = curOp->getParentOp()) {
278 if (!isa<AffineForOp, AffineIfOp, AffineParallelOp>(parentOp))
279 return curOp->getParentRegion();
280 curOp = parentOp;
281 }
282 return nullptr;
283}
284
285// A Value can be used as a dimension id iff it meets one of the following
286// conditions:
287// *) It is valid as a symbol.
288// *) It is an induction variable.
289// *) It is the result of affine apply operation with dimension id arguments.
291 // The value must be an index type.
292 if (!value.getType().isIndex())
293 return false;
294
295 if (auto *defOp = value.getDefiningOp())
296 return isValidDim(value, getAffineScope(defOp));
297
298 // This value has to be a block argument for an op that has the
299 // `AffineScope` trait or an induction var of an affine.for or
300 // affine.parallel.
301 if (isAffineInductionVar(value))
302 return true;
303 auto *parentOp = llvm::cast<BlockArgument>(value).getOwner()->getParentOp();
304 return parentOp && parentOp->hasTrait<OpTrait::AffineScope>();
305}
306
307// Value can be used as a dimension id iff it meets one of the following
308// conditions:
309// *) It is valid as a symbol.
310// *) It is an induction variable.
311// *) It is the result of an affine apply operation with dimension id operands.
312// *) It is the result of a more specialized index transformation (ex.
313// delinearize_index or linearize_index) with dimension id operands.
315 // The value must be an index type.
316 if (!value.getType().isIndex())
317 return false;
318
319 // All valid symbols are okay.
320 if (isValidSymbol(value, region))
321 return true;
322
323 auto *op = value.getDefiningOp();
324 if (!op) {
325 // This value has to be an induction var for an affine.for or an
326 // affine.parallel.
327 return isAffineInductionVar(value);
328 }
329
330 // Affine apply operation is ok if all of its operands are ok.
331 if (auto applyOp = dyn_cast<AffineApplyOp>(op))
332 return applyOp.isValidDim(region);
333 // delinearize_index and linearize_index are special forms of apply
334 // and so are valid dimensions if all their arguments are valid dimensions.
335 if (isa<AffineDelinearizeIndexOp, AffineLinearizeIndexOp>(op))
336 return llvm::all_of(op->getOperands(),
337 [&](Value arg) { return ::isValidDim(arg, region); });
338 // The dim op is okay if its operand memref/tensor is defined at the top
339 // level.
340 if (auto dimOp = dyn_cast<ShapedDimOpInterface>(op))
341 return isTopLevelValue(dimOp.getShapedValue());
342 return false;
343}
344
345/// Returns true if the 'index' dimension of the `memref` defined by
346/// `memrefDefOp` is a statically shaped one or defined using a valid symbol
347/// for `region`.
348template <typename AnyMemRefDefOp>
349static bool isMemRefSizeValidSymbol(AnyMemRefDefOp memrefDefOp, unsigned index,
350 Region *region) {
351 MemRefType memRefType = memrefDefOp.getType();
352
353 // Dimension index is out of bounds.
354 if (index >= memRefType.getRank()) {
355 return false;
356 }
357
358 // Statically shaped.
359 if (!memRefType.isDynamicDim(index))
360 return true;
361 // Get the position of the dimension among dynamic dimensions;
362 unsigned dynamicDimPos = memRefType.getDynamicDimIndex(index);
363 return isValidSymbol(*(memrefDefOp.getDynamicSizes().begin() + dynamicDimPos),
364 region);
365}
366
367/// Returns true if the result of the dim op is a valid symbol for `region`.
368static bool isDimOpValidSymbol(ShapedDimOpInterface dimOp, Region *region) {
369 // The dim op is okay if its source is defined at the top level.
370 if (isTopLevelValue(dimOp.getShapedValue()))
371 return true;
372
373 // Conservatively handle remaining BlockArguments as non-valid symbols.
374 // E.g. scf.for iterArgs.
375 if (llvm::isa<BlockArgument>(dimOp.getShapedValue()))
376 return false;
377
378 // The dim op is also okay if its operand memref is a view/subview whose
379 // corresponding size is a valid symbol.
380 std::optional<int64_t> index = getConstantIntValue(dimOp.getDimension());
381
382 // Be conservative if we can't understand the dimension.
383 if (!index.has_value())
384 return false;
385
386 // Skip over all memref.cast ops (if any).
387 Operation *op = dimOp.getShapedValue().getDefiningOp();
388 while (auto castOp = dyn_cast<memref::CastOp>(op)) {
389 // Bail on unranked memrefs.
390 if (isa<UnrankedMemRefType>(castOp.getSource().getType()))
391 return false;
392 op = castOp.getSource().getDefiningOp();
393 if (!op)
394 return false;
395 }
396
397 int64_t i = index.value();
399 .Case<memref::ViewOp, memref::SubViewOp, memref::AllocOp>(
400 [&](auto op) { return isMemRefSizeValidSymbol(op, i, region); })
401 .Default([](Operation *) { return false; });
402}
403
404// A value can be used as a symbol (at all its use sites) iff it meets one of
405// the following conditions:
406// *) It is a constant.
407// *) Its defining op or block arg appearance is immediately enclosed by an op
408// with `AffineScope` trait.
409// *) It is the result of an affine.apply operation with symbol operands.
410// *) It is a result of the dim op on a memref whose corresponding size is a
411// valid symbol.
413 if (!value)
414 return false;
415
416 // The value must be an index type.
417 if (!value.getType().isIndex())
418 return false;
419
420 // Check that the value is a top level value.
421 if (isTopLevelValue(value))
422 return true;
423
424 if (auto *defOp = value.getDefiningOp())
425 return isValidSymbol(value, getAffineScope(defOp));
426
427 return false;
428}
429
430/// A utility function to check if a value is defined at the top level of
431/// `region` or is an argument of `region` or is defined above the region.
432static bool isTopLevelValueOrAbove(Value value, Region *region) {
433 Region *parentRegion = value.getParentRegion();
434 do {
435 if (parentRegion == region)
436 return true;
437 Operation *regionOp = region->getParentOp();
438 if (regionOp->hasTrait<OpTrait::IsIsolatedFromAbove>())
439 break;
440 region = region->getParentOp()->getParentRegion();
441 } while (region);
442 return false;
443}
444
445/// A value can be used as a symbol for `region` iff it meets one of the
446/// following conditions:
447/// *) It is a constant.
448/// *) It is a result of a `Pure` operation whose operands are valid symbolic
449/// *) identifiers.
450/// *) It is a result of the dim op on a memref whose corresponding size is
451/// a valid symbol.
452/// *) It is defined at the top level of 'region' or is its argument.
453/// *) It dominates `region`'s parent op.
454/// If `region` is null, conservatively assume the symbol definition scope does
455/// not exist and only accept the values that would be symbols regardless of
456/// the surrounding region structure, i.e. the first three cases above.
458 // The value must be an index type.
459 if (!value.getType().isIndex())
460 return false;
461
462 // A top-level value is a valid symbol.
463 if (region && isTopLevelValueOrAbove(value, region))
464 return true;
465
466 auto *defOp = value.getDefiningOp();
467 if (!defOp)
468 return false;
469
470 // Constant operation is ok.
471 Attribute operandCst;
472 if (matchPattern(defOp, m_Constant(&operandCst)))
473 return true;
474
475 // `Pure` operation that whose operands are valid symbolic identifiers.
476 if (isPure(defOp) && llvm::all_of(defOp->getOperands(), [&](Value operand) {
477 return affine::isValidSymbol(operand, region);
478 })) {
479 return true;
480 }
481
482 // Dim op results could be valid symbols at any level.
483 if (auto dimOp = dyn_cast<ShapedDimOpInterface>(defOp))
484 return isDimOpValidSymbol(dimOp, region);
485
486 return false;
487}
488
489// Returns true if 'value' is a valid index to an affine operation (e.g.
490// affine.load, affine.store, affine.dma_start, affine.dma_wait) where
491// `region` provides the polyhedral symbol scope. Returns false otherwise.
492static bool isValidAffineIndexOperand(Value value, Region *region) {
493 return isValidDim(value, region) || isValidSymbol(value, region);
494}
495
496/// Prints dimension and symbol list.
499 unsigned numDims, OpAsmPrinter &printer) {
500 OperandRange operands(begin, end);
501 printer << '(' << operands.take_front(numDims) << ')';
502 if (operands.size() > numDims)
503 printer << '[' << operands.drop_front(numDims) << ']';
504}
505
506/// Parses dimension and symbol list and returns true if parsing failed.
508 OpAsmParser &parser, SmallVectorImpl<Value> &operands, unsigned &numDims) {
511 return failure();
512 // Store number of dimensions for validation by caller.
513 numDims = opInfos.size();
514
515 // Parse the optional symbol operands.
516 auto indexTy = parser.getBuilder().getIndexType();
517 return failure(parser.parseOperandList(
519 parser.resolveOperands(opInfos, indexTy, operands));
520}
521
522/// Utility function to verify that a set of operands are valid dimension and
523/// symbol identifiers. The operands should be laid out such that the dimension
524/// operands are before the symbol operands. This function returns failure if
525/// there was an invalid operand. An operation is provided to emit any necessary
526/// errors.
527template <typename OpTy>
528static LogicalResult
530 unsigned numDims) {
531 unsigned opIt = 0;
532 for (auto operand : operands) {
533 if (opIt++ < numDims) {
534 if (!isValidDim(operand, getAffineScope(op)))
535 return op.emitOpError("operand cannot be used as a dimension id");
536 } else if (!isValidSymbol(operand, getAffineScope(op))) {
537 return op.emitOpError("operand cannot be used as a symbol");
538 }
539 }
540 return success();
541}
542
543//===----------------------------------------------------------------------===//
544// AffineApplyOp
545//===----------------------------------------------------------------------===//
546
547AffineValueMap AffineApplyOp::getAffineValueMap() {
548 return AffineValueMap(getAffineMap(), getOperands(), getResult());
549}
550
551ParseResult AffineApplyOp::parse(OpAsmParser &parser, OperationState &result) {
552 auto &builder = parser.getBuilder();
553 auto indexTy = builder.getIndexType();
554
555 AffineMapAttr mapAttr;
556 unsigned numDims;
557 if (parser.parseAttribute(mapAttr, "map", result.attributes) ||
558 parseDimAndSymbolList(parser, result.operands, numDims) ||
559 parser.parseOptionalAttrDict(result.attributes))
560 return failure();
561 auto map = mapAttr.getValue();
562
563 if (map.getNumDims() != numDims ||
564 numDims + map.getNumSymbols() != result.operands.size()) {
565 return parser.emitError(parser.getNameLoc(),
566 "dimension or symbol index mismatch");
567 }
568
569 result.types.append(map.getNumResults(), indexTy);
570 return success();
571}
572
573void AffineApplyOp::print(OpAsmPrinter &p) {
574 p << " " << getMapAttr();
575 printDimAndSymbolList(operand_begin(), operand_end(),
576 getAffineMap().getNumDims(), p);
577 p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"map"});
578}
579
580LogicalResult AffineApplyOp::verify() {
581 // Check input and output dimensions match.
582 AffineMap affineMap = getMap();
583
584 // Verify that operand count matches affine map dimension and symbol count.
585 if (getNumOperands() != affineMap.getNumDims() + affineMap.getNumSymbols())
586 return emitOpError(
587 "operand count and affine map dimension and symbol count must match");
588
589 // Verify that the map only produces one result.
590 if (affineMap.getNumResults() != 1)
591 return emitOpError("mapping must produce one value");
592
593 // Do not allow valid dims to be used in symbol positions. We do allow
594 // affine.apply to use operands for values that may neither qualify as affine
595 // dims or affine symbols due to usage outside of affine ops, analyses, etc.
596 Region *region = getAffineScope(*this);
597 for (Value operand : getMapOperands().drop_front(affineMap.getNumDims())) {
598 if (::isValidDim(operand, region) && !::isValidSymbol(operand, region))
599 return emitError("dimensional operand cannot be used as a symbol");
600 }
601
602 return success();
603}
604
605// The result of the affine apply operation can be used as a dimension id if all
606// its operands are valid dimension ids.
607bool AffineApplyOp::isValidDim() {
608 return llvm::all_of(getOperands(),
609 [](Value op) { return affine::isValidDim(op); });
610}
611
612// The result of the affine apply operation can be used as a dimension id if all
613// its operands are valid dimension ids with the parent operation of `region`
614// defining the polyhedral scope for symbols.
615bool AffineApplyOp::isValidDim(Region *region) {
616 return llvm::all_of(getOperands(),
617 [&](Value op) { return ::isValidDim(op, region); });
618}
619
620// The result of the affine apply operation can be used as a symbol if all its
621// operands are symbols.
622bool AffineApplyOp::isValidSymbol() {
623 return llvm::all_of(getOperands(),
624 [](Value op) { return affine::isValidSymbol(op); });
625}
626
627// The result of the affine apply operation can be used as a symbol in `region`
628// if all its operands are symbols in `region`.
629bool AffineApplyOp::isValidSymbol(Region *region) {
630 return llvm::all_of(getOperands(), [&](Value operand) {
631 return affine::isValidSymbol(operand, region);
632 });
633}
634
635OpFoldResult AffineApplyOp::fold(FoldAdaptor adaptor) {
636 auto map = getAffineMap();
637
638 // Fold dims and symbols to existing values.
639 auto expr = map.getResult(0);
640 if (auto dim = dyn_cast<AffineDimExpr>(expr))
641 return getOperand(dim.getPosition());
642 if (auto sym = dyn_cast<AffineSymbolExpr>(expr))
643 return getOperand(map.getNumDims() + sym.getPosition());
644
645 // Otherwise, default to folding the map.
647 bool hasPoison = false;
648 auto foldResult =
649 map.constantFold(adaptor.getMapOperands(), result, &hasPoison);
650 if (hasPoison)
651 return ub::PoisonAttr::get(getContext());
652 if (failed(foldResult))
653 return {};
654 return result[0];
655}
656
657/// Returns the largest known divisor of `e`. Exploits information from the
658/// values in `operands`.
660 // This method isn't aware of `operands`.
662
663 // We now make use of operands for the case `e` is a dim expression.
664 // TODO: More powerful simplification would have to modify
665 // getLargestKnownDivisor to take `operands` and exploit that information as
666 // well for dim/sym expressions, but in that case, getLargestKnownDivisor
667 // can't be part of the IR library but of the `Analysis` library. The IR
668 // library can only really depend on simple O(1) checks.
669 auto dimExpr = dyn_cast<AffineDimExpr>(e);
670 // If it's not a dim expr, `div` is the best we have.
671 if (!dimExpr)
672 return div;
673
674 // We simply exploit information from loop IVs.
675 // We don't need to use mlir::getLargestKnownDivisorOfValue since the other
676 // desired simplifications are expected to be part of other
677 // canonicalizations. Also, mlir::getLargestKnownDivisorOfValue is part of the
678 // LoopAnalysis library.
679 Value operand = operands[dimExpr.getPosition()];
680 int64_t operandDivisor = 1;
681 // TODO: With the right accessors, this can be extended to
682 // LoopLikeOpInterface.
683 if (AffineForOp forOp = getForInductionVarOwner(operand)) {
684 if (forOp.hasConstantLowerBound() && forOp.getConstantLowerBound() == 0) {
685 operandDivisor = forOp.getStepAsInt();
686 } else {
687 uint64_t lbLargestKnownDivisor =
688 forOp.getLowerBoundMap().getLargestKnownDivisorOfMapExprs();
689 operandDivisor = std::gcd(lbLargestKnownDivisor, forOp.getStepAsInt());
690 }
691 }
692 return operandDivisor;
693}
694
695/// Check if `e` is known to be: 0 <= `e` < `k`. Handles the simple cases of `e`
696/// being an affine dim expression or a constant.
698 int64_t k) {
699 if (auto constExpr = dyn_cast<AffineConstantExpr>(e)) {
700 int64_t constVal = constExpr.getValue();
701 return constVal >= 0 && constVal < k;
702 }
703 auto dimExpr = dyn_cast<AffineDimExpr>(e);
704 if (!dimExpr)
705 return false;
706 Value operand = operands[dimExpr.getPosition()];
707 // TODO: With the right accessors, this can be extended to
708 // LoopLikeOpInterface.
709 if (AffineForOp forOp = getForInductionVarOwner(operand)) {
710 if (forOp.hasConstantLowerBound() && forOp.getConstantLowerBound() >= 0 &&
711 forOp.hasConstantUpperBound() && forOp.getConstantUpperBound() <= k) {
712 return true;
713 }
714 }
715
716 // We don't consider other cases like `operand` being defined by a constant or
717 // an affine.apply op since such cases will already be handled by other
718 // patterns and propagation of loop IVs or constant would happen.
719 return false;
720}
721
722/// Check if expression `e` is of the form d*e_1 + e_2 where 0 <= e_2 < d.
723/// Set `div` to `d`, `quotientTimesDiv` to e_1 and `rem` to e_2 if the
724/// expression is in that form.
726 AffineExpr &quotientTimesDiv, AffineExpr &rem) {
727 auto bin = dyn_cast<AffineBinaryOpExpr>(e);
728 if (!bin || bin.getKind() != AffineExprKind::Add)
729 return false;
730
731 AffineExpr llhs = bin.getLHS();
732 AffineExpr rlhs = bin.getRHS();
733 div = getLargestKnownDivisor(llhs, operands);
734 if (isNonNegativeBoundedBy(rlhs, operands, div)) {
735 quotientTimesDiv = llhs;
736 rem = rlhs;
737 return true;
738 }
739 div = getLargestKnownDivisor(rlhs, operands);
740 if (isNonNegativeBoundedBy(llhs, operands, div)) {
741 quotientTimesDiv = rlhs;
742 rem = llhs;
743 return true;
744 }
745 return false;
746}
747
748/// Gets the constant lower bound on an `iv`.
749static std::optional<int64_t> getLowerBound(Value iv) {
750 AffineForOp forOp = getForInductionVarOwner(iv);
751 if (forOp && forOp.hasConstantLowerBound())
752 return forOp.getConstantLowerBound();
753 return std::nullopt;
754}
755
756/// Gets the constant upper bound on an affine.for `iv`.
757static std::optional<int64_t> getUpperBound(Value iv) {
758 AffineForOp forOp = getForInductionVarOwner(iv);
759 if (!forOp || !forOp.hasConstantUpperBound())
760 return std::nullopt;
761
762 // If its lower bound is also known, we can get a more precise bound
763 // whenever the step is not one.
764 if (forOp.hasConstantLowerBound()) {
765 return forOp.getConstantUpperBound() - 1 -
766 (forOp.getConstantUpperBound() - forOp.getConstantLowerBound() - 1) %
767 forOp.getStepAsInt();
768 }
769 return forOp.getConstantUpperBound() - 1;
770}
771
772/// Determine a constant upper bound for `expr` if one exists while exploiting
773/// values in `operands`. Note that the upper bound is an inclusive one. `expr`
774/// is guaranteed to be less than or equal to it.
775static std::optional<int64_t> getUpperBound(AffineExpr expr, unsigned numDims,
776 unsigned numSymbols,
777 ArrayRef<Value> operands) {
778 // Get the constant lower or upper bounds on the operands.
779 SmallVector<std::optional<int64_t>> constLowerBounds, constUpperBounds;
780 constLowerBounds.reserve(operands.size());
781 constUpperBounds.reserve(operands.size());
782 for (Value operand : operands) {
783 constLowerBounds.push_back(getLowerBound(operand));
784 constUpperBounds.push_back(getUpperBound(operand));
785 }
786
787 if (auto constExpr = dyn_cast<AffineConstantExpr>(expr))
788 return constExpr.getValue();
789
790 return getBoundForAffineExpr(expr, numDims, numSymbols, constLowerBounds,
791 constUpperBounds,
792 /*isUpper=*/true);
793}
794
795/// Determine a constant lower bound for `expr` if one exists while exploiting
796/// values in `operands`. Note that the upper bound is an inclusive one. `expr`
797/// is guaranteed to be less than or equal to it.
798static std::optional<int64_t> getLowerBound(AffineExpr expr, unsigned numDims,
799 unsigned numSymbols,
800 ArrayRef<Value> operands) {
801 // Get the constant lower or upper bounds on the operands.
802 SmallVector<std::optional<int64_t>> constLowerBounds, constUpperBounds;
803 constLowerBounds.reserve(operands.size());
804 constUpperBounds.reserve(operands.size());
805 for (Value operand : operands) {
806 constLowerBounds.push_back(getLowerBound(operand));
807 constUpperBounds.push_back(getUpperBound(operand));
808 }
809
810 std::optional<int64_t> lowerBound;
811 if (auto constExpr = dyn_cast<AffineConstantExpr>(expr)) {
812 lowerBound = constExpr.getValue();
813 } else {
814 lowerBound = getBoundForAffineExpr(expr, numDims, numSymbols,
815 constLowerBounds, constUpperBounds,
816 /*isUpper=*/false);
817 }
818 return lowerBound;
819}
820
821/// Simplify `expr` while exploiting information from the values in `operands`.
822static void simplifyExprAndOperands(AffineExpr &expr, unsigned numDims,
823 unsigned numSymbols,
824 ArrayRef<Value> operands) {
825 // We do this only for certain floordiv/mod expressions.
826 auto binExpr = dyn_cast<AffineBinaryOpExpr>(expr);
827 if (!binExpr)
828 return;
829
830 // Simplify the child expressions first.
831 AffineExpr lhs = binExpr.getLHS();
832 AffineExpr rhs = binExpr.getRHS();
833 simplifyExprAndOperands(lhs, numDims, numSymbols, operands);
834 simplifyExprAndOperands(rhs, numDims, numSymbols, operands);
835 expr = getAffineBinaryOpExpr(binExpr.getKind(), lhs, rhs);
836
837 binExpr = dyn_cast<AffineBinaryOpExpr>(expr);
838 if (!binExpr || (expr.getKind() != AffineExprKind::FloorDiv &&
840 expr.getKind() != AffineExprKind::Mod)) {
841 return;
842 }
843
844 // The `lhs` and `rhs` may be different post construction of simplified expr.
845 lhs = binExpr.getLHS();
846 rhs = binExpr.getRHS();
847 auto rhsConst = dyn_cast<AffineConstantExpr>(rhs);
848 if (!rhsConst)
849 return;
850
851 int64_t rhsConstVal = rhsConst.getValue();
852 // Undefined exprsessions aren't touched; IR can still be valid with them.
853 if (rhsConstVal <= 0)
854 return;
855
856 // Exploit constant lower/upper bounds to simplify a floordiv or mod.
857 MLIRContext *context = expr.getContext();
858 std::optional<int64_t> lhsLbConst =
859 getLowerBound(lhs, numDims, numSymbols, operands);
860 std::optional<int64_t> lhsUbConst =
861 getUpperBound(lhs, numDims, numSymbols, operands);
862 if (lhsLbConst && lhsUbConst) {
863 int64_t lhsLbConstVal = *lhsLbConst;
864 int64_t lhsUbConstVal = *lhsUbConst;
865 // lhs floordiv c is a single value lhs is bounded in a range `c` that has
866 // the same quotient.
867 if (binExpr.getKind() == AffineExprKind::FloorDiv &&
868 divideFloorSigned(lhsLbConstVal, rhsConstVal) ==
869 divideFloorSigned(lhsUbConstVal, rhsConstVal)) {
871 divideFloorSigned(lhsLbConstVal, rhsConstVal), context);
872 return;
873 }
874 // lhs ceildiv c is a single value if the entire range has the same ceil
875 // quotient.
876 if (binExpr.getKind() == AffineExprKind::CeilDiv &&
877 divideCeilSigned(lhsLbConstVal, rhsConstVal) ==
878 divideCeilSigned(lhsUbConstVal, rhsConstVal)) {
879 expr = getAffineConstantExpr(divideCeilSigned(lhsLbConstVal, rhsConstVal),
880 context);
881 return;
882 }
883 // lhs mod c is lhs if the entire range has quotient 0 w.r.t the rhs.
884 if (binExpr.getKind() == AffineExprKind::Mod && lhsLbConstVal >= 0 &&
885 lhsLbConstVal < rhsConstVal && lhsUbConstVal < rhsConstVal) {
886 expr = lhs;
887 return;
888 }
889 }
890
891 // Simplify expressions of the form e = (e_1 + e_2) floordiv c or (e_1 + e_2)
892 // mod c, where e_1 is a multiple of `k` and 0 <= e_2 < k. In such cases, if
893 // `c` % `k` == 0, (e_1 + e_2) floordiv c can be simplified to e_1 floordiv c.
894 // And when k % c == 0, (e_1 + e_2) mod c can be simplified to e_2 mod c.
895 AffineExpr quotientTimesDiv, rem;
896 int64_t divisor;
897 if (isQTimesDPlusR(lhs, operands, divisor, quotientTimesDiv, rem)) {
898 if (rhsConstVal % divisor == 0 &&
899 binExpr.getKind() == AffineExprKind::FloorDiv) {
900 expr = quotientTimesDiv.floorDiv(rhsConst);
901 } else if (divisor % rhsConstVal == 0 &&
902 binExpr.getKind() == AffineExprKind::Mod) {
903 expr = rem % rhsConst;
904 }
905 return;
906 }
907
908 // Handle the simple case when the LHS expression can be either upper
909 // bounded or is a known multiple of RHS constant.
910 // lhs floordiv c -> 0 if 0 <= lhs < c,
911 // lhs mod c -> 0 if lhs % c = 0.
912 if ((isNonNegativeBoundedBy(lhs, operands, rhsConstVal) &&
913 binExpr.getKind() == AffineExprKind::FloorDiv) ||
914 (getLargestKnownDivisor(lhs, operands) % rhsConstVal == 0 &&
915 binExpr.getKind() == AffineExprKind::Mod)) {
916 expr = getAffineConstantExpr(0, expr.getContext());
917 }
918}
919
920/// Simplify the expressions in `map` while making use of lower or upper bounds
921/// of its operands. If `isMax` is true, the map is to be treated as a max of
922/// its result expressions, and min otherwise. Eg: min (d0, d1) -> (8, 4 * d0 +
923/// d1) can be simplified to (8) if the operands are respectively lower bounded
924/// by 2 and 0 (the second expression can't be lower than 8).
926 ArrayRef<Value> operands,
927 bool isMax) {
928 // Can't simplify.
929 if (operands.empty())
930 return;
931
932 // Get the upper or lower bound on an affine.for op IV using its range.
933 // Get the constant lower or upper bounds on the operands.
934 SmallVector<std::optional<int64_t>> constLowerBounds, constUpperBounds;
935 constLowerBounds.reserve(operands.size());
936 constUpperBounds.reserve(operands.size());
937 for (Value operand : operands) {
938 constLowerBounds.push_back(getLowerBound(operand));
939 constUpperBounds.push_back(getUpperBound(operand));
940 }
941
942 // We will compute the lower and upper bounds on each of the expressions
943 // Then, we will check (depending on max or min) as to whether a specific
944 // bound is redundant by checking if its highest (in case of max) and its
945 // lowest (in the case of min) value is already lower than (or higher than)
946 // the lower bound (or upper bound in the case of min) of another bound.
947 SmallVector<std::optional<int64_t>, 4> lowerBounds, upperBounds;
948 lowerBounds.reserve(map.getNumResults());
949 upperBounds.reserve(map.getNumResults());
950 for (AffineExpr e : map.getResults()) {
951 if (auto constExpr = dyn_cast<AffineConstantExpr>(e)) {
952 lowerBounds.push_back(constExpr.getValue());
953 upperBounds.push_back(constExpr.getValue());
954 } else {
955 lowerBounds.push_back(
957 constLowerBounds, constUpperBounds,
958 /*isUpper=*/false));
959 upperBounds.push_back(
961 constLowerBounds, constUpperBounds,
962 /*isUpper=*/true));
963 }
964 }
965
966 // Collect expressions that are not redundant.
967 SmallVector<AffineExpr, 4> irredundantExprs;
968 for (auto exprEn : llvm::enumerate(map.getResults())) {
969 AffineExpr e = exprEn.value();
970 unsigned i = exprEn.index();
971 // Some expressions can be turned into constants.
972 if (lowerBounds[i] && upperBounds[i] && *lowerBounds[i] == *upperBounds[i])
973 e = getAffineConstantExpr(*lowerBounds[i], e.getContext());
974
975 // Check if the expression is redundant.
976 if (isMax) {
977 if (!upperBounds[i]) {
978 irredundantExprs.push_back(e);
979 continue;
980 }
981 // If there exists another expression such that its lower bound is greater
982 // than this expression's upper bound, it's redundant.
983 if (!llvm::any_of(llvm::enumerate(lowerBounds), [&](const auto &en) {
984 auto otherLowerBound = en.value();
985 unsigned pos = en.index();
986 if (pos == i || !otherLowerBound)
987 return false;
988 if (*otherLowerBound > *upperBounds[i])
989 return true;
990 if (*otherLowerBound < *upperBounds[i])
991 return false;
992 // Equality case. When both expressions are considered redundant, we
993 // don't want to get both of them. We keep the one that appears
994 // first.
995 if (upperBounds[pos] && lowerBounds[i] &&
996 lowerBounds[i] == upperBounds[i] &&
997 otherLowerBound == *upperBounds[pos] && i < pos)
998 return false;
999 return true;
1000 }))
1001 irredundantExprs.push_back(e);
1002 } else {
1003 if (!lowerBounds[i]) {
1004 irredundantExprs.push_back(e);
1005 continue;
1006 }
1007 // Likewise for the `min` case. Use the complement of the condition above.
1008 if (!llvm::any_of(llvm::enumerate(upperBounds), [&](const auto &en) {
1009 auto otherUpperBound = en.value();
1010 unsigned pos = en.index();
1011 if (pos == i || !otherUpperBound)
1012 return false;
1013 if (*otherUpperBound < *lowerBounds[i])
1014 return true;
1015 if (*otherUpperBound > *lowerBounds[i])
1016 return false;
1017 if (lowerBounds[pos] && upperBounds[i] &&
1018 lowerBounds[i] == upperBounds[i] &&
1019 otherUpperBound == lowerBounds[pos] && i < pos)
1020 return false;
1021 return true;
1022 }))
1023 irredundantExprs.push_back(e);
1024 }
1025 }
1026
1027 // Create the map without the redundant expressions.
1028 map = AffineMap::get(map.getNumDims(), map.getNumSymbols(), irredundantExprs,
1029 map.getContext());
1030}
1031
1032/// Simplify the map while exploiting information on the values in `operands`.
1033// Use "unused attribute" marker to silence warning stemming from the inability
1034// to see through the template expansion.
1035[[maybe_unused]] static void simplifyMapWithOperands(AffineMap &map,
1036 ArrayRef<Value> operands) {
1037 assert(map.getNumInputs() == operands.size() && "invalid operands for map");
1038 SmallVector<AffineExpr> newResults;
1039 newResults.reserve(map.getNumResults());
1040 for (AffineExpr expr : map.getResults()) {
1042 operands);
1043 newResults.push_back(expr);
1044 }
1045 map = AffineMap::get(map.getNumDims(), map.getNumSymbols(), newResults,
1046 map.getContext());
1047}
1048
1049/// Assuming `dimOrSym` is a quantity in the apply op map `map` and defined by
1050/// `minOp = affine_min(x_1, ..., x_n)`. This function checks that:
1051/// `0 < affine_min(x_1, ..., x_n)` and proceeds with replacing the patterns:
1052/// ```
1053/// dimOrSym.ceildiv(x_k)
1054/// (dimOrSym + x_k - 1).floordiv(x_k)
1055/// ```
1056/// by `1` for all `k` in `1, ..., n`. This is possible because `x / x_k <= 1`.
1057///
1058///
1059/// Warning: ValueBoundsConstraintSet::computeConstantBound is needed to check
1060/// `minOp` is positive.
1061static LogicalResult replaceAffineMinBoundingBoxExpression(AffineMinOp minOp,
1062 AffineExpr dimOrSym,
1063 AffineMap *map,
1064 ValueRange dims,
1065 ValueRange syms) {
1066 LDBG() << "replaceAffineMinBoundingBoxExpression: `" << minOp << "`";
1067 AffineMap affineMinMap = minOp.getAffineMap();
1068
1069 // Check the value is positive.
1070 for (unsigned i = 0, e = affineMinMap.getNumResults(); i < e; ++i) {
1071 // Compare each expression in the minimum against 0.
1073 getAsIndexOpFoldResult(minOp.getContext(), 0),
1076 minOp.getOperands())))
1077 return failure();
1078 }
1079
1080 /// Convert affine symbols and dimensions in minOp to symbols or dimensions in
1081 /// the apply op affine map.
1082 DenseMap<AffineExpr, AffineExpr> dimSymConversionTable;
1083 SmallVector<unsigned> unmappedDims, unmappedSyms;
1084 for (auto [i, dim] : llvm::enumerate(minOp.getDimOperands())) {
1085 auto it = llvm::find(dims, dim);
1086 if (it == dims.end()) {
1087 unmappedDims.push_back(i);
1088 continue;
1089 }
1090 dimSymConversionTable[getAffineDimExpr(i, minOp.getContext())] =
1091 getAffineDimExpr(it.getIndex(), minOp.getContext());
1092 }
1093 for (auto [i, sym] : llvm::enumerate(minOp.getSymbolOperands())) {
1094 auto it = llvm::find(syms, sym);
1095 if (it == syms.end()) {
1096 unmappedSyms.push_back(i);
1097 continue;
1098 }
1099 dimSymConversionTable[getAffineSymbolExpr(i, minOp.getContext())] =
1100 getAffineSymbolExpr(it.getIndex(), minOp.getContext());
1101 }
1102
1103 // Create the replacement map.
1105 AffineExpr c1 = getAffineConstantExpr(1, minOp.getContext());
1106 for (AffineExpr expr : affineMinMap.getResults()) {
1107 // If we cannot express the result in terms of the apply map symbols and
1108 // sims then continue.
1109 if (llvm::any_of(unmappedDims,
1110 [&](unsigned i) { return expr.isFunctionOfDim(i); }) ||
1111 llvm::any_of(unmappedSyms,
1112 [&](unsigned i) { return expr.isFunctionOfSymbol(i); }))
1113 continue;
1114
1115 AffineExpr convertedExpr = expr.replace(dimSymConversionTable);
1116
1117 // dimOrSym.ceilDiv(expr) -> 1
1118 repl[dimOrSym.ceilDiv(convertedExpr)] = c1;
1119 // (dimOrSym + expr - 1).floorDiv(expr) -> 1
1120 repl[(dimOrSym + convertedExpr - 1).floorDiv(convertedExpr)] = c1;
1121 }
1122 AffineMap initialMap = *map;
1123 *map = initialMap.replace(repl, initialMap.getNumDims(),
1124 initialMap.getNumSymbols());
1125 return success(*map != initialMap);
1126}
1127
1128/// Recursively traverse `e`. If `e` or one of its sub-expressions has the form
1129/// e1 + e2 + ... + eK, where the e_i are a super(multi)set of `exprsToRemove`,
1130/// place a map between e and `newVal` + sum({e1, e2, .. eK} - exprsToRemove)
1131/// into `replacementsMap`. If no entries were added to `replacementsMap`,
1132/// nothing was found.
1134 AffineExpr e, const llvm::SmallDenseSet<AffineExpr, 4> &exprsToRemove,
1135 AffineExpr newVal, DenseMap<AffineExpr, AffineExpr> &replacementsMap) {
1136 auto binOp = dyn_cast<AffineBinaryOpExpr>(e);
1137 if (!binOp)
1138 return;
1139 AffineExpr lhs = binOp.getLHS();
1140 AffineExpr rhs = binOp.getRHS();
1141 if (binOp.getKind() != AffineExprKind::Add) {
1142 shortenAddChainsContainingAll(lhs, exprsToRemove, newVal, replacementsMap);
1143 shortenAddChainsContainingAll(rhs, exprsToRemove, newVal, replacementsMap);
1144 return;
1145 }
1146 SmallVector<AffineExpr> toPreserve;
1147 llvm::SmallDenseSet<AffineExpr, 4> ourTracker(exprsToRemove);
1148 AffineExpr thisTerm = rhs;
1149 AffineExpr nextTerm = lhs;
1150
1151 while (thisTerm) {
1152 if (!ourTracker.erase(thisTerm)) {
1153 toPreserve.push_back(thisTerm);
1154 shortenAddChainsContainingAll(thisTerm, exprsToRemove, newVal,
1155 replacementsMap);
1156 }
1157 auto nextBinOp = dyn_cast_if_present<AffineBinaryOpExpr>(nextTerm);
1158 if (!nextBinOp || nextBinOp.getKind() != AffineExprKind::Add) {
1159 thisTerm = nextTerm;
1160 nextTerm = AffineExpr();
1161 } else {
1162 thisTerm = nextBinOp.getRHS();
1163 nextTerm = nextBinOp.getLHS();
1164 }
1165 }
1166 if (!ourTracker.empty())
1167 return;
1168 // We reverse the terms to be preserved here in order to preserve
1169 // associativity between them.
1170 AffineExpr newExpr = newVal;
1171 for (AffineExpr preserved : llvm::reverse(toPreserve))
1172 newExpr = newExpr + preserved;
1173 replacementsMap.insert({e, newExpr});
1174}
1175
1176/// If this map contains of the expression `x_1 + x_1 * C_1 + ... x_n * C_N +
1177/// ...` (not necessarily in order) where the set of the `x_i` is the set of
1178/// outputs of an `affine.delinearize_index` whos inverse is that expression,
1179/// replace that expression with the input of that delinearize_index op.
1180///
1181/// `unitDimInput` is the input that was detected as the potential start to this
1182/// replacement chain - if it isn't the rightmost result of the delinearization,
1183/// this method fails. (This is intended to ensure we don't have redundant scans
1184/// over the same expression).
1185///
1186/// While this currently only handles delinearizations with a constant basis,
1187/// that isn't a fundamental limitation.
1188///
1189/// This is a utility function for `replaceDimOrSym` below.
1191 AffineDelinearizeIndexOp delinOp, Value resultToReplace, AffineMap *map,
1193 if (!delinOp.getDynamicBasis().empty())
1194 return failure();
1195 if (resultToReplace != delinOp.getMultiIndex().back())
1196 return failure();
1197
1198 MLIRContext *ctx = delinOp.getContext();
1199 SmallVector<AffineExpr> resToExpr(delinOp.getNumResults(), AffineExpr());
1200 for (auto [pos, dim] : llvm::enumerate(dims)) {
1201 auto asResult = dyn_cast_if_present<OpResult>(dim);
1202 if (!asResult)
1203 continue;
1204 if (asResult.getOwner() == delinOp.getOperation())
1205 resToExpr[asResult.getResultNumber()] = getAffineDimExpr(pos, ctx);
1206 }
1207 for (auto [pos, sym] : llvm::enumerate(syms)) {
1208 auto asResult = dyn_cast_if_present<OpResult>(sym);
1209 if (!asResult)
1210 continue;
1211 if (asResult.getOwner() == delinOp.getOperation())
1212 resToExpr[asResult.getResultNumber()] = getAffineSymbolExpr(pos, ctx);
1213 }
1214 if (llvm::is_contained(resToExpr, AffineExpr()))
1215 return failure();
1216
1217 bool isDimReplacement = llvm::all_of(resToExpr, llvm::IsaPred<AffineDimExpr>);
1218 int64_t stride = 1;
1219 llvm::SmallDenseSet<AffineExpr, 4> expectedExprs;
1220 // This isn't zip_equal since sometimes the delinearize basis is missing a
1221 // size for the first result.
1222 for (auto [binding, size] : llvm::zip(
1223 llvm::reverse(resToExpr), llvm::reverse(delinOp.getStaticBasis()))) {
1224 expectedExprs.insert(binding * getAffineConstantExpr(stride, ctx));
1225 stride *= size;
1226 }
1227 if (resToExpr.size() != delinOp.getStaticBasis().size())
1228 expectedExprs.insert(resToExpr[0] * stride);
1229
1231 AffineExpr delinInExpr = isDimReplacement
1232 ? getAffineDimExpr(dims.size(), ctx)
1233 : getAffineSymbolExpr(syms.size(), ctx);
1234
1235 for (AffineExpr e : map->getResults())
1236 shortenAddChainsContainingAll(e, expectedExprs, delinInExpr, replacements);
1237 if (replacements.empty())
1238 return failure();
1239
1240 AffineMap origMap = *map;
1241 if (isDimReplacement)
1242 dims.push_back(delinOp.getLinearIndex());
1243 else
1244 syms.push_back(delinOp.getLinearIndex());
1245 *map = origMap.replace(replacements, dims.size(), syms.size());
1246
1247 // Blank out dead dimensions and symbols
1248 for (AffineExpr e : resToExpr) {
1249 if (auto d = dyn_cast<AffineDimExpr>(e)) {
1250 unsigned pos = d.getPosition();
1251 if (!map->isFunctionOfDim(pos))
1252 dims[pos] = nullptr;
1253 }
1254 if (auto s = dyn_cast<AffineSymbolExpr>(e)) {
1255 unsigned pos = s.getPosition();
1256 if (!map->isFunctionOfSymbol(pos))
1257 syms[pos] = nullptr;
1258 }
1259 }
1260 return success();
1261}
1262
1263/// Replace all occurrences of AffineExpr at position `pos` in `map` by the
1264/// defining AffineApplyOp expression and operands.
1265/// When `dimOrSymbolPosition < dims.size()`, AffineDimExpr@[pos] is replaced.
1266/// When `dimOrSymbolPosition >= dims.size()`,
1267/// AffineSymbolExpr@[pos - dims.size()] is replaced.
1268/// Mutate `map`,`dims` and `syms` in place as follows:
1269/// 1. `dims` and `syms` are only appended to.
1270/// 2. `map` dim and symbols are gradually shifted to higher positions.
1271/// 3. Old `dim` and `sym` entries are replaced by nullptr
1272/// This avoids the need for any bookkeeping.
1273/// If `replaceAffineMin` is set to true, additionally triggers more expensive
1274/// replacements involving affine_min operations.
1275static LogicalResult replaceDimOrSym(AffineMap *map,
1276 unsigned dimOrSymbolPosition,
1279 bool replaceAffineMin) {
1280 MLIRContext *ctx = map->getContext();
1281 bool isDimReplacement = (dimOrSymbolPosition < dims.size());
1282 unsigned pos = isDimReplacement ? dimOrSymbolPosition
1283 : dimOrSymbolPosition - dims.size();
1284 Value &v = isDimReplacement ? dims[pos] : syms[pos];
1285 if (!v)
1286 return failure();
1287
1288 if (auto minOp = v.getDefiningOp<AffineMinOp>(); minOp && replaceAffineMin) {
1289 AffineExpr dimOrSym = isDimReplacement ? getAffineDimExpr(pos, ctx)
1290 : getAffineSymbolExpr(pos, ctx);
1291 return replaceAffineMinBoundingBoxExpression(minOp, dimOrSym, map, dims,
1292 syms);
1293 }
1294
1295 if (auto delinOp = v.getDefiningOp<affine::AffineDelinearizeIndexOp>()) {
1296 return replaceAffineDelinearizeIndexInverseExpression(delinOp, v, map, dims,
1297 syms);
1298 }
1299
1300 auto affineApply = v.getDefiningOp<AffineApplyOp>();
1301 if (!affineApply)
1302 return failure();
1303
1304 // At this point we will perform a replacement of `v`, set the entry in `dim`
1305 // or `sym` to nullptr immediately.
1306 v = nullptr;
1307
1308 // Compute the map, dims and symbols coming from the AffineApplyOp.
1309 AffineMap composeMap = affineApply.getAffineMap();
1310 assert(composeMap.getNumResults() == 1 && "affine.apply with >1 results");
1311 SmallVector<Value> composeOperands(affineApply.getMapOperands().begin(),
1312 affineApply.getMapOperands().end());
1313 // Canonicalize the map to promote dims to symbols when possible. This is to
1314 // avoid generating invalid maps.
1315 canonicalizeMapAndOperands(&composeMap, &composeOperands);
1316 AffineExpr replacementExpr =
1317 composeMap.shiftDims(dims.size()).shiftSymbols(syms.size()).getResult(0);
1318 ValueRange composeDims =
1319 ArrayRef<Value>(composeOperands).take_front(composeMap.getNumDims());
1320 ValueRange composeSyms =
1321 ArrayRef<Value>(composeOperands).take_back(composeMap.getNumSymbols());
1322 AffineExpr toReplace = isDimReplacement ? getAffineDimExpr(pos, ctx)
1323 : getAffineSymbolExpr(pos, ctx);
1324
1325 // Append the dims and symbols where relevant and perform the replacement.
1326 dims.append(composeDims.begin(), composeDims.end());
1327 syms.append(composeSyms.begin(), composeSyms.end());
1328 *map = map->replace(toReplace, replacementExpr, dims.size(), syms.size());
1329
1330 return success();
1331}
1332
1333/// Iterate over `operands` and fold away all those produced by an AffineApplyOp
1334/// iteratively. Perform canonicalization of map and operands as well as
1335/// AffineMap simplification. `map` and `operands` are mutated in place.
1337 SmallVectorImpl<Value> *operands,
1338 bool composeAffineMin = false) {
1339 if (map->getNumResults() == 0) {
1340 canonicalizeMapAndOperands(map, operands);
1341 *map = simplifyAffineMap(*map);
1342 return;
1343 }
1344
1345 MLIRContext *ctx = map->getContext();
1346 SmallVector<Value, 4> dims(operands->begin(),
1347 operands->begin() + map->getNumDims());
1348 SmallVector<Value, 4> syms(operands->begin() + map->getNumDims(),
1349 operands->end());
1350
1351 // Iterate over dims and symbols coming from AffineApplyOp and replace until
1352 // exhaustion. This iteratively mutates `map`, `dims` and `syms`. Both `dims`
1353 // and `syms` can only increase by construction.
1354 // The implementation uses a `while` loop to support the case of symbols
1355 // that may be constructed from dims ;this may be overkill.
1356 while (true) {
1357 bool changed = false;
1358 for (unsigned pos = 0; pos != dims.size() + syms.size(); ++pos)
1359 if ((changed |=
1360 succeeded(replaceDimOrSym(map, pos, dims, syms, composeAffineMin))))
1361 break;
1362 if (!changed)
1363 break;
1364 }
1365
1366 // Clear operands so we can fill them anew.
1367 operands->clear();
1368
1369 // At this point we may have introduced null operands, prune them out before
1370 // canonicalizing map and operands.
1371 unsigned nDims = 0, nSyms = 0;
1372 SmallVector<AffineExpr, 4> dimReplacements, symReplacements;
1373 dimReplacements.reserve(dims.size());
1374 symReplacements.reserve(syms.size());
1375 for (auto *container : {&dims, &syms}) {
1376 bool isDim = (container == &dims);
1377 auto &repls = isDim ? dimReplacements : symReplacements;
1378 for (const auto &en : llvm::enumerate(*container)) {
1379 Value v = en.value();
1380 if (!v) {
1381 assert(isDim ? !map->isFunctionOfDim(en.index())
1382 : !map->isFunctionOfSymbol(en.index()) &&
1383 "map is function of unexpected expr@pos");
1384 repls.push_back(getAffineConstantExpr(0, ctx));
1385 continue;
1386 }
1387 repls.push_back(isDim ? getAffineDimExpr(nDims++, ctx)
1388 : getAffineSymbolExpr(nSyms++, ctx));
1389 operands->push_back(v);
1390 }
1391 }
1392 *map = map->replaceDimsAndSymbols(dimReplacements, symReplacements, nDims,
1393 nSyms);
1394
1395 // Canonicalize and simplify before returning.
1396 canonicalizeMapAndOperands(map, operands);
1397 *map = simplifyAffineMap(*map);
1398}
1399
1401 AffineMap *map, SmallVectorImpl<Value> *operands, bool composeAffineMin) {
1402 while (llvm::any_of(*operands, [](Value v) {
1403 return isa_and_nonnull<AffineApplyOp>(v.getDefiningOp());
1404 })) {
1405 composeAffineMapAndOperands(map, operands, composeAffineMin);
1406 }
1407 // Additional trailing step for AffineMinOps in case no chains of AffineApply.
1408 if (composeAffineMin && llvm::any_of(*operands, [](Value v) {
1409 return isa_and_nonnull<AffineMinOp>(v.getDefiningOp());
1410 })) {
1411 composeAffineMapAndOperands(map, operands, composeAffineMin);
1412 }
1413}
1414
1415AffineApplyOp
1417 ArrayRef<OpFoldResult> operands,
1418 bool composeAffineMin) {
1419 SmallVector<Value> valueOperands;
1420 map = foldAttributesIntoMap(b, map, operands, valueOperands);
1421 composeAffineMapAndOperands(&map, &valueOperands, composeAffineMin);
1422 assert(map);
1423 return AffineApplyOp::create(b, loc, map, valueOperands);
1424}
1425
1426AffineApplyOp
1428 ArrayRef<OpFoldResult> operands,
1429 bool composeAffineMin) {
1431 b, loc,
1433 .front(),
1434 operands, composeAffineMin);
1435}
1436
1437/// Composes the given affine map with the given list of operands, pulling in
1438/// the maps from any affine.apply operations that supply the operands.
1440 SmallVectorImpl<Value> &operands,
1441 bool composeAffineMin = false) {
1442 // Compose and canonicalize each expression in the map individually because
1443 // composition only applies to single-result maps, collecting potentially
1444 // duplicate operands in a single list with shifted dimensions and symbols.
1445 SmallVector<Value> dims, symbols;
1447 for (unsigned i : llvm::seq<unsigned>(0, map.getNumResults())) {
1448 SmallVector<Value> submapOperands(operands.begin(), operands.end());
1449 AffineMap submap = map.getSubMap({i});
1450 fullyComposeAffineMapAndOperands(&submap, &submapOperands,
1451 composeAffineMin);
1452 canonicalizeMapAndOperands(&submap, &submapOperands);
1453 unsigned numNewDims = submap.getNumDims();
1454 submap = submap.shiftDims(dims.size()).shiftSymbols(symbols.size());
1455 llvm::append_range(dims,
1456 ArrayRef<Value>(submapOperands).take_front(numNewDims));
1457 llvm::append_range(symbols,
1458 ArrayRef<Value>(submapOperands).drop_front(numNewDims));
1459 exprs.push_back(submap.getResult(0));
1460 }
1461
1462 // Canonicalize the map created from composed expressions to deduplicate the
1463 // dimension and symbol operands.
1464 operands = llvm::to_vector(llvm::concat<Value>(dims, symbols));
1465 map = AffineMap::get(dims.size(), symbols.size(), exprs, map.getContext());
1466 canonicalizeMapAndOperands(&map, &operands);
1467}
1468
1471 bool composeAffineMin) {
1472 assert(map.getNumResults() == 1 && "building affine.apply with !=1 result");
1473
1474 // Create new builder without a listener, so that no notification is
1475 // triggered if the op is folded.
1476 // TODO: OpBuilder::createOrFold should return OpFoldResults, then this
1477 // workaround is no longer needed.
1478 OpBuilder newBuilder(b.getContext());
1479 newBuilder.setInsertionPoint(b.getInsertionBlock(), b.getInsertionPoint());
1480
1481 // Create op.
1482 AffineApplyOp applyOp =
1483 makeComposedAffineApply(newBuilder, loc, map, operands, composeAffineMin);
1484
1485 // Get constant operands.
1486 SmallVector<Attribute> constOperands(applyOp->getNumOperands());
1487 for (unsigned i = 0, e = constOperands.size(); i != e; ++i)
1488 matchPattern(applyOp->getOperand(i), m_Constant(&constOperands[i]));
1489
1490 // Try to fold the operation.
1491 SmallVector<OpFoldResult> foldResults;
1492 if (failed(applyOp->fold(constOperands, foldResults)) ||
1493 foldResults.empty()) {
1494 if (OpBuilder::Listener *listener = b.getListener())
1495 listener->notifyOperationInserted(applyOp, /*previous=*/{});
1496 return applyOp.getResult();
1497 }
1498
1499 applyOp->erase();
1500 return llvm::getSingleElement(foldResults);
1501}
1502
1504 OpBuilder &b, Location loc, AffineExpr expr,
1505 ArrayRef<OpFoldResult> operands, bool composeAffineMin) {
1507 b, loc,
1509 .front(),
1510 operands, composeAffineMin);
1511}
1512
1516 bool composeAffineMin) {
1517 return llvm::map_to_vector(
1518 llvm::seq<unsigned>(0, map.getNumResults()), [&](unsigned i) {
1519 return makeComposedFoldedAffineApply(b, loc, map.getSubMap({i}),
1520 operands, composeAffineMin);
1521 });
1522}
1523
1524template <typename OpTy>
1526 ArrayRef<OpFoldResult> operands) {
1527 SmallVector<Value> valueOperands;
1528 map = foldAttributesIntoMap(b, map, operands, valueOperands);
1529 composeMultiResultAffineMap(map, valueOperands);
1530 return OpTy::create(b, loc, b.getIndexType(), map, valueOperands);
1531}
1532
1533AffineMinOp
1538
1539template <typename OpTy>
1541 AffineMap map,
1542 ArrayRef<OpFoldResult> operands) {
1543 // Create new builder without a listener, so that no notification is
1544 // triggered if the op is folded.
1545 // TODO: OpBuilder::createOrFold should return OpFoldResults, then this
1546 // workaround is no longer needed.
1547 OpBuilder newBuilder(b.getContext());
1548 newBuilder.setInsertionPoint(b.getInsertionBlock(), b.getInsertionPoint());
1549
1550 // Create op.
1551 auto minMaxOp = makeComposedMinMax<OpTy>(newBuilder, loc, map, operands);
1552
1553 // Get constant operands.
1554 SmallVector<Attribute> constOperands(minMaxOp->getNumOperands());
1555 for (unsigned i = 0, e = constOperands.size(); i != e; ++i)
1556 matchPattern(minMaxOp->getOperand(i), m_Constant(&constOperands[i]));
1557
1558 // Try to fold the operation.
1559 SmallVector<OpFoldResult> foldResults;
1560 if (failed(minMaxOp->fold(constOperands, foldResults)) ||
1561 foldResults.empty()) {
1562 if (OpBuilder::Listener *listener = b.getListener())
1563 listener->notifyOperationInserted(minMaxOp, /*previous=*/{});
1564 return minMaxOp.getResult();
1565 }
1566
1567 minMaxOp->erase();
1568 return llvm::getSingleElement(foldResults);
1569}
1570
1577
1584
1585// A symbol may appear as a dim in affine.apply operations. This function
1586// canonicalizes dims that are valid symbols into actual symbols.
1587template <class MapOrSet>
1588static void canonicalizePromotedSymbols(MapOrSet *mapOrSet,
1589 SmallVectorImpl<Value> *operands) {
1590 if (!mapOrSet || operands->empty())
1591 return;
1592
1593 assert(mapOrSet->getNumInputs() == operands->size() &&
1594 "map/set inputs must match number of operands");
1595
1596 auto *context = mapOrSet->getContext();
1597 SmallVector<Value, 8> resultOperands;
1598 resultOperands.reserve(operands->size());
1599 SmallVector<Value, 8> remappedSymbols;
1600 remappedSymbols.reserve(operands->size());
1601 unsigned nextDim = 0;
1602 unsigned nextSym = 0;
1603 unsigned oldNumSyms = mapOrSet->getNumSymbols();
1604 SmallVector<AffineExpr, 8> dimRemapping(mapOrSet->getNumDims());
1605 for (unsigned i = 0, e = mapOrSet->getNumInputs(); i != e; ++i) {
1606 if (i < mapOrSet->getNumDims()) {
1607 if (isValidSymbol((*operands)[i])) {
1608 // This is a valid symbol that appears as a dim, canonicalize it.
1609 dimRemapping[i] = getAffineSymbolExpr(oldNumSyms + nextSym++, context);
1610 remappedSymbols.push_back((*operands)[i]);
1611 } else {
1612 dimRemapping[i] = getAffineDimExpr(nextDim++, context);
1613 resultOperands.push_back((*operands)[i]);
1614 }
1615 } else {
1616 resultOperands.push_back((*operands)[i]);
1617 }
1618 }
1619
1620 resultOperands.append(remappedSymbols.begin(), remappedSymbols.end());
1621 *operands = resultOperands;
1622 *mapOrSet = mapOrSet->replaceDimsAndSymbols(
1623 dimRemapping, /*symReplacements=*/{}, nextDim, oldNumSyms + nextSym);
1624
1625 assert(mapOrSet->getNumInputs() == operands->size() &&
1626 "map/set inputs must match number of operands");
1627}
1628
1629/// A valid affine dimension may appear as a symbol in affine.apply operations.
1630/// Given an application of `operands` to an affine map or integer set
1631/// `mapOrSet`, this function canonicalizes symbols of `mapOrSet` that are valid
1632/// dims, but not valid symbols into actual dims. Without such a legalization,
1633/// the affine.apply will be invalid. This method is the exact inverse of
1634/// canonicalizePromotedSymbols.
1635template <class MapOrSet>
1636static void legalizeDemotedDims(MapOrSet &mapOrSet,
1637 SmallVectorImpl<Value> &operands) {
1638 if (!mapOrSet || operands.empty())
1639 return;
1640
1641 unsigned numOperands = operands.size();
1642
1643 assert(mapOrSet.getNumInputs() == numOperands &&
1644 "map/set inputs must match number of operands");
1645
1646 auto *context = mapOrSet.getContext();
1647 SmallVector<Value, 8> resultOperands;
1648 resultOperands.reserve(numOperands);
1649 SmallVector<Value, 8> remappedDims;
1650 remappedDims.reserve(numOperands);
1651 SmallVector<Value, 8> symOperands;
1652 symOperands.reserve(mapOrSet.getNumSymbols());
1653 unsigned nextSym = 0;
1654 unsigned nextDim = 0;
1655 unsigned oldNumDims = mapOrSet.getNumDims();
1656 SmallVector<AffineExpr, 8> symRemapping(mapOrSet.getNumSymbols());
1657 resultOperands.assign(operands.begin(), operands.begin() + oldNumDims);
1658 for (unsigned i = oldNumDims, e = mapOrSet.getNumInputs(); i != e; ++i) {
1659 if (operands[i] && isValidDim(operands[i]) && !isValidSymbol(operands[i])) {
1660 // This is a valid dim that appears as a symbol, legalize it.
1661 symRemapping[i - oldNumDims] =
1662 getAffineDimExpr(oldNumDims + nextDim++, context);
1663 remappedDims.push_back(operands[i]);
1664 } else {
1665 symRemapping[i - oldNumDims] = getAffineSymbolExpr(nextSym++, context);
1666 symOperands.push_back(operands[i]);
1667 }
1668 }
1669
1670 append_range(resultOperands, remappedDims);
1671 append_range(resultOperands, symOperands);
1672 operands = resultOperands;
1673 mapOrSet = mapOrSet.replaceDimsAndSymbols(
1674 /*dimReplacements=*/{}, symRemapping, oldNumDims + nextDim, nextSym);
1675
1676 assert(mapOrSet.getNumInputs() == operands.size() &&
1677 "map/set inputs must match number of operands");
1678}
1679
1680// Works for either an affine map or an integer set.
1681template <class MapOrSet>
1682static void canonicalizeMapOrSetAndOperands(MapOrSet *mapOrSet,
1683 SmallVectorImpl<Value> *operands) {
1684 static_assert(llvm::is_one_of<MapOrSet, AffineMap, IntegerSet>::value,
1685 "Argument must be either of AffineMap or IntegerSet type");
1686
1687 if (!mapOrSet || operands->empty())
1688 return;
1689
1690 assert(mapOrSet->getNumInputs() == operands->size() &&
1691 "map/set inputs must match number of operands");
1692
1693 canonicalizePromotedSymbols<MapOrSet>(mapOrSet, operands);
1694 legalizeDemotedDims<MapOrSet>(*mapOrSet, *operands);
1695
1696 // Check to see what dims are used.
1697 llvm::SmallBitVector usedDims(mapOrSet->getNumDims());
1698 llvm::SmallBitVector usedSyms(mapOrSet->getNumSymbols());
1699 mapOrSet->walkExprs([&](AffineExpr expr) {
1700 if (auto dimExpr = dyn_cast<AffineDimExpr>(expr))
1701 usedDims[dimExpr.getPosition()] = true;
1702 else if (auto symExpr = dyn_cast<AffineSymbolExpr>(expr))
1703 usedSyms[symExpr.getPosition()] = true;
1704 });
1705
1706 auto *context = mapOrSet->getContext();
1707
1708 SmallVector<Value, 8> resultOperands;
1709 resultOperands.reserve(operands->size());
1710
1711 llvm::SmallDenseMap<Value, AffineExpr, 8> seenDims;
1712 SmallVector<AffineExpr, 8> dimRemapping(mapOrSet->getNumDims());
1713 unsigned nextDim = 0;
1714 for (unsigned i = 0, e = mapOrSet->getNumDims(); i != e; ++i) {
1715 if (usedDims[i]) {
1716 // Remap dim positions for duplicate operands.
1717 auto it = seenDims.find((*operands)[i]);
1718 if (it == seenDims.end()) {
1719 dimRemapping[i] = getAffineDimExpr(nextDim++, context);
1720 resultOperands.push_back((*operands)[i]);
1721 seenDims.insert(std::make_pair((*operands)[i], dimRemapping[i]));
1722 } else {
1723 dimRemapping[i] = it->second;
1724 }
1725 }
1726 }
1727 llvm::SmallDenseMap<Value, AffineExpr, 8> seenSymbols;
1728 SmallVector<AffineExpr, 8> symRemapping(mapOrSet->getNumSymbols());
1729 unsigned nextSym = 0;
1730 for (unsigned i = 0, e = mapOrSet->getNumSymbols(); i != e; ++i) {
1731 if (!usedSyms[i])
1732 continue;
1733 // Handle constant operands (only needed for symbolic operands since
1734 // constant operands in dimensional positions would have already been
1735 // promoted to symbolic positions above).
1736 IntegerAttr operandCst;
1737 if (matchPattern((*operands)[i + mapOrSet->getNumDims()],
1738 m_Constant(&operandCst))) {
1739 symRemapping[i] =
1740 getAffineConstantExpr(operandCst.getValue().getSExtValue(), context);
1741 continue;
1742 }
1743 // Remap symbol positions for duplicate operands.
1744 auto it = seenSymbols.find((*operands)[i + mapOrSet->getNumDims()]);
1745 if (it == seenSymbols.end()) {
1746 symRemapping[i] = getAffineSymbolExpr(nextSym++, context);
1747 resultOperands.push_back((*operands)[i + mapOrSet->getNumDims()]);
1748 seenSymbols.insert(std::make_pair((*operands)[i + mapOrSet->getNumDims()],
1749 symRemapping[i]));
1750 } else {
1751 symRemapping[i] = it->second;
1752 }
1753 }
1754 *mapOrSet = mapOrSet->replaceDimsAndSymbols(dimRemapping, symRemapping,
1755 nextDim, nextSym);
1756 *operands = resultOperands;
1757}
1758
1763
1768
1769namespace {
1770/// Simplify AffineApply, AffineLoad, and AffineStore operations by composing
1771/// maps that supply results into them.
1772///
1773template <typename AffineOpTy>
1774struct SimplifyAffineOp : public OpRewritePattern<AffineOpTy> {
1775 using OpRewritePattern<AffineOpTy>::OpRewritePattern;
1776
1777 /// Replace the affine op with another instance of it with the supplied
1778 /// map and mapOperands.
1779 void replaceAffineOp(PatternRewriter &rewriter, AffineOpTy affineOp,
1780 AffineMap map, ArrayRef<Value> mapOperands) const;
1781
1782 LogicalResult matchAndRewrite(AffineOpTy affineOp,
1783 PatternRewriter &rewriter) const override {
1784 static_assert(
1785 llvm::is_one_of<AffineOpTy, AffineLoadOp, AffinePrefetchOp,
1786 AffineStoreOp, AffineApplyOp, AffineMinOp, AffineMaxOp,
1787 AffineVectorStoreOp, AffineVectorLoadOp>::value,
1788 "affine load/store/vectorstore/vectorload/apply/prefetch/min/max op "
1789 "expected");
1790 auto map = affineOp.getAffineMap();
1791 AffineMap oldMap = map;
1792 auto oldOperands = affineOp.getMapOperands();
1793 SmallVector<Value, 8> resultOperands(oldOperands);
1794 composeAffineMapAndOperands(&map, &resultOperands);
1795 canonicalizeMapAndOperands(&map, &resultOperands);
1796 simplifyMapWithOperands(map, resultOperands);
1797 if (map == oldMap && std::equal(oldOperands.begin(), oldOperands.end(),
1798 resultOperands.begin()))
1799 return failure();
1800
1801 replaceAffineOp(rewriter, affineOp, map, resultOperands);
1802 return success();
1803 }
1804};
1805
1806// Specialize the template to account for the different build signatures for
1807// affine load, store, and apply ops.
1808template <>
1809void SimplifyAffineOp<AffineLoadOp>::replaceAffineOp(
1810 PatternRewriter &rewriter, AffineLoadOp load, AffineMap map,
1811 ArrayRef<Value> mapOperands) const {
1812 rewriter.replaceOpWithNewOp<AffineLoadOp>(load, load.getMemRef(), map,
1813 mapOperands, load.getMaybeAlign());
1814}
1815template <>
1816void SimplifyAffineOp<AffinePrefetchOp>::replaceAffineOp(
1817 PatternRewriter &rewriter, AffinePrefetchOp prefetch, AffineMap map,
1818 ArrayRef<Value> mapOperands) const {
1819 rewriter.replaceOpWithNewOp<AffinePrefetchOp>(
1820 prefetch, prefetch.getMemref(), map, mapOperands, prefetch.getIsWrite(),
1821 prefetch.getLocalityHint(), prefetch.getIsDataCache());
1822}
1823template <>
1824void SimplifyAffineOp<AffineStoreOp>::replaceAffineOp(
1825 PatternRewriter &rewriter, AffineStoreOp store, AffineMap map,
1826 ArrayRef<Value> mapOperands) const {
1827 rewriter.replaceOpWithNewOp<AffineStoreOp>(
1828 store, store.getValueToStore(), store.getMemRef(), map, mapOperands,
1829 store.getMaybeAlign());
1830}
1831template <>
1832void SimplifyAffineOp<AffineVectorLoadOp>::replaceAffineOp(
1833 PatternRewriter &rewriter, AffineVectorLoadOp vectorload, AffineMap map,
1834 ArrayRef<Value> mapOperands) const {
1835 rewriter.replaceOpWithNewOp<AffineVectorLoadOp>(
1836 vectorload, vectorload.getVectorType(), vectorload.getMemRef(), map,
1837 mapOperands, vectorload.getMaybeAlign());
1838}
1839template <>
1840void SimplifyAffineOp<AffineVectorStoreOp>::replaceAffineOp(
1841 PatternRewriter &rewriter, AffineVectorStoreOp vectorstore, AffineMap map,
1842 ArrayRef<Value> mapOperands) const {
1843 rewriter.replaceOpWithNewOp<AffineVectorStoreOp>(
1844 vectorstore, vectorstore.getValueToStore(), vectorstore.getMemRef(), map,
1845 mapOperands, vectorstore.getMaybeAlign());
1846}
1847
1848// Generic version for ops that don't have extra operands.
1849template <typename AffineOpTy>
1850void SimplifyAffineOp<AffineOpTy>::replaceAffineOp(
1851 PatternRewriter &rewriter, AffineOpTy op, AffineMap map,
1852 ArrayRef<Value> mapOperands) const {
1853 rewriter.replaceOpWithNewOp<AffineOpTy>(op, map, mapOperands);
1854}
1855} // namespace
1856
1857void AffineApplyOp::getCanonicalizationPatterns(RewritePatternSet &results,
1858 MLIRContext *context) {
1859 results.add<SimplifyAffineOp<AffineApplyOp>>(context);
1860}
1861
1862//===----------------------------------------------------------------------===//
1863// AffineDmaStartOp
1864//===----------------------------------------------------------------------===//
1865
1866// TODO: Check that map operands are loop IVs or symbols.
1867void AffineDmaStartOp::build(OpBuilder &builder, OperationState &result,
1868 Value srcMemRef, AffineMap srcMap,
1869 ValueRange srcIndices, Value destMemRef,
1870 AffineMap dstMap, ValueRange destIndices,
1871 Value tagMemRef, AffineMap tagMap,
1872 ValueRange tagIndices, Value numElements,
1873 Value stride, Value elementsPerStride) {
1874 result.addOperands(srcMemRef);
1875 result.addAttribute(getSrcMapAttrStrName(), AffineMapAttr::get(srcMap));
1876 result.addOperands(srcIndices);
1877 result.addOperands(destMemRef);
1878 result.addAttribute(getDstMapAttrStrName(), AffineMapAttr::get(dstMap));
1879 result.addOperands(destIndices);
1880 result.addOperands(tagMemRef);
1881 result.addAttribute(getTagMapAttrStrName(), AffineMapAttr::get(tagMap));
1882 result.addOperands(tagIndices);
1883 result.addOperands(numElements);
1884 if (stride) {
1885 result.addOperands({stride, elementsPerStride});
1886 }
1887}
1888
1889void AffineDmaStartOp::print(OpAsmPrinter &p) {
1890 p << " " << getSrcMemRef() << '[';
1891 p.printAffineMapOfSSAIds(getSrcMapAttr(), getSrcIndices());
1892 p << "], " << getDstMemRef() << '[';
1893 p.printAffineMapOfSSAIds(getDstMapAttr(), getDstIndices());
1894 p << "], " << getTagMemRef() << '[';
1895 p.printAffineMapOfSSAIds(getTagMapAttr(), getTagIndices());
1896 p << "], " << getNumElements();
1897 if (isStrided()) {
1898 p << ", " << getStride();
1899 p << ", " << getNumElementsPerStride();
1900 }
1901 p << " : " << getSrcMemRefType() << ", " << getDstMemRefType() << ", "
1902 << getTagMemRefType();
1903}
1904
1905// Parse AffineDmaStartOp.
1906// Ex:
1907// affine.dma_start %src[%i, %j], %dst[%k, %l], %tag[%index], %size,
1908// %stride, %num_elt_per_stride
1909// : memref<3076 x f32, 0>, memref<1024 x f32, 2>, memref<1 x i32>
1910//
1911ParseResult AffineDmaStartOp::parse(OpAsmParser &parser,
1913 OpAsmParser::UnresolvedOperand srcMemRefInfo;
1914 AffineMapAttr srcMapAttr;
1916 OpAsmParser::UnresolvedOperand dstMemRefInfo;
1917 AffineMapAttr dstMapAttr;
1919 OpAsmParser::UnresolvedOperand tagMemRefInfo;
1920 AffineMapAttr tagMapAttr;
1922 OpAsmParser::UnresolvedOperand numElementsInfo;
1924
1926 auto indexType = parser.getBuilder().getIndexType();
1927
1928 // Parse and resolve the following list of operands:
1929 // *) dst memref followed by its affine maps operands (in square brackets).
1930 // *) src memref followed by its affine map operands (in square brackets).
1931 // *) tag memref followed by its affine map operands (in square brackets).
1932 // *) number of elements transferred by DMA operation.
1933 if (parser.parseOperand(srcMemRefInfo) ||
1934 parser.parseAffineMapOfSSAIds(srcMapOperands, srcMapAttr,
1935 getSrcMapAttrStrName(),
1936 result.attributes) ||
1937 parser.parseComma() || parser.parseOperand(dstMemRefInfo) ||
1938 parser.parseAffineMapOfSSAIds(dstMapOperands, dstMapAttr,
1939 getDstMapAttrStrName(),
1940 result.attributes) ||
1941 parser.parseComma() || parser.parseOperand(tagMemRefInfo) ||
1942 parser.parseAffineMapOfSSAIds(tagMapOperands, tagMapAttr,
1943 getTagMapAttrStrName(),
1944 result.attributes) ||
1945 parser.parseComma() || parser.parseOperand(numElementsInfo))
1946 return failure();
1947
1948 // Parse optional stride and elements per stride.
1949 if (parser.parseTrailingOperandList(strideInfo))
1950 return failure();
1951
1952 if (!strideInfo.empty() && strideInfo.size() != 2) {
1953 return parser.emitError(parser.getNameLoc(),
1954 "expected two stride related operands");
1955 }
1956 bool isStrided = strideInfo.size() == 2;
1957
1958 if (parser.parseColonTypeList(types))
1959 return failure();
1960
1961 if (types.size() != 3)
1962 return parser.emitError(parser.getNameLoc(), "expected three types");
1963
1964 if (parser.resolveOperand(srcMemRefInfo, types[0], result.operands) ||
1965 parser.resolveOperands(srcMapOperands, indexType, result.operands) ||
1966 parser.resolveOperand(dstMemRefInfo, types[1], result.operands) ||
1967 parser.resolveOperands(dstMapOperands, indexType, result.operands) ||
1968 parser.resolveOperand(tagMemRefInfo, types[2], result.operands) ||
1969 parser.resolveOperands(tagMapOperands, indexType, result.operands) ||
1970 parser.resolveOperand(numElementsInfo, indexType, result.operands))
1971 return failure();
1972
1973 if (isStrided) {
1974 if (parser.resolveOperands(strideInfo, indexType, result.operands))
1975 return failure();
1976 }
1977
1978 // Check that src/dst/tag operand counts match their map.numInputs.
1979 if (srcMapOperands.size() != srcMapAttr.getValue().getNumInputs() ||
1980 dstMapOperands.size() != dstMapAttr.getValue().getNumInputs() ||
1981 tagMapOperands.size() != tagMapAttr.getValue().getNumInputs())
1982 return parser.emitError(parser.getNameLoc(),
1983 "memref operand count not equal to map.numInputs");
1984 return success();
1985}
1986
1987LogicalResult AffineDmaStartOp::verify() {
1988 if (!llvm::isa<MemRefType>(getOperand(getSrcMemRefOperandIndex()).getType()))
1989 return emitOpError("expected DMA source to be of memref type");
1990 if (!llvm::isa<MemRefType>(getOperand(getDstMemRefOperandIndex()).getType()))
1991 return emitOpError("expected DMA destination to be of memref type");
1992 if (!llvm::isa<MemRefType>(getOperand(getTagMemRefOperandIndex()).getType()))
1993 return emitOpError("expected DMA tag to be of memref type");
1994
1995 unsigned numInputsAllMaps = getSrcMap().getNumInputs() +
1996 getDstMap().getNumInputs() +
1997 getTagMap().getNumInputs();
1998 if (getNumOperands() != numInputsAllMaps + 3 + 1 &&
1999 getNumOperands() != numInputsAllMaps + 3 + 1 + 2) {
2000 return emitOpError("incorrect number of operands");
2001 }
2002
2003 Region *scope = getAffineScope(*this);
2004 for (auto idx : getSrcIndices()) {
2005 if (!idx.getType().isIndex())
2006 return emitOpError("src index to dma_start must have 'index' type");
2007 if (!isValidAffineIndexOperand(idx, scope))
2008 return emitOpError(
2009 "src index must be a valid dimension or symbol identifier");
2010 }
2011 for (auto idx : getDstIndices()) {
2012 if (!idx.getType().isIndex())
2013 return emitOpError("dst index to dma_start must have 'index' type");
2014 if (!isValidAffineIndexOperand(idx, scope))
2015 return emitOpError(
2016 "dst index must be a valid dimension or symbol identifier");
2017 }
2018 for (auto idx : getTagIndices()) {
2019 if (!idx.getType().isIndex())
2020 return emitOpError("tag index to dma_start must have 'index' type");
2021 if (!isValidAffineIndexOperand(idx, scope))
2022 return emitOpError(
2023 "tag index must be a valid dimension or symbol identifier");
2024 }
2025 return success();
2026}
2027
2028LogicalResult AffineDmaStartOp::fold(FoldAdaptor adaptor,
2030 /// dma_start(memrefcast) -> dma_start
2031 return memref::foldMemRefCast(*this);
2032}
2033
2034void AffineDmaStartOp::getEffects(
2036 &effects) {
2037 effects.emplace_back(MemoryEffects::Read::get(), &getSrcMemRefMutable(),
2039 effects.emplace_back(MemoryEffects::Write::get(), &getDstMemRefMutable(),
2041 effects.emplace_back(MemoryEffects::Read::get(), &getTagMemRefMutable(),
2043}
2044
2045//===----------------------------------------------------------------------===//
2046// AffineDmaWaitOp
2047//===----------------------------------------------------------------------===//
2048
2049// TODO: Check that map operands are loop IVs or symbols.
2050void AffineDmaWaitOp::build(OpBuilder &builder, OperationState &result,
2051 Value tagMemRef, AffineMap tagMap,
2052 ValueRange tagIndices, Value numElements) {
2053 result.addOperands(tagMemRef);
2054 result.addAttribute(getTagMapAttrStrName(), AffineMapAttr::get(tagMap));
2055 result.addOperands(tagIndices);
2056 result.addOperands(numElements);
2057}
2058
2059void AffineDmaWaitOp::print(OpAsmPrinter &p) {
2060 p << " " << getTagMemRef() << '[';
2061 SmallVector<Value, 2> operands(getTagIndices());
2062 p.printAffineMapOfSSAIds(getTagMapAttr(), operands);
2063 p << "], ";
2065 p << " : " << getTagMemRef().getType();
2066}
2067
2068// Parse AffineDmaWaitOp.
2069// Eg:
2070// affine.dma_wait %tag[%index], %num_elements
2071// : memref<1 x i32, (d0) -> (d0), 4>
2072//
2073ParseResult AffineDmaWaitOp::parse(OpAsmParser &parser,
2075 OpAsmParser::UnresolvedOperand tagMemRefInfo;
2076 AffineMapAttr tagMapAttr;
2078 Type type;
2079 auto indexType = parser.getBuilder().getIndexType();
2080 OpAsmParser::UnresolvedOperand numElementsInfo;
2081
2082 // Parse tag memref, its map operands, and dma size.
2083 if (parser.parseOperand(tagMemRefInfo) ||
2084 parser.parseAffineMapOfSSAIds(tagMapOperands, tagMapAttr,
2085 getTagMapAttrStrName(),
2086 result.attributes) ||
2087 parser.parseComma() || parser.parseOperand(numElementsInfo) ||
2088 parser.parseColonType(type) ||
2089 parser.resolveOperand(tagMemRefInfo, type, result.operands) ||
2090 parser.resolveOperands(tagMapOperands, indexType, result.operands) ||
2091 parser.resolveOperand(numElementsInfo, indexType, result.operands))
2092 return failure();
2093
2094 if (!llvm::isa<MemRefType>(type))
2095 return parser.emitError(parser.getNameLoc(),
2096 "expected tag to be of memref type");
2097
2098 if (tagMapOperands.size() != tagMapAttr.getValue().getNumInputs())
2099 return parser.emitError(parser.getNameLoc(),
2100 "tag memref operand count != to map.numInputs");
2101 return success();
2102}
2103
2104LogicalResult AffineDmaWaitOp::verify() {
2105 if (!llvm::isa<MemRefType>(getOperand(0).getType()))
2106 return emitOpError("expected DMA tag to be of memref type");
2107 Region *scope = getAffineScope(*this);
2108 for (auto idx : getTagIndices()) {
2109 if (!idx.getType().isIndex())
2110 return emitOpError("index to dma_wait must have 'index' type");
2111 if (!isValidAffineIndexOperand(idx, scope))
2112 return emitOpError(
2113 "index must be a valid dimension or symbol identifier");
2114 }
2115 return success();
2116}
2117
2118LogicalResult AffineDmaWaitOp::fold(FoldAdaptor adaptor,
2120 /// dma_wait(memrefcast) -> dma_wait
2121 return memref::foldMemRefCast(*this);
2122}
2123
2124void AffineDmaWaitOp::getEffects(
2126 &effects) {
2127 effects.emplace_back(MemoryEffects::Read::get(), &getTagMemRefMutable(),
2129}
2130
2131//===----------------------------------------------------------------------===//
2132// AffineForOp
2133//===----------------------------------------------------------------------===//
2134
2135/// 'bodyBuilder' is used to build the body of affine.for. If iterArgs and
2136/// bodyBuilder are empty/null, we include default terminator op.
2137void AffineForOp::build(OpBuilder &builder, OperationState &result,
2138 ValueRange lbOperands, AffineMap lbMap,
2139 ValueRange ubOperands, AffineMap ubMap, int64_t step,
2140 ValueRange iterArgs, BodyBuilderFn bodyBuilder) {
2141 assert(((!lbMap && lbOperands.empty()) ||
2142 lbOperands.size() == lbMap.getNumInputs()) &&
2143 "lower bound operand count does not match the affine map");
2144 assert(((!ubMap && ubOperands.empty()) ||
2145 ubOperands.size() == ubMap.getNumInputs()) &&
2146 "upper bound operand count does not match the affine map");
2147 assert(step > 0 && "step has to be a positive integer constant");
2148
2149 OpBuilder::InsertionGuard guard(builder);
2150
2151 // Set variadic segment sizes.
2152 result.addAttribute(
2153 getOperandSegmentSizeAttr(),
2154 builder.getDenseI32ArrayAttr({static_cast<int32_t>(lbOperands.size()),
2155 static_cast<int32_t>(ubOperands.size()),
2156 static_cast<int32_t>(iterArgs.size())}));
2157
2158 for (Value val : iterArgs)
2159 result.addTypes(val.getType());
2160
2161 // Add an attribute for the step.
2162 result.addAttribute(getStepAttrName(result.name),
2163 builder.getIntegerAttr(builder.getIndexType(), step));
2164
2165 // Add the lower bound.
2166 result.addAttribute(getLowerBoundMapAttrName(result.name),
2167 AffineMapAttr::get(lbMap));
2168 result.addOperands(lbOperands);
2169
2170 // Add the upper bound.
2171 result.addAttribute(getUpperBoundMapAttrName(result.name),
2172 AffineMapAttr::get(ubMap));
2173 result.addOperands(ubOperands);
2174
2175 result.addOperands(iterArgs);
2176 // Create a region and a block for the body. The argument of the region is
2177 // the loop induction variable.
2178 Region *bodyRegion = result.addRegion();
2179 Block *bodyBlock = builder.createBlock(bodyRegion);
2180 Value inductionVar =
2181 bodyBlock->addArgument(builder.getIndexType(), result.location);
2182 for (Value val : iterArgs)
2183 bodyBlock->addArgument(val.getType(), val.getLoc());
2184
2185 // Create the default terminator if the builder is not provided and if the
2186 // iteration arguments are not provided. Otherwise, leave this to the caller
2187 // because we don't know which values to return from the loop.
2188 if (iterArgs.empty() && !bodyBuilder) {
2189 ensureTerminator(*bodyRegion, builder, result.location);
2190 } else if (bodyBuilder) {
2191 OpBuilder::InsertionGuard guard(builder);
2192 builder.setInsertionPointToStart(bodyBlock);
2193 bodyBuilder(builder, result.location, inductionVar,
2194 bodyBlock->getArguments().drop_front());
2195 }
2196}
2197
2198void AffineForOp::build(OpBuilder &builder, OperationState &result, int64_t lb,
2199 int64_t ub, int64_t step, ValueRange iterArgs,
2200 BodyBuilderFn bodyBuilder) {
2201 auto lbMap = AffineMap::getConstantMap(lb, builder.getContext());
2202 auto ubMap = AffineMap::getConstantMap(ub, builder.getContext());
2203 return build(builder, result, {}, lbMap, {}, ubMap, step, iterArgs,
2204 bodyBuilder);
2205}
2206
2207LogicalResult AffineForOp::verify() {
2208 auto *body = getBody();
2209 if (body->getNumArguments() == 0 || !getInductionVar().getType().isIndex())
2210 return emitOpError("expected body to have an index argument for the "
2211 "induction variable");
2212
2213 return success();
2214}
2215
2216LogicalResult AffineForOp::verifyRegions() {
2217 // Step must be a strictly positive integer.
2218 if (getStepAsInt() <= 0)
2219 return emitOpError("expected step to be a positive integer, got ")
2220 << getStepAsInt();
2221
2222 // Verify that the bound operands are valid dimension/symbols.
2223 /// Lower bound.
2224 if (getLowerBoundMap().getNumInputs() > 0)
2226 getLowerBoundMap().getNumDims())))
2227 return failure();
2228 /// Upper bound.
2229 if (getUpperBoundMap().getNumInputs() > 0)
2231 getUpperBoundMap().getNumDims())))
2232 return failure();
2233 if (getLowerBoundMap().getNumResults() < 1)
2234 return emitOpError("expected lower bound map to have at least one result");
2235 if (getUpperBoundMap().getNumResults() < 1)
2236 return emitOpError("expected upper bound map to have at least one result");
2237
2238 unsigned opNumResults = getNumResults();
2239 if (opNumResults == 0)
2240 return success();
2241
2242 // If ForOp defines values, check that the number and types of the defined
2243 // values match ForOp initial iter operands and backedge basic block
2244 // arguments.
2245 if (getNumIterOperands() != opNumResults)
2246 return emitOpError(
2247 "mismatch between the number of loop-carried values and results");
2248 if (getNumRegionIterArgs() != opNumResults)
2249 return emitOpError(
2250 "mismatch between the number of basic block args and results");
2251
2252 return success();
2253}
2254
2255/// Parse a for operation loop bounds.
2256static ParseResult parseBound(bool isLower, OperationState &result,
2257 OpAsmParser &p) {
2258 // 'min' / 'max' prefixes are generally syntactic sugar, but are required if
2259 // the map has multiple results.
2260 bool failedToParsedMinMax =
2261 failed(p.parseOptionalKeyword(isLower ? "max" : "min"));
2262
2263 auto &builder = p.getBuilder();
2264 auto boundAttrStrName =
2265 isLower ? AffineForOp::getLowerBoundMapAttrName(result.name)
2266 : AffineForOp::getUpperBoundMapAttrName(result.name);
2267
2268 // Parse ssa-id as identity map.
2270 if (p.parseOperandList(boundOpInfos))
2271 return failure();
2272
2273 if (!boundOpInfos.empty()) {
2274 // Check that only one operand was parsed.
2275 if (boundOpInfos.size() > 1)
2276 return p.emitError(p.getNameLoc(),
2277 "expected only one loop bound operand");
2278
2279 // TODO: improve error message when SSA value is not of index type.
2280 // Currently it is 'use of value ... expects different type than prior uses'
2281 if (p.resolveOperand(boundOpInfos.front(), builder.getIndexType(),
2282 result.operands))
2283 return failure();
2284
2285 // Create an identity map using symbol id. This representation is optimized
2286 // for storage. Analysis passes may expand it into a multi-dimensional map
2287 // if desired.
2288 AffineMap map = builder.getSymbolIdentityMap();
2289 result.addAttribute(boundAttrStrName, AffineMapAttr::get(map));
2290 return success();
2291 }
2292
2293 // Get the attribute location.
2294 SMLoc attrLoc = p.getCurrentLocation();
2295
2296 Attribute boundAttr;
2297 if (p.parseAttribute(boundAttr, builder.getIndexType(), boundAttrStrName,
2298 result.attributes))
2299 return failure();
2300
2301 // Parse full form - affine map followed by dim and symbol list.
2302 if (auto affineMapAttr = dyn_cast<AffineMapAttr>(boundAttr)) {
2303 unsigned currentNumOperands = result.operands.size();
2304 unsigned numDims;
2305 if (parseDimAndSymbolList(p, result.operands, numDims))
2306 return failure();
2307
2308 auto map = affineMapAttr.getValue();
2309 if (map.getNumDims() != numDims)
2310 return p.emitError(
2311 p.getNameLoc(),
2312 "dim operand count and affine map dim count must match");
2313
2314 unsigned numDimAndSymbolOperands =
2315 result.operands.size() - currentNumOperands;
2316 if (numDims + map.getNumSymbols() != numDimAndSymbolOperands)
2317 return p.emitError(
2318 p.getNameLoc(),
2319 "symbol operand count and affine map symbol count must match");
2320
2321 // If the map has multiple results, make sure that we parsed the min/max
2322 // prefix.
2323 if (map.getNumResults() > 1 && failedToParsedMinMax) {
2324 if (isLower) {
2325 return p.emitError(attrLoc, "lower loop bound affine map with "
2326 "multiple results requires 'max' prefix");
2327 }
2328 return p.emitError(attrLoc, "upper loop bound affine map with multiple "
2329 "results requires 'min' prefix");
2330 }
2331 return success();
2332 }
2333
2334 // Parse custom assembly form.
2335 if (auto integerAttr = dyn_cast<IntegerAttr>(boundAttr)) {
2336 result.attributes.pop_back();
2337 result.addAttribute(
2338 boundAttrStrName,
2339 AffineMapAttr::get(builder.getConstantAffineMap(integerAttr.getInt())));
2340 return success();
2341 }
2342
2343 return p.emitError(
2344 p.getNameLoc(),
2345 "expected valid affine map representation for loop bounds");
2346}
2347
2348ParseResult AffineForOp::parse(OpAsmParser &parser, OperationState &result) {
2349 auto &builder = parser.getBuilder();
2350 OpAsmParser::Argument inductionVariable;
2351 inductionVariable.type = builder.getIndexType();
2352 // Parse the induction variable followed by '='.
2353 if (parser.parseArgument(inductionVariable) || parser.parseEqual())
2354 return failure();
2355
2356 // Parse loop bounds.
2357 int64_t numOperands = result.operands.size();
2358 if (parseBound(/*isLower=*/true, result, parser))
2359 return failure();
2360 int64_t numLbOperands = result.operands.size() - numOperands;
2361 if (parser.parseKeyword("to", " between bounds"))
2362 return failure();
2363 numOperands = result.operands.size();
2364 if (parseBound(/*isLower=*/false, result, parser))
2365 return failure();
2366 int64_t numUbOperands = result.operands.size() - numOperands;
2367
2368 // Parse the optional loop step, we default to 1 if one is not present.
2369 if (parser.parseOptionalKeyword("step")) {
2370 result.addAttribute(
2371 getStepAttrName(result.name),
2372 builder.getIntegerAttr(builder.getIndexType(), /*value=*/1));
2373 } else {
2374 SMLoc stepLoc = parser.getCurrentLocation();
2375 IntegerAttr stepAttr;
2376 if (parser.parseAttribute(stepAttr, builder.getIndexType(),
2377 getStepAttrName(result.name).data(),
2378 result.attributes))
2379 return failure();
2380
2381 if (!stepAttr.getValue().isStrictlyPositive())
2382 return parser.emitError(
2383 stepLoc,
2384 "expected step to be representable as a positive signed integer");
2385 }
2386
2387 // Parse the optional initial iteration arguments.
2388 SmallVector<OpAsmParser::Argument, 4> regionArgs;
2389 SmallVector<OpAsmParser::UnresolvedOperand, 4> operands;
2390
2391 // Induction variable.
2392 regionArgs.push_back(inductionVariable);
2393
2394 if (succeeded(parser.parseOptionalKeyword("iter_args"))) {
2395 // Parse assignment list and results type list.
2396 if (parser.parseAssignmentList(regionArgs, operands) ||
2397 parser.parseArrowTypeList(result.types))
2398 return failure();
2399 // Resolve input operands.
2400 for (auto argOperandType :
2401 llvm::zip(llvm::drop_begin(regionArgs), operands, result.types)) {
2402 Type type = std::get<2>(argOperandType);
2403 std::get<0>(argOperandType).type = type;
2404 if (parser.resolveOperand(std::get<1>(argOperandType), type,
2405 result.operands))
2406 return failure();
2407 }
2408 }
2409
2410 result.addAttribute(
2411 getOperandSegmentSizeAttr(),
2412 builder.getDenseI32ArrayAttr({static_cast<int32_t>(numLbOperands),
2413 static_cast<int32_t>(numUbOperands),
2414 static_cast<int32_t>(operands.size())}));
2415
2416 // Parse the body region.
2417 Region *body = result.addRegion();
2418 if (regionArgs.size() != result.types.size() + 1)
2419 return parser.emitError(
2420 parser.getNameLoc(),
2421 "mismatch between the number of loop-carried values and results");
2422 if (parser.parseRegion(*body, regionArgs))
2423 return failure();
2424
2425 AffineForOp::ensureTerminator(*body, builder, result.location);
2426
2427 // Parse the optional attribute list.
2428 return parser.parseOptionalAttrDict(result.attributes);
2429}
2430
2431static void printBound(AffineMapAttr boundMap,
2432 Operation::operand_range boundOperands,
2433 const char *prefix, OpAsmPrinter &p) {
2434 AffineMap map = boundMap.getValue();
2435
2436 // Check if this bound should be printed using custom assembly form.
2437 // The decision to restrict printing custom assembly form to trivial cases
2438 // comes from the will to roundtrip MLIR binary -> text -> binary in a
2439 // lossless way.
2440 // Therefore, custom assembly form parsing and printing is only supported for
2441 // zero-operand constant maps and single symbol operand identity maps.
2442 if (map.getNumResults() == 1) {
2443 AffineExpr expr = map.getResult(0);
2444
2445 // Print constant bound.
2446 if (map.getNumDims() == 0 && map.getNumSymbols() == 0) {
2447 if (auto constExpr = dyn_cast<AffineConstantExpr>(expr)) {
2448 p << constExpr.getValue();
2449 return;
2450 }
2451 }
2452
2453 // Print bound that consists of a single SSA symbol if the map is over a
2454 // single symbol.
2455 if (map.getNumDims() == 0 && map.getNumSymbols() == 1) {
2456 if (isa<AffineSymbolExpr>(expr)) {
2457 p.printOperand(*boundOperands.begin());
2458 return;
2459 }
2460 }
2461 } else {
2462 // Map has multiple results. Print 'min' or 'max' prefix.
2463 p << prefix << ' ';
2464 }
2465
2466 // Print the map and its operands.
2467 p << boundMap;
2468 printDimAndSymbolList(boundOperands.begin(), boundOperands.end(),
2469 map.getNumDims(), p);
2470}
2471
2472unsigned AffineForOp::getNumIterOperands() {
2473 AffineMap lbMap = getLowerBoundMapAttr().getValue();
2474 AffineMap ubMap = getUpperBoundMapAttr().getValue();
2475
2476 return getNumOperands() - lbMap.getNumInputs() - ubMap.getNumInputs();
2477}
2478
2479std::optional<MutableArrayRef<OpOperand>>
2480AffineForOp::getYieldedValuesMutable() {
2481 return cast<AffineYieldOp>(getBody()->getTerminator()).getOperandsMutable();
2482}
2483
2484void AffineForOp::print(OpAsmPrinter &p) {
2485 p << ' ';
2486 p.printRegionArgument(getBody()->getArgument(0), /*argAttrs=*/{},
2487 /*omitType=*/true);
2488 p << " = ";
2489 printBound(getLowerBoundMapAttr(), getLowerBoundOperands(), "max", p);
2490 p << " to ";
2491 printBound(getUpperBoundMapAttr(), getUpperBoundOperands(), "min", p);
2492
2493 if (getStepAsInt() != 1)
2494 p << " step " << getStepAsInt();
2495
2496 bool printBlockTerminators = false;
2497 if (getNumIterOperands() > 0) {
2498 p << " iter_args(";
2499 auto regionArgs = getRegionIterArgs();
2500 auto operands = getInits();
2501
2502 llvm::interleaveComma(llvm::zip(regionArgs, operands), p, [&](auto it) {
2503 p << std::get<0>(it) << " = " << std::get<1>(it);
2504 });
2505 p << ") -> (" << getResultTypes() << ")";
2506 printBlockTerminators = true;
2507 }
2508
2509 p << ' ';
2510 p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,
2511 printBlockTerminators);
2513 (*this)->getAttrs(),
2514 /*elidedAttrs=*/{getLowerBoundMapAttrName(getOperation()->getName()),
2515 getUpperBoundMapAttrName(getOperation()->getName()),
2516 getStepAttrName(getOperation()->getName()),
2517 getOperandSegmentSizeAttr()});
2518}
2519
2520/// Fold the constant bounds of a loop.
2521static LogicalResult foldLoopBounds(AffineForOp forOp) {
2522 auto foldLowerOrUpperBound = [&forOp](bool lower) {
2523 // Check to see if each of the operands is the result of a constant. If
2524 // so, get the value. If not, ignore it.
2525 SmallVector<Attribute, 8> operandConstants;
2526 auto boundOperands =
2527 lower ? forOp.getLowerBoundOperands() : forOp.getUpperBoundOperands();
2528 for (auto operand : boundOperands) {
2529 Attribute operandCst;
2530 matchPattern(operand, m_Constant(&operandCst));
2531 operandConstants.push_back(operandCst);
2532 }
2533
2534 AffineMap boundMap =
2535 lower ? forOp.getLowerBoundMap() : forOp.getUpperBoundMap();
2536 assert(boundMap.getNumResults() >= 1 &&
2537 "bound maps should have at least one result");
2538 SmallVector<Attribute, 4> foldedResults;
2539 if (failed(boundMap.constantFold(operandConstants, foldedResults)))
2540 return failure();
2541
2542 // Compute the max or min as applicable over the results.
2543 assert(!foldedResults.empty() && "bounds should have at least one result");
2544 auto maxOrMin = llvm::cast<IntegerAttr>(foldedResults[0]).getValue();
2545 for (unsigned i = 1, e = foldedResults.size(); i < e; i++) {
2546 auto foldedResult = llvm::cast<IntegerAttr>(foldedResults[i]).getValue();
2547 maxOrMin = lower ? llvm::APIntOps::smax(maxOrMin, foldedResult)
2548 : llvm::APIntOps::smin(maxOrMin, foldedResult);
2549 }
2550 lower ? forOp.setConstantLowerBound(maxOrMin.getSExtValue())
2551 : forOp.setConstantUpperBound(maxOrMin.getSExtValue());
2552 return success();
2553 };
2554
2555 // Try to fold the lower bound.
2556 bool folded = false;
2557 if (!forOp.hasConstantLowerBound())
2558 folded |= succeeded(foldLowerOrUpperBound(/*lower=*/true));
2559
2560 // Try to fold the upper bound.
2561 if (!forOp.hasConstantUpperBound())
2562 folded |= succeeded(foldLowerOrUpperBound(/*lower=*/false));
2563 return success(folded);
2564}
2565
2566/// Returns constant trip count in trivial cases.
2567static std::optional<uint64_t> getTrivialConstantTripCount(AffineForOp forOp) {
2568 int64_t step = forOp.getStepAsInt();
2569 if (!forOp.hasConstantBounds() || step <= 0)
2570 return std::nullopt;
2571 int64_t lb = forOp.getConstantLowerBound();
2572 int64_t ub = forOp.getConstantUpperBound();
2573 return ub - lb <= 0 ? 0 : (ub - lb + step - 1) / step;
2574}
2575
2576/// Fold the empty loop.
2578 if (!llvm::hasSingleElement(*forOp.getBody()))
2579 return {};
2580 if (forOp.getNumResults() == 0)
2581 return {};
2582 std::optional<uint64_t> tripCount = getTrivialConstantTripCount(forOp);
2583 if (tripCount == 0) {
2584 // The initial values of the iteration arguments would be the op's
2585 // results.
2586 return forOp.getInits();
2587 }
2588 SmallVector<Value, 4> replacements;
2589 auto yieldOp = cast<AffineYieldOp>(forOp.getBody()->getTerminator());
2590 auto iterArgs = forOp.getRegionIterArgs();
2591 bool hasValDefinedOutsideLoop = false;
2592 bool iterArgsNotInOrder = false;
2593 for (unsigned i = 0, e = yieldOp->getNumOperands(); i < e; ++i) {
2594 Value val = yieldOp.getOperand(i);
2595 BlockArgument *iterArgIt = llvm::find(iterArgs, val);
2596 // TODO: It should be possible to perform a replacement by computing the
2597 // last value of the IV based on the bounds and the step.
2598 if (val == forOp.getInductionVar())
2599 return {};
2600 if (iterArgIt == iterArgs.end()) {
2601 // `val` is defined outside of the loop.
2602 assert(forOp.isDefinedOutsideOfLoop(val) &&
2603 "must be defined outside of the loop");
2604 hasValDefinedOutsideLoop = true;
2605 replacements.push_back(val);
2606 } else {
2607 unsigned pos = std::distance(iterArgs.begin(), iterArgIt);
2608 if (pos != i)
2609 iterArgsNotInOrder = true;
2610 replacements.push_back(forOp.getInits()[pos]);
2611 }
2612 }
2613 // Bail out when the trip count is unknown and the loop returns any value
2614 // defined outside of the loop or any iterArg out of order.
2615 if (!tripCount.has_value() &&
2616 (hasValDefinedOutsideLoop || iterArgsNotInOrder))
2617 return {};
2618 // Bail out when the loop iterates more than once and it returns any iterArg
2619 // out of order.
2620 if (tripCount.has_value() && tripCount.value() >= 2 && iterArgsNotInOrder)
2621 return {};
2622 return llvm::to_vector_of<OpFoldResult>(replacements);
2623}
2624
2625/// Canonicalize the bounds of the given loop.
2626static LogicalResult canonicalizeLoopBounds(AffineForOp forOp) {
2627 SmallVector<Value, 4> lbOperands(forOp.getLowerBoundOperands());
2628 SmallVector<Value, 4> ubOperands(forOp.getUpperBoundOperands());
2629
2630 auto lbMap = forOp.getLowerBoundMap();
2631 auto ubMap = forOp.getUpperBoundMap();
2632 auto prevLbMap = lbMap;
2633 auto prevUbMap = ubMap;
2634
2635 composeAffineMapAndOperands(&lbMap, &lbOperands);
2636 canonicalizeMapAndOperands(&lbMap, &lbOperands);
2637 simplifyMinOrMaxExprWithOperands(lbMap, lbOperands, /*isMax=*/true);
2638 simplifyMinOrMaxExprWithOperands(ubMap, ubOperands, /*isMax=*/false);
2639 lbMap = removeDuplicateExprs(lbMap);
2640
2641 composeAffineMapAndOperands(&ubMap, &ubOperands);
2642 canonicalizeMapAndOperands(&ubMap, &ubOperands);
2643 ubMap = removeDuplicateExprs(ubMap);
2644
2645 // Any canonicalization change always leads to updated map(s).
2646 if (lbMap == prevLbMap && ubMap == prevUbMap)
2647 return failure();
2648
2649 if (lbMap != prevLbMap)
2650 forOp.setLowerBound(lbOperands, lbMap);
2651 if (ubMap != prevUbMap)
2652 forOp.setUpperBound(ubOperands, ubMap);
2653 return success();
2654}
2655
2656/// Returns true if the affine.for has zero iterations in trivial cases.
2657static bool hasTrivialZeroTripCount(AffineForOp op) {
2658 return getTrivialConstantTripCount(op) == 0;
2659}
2660
2661LogicalResult AffineForOp::fold(FoldAdaptor adaptor,
2663 bool folded = succeeded(foldLoopBounds(*this));
2664 folded |= succeeded(canonicalizeLoopBounds(*this));
2665 if (hasTrivialZeroTripCount(*this) && getNumResults() != 0) {
2666 // The initial values of the loop-carried variables (iter_args) are the
2667 // results of the op. But this must be avoided for an affine.for op that
2668 // does not return any results. Since ops that do not return results cannot
2669 // be folded away, we would enter an infinite loop of folds on the same
2670 // affine.for op.
2671 results.assign(getInits().begin(), getInits().end());
2672 folded = true;
2673 }
2674 SmallVector<OpFoldResult> foldResults = AffineForEmptyLoopFolder(*this);
2675 if (!foldResults.empty()) {
2676 results.assign(foldResults);
2677 folded = true;
2678 }
2679 return success(folded);
2680}
2681
2682OperandRange AffineForOp::getEntrySuccessorOperands(RegionSuccessor successor) {
2683 assert(
2684 (successor.isOperation() || successor.getSuccessor() == &getRegion()) &&
2685 "invalid region point");
2686
2687 // The initial operands map to the loop arguments after the induction
2688 // variable or are forwarded to the results when the trip count is zero.
2689 return getInits();
2690}
2691
2692void AffineForOp::getSuccessorRegions(
2694 assert((point.isParent() ||
2695 point.getTerminatorPredecessorOrNull()->getParentRegion() ==
2696 &getRegion()) &&
2697 "expected loop region");
2698 // The loop may typically branch back to its body or to the parent operation.
2699 // If the predecessor is the parent op and the trip count is known to be at
2700 // least one, branch into the body using the iterator arguments. And in cases
2701 // we know the trip count is zero, it can only branch back to its parent.
2702 std::optional<uint64_t> tripCount = getTrivialConstantTripCount(*this);
2703 if (tripCount.has_value()) {
2704 if (!point.isParent()) {
2705 // From the loop body, if the trip count is one, we can only branch back
2706 // to the parent.
2707 if (tripCount == 1) {
2708 regions.push_back(RegionSuccessor(getOperation()));
2709 return;
2710 }
2711 if (tripCount == 0)
2712 return;
2713 } else {
2714 if (tripCount.value() > 0) {
2715 regions.push_back(RegionSuccessor(&getRegion()));
2716 return;
2717 }
2718 if (tripCount.value() == 0) {
2719 regions.push_back(RegionSuccessor(getOperation()));
2720 return;
2721 }
2722 }
2723 }
2724
2725 // In all other cases, the loop may branch back to itself or the parent
2726 // operation.
2727 regions.push_back(RegionSuccessor(&getRegion()));
2728 regions.push_back(RegionSuccessor(getOperation()));
2729}
2730
2731ValueRange AffineForOp::getSuccessorInputs(RegionSuccessor successor) {
2732 if (successor.isOperation())
2733 return getResults();
2734 return getRegionIterArgs();
2735}
2736
2737AffineBound AffineForOp::getLowerBound() {
2738 return AffineBound(*this, getLowerBoundOperands(), getLowerBoundMap());
2739}
2740
2741AffineBound AffineForOp::getUpperBound() {
2742 return AffineBound(*this, getUpperBoundOperands(), getUpperBoundMap());
2743}
2744
2745void AffineForOp::setLowerBound(ValueRange lbOperands, AffineMap map) {
2746 assert(lbOperands.size() == map.getNumInputs());
2747 assert(map.getNumResults() >= 1 && "bound map has at least one result");
2748 getLowerBoundOperandsMutable().assign(lbOperands);
2749 setLowerBoundMap(map);
2750}
2751
2752void AffineForOp::setUpperBound(ValueRange ubOperands, AffineMap map) {
2753 assert(ubOperands.size() == map.getNumInputs());
2754 assert(map.getNumResults() >= 1 && "bound map has at least one result");
2755 getUpperBoundOperandsMutable().assign(ubOperands);
2756 setUpperBoundMap(map);
2757}
2758
2759bool AffineForOp::hasConstantLowerBound() {
2760 return getLowerBoundMap().isSingleConstant();
2761}
2762
2763bool AffineForOp::hasConstantUpperBound() {
2764 return getUpperBoundMap().isSingleConstant();
2765}
2766
2767int64_t AffineForOp::getConstantLowerBound() {
2768 return getLowerBoundMap().getSingleConstantResult();
2769}
2770
2771int64_t AffineForOp::getConstantUpperBound() {
2772 return getUpperBoundMap().getSingleConstantResult();
2773}
2774
2775void AffineForOp::setConstantLowerBound(int64_t value) {
2776 setLowerBound({}, AffineMap::getConstantMap(value, getContext()));
2777}
2778
2779void AffineForOp::setConstantUpperBound(int64_t value) {
2780 setUpperBound({}, AffineMap::getConstantMap(value, getContext()));
2781}
2782
2783AffineForOp::operand_range AffineForOp::getControlOperands() {
2784 return {operand_begin(), operand_begin() + getLowerBoundOperands().size() +
2785 getUpperBoundOperands().size()};
2786}
2787
2788bool AffineForOp::matchingBoundOperandList() {
2789 auto lbMap = getLowerBoundMap();
2790 auto ubMap = getUpperBoundMap();
2791 if (lbMap.getNumDims() != ubMap.getNumDims() ||
2792 lbMap.getNumSymbols() != ubMap.getNumSymbols())
2793 return false;
2794
2795 unsigned numOperands = lbMap.getNumInputs();
2796 for (unsigned i = 0, e = lbMap.getNumInputs(); i < e; i++) {
2797 // Compare Value 's.
2798 if (getOperand(i) != getOperand(numOperands + i))
2799 return false;
2800 }
2801 return true;
2802}
2803
2804SmallVector<Region *> AffineForOp::getLoopRegions() { return {&getRegion()}; }
2805
2806std::optional<SmallVector<Value>> AffineForOp::getLoopInductionVars() {
2807 return SmallVector<Value>{getInductionVar()};
2808}
2809
2810std::optional<SmallVector<OpFoldResult>> AffineForOp::getLoopLowerBounds() {
2811 if (!hasConstantLowerBound())
2812 return std::nullopt;
2813 OpBuilder b(getContext());
2814 return SmallVector<OpFoldResult>{
2815 OpFoldResult(b.getI64IntegerAttr(getConstantLowerBound()))};
2816}
2817
2818std::optional<SmallVector<OpFoldResult>> AffineForOp::getLoopSteps() {
2819 OpBuilder b(getContext());
2820 return SmallVector<OpFoldResult>{
2821 OpFoldResult(b.getI64IntegerAttr(getStepAsInt()))};
2822}
2823
2824std::optional<SmallVector<OpFoldResult>> AffineForOp::getLoopUpperBounds() {
2825 if (!hasConstantUpperBound())
2826 return {};
2827 OpBuilder b(getContext());
2828 return SmallVector<OpFoldResult>{
2829 OpFoldResult(b.getI64IntegerAttr(getConstantUpperBound()))};
2830}
2831
2832std::optional<APInt> AffineForOp::getStaticTripCount() {
2833 MLIRContext *context = getContext();
2834 int64_t step = getStepAsInt();
2835 if (step <= 0)
2836 return std::nullopt;
2837
2838 if (hasConstantBounds()) {
2839 int64_t lb = getConstantLowerBound();
2840 int64_t ub = getConstantUpperBound();
2841 int64_t loopSpan = ub - lb;
2842 if (loopSpan < 0)
2843 loopSpan = 0;
2844 return APInt(64, llvm::divideCeilSigned(loopSpan, step));
2845 }
2846
2847 auto lbMap = getLowerBoundMap();
2848 auto ubMap = getUpperBoundMap();
2849 if (lbMap.getNumResults() != 1)
2850 return std::nullopt;
2851
2852 // Difference of each upper bound expression from the single lower bound
2853 // expression (divided by the step) provides the expressions for the trip
2854 // count map.
2855 AffineValueMap ubValueMap(ubMap, getUpperBoundOperands());
2856
2857 SmallVector<AffineExpr, 4> lbSplatExpr(ubValueMap.getNumResults(),
2858 lbMap.getResult(0));
2859 auto lbMapSplat = AffineMap::get(lbMap.getNumDims(), lbMap.getNumSymbols(),
2860 lbSplatExpr, context);
2861 AffineValueMap lbSplatValueMap(lbMapSplat, getLowerBoundOperands());
2862
2863 AffineValueMap tripCountValueMap;
2864 AffineValueMap::difference(ubValueMap, lbSplatValueMap, &tripCountValueMap);
2865
2866 // Take the min if all trip counts are constant.
2867 std::optional<uint64_t> tripCount;
2868 for (unsigned i = 0, e = tripCountValueMap.getNumResults(); i < e; ++i) {
2869 AffineExpr expr = tripCountValueMap.getResult(i).ceilDiv(step);
2870 if (auto constExpr = llvm::dyn_cast<AffineConstantExpr>(expr)) {
2871 uint64_t value = constExpr.getValue();
2872 if (tripCount.has_value())
2873 tripCount = std::min(*tripCount, value);
2874 else
2875 tripCount = value;
2876 } else {
2877 return std::nullopt;
2878 }
2879 }
2880
2881 if (tripCount.has_value())
2882 return APInt(64, *tripCount);
2883
2884 return std::nullopt;
2885}
2886
2887FailureOr<LoopLikeOpInterface> AffineForOp::replaceWithAdditionalYields(
2888 RewriterBase &rewriter, ValueRange newInitOperands,
2889 bool replaceInitOperandUsesInLoop,
2890 const NewYieldValuesFn &newYieldValuesFn) {
2891 // Create a new loop before the existing one, with the extra operands.
2892 OpBuilder::InsertionGuard g(rewriter);
2893 rewriter.setInsertionPoint(getOperation());
2894 auto inits = llvm::to_vector(getInits());
2895 inits.append(newInitOperands.begin(), newInitOperands.end());
2896 AffineForOp newLoop = AffineForOp::create(
2897 rewriter, getLoc(), getLowerBoundOperands(), getLowerBoundMap(),
2898 getUpperBoundOperands(), getUpperBoundMap(), getStepAsInt(), inits);
2899 // Existing operands, results, and region arguments retain their positions;
2900 // only new loop-carried values are appended.
2901 newLoop->setDiscardableAttrs(getOperation()->getDiscardableAttrDictionary());
2902
2903 // Generate the new yield values and append them to the scf.yield operation.
2904 auto yieldOp = cast<AffineYieldOp>(getBody()->getTerminator());
2905 ArrayRef<BlockArgument> newIterArgs =
2906 newLoop.getBody()->getArguments().take_back(newInitOperands.size());
2907 {
2908 OpBuilder::InsertionGuard g(rewriter);
2909 rewriter.setInsertionPoint(yieldOp);
2910 SmallVector<Value> newYieldedValues =
2911 newYieldValuesFn(rewriter, getLoc(), newIterArgs);
2912 assert(newInitOperands.size() == newYieldedValues.size() &&
2913 "expected as many new yield values as new iter operands");
2914 rewriter.modifyOpInPlace(yieldOp, [&]() {
2915 yieldOp.getOperandsMutable().append(newYieldedValues);
2916 });
2917 }
2918
2919 // Move the loop body to the new op.
2920 rewriter.mergeBlocks(getBody(), newLoop.getBody(),
2921 newLoop.getBody()->getArguments().take_front(
2922 getBody()->getNumArguments()));
2923
2924 if (replaceInitOperandUsesInLoop) {
2925 // Replace all uses of `newInitOperands` with the corresponding basic block
2926 // arguments.
2927 for (auto it : llvm::zip(newInitOperands, newIterArgs)) {
2928 rewriter.replaceUsesWithIf(std::get<0>(it), std::get<1>(it),
2929 [&](OpOperand &use) {
2930 Operation *user = use.getOwner();
2931 return newLoop->isProperAncestor(user);
2932 });
2933 }
2934 }
2935
2936 // Replace the old loop.
2937 rewriter.replaceOp(getOperation(),
2938 newLoop->getResults().take_front(getNumResults()));
2939 return cast<LoopLikeOpInterface>(newLoop.getOperation());
2940}
2941
2942Speculation::Speculatability AffineForOp::getSpeculatability() {
2943 // `affine.for (I = Start; I < End; I += 1)` terminates for all values of
2944 // Start and End.
2945 //
2946 // For Step != 1, the loop may not terminate. We can add more smarts here if
2947 // needed.
2948 return getStepAsInt() == 1 ? Speculation::RecursivelySpeculatable
2950}
2951
2952/// Returns true if the provided value is the induction variable of a
2953/// AffineForOp.
2955 return getForInductionVarOwner(val) != AffineForOp();
2956}
2957
2961
2965
2967 auto ivArg = dyn_cast<BlockArgument>(val);
2968 if (!ivArg || !ivArg.getOwner() || !ivArg.getOwner()->getParent())
2969 return AffineForOp();
2970 if (auto forOp =
2971 ivArg.getOwner()->getParent()->getParentOfType<AffineForOp>())
2972 // Check to make sure `val` is the induction variable, not an iter_arg.
2973 return forOp.getInductionVar() == val ? forOp : AffineForOp();
2974 return AffineForOp();
2975}
2976
2978 auto ivArg = dyn_cast<BlockArgument>(val);
2979 if (!ivArg || !ivArg.getOwner())
2980 return nullptr;
2981 Operation *containingOp = ivArg.getOwner()->getParentOp();
2982 auto parallelOp = dyn_cast_if_present<AffineParallelOp>(containingOp);
2983 if (parallelOp && llvm::is_contained(parallelOp.getIVs(), val))
2984 return parallelOp;
2985 return nullptr;
2986}
2987
2988/// Extracts the induction variables from a list of AffineForOps and returns
2989/// them.
2992 ivs->reserve(forInsts.size());
2993 for (auto forInst : forInsts)
2994 ivs->push_back(forInst.getInductionVar());
2995}
2996
2999 ivs.reserve(affineOps.size());
3000 for (Operation *op : affineOps) {
3001 // Add constraints from forOp's bounds.
3002 if (auto forOp = dyn_cast<AffineForOp>(op))
3003 ivs.push_back(forOp.getInductionVar());
3004 else if (auto parallelOp = dyn_cast<AffineParallelOp>(op))
3005 for (size_t i = 0; i < parallelOp.getBody()->getNumArguments(); i++)
3006 ivs.push_back(parallelOp.getBody()->getArgument(i));
3007 }
3008}
3009
3010/// Builds an affine loop nest, using "loopCreatorFn" to create individual loop
3011/// operations.
3012template <typename BoundListTy, typename LoopCreatorTy>
3014 OpBuilder &builder, Location loc, BoundListTy lbs, BoundListTy ubs,
3015 ArrayRef<int64_t> steps,
3016 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilderFn,
3017 LoopCreatorTy &&loopCreatorFn) {
3018 assert(lbs.size() == ubs.size() && "Mismatch in number of arguments");
3019 assert(lbs.size() == steps.size() && "Mismatch in number of arguments");
3020
3021 // If there are no loops to be constructed, construct the body anyway.
3022 OpBuilder::InsertionGuard guard(builder);
3023 if (lbs.empty()) {
3024 if (bodyBuilderFn)
3025 bodyBuilderFn(builder, loc, ValueRange());
3026 return;
3027 }
3028
3029 // Create the loops iteratively and store the induction variables.
3031 ivs.reserve(lbs.size());
3032 for (unsigned i = 0, e = lbs.size(); i < e; ++i) {
3033 // Callback for creating the loop body, always creates the terminator.
3034 auto loopBody = [&](OpBuilder &nestedBuilder, Location nestedLoc, Value iv,
3035 ValueRange iterArgs) {
3036 ivs.push_back(iv);
3037 // In the innermost loop, call the body builder.
3038 if (i == e - 1 && bodyBuilderFn) {
3039 OpBuilder::InsertionGuard nestedGuard(nestedBuilder);
3040 bodyBuilderFn(nestedBuilder, nestedLoc, ivs);
3041 }
3042 AffineYieldOp::create(nestedBuilder, nestedLoc);
3043 };
3044
3045 // Delegate actual loop creation to the callback in order to dispatch
3046 // between constant- and variable-bound loops.
3047 auto loop = loopCreatorFn(builder, loc, lbs[i], ubs[i], steps[i], loopBody);
3048 builder.setInsertionPointToStart(loop.getBody());
3049 }
3050}
3051
3052/// Creates an affine loop from the bounds known to be constants.
3053static AffineForOp
3055 int64_t ub, int64_t step,
3056 AffineForOp::BodyBuilderFn bodyBuilderFn) {
3057 return AffineForOp::create(builder, loc, lb, ub, step,
3058 /*iterArgs=*/ValueRange(), bodyBuilderFn);
3059}
3060
3061/// Creates an affine loop from the bounds that may or may not be constants.
3062static AffineForOp
3064 int64_t step,
3065 AffineForOp::BodyBuilderFn bodyBuilderFn) {
3066 std::optional<int64_t> lbConst = getConstantIntValue(lb);
3067 std::optional<int64_t> ubConst = getConstantIntValue(ub);
3068 if (lbConst && ubConst)
3069 return buildAffineLoopFromConstants(builder, loc, lbConst.value(),
3070 ubConst.value(), step, bodyBuilderFn);
3071 return AffineForOp::create(builder, loc, lb, builder.getDimIdentityMap(), ub,
3072 builder.getDimIdentityMap(), step,
3073 /*iterArgs=*/ValueRange(), bodyBuilderFn);
3074}
3075
3077 OpBuilder &builder, Location loc, ArrayRef<int64_t> lbs,
3079 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilderFn) {
3080 buildAffineLoopNestImpl(builder, loc, lbs, ubs, steps, bodyBuilderFn,
3082}
3083
3085 OpBuilder &builder, Location loc, ValueRange lbs, ValueRange ubs,
3086 ArrayRef<int64_t> steps,
3087 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilderFn) {
3088 buildAffineLoopNestImpl(builder, loc, lbs, ubs, steps, bodyBuilderFn,
3090}
3091
3092//===----------------------------------------------------------------------===//
3093// AffineIfOp
3094//===----------------------------------------------------------------------===//
3095
3096namespace {
3097/// Remove else blocks that have nothing other than a zero value yield.
3098struct SimplifyDeadElse : public OpRewritePattern<AffineIfOp> {
3099 using OpRewritePattern<AffineIfOp>::OpRewritePattern;
3100
3101 LogicalResult matchAndRewrite(AffineIfOp ifOp,
3102 PatternRewriter &rewriter) const override {
3103 if (ifOp.getElseRegion().empty() ||
3104 !llvm::hasSingleElement(*ifOp.getElseBlock()) || ifOp.getNumResults())
3105 return failure();
3106
3107 rewriter.startOpModification(ifOp);
3108 rewriter.eraseBlock(ifOp.getElseBlock());
3109 rewriter.finalizeOpModification(ifOp);
3110 return success();
3111 }
3112};
3113
3114/// Removes affine.if cond if the condition is always true or false in certain
3115/// trivial cases. Promotes the then/else block in the parent operation block.
3116struct AlwaysTrueOrFalseIf : public OpRewritePattern<AffineIfOp> {
3117 using OpRewritePattern<AffineIfOp>::OpRewritePattern;
3118
3119 LogicalResult matchAndRewrite(AffineIfOp op,
3120 PatternRewriter &rewriter) const override {
3121
3122 auto isTriviallyFalse = [](IntegerSet iSet) {
3123 return iSet.isEmptyIntegerSet();
3124 };
3125
3126 auto isTriviallyTrue = [](IntegerSet iSet) {
3127 return (iSet.getNumEqualities() == 1 && iSet.getNumInequalities() == 0 &&
3128 iSet.getConstraint(0) == 0);
3129 };
3130
3131 IntegerSet affineIfConditions = op.getIntegerSet();
3132 Block *blockToMove;
3133 if (isTriviallyFalse(affineIfConditions)) {
3134 // The absence, or equivalently, the emptiness of the else region need not
3135 // be checked when affine.if is returning results because if an affine.if
3136 // operation is returning results, it always has a non-empty else region.
3137 if (op.getNumResults() == 0 && !op.hasElse()) {
3138 // If the else region is absent, or equivalently, empty, remove the
3139 // affine.if operation (which is not returning any results).
3140 rewriter.eraseOp(op);
3141 return success();
3142 }
3143 blockToMove = op.getElseBlock();
3144 } else if (isTriviallyTrue(affineIfConditions)) {
3145 blockToMove = op.getThenBlock();
3146 } else {
3147 return failure();
3148 }
3149 Operation *blockToMoveTerminator = blockToMove->getTerminator();
3150 // Promote the "blockToMove" block to the parent operation block between the
3151 // prologue and epilogue of "op".
3152 rewriter.inlineBlockBefore(blockToMove, op);
3153 // Replace the "op" operation with the operands of the
3154 // "blockToMoveTerminator" operation. Note that "blockToMoveTerminator" is
3155 // the affine.yield operation present in the "blockToMove" block. It has no
3156 // operands when affine.if is not returning results and therefore, in that
3157 // case, replaceOp just erases "op". When affine.if is not returning
3158 // results, the affine.yield operation can be omitted. It gets inserted
3159 // implicitly.
3160 rewriter.replaceOp(op, blockToMoveTerminator->getOperands());
3161 // Erase the "blockToMoveTerminator" operation since it is now in the parent
3162 // operation block, which already has its own terminator.
3163 rewriter.eraseOp(blockToMoveTerminator);
3164 return success();
3165 }
3166};
3167} // namespace
3168
3169/// AffineIfOp has two regions -- `then` and `else`. The flow of data should be
3170/// as follows: AffineIfOp -> `then`/`else` -> AffineIfOp
3171void AffineIfOp::getSuccessorRegions(
3173 // If the predecessor is an AffineIfOp, then branching into both `then` and
3174 // `else` region is valid.
3175 if (point.isParent()) {
3176 regions.reserve(2);
3177 regions.push_back(RegionSuccessor(&getThenRegion()));
3178 // If the "else" region is empty, branch bach into parent.
3179 if (getElseRegion().empty()) {
3180 regions.push_back(RegionSuccessor(getOperation()));
3181 } else {
3182 regions.push_back(RegionSuccessor(&getElseRegion()));
3183 }
3184 return;
3185 }
3186
3187 // If the predecessor is the `else`/`then` region, then branching into parent
3188 // op is valid.
3189 regions.push_back(RegionSuccessor(getOperation()));
3190}
3191
3192ValueRange AffineIfOp::getSuccessorInputs(RegionSuccessor successor) {
3193 if (successor.isOperation())
3194 return getResults();
3195 if (successor == &getThenRegion())
3196 return getThenRegion().getArguments();
3197 if (successor == &getElseRegion())
3198 return getElseRegion().getArguments();
3199 llvm_unreachable("invalid region successor");
3200}
3201
3202LogicalResult AffineIfOp::verify() {
3203 // Verify that we have a condition attribute.
3204 // FIXME: This should be specified in the arguments list in ODS.
3205 auto conditionAttr =
3206 (*this)->getAttrOfType<IntegerSetAttr>(getConditionAttrStrName());
3207 if (!conditionAttr)
3208 return emitOpError("requires an integer set attribute named 'condition'");
3209
3210 // Verify that there are enough operands for the condition.
3211 IntegerSet condition = conditionAttr.getValue();
3212 if (getNumOperands() != condition.getNumInputs())
3213 return emitOpError("operand count and condition integer set dimension and "
3214 "symbol count must match");
3215
3216 // Verify that the operands are valid dimension/symbols.
3217 if (failed(verifyDimAndSymbolIdentifiers(*this, getOperands(),
3218 condition.getNumDims())))
3219 return failure();
3220
3221 return success();
3222}
3223
3224ParseResult AffineIfOp::parse(OpAsmParser &parser, OperationState &result) {
3225 // Parse the condition attribute set.
3226 IntegerSetAttr conditionAttr;
3227 unsigned numDims;
3228 if (parser.parseAttribute(conditionAttr,
3229 AffineIfOp::getConditionAttrStrName(),
3230 result.attributes) ||
3231 parseDimAndSymbolList(parser, result.operands, numDims))
3232 return failure();
3233
3234 // Verify the condition operands.
3235 auto set = conditionAttr.getValue();
3236 if (set.getNumDims() != numDims)
3237 return parser.emitError(
3238 parser.getNameLoc(),
3239 "dim operand count and integer set dim count must match");
3240 if (numDims + set.getNumSymbols() != result.operands.size())
3241 return parser.emitError(
3242 parser.getNameLoc(),
3243 "symbol operand count and integer set symbol count must match");
3244
3245 if (parser.parseOptionalArrowTypeList(result.types))
3246 return failure();
3247
3248 // Create the regions for 'then' and 'else'. The latter must be created even
3249 // if it remains empty for the validity of the operation.
3250 result.regions.reserve(2);
3251 Region *thenRegion = result.addRegion();
3252 Region *elseRegion = result.addRegion();
3253
3254 // Parse the 'then' region.
3255 if (parser.parseRegion(*thenRegion, {}, {}))
3256 return failure();
3257 AffineIfOp::ensureTerminator(*thenRegion, parser.getBuilder(),
3258 result.location);
3259
3260 // If we find an 'else' keyword then parse the 'else' region.
3261 if (!parser.parseOptionalKeyword("else")) {
3262 if (parser.parseRegion(*elseRegion, {}, {}))
3263 return failure();
3264 AffineIfOp::ensureTerminator(*elseRegion, parser.getBuilder(),
3265 result.location);
3266 }
3267
3268 // Parse the optional attribute list.
3269 if (parser.parseOptionalAttrDict(result.attributes))
3270 return failure();
3271
3272 return success();
3273}
3274
3275void AffineIfOp::print(OpAsmPrinter &p) {
3276 auto conditionAttr =
3277 (*this)->getAttrOfType<IntegerSetAttr>(getConditionAttrStrName());
3278 p << " " << conditionAttr;
3279 printDimAndSymbolList(operand_begin(), operand_end(),
3280 conditionAttr.getValue().getNumDims(), p);
3281 p.printOptionalArrowTypeList(getResultTypes());
3282 p << ' ';
3283 p.printRegion(getThenRegion(), /*printEntryBlockArgs=*/false,
3284 /*printBlockTerminators=*/getNumResults());
3285
3286 // Print the 'else' regions if it has any blocks.
3287 auto &elseRegion = this->getElseRegion();
3288 if (!elseRegion.empty()) {
3289 p << " else ";
3290 p.printRegion(elseRegion,
3291 /*printEntryBlockArgs=*/false,
3292 /*printBlockTerminators=*/getNumResults());
3293 }
3294
3295 // Print the attribute list.
3296 p.printOptionalAttrDict((*this)->getAttrs(),
3297 /*elidedAttrs=*/getConditionAttrStrName());
3298}
3299
3300IntegerSet AffineIfOp::getIntegerSet() {
3301 return (*this)
3302 ->getAttrOfType<IntegerSetAttr>(getConditionAttrStrName())
3303 .getValue();
3304}
3305
3306void AffineIfOp::setIntegerSet(IntegerSet newSet) {
3307 (*this)->setAttr(getConditionAttrStrName(), IntegerSetAttr::get(newSet));
3308}
3309
3310void AffineIfOp::setConditional(IntegerSet set, ValueRange operands) {
3311 setIntegerSet(set);
3312 (*this)->setOperands(operands);
3313}
3314
3315void AffineIfOp::build(OpBuilder &builder, OperationState &result,
3316 TypeRange resultTypes, IntegerSet set, ValueRange args,
3317 bool withElseRegion) {
3318 assert(resultTypes.empty() || withElseRegion);
3319 OpBuilder::InsertionGuard guard(builder);
3320
3321 result.addTypes(resultTypes);
3322 result.addOperands(args);
3323 result.addAttribute(getConditionAttrStrName(), IntegerSetAttr::get(set));
3324
3325 Region *thenRegion = result.addRegion();
3326 builder.createBlock(thenRegion);
3327 if (resultTypes.empty())
3328 AffineIfOp::ensureTerminator(*thenRegion, builder, result.location);
3329
3330 Region *elseRegion = result.addRegion();
3331 if (withElseRegion) {
3332 builder.createBlock(elseRegion);
3333 if (resultTypes.empty())
3334 AffineIfOp::ensureTerminator(*elseRegion, builder, result.location);
3335 }
3336}
3337
3338void AffineIfOp::build(OpBuilder &builder, OperationState &result,
3339 IntegerSet set, ValueRange args, bool withElseRegion) {
3340 AffineIfOp::build(builder, result, /*resultTypes=*/{}, set, args,
3341 withElseRegion);
3342}
3343
3344/// Compose any affine.apply ops feeding into `operands` of the integer set
3345/// `set` by composing the maps of such affine.apply ops with the integer
3346/// set constraints.
3348 SmallVectorImpl<Value> &operands,
3349 bool composeAffineMin = false) {
3350 // We will simply reuse the API of the map composition by viewing the LHSs of
3351 // the equalities and inequalities of `set` as the affine exprs of an affine
3352 // map. Convert to equivalent map, compose, and convert back to set.
3353 auto map = AffineMap::get(set.getNumDims(), set.getNumSymbols(),
3354 set.getConstraints(), set.getContext());
3355 // Check if any composition is possible.
3356 if (llvm::none_of(operands,
3357 [](Value v) { return v.getDefiningOp<AffineApplyOp>(); }))
3358 return;
3359
3360 composeAffineMapAndOperands(&map, &operands, composeAffineMin);
3361 set = IntegerSet::get(map.getNumDims(), map.getNumSymbols(), map.getResults(),
3362 set.getEqFlags());
3363}
3364
3365/// Canonicalize an affine if op's conditional (integer set + operands).
3366LogicalResult AffineIfOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
3367 auto set = getIntegerSet();
3368 SmallVector<Value, 4> operands(getOperands());
3369 composeSetAndOperands(set, operands);
3370 canonicalizeSetAndOperands(&set, &operands);
3371
3372 // Check if the canonicalization or composition led to any change.
3373 if (getIntegerSet() == set && llvm::equal(operands, getOperands()))
3374 return failure();
3375
3376 setConditional(set, operands);
3377 return success();
3378}
3379
3380void AffineIfOp::getCanonicalizationPatterns(RewritePatternSet &results,
3381 MLIRContext *context) {
3382 results.add<SimplifyDeadElse, AlwaysTrueOrFalseIf>(context);
3383}
3384
3385/// Adds the optional `alignment` attribute to `result`, if one is given.
3387 StringAttr attrName, llvm::MaybeAlign alignment) {
3388 if (alignment)
3389 result.addAttribute(attrName,
3390 builder.getI64IntegerAttr(alignment->value()));
3391}
3392
3393//===----------------------------------------------------------------------===//
3394// AffineLoadOp
3395//===----------------------------------------------------------------------===//
3396
3397void AffineLoadOp::build(OpBuilder &builder, OperationState &result,
3398 AffineMap map, ValueRange operands,
3399 llvm::MaybeAlign alignment) {
3400 assert(operands.size() == 1 + map.getNumInputs() && "inconsistent operands");
3401 result.addOperands(operands);
3402 if (map)
3403 result.addAttribute(getMapAttrStrName(), AffineMapAttr::get(map));
3404 addAlignmentAttr(builder, result, getAlignmentAttrName(result.name),
3405 alignment);
3406 auto memrefType = llvm::cast<MemRefType>(operands[0].getType());
3407 result.types.push_back(memrefType.getElementType());
3408}
3409
3410void AffineLoadOp::build(OpBuilder &builder, OperationState &result,
3411 Value memref, AffineMap map, ValueRange mapOperands,
3412 llvm::MaybeAlign alignment) {
3413 assert(map.getNumInputs() == mapOperands.size() && "inconsistent index info");
3414 result.addOperands(memref);
3415 result.addOperands(mapOperands);
3416 auto memrefType = llvm::cast<MemRefType>(memref.getType());
3417 result.addAttribute(getMapAttrStrName(), AffineMapAttr::get(map));
3418 addAlignmentAttr(builder, result, getAlignmentAttrName(result.name),
3419 alignment);
3420 result.types.push_back(memrefType.getElementType());
3421}
3422
3423void AffineLoadOp::build(OpBuilder &builder, OperationState &result,
3425 llvm::MaybeAlign alignment) {
3426 auto memrefType = llvm::cast<MemRefType>(memref.getType());
3427 int64_t rank = memrefType.getRank();
3428 // Create identity map for memrefs with at least one dimension or () -> ()
3429 // for zero-dimensional memrefs.
3430 auto map =
3431 rank ? builder.getMultiDimIdentityMap(rank) : builder.getEmptyAffineMap();
3432 build(builder, result, memref, map, indices, alignment);
3433}
3434
3435ParseResult AffineLoadOp::parse(OpAsmParser &parser, OperationState &result) {
3436 auto &builder = parser.getBuilder();
3437 auto indexTy = builder.getIndexType();
3438
3439 MemRefType type;
3441 AffineMapAttr mapAttr;
3443 return failure(
3444 parser.parseOperand(memrefInfo) ||
3445 parser.parseAffineMapOfSSAIds(mapOperands, mapAttr,
3446 AffineLoadOp::getMapAttrStrName(),
3447 result.attributes) ||
3448 parser.parseOptionalAttrDict(result.attributes) ||
3449 parser.parseColonType(type) ||
3450 parser.resolveOperand(memrefInfo, type, result.operands) ||
3451 parser.resolveOperands(mapOperands, indexTy, result.operands) ||
3452 parser.addTypeToList(type.getElementType(), result.types));
3453}
3454
3455void AffineLoadOp::print(OpAsmPrinter &p) {
3456 p << " " << getMemRef() << '[';
3457 if (AffineMapAttr mapAttr =
3458 (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()))
3459 p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
3460 p << ']';
3461 p.printOptionalAttrDict((*this)->getAttrs(),
3462 /*elidedAttrs=*/{getMapAttrStrName()});
3463 p << " : " << getMemRefType();
3464}
3465
3466/// Verify common indexing invariants of affine.load, affine.store,
3467/// affine.vector_load and affine.vector_store.
3468template <typename AffineMemOpTy>
3469static LogicalResult
3470verifyMemoryOpIndexing(AffineMemOpTy op, AffineMapAttr mapAttr,
3471 Operation::operand_range mapOperands,
3472 MemRefType memrefType, unsigned numIndexOperands) {
3473 AffineMap map = mapAttr.getValue();
3474 if (map.getNumResults() != memrefType.getRank())
3475 return op->emitOpError("affine map num results must equal memref rank");
3476 if (map.getNumInputs() != numIndexOperands)
3477 return op->emitOpError("expects as many subscripts as affine map inputs");
3478
3479 for (auto idx : mapOperands) {
3480 if (!idx.getType().isIndex())
3481 return op->emitOpError("index to load must have 'index' type");
3482 }
3483 if (failed(verifyDimAndSymbolIdentifiers(op, mapOperands, map.getNumDims())))
3484 return failure();
3485
3486 return success();
3487}
3488
3489LogicalResult AffineLoadOp::verify() {
3490 auto memrefType = getMemRefType();
3491 if (getType() != memrefType.getElementType())
3492 return emitOpError("result type must match element type of memref");
3493
3495 *this, (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()),
3496 getMapOperands(), memrefType,
3497 /*numIndexOperands=*/getNumOperands() - 1)))
3498 return failure();
3499
3500 return success();
3501}
3502
3503void AffineLoadOp::getCanonicalizationPatterns(RewritePatternSet &results,
3504 MLIRContext *context) {
3505 results.add<SimplifyAffineOp<AffineLoadOp>>(context);
3506}
3507
3508OpFoldResult AffineLoadOp::fold(FoldAdaptor adaptor) {
3509 /// load(memrefcast) -> load
3510 if (succeeded(memref::foldMemRefCast(*this)))
3511 return getResult();
3512
3513 // Fold load from a global constant memref.
3514 auto getGlobalOp = getMemref().getDefiningOp<memref::GetGlobalOp>();
3515 if (!getGlobalOp)
3516 return {};
3517 // Get to the memref.global defining the symbol.
3519 getGlobalOp, getGlobalOp.getNameAttr());
3520 if (!global)
3521 return {};
3522
3523 // Check if the global memref is a constant.
3524 auto cstAttr =
3525 dyn_cast_or_null<DenseElementsAttr>(global.getConstantInitValue());
3526 if (!cstAttr)
3527 return {};
3528 // If it's a splat constant, we can fold irrespective of indices.
3529 if (auto splatAttr = dyn_cast<SplatElementsAttr>(cstAttr))
3530 return splatAttr.getSplatValue<Attribute>();
3531 // Otherwise, we can fold only if we know the indices.
3532 if (!getAffineMap().isConstant())
3533 return {};
3534 auto indices =
3535 llvm::map_to_vector<4>(getAffineMap().getConstantResults(),
3536 [](int64_t v) -> uint64_t { return v; });
3537 return cstAttr.getValues<Attribute>()[indices];
3538}
3539
3540//===----------------------------------------------------------------------===//
3541// AffineStoreOp
3542//===----------------------------------------------------------------------===//
3543
3544void AffineStoreOp::build(OpBuilder &builder, OperationState &result,
3545 Value valueToStore, Value memref, AffineMap map,
3546 ValueRange mapOperands, llvm::MaybeAlign alignment) {
3547 assert(map.getNumInputs() == mapOperands.size() && "inconsistent index info");
3548 result.addOperands(valueToStore);
3549 result.addOperands(memref);
3550 result.addOperands(mapOperands);
3551 result.getOrAddProperties<Properties>().map = AffineMapAttr::get(map);
3552 addAlignmentAttr(builder, result, getAlignmentAttrName(result.name),
3553 alignment);
3554}
3555
3556// Use identity map.
3557void AffineStoreOp::build(OpBuilder &builder, OperationState &result,
3558 Value valueToStore, Value memref, ValueRange indices,
3559 llvm::MaybeAlign alignment) {
3560 auto memrefType = llvm::cast<MemRefType>(memref.getType());
3561 int64_t rank = memrefType.getRank();
3562 // Create identity map for memrefs with at least one dimension or () -> ()
3563 // for zero-dimensional memrefs.
3564 auto map =
3565 rank ? builder.getMultiDimIdentityMap(rank) : builder.getEmptyAffineMap();
3566 build(builder, result, valueToStore, memref, map, indices, alignment);
3567}
3568
3569ParseResult AffineStoreOp::parse(OpAsmParser &parser, OperationState &result) {
3570 auto indexTy = parser.getBuilder().getIndexType();
3571
3572 MemRefType type;
3573 OpAsmParser::UnresolvedOperand storeValueInfo;
3575 AffineMapAttr mapAttr;
3577 return failure(parser.parseOperand(storeValueInfo) || parser.parseComma() ||
3578 parser.parseOperand(memrefInfo) ||
3580 mapOperands, mapAttr, AffineStoreOp::getMapAttrStrName(),
3581 result.attributes) ||
3582 parser.parseOptionalAttrDict(result.attributes) ||
3583 parser.parseColonType(type) ||
3584 parser.resolveOperand(storeValueInfo, type.getElementType(),
3585 result.operands) ||
3586 parser.resolveOperand(memrefInfo, type, result.operands) ||
3587 parser.resolveOperands(mapOperands, indexTy, result.operands));
3588}
3589
3590void AffineStoreOp::print(OpAsmPrinter &p) {
3591 p << " " << getValueToStore();
3592 p << ", " << getMemRef() << '[';
3593 if (AffineMapAttr mapAttr =
3594 (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()))
3595 p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
3596 p << ']';
3597 p.printOptionalAttrDict((*this)->getAttrs(),
3598 /*elidedAttrs=*/{getMapAttrStrName()});
3599 p << " : " << getMemRefType();
3600}
3601
3602LogicalResult AffineStoreOp::verify() {
3603 // The value to store must have the same type as memref element type.
3604 auto memrefType = getMemRefType();
3605 if (getValueToStore().getType() != memrefType.getElementType())
3606 return emitOpError(
3607 "value to store must have the same type as memref element type");
3608
3610 *this, (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()),
3611 getMapOperands(), memrefType,
3612 /*numIndexOperands=*/getNumOperands() - 2)))
3613 return failure();
3614
3615 return success();
3616}
3617
3618void AffineStoreOp::getCanonicalizationPatterns(RewritePatternSet &results,
3619 MLIRContext *context) {
3620 results.add<SimplifyAffineOp<AffineStoreOp>>(context);
3621}
3622
3623LogicalResult AffineStoreOp::fold(FoldAdaptor adaptor,
3625 /// store(memrefcast) -> store
3626 return memref::foldMemRefCast(*this, getValueToStore());
3627}
3628
3629//===----------------------------------------------------------------------===//
3630// AffineMinMaxOpBase
3631//===----------------------------------------------------------------------===//
3632
3633template <typename T>
3634static LogicalResult verifyAffineMinMaxOp(T op) {
3635 // Verify that operand count matches affine map dimension and symbol count.
3636 if (op.getNumOperands() !=
3637 op.getMap().getNumDims() + op.getMap().getNumSymbols())
3638 return op.emitOpError(
3639 "operand count and affine map dimension and symbol count must match");
3640
3641 if (op.getMap().getNumResults() == 0)
3642 return op.emitOpError("affine map expect at least one result");
3643 return success();
3644}
3645
3646template <typename T>
3647static void printAffineMinMaxOp(OpAsmPrinter &p, T op) {
3648 p << ' ' << op->getAttr(T::getMapAttrStrName());
3649 auto operands = op.getOperands();
3650 unsigned numDims = op.getMap().getNumDims();
3651 p << '(' << operands.take_front(numDims) << ')';
3652
3653 if (operands.size() != numDims)
3654 p << '[' << operands.drop_front(numDims) << ']';
3655 p.printOptionalAttrDict(op->getAttrs(),
3656 /*elidedAttrs=*/{T::getMapAttrStrName()});
3657}
3658
3659template <typename T>
3660static ParseResult parseAffineMinMaxOp(OpAsmParser &parser,
3662 auto &builder = parser.getBuilder();
3663 auto indexType = builder.getIndexType();
3666 AffineMapAttr mapAttr;
3667 return failure(
3668 parser.parseAttribute(mapAttr, T::getMapAttrStrName(),
3669 result.attributes) ||
3671 parser.parseOperandList(symInfos,
3673 parser.parseOptionalAttrDict(result.attributes) ||
3674 parser.resolveOperands(dimInfos, indexType, result.operands) ||
3675 parser.resolveOperands(symInfos, indexType, result.operands) ||
3676 parser.addTypeToList(indexType, result.types));
3677}
3678
3679/// Fold an affine min or max operation with the given operands. The operand
3680/// list may contain nulls, which are interpreted as the operand not being a
3681/// constant.
3682template <typename T>
3684 static_assert(llvm::is_one_of<T, AffineMinOp, AffineMaxOp>::value,
3685 "expected affine min or max op");
3686
3687 // Fold the affine map.
3688 // TODO: Fold more cases:
3689 // min(some_affine, some_affine + constant, ...), etc.
3691 auto foldedMap = op.getMap().partialConstantFold(operands, &results);
3692
3693 if (foldedMap.getNumSymbols() == 1 && foldedMap.isSymbolIdentity())
3694 return op.getOperand(0);
3695
3696 // If some of the map results are not constant, try changing the map in-place.
3697 if (results.empty()) {
3698 // If the map is the same, report that folding did not happen.
3699 if (foldedMap == op.getMap())
3700 return {};
3701 op->setAttr("map", AffineMapAttr::get(foldedMap));
3702 return op.getResult();
3703 }
3704
3705 // Otherwise, completely fold the op into a constant.
3706 auto resultIt = std::is_same<T, AffineMinOp>::value
3707 ? llvm::min_element(results)
3708 : llvm::max_element(results);
3709 if (resultIt == results.end())
3710 return {};
3711 return IntegerAttr::get(IndexType::get(op.getContext()), *resultIt);
3712}
3713
3714/// Remove duplicated expressions in affine min/max ops.
3715template <typename T>
3718
3719 LogicalResult matchAndRewrite(T affineOp,
3720 PatternRewriter &rewriter) const override {
3721 AffineMap oldMap = affineOp.getAffineMap();
3722
3724 for (AffineExpr expr : oldMap.getResults()) {
3725 // This is a linear scan over newExprs, but it should be fine given that
3726 // we typically just have a few expressions per op.
3727 if (!llvm::is_contained(newExprs, expr))
3728 newExprs.push_back(expr);
3729 }
3730
3731 if (newExprs.size() == oldMap.getNumResults())
3732 return failure();
3733
3734 auto newMap = AffineMap::get(oldMap.getNumDims(), oldMap.getNumSymbols(),
3735 newExprs, rewriter.getContext());
3736 rewriter.replaceOpWithNewOp<T>(affineOp, newMap, affineOp.getMapOperands());
3737
3738 return success();
3739 }
3740};
3741
3742/// Merge an affine min/max op to its consumers if its consumer is also an
3743/// affine min/max op.
3744///
3745/// This pattern requires the producer affine min/max op is bound to a
3746/// dimension/symbol that is used as a standalone expression in the consumer
3747/// affine op's map.
3748///
3749/// For example, a pattern like the following:
3750///
3751/// %0 = affine.min affine_map<()[s0] -> (s0 + 16, s0 * 8)> ()[%sym1]
3752/// %1 = affine.min affine_map<(d0)[s0] -> (s0 + 4, d0)> (%0)[%sym2]
3753///
3754/// Can be turned into:
3755///
3756/// %1 = affine.min affine_map<
3757/// ()[s0, s1] -> (s0 + 4, s1 + 16, s1 * 8)> ()[%sym2, %sym1]
3758template <typename T>
3761
3762 LogicalResult matchAndRewrite(T affineOp,
3763 PatternRewriter &rewriter) const override {
3764 AffineMap oldMap = affineOp.getAffineMap();
3765 ValueRange dimOperands =
3766 affineOp.getMapOperands().take_front(oldMap.getNumDims());
3767 ValueRange symOperands =
3768 affineOp.getMapOperands().take_back(oldMap.getNumSymbols());
3769
3770 auto newDimOperands = llvm::to_vector<8>(dimOperands);
3771 auto newSymOperands = llvm::to_vector<8>(symOperands);
3773 SmallVector<T, 4> producerOps;
3774
3775 // Go over each expression to see whether it's a single dimension/symbol
3776 // with the corresponding operand which is the result of another affine
3777 // min/max op. If So it can be merged into this affine op.
3778 for (AffineExpr expr : oldMap.getResults()) {
3779 if (auto symExpr = dyn_cast<AffineSymbolExpr>(expr)) {
3780 Value symValue = symOperands[symExpr.getPosition()];
3781 if (auto producerOp = symValue.getDefiningOp<T>()) {
3782 producerOps.push_back(producerOp);
3783 continue;
3784 }
3785 } else if (auto dimExpr = dyn_cast<AffineDimExpr>(expr)) {
3786 Value dimValue = dimOperands[dimExpr.getPosition()];
3787 if (auto producerOp = dimValue.getDefiningOp<T>()) {
3788 producerOps.push_back(producerOp);
3789 continue;
3790 }
3791 }
3792 // For the above cases we will remove the expression by merging the
3793 // producer affine min/max's affine expressions. Otherwise we need to
3794 // keep the existing expression.
3795 newExprs.push_back(expr);
3796 }
3797
3798 if (producerOps.empty())
3799 return failure();
3800
3801 unsigned numUsedDims = oldMap.getNumDims();
3802 unsigned numUsedSyms = oldMap.getNumSymbols();
3803
3804 // Now go over all producer affine ops and merge their expressions.
3805 for (T producerOp : producerOps) {
3806 AffineMap producerMap = producerOp.getAffineMap();
3807 unsigned numProducerDims = producerMap.getNumDims();
3808 unsigned numProducerSyms = producerMap.getNumSymbols();
3809
3810 // Collect all dimension/symbol values.
3811 ValueRange dimValues =
3812 producerOp.getMapOperands().take_front(numProducerDims);
3813 ValueRange symValues =
3814 producerOp.getMapOperands().take_back(numProducerSyms);
3815 newDimOperands.append(dimValues.begin(), dimValues.end());
3816 newSymOperands.append(symValues.begin(), symValues.end());
3817
3818 // For expressions we need to shift to avoid overlap.
3819 for (AffineExpr expr : producerMap.getResults()) {
3820 newExprs.push_back(expr.shiftDims(numProducerDims, numUsedDims)
3821 .shiftSymbols(numProducerSyms, numUsedSyms));
3822 }
3823
3824 numUsedDims += numProducerDims;
3825 numUsedSyms += numProducerSyms;
3826 }
3827
3828 auto newMap = AffineMap::get(numUsedDims, numUsedSyms, newExprs,
3829 rewriter.getContext());
3830 auto newOperands =
3831 llvm::to_vector<8>(llvm::concat<Value>(newDimOperands, newSymOperands));
3832 rewriter.replaceOpWithNewOp<T>(affineOp, newMap, newOperands);
3833
3834 return success();
3835 }
3836};
3837
3838/// Canonicalize the result expression order of an affine map and return success
3839/// if the order changed.
3840///
3841/// The function flattens the map's affine expressions to coefficient arrays and
3842/// sorts them in lexicographic order. A coefficient array contains a multiplier
3843/// for every dimension/symbol and a constant term. The canonicalization fails
3844/// if a result expression is not pure or if the flattening requires local
3845/// variables that, unlike dimensions and symbols, have no global order.
3846static LogicalResult canonicalizeMapExprAndTermOrder(AffineMap &map) {
3847 SmallVector<SmallVector<int64_t>> flattenedExprs;
3848 for (const AffineExpr &resultExpr : map.getResults()) {
3849 // Fail if the expression is not pure.
3850 if (!resultExpr.isPureAffine())
3851 return failure();
3852
3853 SimpleAffineExprFlattener flattener(map.getNumDims(), map.getNumSymbols());
3854 auto flattenResult = flattener.walkPostOrder(resultExpr);
3855 if (failed(flattenResult))
3856 return failure();
3857
3858 // Fail if the flattened expression has local variables.
3859 if (flattener.operandExprStack.back().size() !=
3860 map.getNumDims() + map.getNumSymbols() + 1)
3861 return failure();
3862
3863 flattenedExprs.emplace_back(flattener.operandExprStack.back().begin(),
3864 flattener.operandExprStack.back().end());
3865 }
3866
3867 // Fail if sorting is not necessary.
3868 if (llvm::is_sorted(flattenedExprs))
3869 return failure();
3870
3871 // Reorder the result expressions according to their flattened form.
3872 SmallVector<unsigned> resultPermutation =
3873 llvm::to_vector(llvm::seq<unsigned>(0, map.getNumResults()));
3874 llvm::sort(resultPermutation, [&](unsigned lhs, unsigned rhs) {
3875 return flattenedExprs[lhs] < flattenedExprs[rhs];
3876 });
3877 SmallVector<AffineExpr> newExprs;
3878 for (unsigned idx : resultPermutation)
3879 newExprs.push_back(map.getResult(idx));
3880
3881 map = AffineMap::get(map.getNumDims(), map.getNumSymbols(), newExprs,
3882 map.getContext());
3883 return success();
3884}
3885
3886/// Canonicalize the affine map result expression order of an affine min/max
3887/// operation.
3888///
3889/// The pattern calls `canonicalizeMapExprAndTermOrder` to order the result
3890/// expressions and replaces the operation if the order changed.
3891///
3892/// For example, the following operation:
3893///
3894/// %0 = affine.min affine_map<(d0, d1) -> (d0 + d1, d1 + 16, 32)> (%i0, %i1)
3895///
3896/// Turns into:
3897///
3898/// %0 = affine.min affine_map<(d0, d1) -> (32, d1 + 16, d0 + d1)> (%i0, %i1)
3899template <typename T>
3902
3903 LogicalResult matchAndRewrite(T affineOp,
3904 PatternRewriter &rewriter) const override {
3905 AffineMap map = affineOp.getAffineMap();
3906 if (failed(canonicalizeMapExprAndTermOrder(map)))
3907 return failure();
3908 rewriter.replaceOpWithNewOp<T>(affineOp, map, affineOp.getMapOperands());
3909 return success();
3910 }
3911};
3912
3913template <typename T>
3916
3917 LogicalResult matchAndRewrite(T affineOp,
3918 PatternRewriter &rewriter) const override {
3919 if (affineOp.getMap().getNumResults() != 1)
3920 return failure();
3921 rewriter.replaceOpWithNewOp<AffineApplyOp>(affineOp, affineOp.getMap(),
3922 affineOp.getOperands());
3923 return success();
3924 }
3925};
3926
3927//===----------------------------------------------------------------------===//
3928// AffineMinOp
3929//===----------------------------------------------------------------------===//
3930//
3931// %0 = affine.min (d0) -> (1000, d0 + 512) (%i0)
3932//
3933
3934OpFoldResult AffineMinOp::fold(FoldAdaptor adaptor) {
3935 return foldMinMaxOp(*this, adaptor.getOperands());
3936}
3937
3938void AffineMinOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
3939 MLIRContext *context) {
3942 MergeAffineMinMaxOp<AffineMinOp>, SimplifyAffineOp<AffineMinOp>,
3944 context);
3945}
3946
3947LogicalResult AffineMinOp::verify() { return verifyAffineMinMaxOp(*this); }
3948
3949ParseResult AffineMinOp::parse(OpAsmParser &parser, OperationState &result) {
3951}
3952
3953void AffineMinOp::print(OpAsmPrinter &p) { printAffineMinMaxOp(p, *this); }
3954
3955//===----------------------------------------------------------------------===//
3956// AffineMaxOp
3957//===----------------------------------------------------------------------===//
3958//
3959// %0 = affine.max (d0) -> (1000, d0 + 512) (%i0)
3960//
3961
3962OpFoldResult AffineMaxOp::fold(FoldAdaptor adaptor) {
3963 return foldMinMaxOp(*this, adaptor.getOperands());
3964}
3965
3966void AffineMaxOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
3967 MLIRContext *context) {
3970 MergeAffineMinMaxOp<AffineMaxOp>, SimplifyAffineOp<AffineMaxOp>,
3972 context);
3973}
3974
3975LogicalResult AffineMaxOp::verify() { return verifyAffineMinMaxOp(*this); }
3976
3977ParseResult AffineMaxOp::parse(OpAsmParser &parser, OperationState &result) {
3979}
3980
3981void AffineMaxOp::print(OpAsmPrinter &p) { printAffineMinMaxOp(p, *this); }
3982
3983//===----------------------------------------------------------------------===//
3984// AffinePrefetchOp
3985//===----------------------------------------------------------------------===//
3986
3987//
3988// affine.prefetch %0[%i, %j + 5], read, locality<3>, data : memref<400x400xi32>
3989//
3990ParseResult AffinePrefetchOp::parse(OpAsmParser &parser,
3992 auto &builder = parser.getBuilder();
3993 auto indexTy = builder.getIndexType();
3994
3995 MemRefType type;
3997 IntegerAttr hintInfo;
3998 auto i32Type = parser.getBuilder().getIntegerType(32);
3999 StringRef readOrWrite, cacheType;
4000
4001 AffineMapAttr mapAttr;
4003 if (parser.parseOperand(memrefInfo) ||
4004 parser.parseAffineMapOfSSAIds(mapOperands, mapAttr,
4005 AffinePrefetchOp::getMapAttrStrName(),
4006 result.attributes) ||
4007 parser.parseComma() || parser.parseKeyword(&readOrWrite) ||
4008 parser.parseComma() || parser.parseKeyword("locality") ||
4009 parser.parseLess() ||
4010 parser.parseAttribute(hintInfo, i32Type,
4011 AffinePrefetchOp::getLocalityHintAttrStrName(),
4012 result.attributes) ||
4013 parser.parseGreater() || parser.parseComma() ||
4014 parser.parseKeyword(&cacheType) ||
4015 parser.parseOptionalAttrDict(result.attributes) ||
4016 parser.parseColonType(type) ||
4017 parser.resolveOperand(memrefInfo, type, result.operands) ||
4018 parser.resolveOperands(mapOperands, indexTy, result.operands))
4019 return failure();
4020
4021 if (readOrWrite != "read" && readOrWrite != "write")
4022 return parser.emitError(parser.getNameLoc(),
4023 "rw specifier has to be 'read' or 'write'");
4024 result.addAttribute(AffinePrefetchOp::getIsWriteAttrStrName(),
4025 parser.getBuilder().getBoolAttr(readOrWrite == "write"));
4026
4027 if (cacheType != "data" && cacheType != "instr")
4028 return parser.emitError(parser.getNameLoc(),
4029 "cache type has to be 'data' or 'instr'");
4030
4031 result.addAttribute(AffinePrefetchOp::getIsDataCacheAttrStrName(),
4032 parser.getBuilder().getBoolAttr(cacheType == "data"));
4033
4034 return success();
4035}
4036
4037void AffinePrefetchOp::print(OpAsmPrinter &p) {
4038 p << " " << getMemref() << '[';
4039 AffineMapAttr mapAttr =
4040 (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName());
4041 if (mapAttr)
4042 p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
4043 p << ']' << ", " << (getIsWrite() ? "write" : "read") << ", " << "locality<"
4044 << getLocalityHint() << ">, " << (getIsDataCache() ? "data" : "instr");
4046 (*this)->getAttrs(),
4047 /*elidedAttrs=*/{getMapAttrStrName(), getLocalityHintAttrStrName(),
4048 getIsDataCacheAttrStrName(), getIsWriteAttrStrName()});
4049 p << " : " << getMemRefType();
4050}
4051
4052LogicalResult AffinePrefetchOp::verify() {
4053 auto mapAttr = (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName());
4054 if (mapAttr) {
4055 AffineMap map = mapAttr.getValue();
4056 if (map.getNumResults() != getMemRefType().getRank())
4057 return emitOpError("affine.prefetch affine map num results must equal"
4058 " memref rank");
4059 if (map.getNumInputs() + 1 != getNumOperands())
4060 return emitOpError("too few operands");
4061 } else {
4062 if (getNumOperands() != 1)
4063 return emitOpError("too few operands");
4064 }
4065
4066 Region *scope = getAffineScope(*this);
4067 for (auto idx : getMapOperands()) {
4068 if (!isValidAffineIndexOperand(idx, scope))
4069 return emitOpError(
4070 "index must be a valid dimension or symbol identifier");
4071 }
4072 return success();
4073}
4074
4075void AffinePrefetchOp::getCanonicalizationPatterns(RewritePatternSet &results,
4076 MLIRContext *context) {
4077 // prefetch(memrefcast) -> prefetch
4078 results.add<SimplifyAffineOp<AffinePrefetchOp>>(context);
4079}
4080
4081LogicalResult AffinePrefetchOp::fold(FoldAdaptor adaptor,
4083 /// prefetch(memrefcast) -> prefetch
4084 return memref::foldMemRefCast(*this);
4085}
4086
4087//===----------------------------------------------------------------------===//
4088// AffineParallelOp
4089//===----------------------------------------------------------------------===//
4090
4091void AffineParallelOp::build(OpBuilder &builder, OperationState &result,
4092 TypeRange resultTypes,
4094 ArrayRef<int64_t> ranges) {
4095 SmallVector<AffineMap> lbs(ranges.size(), builder.getConstantAffineMap(0));
4096 auto ubs = llvm::map_to_vector<4>(ranges, [&](int64_t value) {
4097 return builder.getConstantAffineMap(value);
4098 });
4099 SmallVector<int64_t> steps(ranges.size(), 1);
4100 build(builder, result, resultTypes, reductions, lbs, /*lbArgs=*/{}, ubs,
4101 /*ubArgs=*/{}, steps);
4102}
4103
4104void AffineParallelOp::build(OpBuilder &builder, OperationState &result,
4105 TypeRange resultTypes,
4107 ArrayRef<AffineMap> lbMaps, ValueRange lbArgs,
4108 ArrayRef<AffineMap> ubMaps, ValueRange ubArgs,
4109 ArrayRef<int64_t> steps) {
4110 assert(llvm::all_of(lbMaps,
4111 [lbMaps](AffineMap m) {
4112 return m.getNumDims() == lbMaps[0].getNumDims() &&
4113 m.getNumSymbols() == lbMaps[0].getNumSymbols();
4114 }) &&
4115 "expected all lower bounds maps to have the same number of dimensions "
4116 "and symbols");
4117 assert(llvm::all_of(ubMaps,
4118 [ubMaps](AffineMap m) {
4119 return m.getNumDims() == ubMaps[0].getNumDims() &&
4120 m.getNumSymbols() == ubMaps[0].getNumSymbols();
4121 }) &&
4122 "expected all upper bounds maps to have the same number of dimensions "
4123 "and symbols");
4124 assert((lbMaps.empty() || lbMaps[0].getNumInputs() == lbArgs.size()) &&
4125 "expected lower bound maps to have as many inputs as lower bound "
4126 "operands");
4127 assert((ubMaps.empty() || ubMaps[0].getNumInputs() == ubArgs.size()) &&
4128 "expected upper bound maps to have as many inputs as upper bound "
4129 "operands");
4130
4131 OpBuilder::InsertionGuard guard(builder);
4132 result.addTypes(resultTypes);
4133
4134 // Convert the reductions to integer attributes.
4135 SmallVector<Attribute, 4> reductionAttrs;
4136 for (arith::AtomicRMWKind reduction : reductions)
4137 reductionAttrs.push_back(
4138 builder.getI64IntegerAttr(static_cast<int64_t>(reduction)));
4139 result.addAttribute(getReductionsAttrStrName(),
4140 builder.getArrayAttr(reductionAttrs));
4141
4142 // Concatenates maps defined in the same input space (same dimensions and
4143 // symbols), assumes there is at least one map.
4144 auto concatMapsSameInput = [&builder](ArrayRef<AffineMap> maps,
4145 SmallVectorImpl<int32_t> &groups) {
4146 if (maps.empty())
4147 return AffineMap::get(builder.getContext());
4149 groups.reserve(groups.size() + maps.size());
4150 exprs.reserve(maps.size());
4151 for (AffineMap m : maps) {
4152 llvm::append_range(exprs, m.getResults());
4153 groups.push_back(m.getNumResults());
4154 }
4155 return AffineMap::get(maps[0].getNumDims(), maps[0].getNumSymbols(), exprs,
4156 maps[0].getContext());
4157 };
4158
4159 // Set up the bounds.
4160 SmallVector<int32_t> lbGroups, ubGroups;
4161 AffineMap lbMap = concatMapsSameInput(lbMaps, lbGroups);
4162 AffineMap ubMap = concatMapsSameInput(ubMaps, ubGroups);
4163 result.addAttribute(getLowerBoundsMapAttrStrName(),
4164 AffineMapAttr::get(lbMap));
4165 result.addAttribute(getLowerBoundsGroupsAttrStrName(),
4166 builder.getI32TensorAttr(lbGroups));
4167 result.addAttribute(getUpperBoundsMapAttrStrName(),
4168 AffineMapAttr::get(ubMap));
4169 result.addAttribute(getUpperBoundsGroupsAttrStrName(),
4170 builder.getI32TensorAttr(ubGroups));
4171 result.addAttribute(getStepsAttrStrName(), builder.getI64ArrayAttr(steps));
4172 result.addOperands(lbArgs);
4173 result.addOperands(ubArgs);
4174
4175 // Create a region and a block for the body.
4176 auto *bodyRegion = result.addRegion();
4177 Block *body = builder.createBlock(bodyRegion);
4178
4179 // Add all the block arguments.
4180 for (unsigned i = 0, e = steps.size(); i < e; ++i)
4181 body->addArgument(IndexType::get(builder.getContext()), result.location);
4182 if (resultTypes.empty())
4183 ensureTerminator(*bodyRegion, builder, result.location);
4184}
4185
4186SmallVector<Region *> AffineParallelOp::getLoopRegions() {
4187 return {&getRegion()};
4188}
4189
4190unsigned AffineParallelOp::getNumDims() { return getSteps().size(); }
4191
4192AffineParallelOp::operand_range AffineParallelOp::getLowerBoundsOperands() {
4193 return getOperands().take_front(getLowerBoundsMap().getNumInputs());
4194}
4195
4196AffineParallelOp::operand_range AffineParallelOp::getUpperBoundsOperands() {
4197 return getOperands().drop_front(getLowerBoundsMap().getNumInputs());
4198}
4199
4200AffineMap AffineParallelOp::getLowerBoundMap(unsigned pos) {
4201 auto values = getLowerBoundsGroups().getValues<int32_t>();
4202 unsigned start = 0;
4203 for (unsigned i = 0; i < pos; ++i)
4204 start += values[i];
4205 return getLowerBoundsMap().getSliceMap(start, values[pos]);
4206}
4207
4208AffineMap AffineParallelOp::getUpperBoundMap(unsigned pos) {
4209 auto values = getUpperBoundsGroups().getValues<int32_t>();
4210 unsigned start = 0;
4211 for (unsigned i = 0; i < pos; ++i)
4212 start += values[i];
4213 return getUpperBoundsMap().getSliceMap(start, values[pos]);
4214}
4215
4216AffineValueMap AffineParallelOp::getLowerBoundsValueMap() {
4217 return AffineValueMap(getLowerBoundsMap(), getLowerBoundsOperands());
4218}
4219
4220AffineValueMap AffineParallelOp::getUpperBoundsValueMap() {
4221 return AffineValueMap(getUpperBoundsMap(), getUpperBoundsOperands());
4222}
4223
4224std::optional<SmallVector<int64_t, 8>> AffineParallelOp::getConstantRanges() {
4225 if (hasMinMaxBounds())
4226 return std::nullopt;
4227
4228 // Try to convert all the ranges to constant expressions.
4230 AffineValueMap rangesValueMap;
4231 AffineValueMap::difference(getUpperBoundsValueMap(), getLowerBoundsValueMap(),
4232 &rangesValueMap);
4233 out.reserve(rangesValueMap.getNumResults());
4234 for (unsigned i = 0, e = rangesValueMap.getNumResults(); i < e; ++i) {
4235 auto expr = rangesValueMap.getResult(i);
4236 auto cst = dyn_cast<AffineConstantExpr>(expr);
4237 if (!cst)
4238 return std::nullopt;
4239 out.push_back(cst.getValue());
4240 }
4241 return out;
4242}
4243
4244Block *AffineParallelOp::getBody() { return &getRegion().front(); }
4245
4246OpBuilder AffineParallelOp::getBodyBuilder() {
4247 return OpBuilder(getBody(), std::prev(getBody()->end()));
4248}
4249
4250void AffineParallelOp::setLowerBounds(ValueRange lbOperands, AffineMap map) {
4251 assert(lbOperands.size() == map.getNumInputs() &&
4252 "operands to map must match number of inputs");
4253
4254 auto ubOperands = getUpperBoundsOperands();
4255
4256 SmallVector<Value, 4> newOperands(lbOperands);
4257 newOperands.append(ubOperands.begin(), ubOperands.end());
4258 (*this)->setOperands(newOperands);
4259
4260 setLowerBoundsMapAttr(AffineMapAttr::get(map));
4261}
4262
4263void AffineParallelOp::setUpperBounds(ValueRange ubOperands, AffineMap map) {
4264 assert(ubOperands.size() == map.getNumInputs() &&
4265 "operands to map must match number of inputs");
4266
4267 SmallVector<Value, 4> newOperands(getLowerBoundsOperands());
4268 newOperands.append(ubOperands.begin(), ubOperands.end());
4269 (*this)->setOperands(newOperands);
4270
4271 setUpperBoundsMapAttr(AffineMapAttr::get(map));
4272}
4273
4274void AffineParallelOp::setSteps(ArrayRef<int64_t> newSteps) {
4275 setStepsAttr(getBodyBuilder().getI64ArrayAttr(newSteps));
4276}
4277
4278// check whether resultType match op or not in affine.parallel
4280 arith::AtomicRMWKind op) {
4281 switch (op) {
4282 case arith::AtomicRMWKind::addf:
4283 return isa<FloatType>(resultType);
4284 case arith::AtomicRMWKind::addi:
4285 return isa<IntegerType>(resultType);
4286 case arith::AtomicRMWKind::assign:
4287 return true;
4288 case arith::AtomicRMWKind::mulf:
4289 return isa<FloatType>(resultType);
4290 case arith::AtomicRMWKind::muli:
4291 return isa<IntegerType>(resultType);
4292 case arith::AtomicRMWKind::maximumf:
4293 case arith::AtomicRMWKind::maxnumf:
4294 case arith::AtomicRMWKind::minimumf:
4295 case arith::AtomicRMWKind::minnumf:
4296 return isa<FloatType>(resultType);
4297 case arith::AtomicRMWKind::maxs: {
4298 auto intType = dyn_cast<IntegerType>(resultType);
4299 return intType && intType.isSigned();
4300 }
4301 case arith::AtomicRMWKind::mins: {
4302 auto intType = dyn_cast<IntegerType>(resultType);
4303 return intType && intType.isSigned();
4304 }
4305 case arith::AtomicRMWKind::maxu: {
4306 auto intType = dyn_cast<IntegerType>(resultType);
4307 return intType && intType.isUnsigned();
4308 }
4309 case arith::AtomicRMWKind::minu: {
4310 auto intType = dyn_cast<IntegerType>(resultType);
4311 return intType && intType.isUnsigned();
4312 }
4313 case arith::AtomicRMWKind::ori:
4314 case arith::AtomicRMWKind::andi:
4315 case arith::AtomicRMWKind::xori:
4316 return isa<IntegerType>(resultType);
4317 }
4318 llvm_unreachable("Unhandled atomic rmw kind");
4319}
4320
4321LogicalResult AffineParallelOp::verify() {
4322 auto numDims = getNumDims();
4323 if (getLowerBoundsGroups().getNumElements() != numDims ||
4324 getUpperBoundsGroups().getNumElements() != numDims ||
4325 getSteps().size() != numDims || getBody()->getNumArguments() != numDims) {
4326 return emitOpError() << "the number of region arguments ("
4327 << getBody()->getNumArguments()
4328 << ") and the number of map groups for lower ("
4329 << getLowerBoundsGroups().getNumElements()
4330 << ") and upper bound ("
4331 << getUpperBoundsGroups().getNumElements()
4332 << "), and the number of steps (" << getSteps().size()
4333 << ") must all match";
4334 }
4335
4336 unsigned expectedNumLBResults = 0;
4337 for (APInt v : getLowerBoundsGroups()) {
4338 unsigned results = v.getZExtValue();
4339 if (results == 0)
4340 return emitOpError()
4341 << "expected lower bound map to have at least one result";
4342 expectedNumLBResults += results;
4343 }
4344 if (expectedNumLBResults != getLowerBoundsMap().getNumResults())
4345 return emitOpError() << "expected lower bounds map to have "
4346 << expectedNumLBResults << " results";
4347 unsigned expectedNumUBResults = 0;
4348 for (APInt v : getUpperBoundsGroups()) {
4349 unsigned results = v.getZExtValue();
4350 if (results == 0)
4351 return emitOpError()
4352 << "expected upper bound map to have at least one result";
4353 expectedNumUBResults += results;
4354 }
4355 if (expectedNumUBResults != getUpperBoundsMap().getNumResults())
4356 return emitOpError() << "expected upper bounds map to have "
4357 << expectedNumUBResults << " results";
4358
4359 if (getReductions().size() != getNumResults())
4360 return emitOpError("a reduction must be specified for each output");
4361
4362 // Verify reduction ops are all valid and each result type matches reduction
4363 // ops
4364 for (auto it : llvm::enumerate((getReductions()))) {
4365 Attribute attr = it.value();
4366 auto intAttr = dyn_cast<IntegerAttr>(attr);
4367 if (!intAttr || !arith::symbolizeAtomicRMWKind(intAttr.getInt()))
4368 return emitOpError("invalid reduction attribute");
4369 auto kind = arith::symbolizeAtomicRMWKind(intAttr.getInt()).value();
4370 if (!isResultTypeMatchAtomicRMWKind(getResult(it.index()).getType(), kind))
4371 return emitOpError("result type cannot match reduction attribute");
4372 }
4373
4374 // Verify that the bound operands are valid dimension/symbols.
4375 /// Lower bounds.
4376 if (failed(verifyDimAndSymbolIdentifiers(*this, getLowerBoundsOperands(),
4377 getLowerBoundsMap().getNumDims())))
4378 return failure();
4379 /// Upper bounds.
4380 if (failed(verifyDimAndSymbolIdentifiers(*this, getUpperBoundsOperands(),
4381 getUpperBoundsMap().getNumDims())))
4382 return failure();
4383 return success();
4384}
4385
4387 SmallVector<Value, 4> newOperands{operands};
4388 auto newMap = getAffineMap();
4389 composeAffineMapAndOperands(&newMap, &newOperands);
4390 if (newMap == getAffineMap() && newOperands == operands)
4391 return failure();
4392 reset(newMap, newOperands);
4393 return success();
4394}
4395
4396/// Canonicalize the bounds of the given loop.
4397static LogicalResult canonicalizeLoopBounds(AffineParallelOp op) {
4398 AffineValueMap lb = op.getLowerBoundsValueMap();
4399 bool lbCanonicalized = succeeded(lb.canonicalize());
4400
4401 AffineValueMap ub = op.getUpperBoundsValueMap();
4402 bool ubCanonicalized = succeeded(ub.canonicalize());
4403
4404 // Any canonicalization change always leads to updated map(s).
4405 if (!lbCanonicalized && !ubCanonicalized)
4406 return failure();
4407
4408 if (lbCanonicalized)
4409 op.setLowerBounds(lb.getOperands(), lb.getAffineMap());
4410 if (ubCanonicalized)
4411 op.setUpperBounds(ub.getOperands(), ub.getAffineMap());
4412
4413 return success();
4414}
4415
4416LogicalResult AffineParallelOp::fold(FoldAdaptor adaptor,
4417 SmallVectorImpl<OpFoldResult> &results) {
4418 return canonicalizeLoopBounds(*this);
4419}
4420
4421/// Prints a lower(upper) bound of an affine parallel loop with max(min)
4422/// conditions in it. `mapAttr` is a flat list of affine expressions and `group`
4423/// identifies which of the those expressions form max/min groups. `operands`
4424/// are the SSA values of dimensions and symbols and `keyword` is either "min"
4425/// or "max".
4426static void printMinMaxBound(OpAsmPrinter &p, AffineMapAttr mapAttr,
4427 DenseIntElementsAttr group, ValueRange operands,
4428 StringRef keyword) {
4429 AffineMap map = mapAttr.getValue();
4430 unsigned numDims = map.getNumDims();
4431 ValueRange dimOperands = operands.take_front(numDims);
4432 ValueRange symOperands = operands.drop_front(numDims);
4433 unsigned start = 0;
4434 for (llvm::APInt groupSize : group) {
4435 if (start != 0)
4436 p << ", ";
4437
4438 unsigned size = groupSize.getZExtValue();
4439 if (size == 1) {
4440 p.printAffineExprOfSSAIds(map.getResult(start), dimOperands, symOperands);
4441 ++start;
4442 } else {
4443 p << keyword << '(';
4444 AffineMap submap = map.getSliceMap(start, size);
4445 p.printAffineMapOfSSAIds(AffineMapAttr::get(submap), operands);
4446 p << ')';
4447 start += size;
4448 }
4449 }
4450}
4451
4452void AffineParallelOp::print(OpAsmPrinter &p) {
4453 p << " (" << getBody()->getArguments() << ") = (";
4454 printMinMaxBound(p, getLowerBoundsMapAttr(), getLowerBoundsGroupsAttr(),
4455 getLowerBoundsOperands(), "max");
4456 p << ") to (";
4457 printMinMaxBound(p, getUpperBoundsMapAttr(), getUpperBoundsGroupsAttr(),
4458 getUpperBoundsOperands(), "min");
4459 p << ')';
4460 SmallVector<int64_t, 8> steps = getSteps();
4461 bool elideSteps = llvm::all_of(steps, [](int64_t step) { return step == 1; });
4462 if (!elideSteps) {
4463 p << " step (";
4464 llvm::interleaveComma(steps, p);
4465 p << ')';
4466 }
4467 if (getNumResults()) {
4468 p << " reduce (";
4469 llvm::interleaveComma(getReductions(), p, [&](auto &attr) {
4470 arith::AtomicRMWKind sym = *arith::symbolizeAtomicRMWKind(
4471 llvm::cast<IntegerAttr>(attr).getInt());
4472 p << "\"" << arith::stringifyAtomicRMWKind(sym) << "\"";
4473 });
4474 p << ") -> (" << getResultTypes() << ")";
4475 }
4476
4477 p << ' ';
4478 p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,
4479 /*printBlockTerminators=*/getNumResults());
4481 (*this)->getAttrs(),
4482 /*elidedAttrs=*/{AffineParallelOp::getReductionsAttrStrName(),
4483 AffineParallelOp::getLowerBoundsMapAttrStrName(),
4484 AffineParallelOp::getLowerBoundsGroupsAttrStrName(),
4485 AffineParallelOp::getUpperBoundsMapAttrStrName(),
4486 AffineParallelOp::getUpperBoundsGroupsAttrStrName(),
4487 AffineParallelOp::getStepsAttrStrName()});
4488}
4489
4490/// Given a list of lists of parsed operands, populates `uniqueOperands` with
4491/// unique operands. Also populates `replacements with affine expressions of
4492/// `kind` that can be used to update affine maps previously accepting a
4493/// `operands` to accept `uniqueOperands` instead.
4494static ParseResult deduplicateAndResolveOperands(
4495 OpAsmParser &parser,
4496 ArrayRef<SmallVector<OpAsmParser::UnresolvedOperand>> operands,
4497 SmallVectorImpl<Value> &uniqueOperands,
4498 SmallVectorImpl<AffineExpr> &replacements, AffineExprKind kind) {
4499 assert((kind == AffineExprKind::DimId || kind == AffineExprKind::SymbolId) &&
4500 "expected operands to be dim or symbol expression");
4501
4502 Type indexType = parser.getBuilder().getIndexType();
4503 for (const auto &list : operands) {
4504 SmallVector<Value> valueOperands;
4505 if (parser.resolveOperands(list, indexType, valueOperands))
4506 return failure();
4507 for (Value operand : valueOperands) {
4508 unsigned pos = std::distance(uniqueOperands.begin(),
4509 llvm::find(uniqueOperands, operand));
4510 if (pos == uniqueOperands.size())
4511 uniqueOperands.push_back(operand);
4512 replacements.push_back(
4513 kind == AffineExprKind::DimId
4514 ? getAffineDimExpr(pos, parser.getContext())
4515 : getAffineSymbolExpr(pos, parser.getContext()));
4516 }
4517 }
4518 return success();
4519}
4520
4521namespace {
4522enum class MinMaxKind { Min, Max };
4523} // namespace
4524
4525/// Parses an affine map that can contain a min/max for groups of its results,
4526/// e.g., max(expr-1, expr-2), expr-3, max(expr-4, expr-5, expr-6). Populates
4527/// `result` attributes with the map (flat list of expressions) and the grouping
4528/// (list of integers that specify how many expressions to put into each
4529/// min/max) attributes. Deduplicates repeated operands.
4530///
4531/// parallel-bound ::= `(` parallel-group-list `)`
4532/// parallel-group-list ::= parallel-group (`,` parallel-group-list)?
4533/// parallel-group ::= simple-group | min-max-group
4534/// simple-group ::= expr-of-ssa-ids
4535/// min-max-group ::= ( `min` | `max` ) `(` expr-of-ssa-ids-list `)`
4536/// expr-of-ssa-ids-list ::= expr-of-ssa-ids (`,` expr-of-ssa-id-list)?
4537///
4538/// Examples:
4539/// (%0, min(%1 + %2, %3), %4, min(%5 floordiv 32, %6))
4540/// (%0, max(%1 - 2 * %2))
4541static ParseResult parseAffineMapWithMinMax(OpAsmParser &parser,
4542 OperationState &result,
4543 MinMaxKind kind) {
4544 // Using `const` not `constexpr` below to workaround a MSVC optimizer bug,
4545 // see: https://reviews.llvm.org/D134227#3821753
4546 const llvm::StringLiteral tmpAttrStrName = "__pseudo_bound_map";
4547
4548 StringRef mapName = kind == MinMaxKind::Min
4549 ? AffineParallelOp::getUpperBoundsMapAttrStrName()
4550 : AffineParallelOp::getLowerBoundsMapAttrStrName();
4551 StringRef groupsName =
4552 kind == MinMaxKind::Min
4553 ? AffineParallelOp::getUpperBoundsGroupsAttrStrName()
4554 : AffineParallelOp::getLowerBoundsGroupsAttrStrName();
4555
4556 if (failed(parser.parseLParen()))
4557 return failure();
4558
4559 if (succeeded(parser.parseOptionalRParen())) {
4560 result.addAttribute(
4561 mapName, AffineMapAttr::get(parser.getBuilder().getEmptyAffineMap()));
4562 result.addAttribute(groupsName, parser.getBuilder().getI32TensorAttr({}));
4563 return success();
4564 }
4565
4566 SmallVector<AffineExpr> flatExprs;
4567 SmallVector<SmallVector<OpAsmParser::UnresolvedOperand>> flatDimOperands;
4568 SmallVector<SmallVector<OpAsmParser::UnresolvedOperand>> flatSymOperands;
4569 SmallVector<int32_t> numMapsPerGroup;
4570 SmallVector<OpAsmParser::UnresolvedOperand> mapOperands;
4571 auto parseOperands = [&]() {
4572 if (succeeded(parser.parseOptionalKeyword(
4573 kind == MinMaxKind::Min ? "min" : "max"))) {
4574 mapOperands.clear();
4575 AffineMapAttr map;
4576 if (failed(parser.parseAffineMapOfSSAIds(mapOperands, map, tmpAttrStrName,
4577 result.attributes,
4579 return failure();
4580 result.attributes.erase(tmpAttrStrName);
4581 llvm::append_range(flatExprs, map.getValue().getResults());
4582 auto operandsRef = llvm::ArrayRef(mapOperands);
4583 auto dimsRef = operandsRef.take_front(map.getValue().getNumDims());
4584 SmallVector<OpAsmParser::UnresolvedOperand> dims(dimsRef);
4585 auto symsRef = operandsRef.drop_front(map.getValue().getNumDims());
4586 SmallVector<OpAsmParser::UnresolvedOperand> syms(symsRef);
4587 flatDimOperands.append(map.getValue().getNumResults(), dims);
4588 flatSymOperands.append(map.getValue().getNumResults(), syms);
4589 numMapsPerGroup.push_back(map.getValue().getNumResults());
4590 } else {
4591 if (failed(parser.parseAffineExprOfSSAIds(flatDimOperands.emplace_back(),
4592 flatSymOperands.emplace_back(),
4593 flatExprs.emplace_back())))
4594 return failure();
4595 numMapsPerGroup.push_back(1);
4596 }
4597 return success();
4598 };
4599 if (parser.parseCommaSeparatedList(parseOperands) || parser.parseRParen())
4600 return failure();
4601
4602 unsigned totalNumDims = 0;
4603 unsigned totalNumSyms = 0;
4604 for (unsigned i = 0, e = flatExprs.size(); i < e; ++i) {
4605 unsigned numDims = flatDimOperands[i].size();
4606 unsigned numSyms = flatSymOperands[i].size();
4607 flatExprs[i] = flatExprs[i]
4608 .shiftDims(numDims, totalNumDims)
4609 .shiftSymbols(numSyms, totalNumSyms);
4610 totalNumDims += numDims;
4611 totalNumSyms += numSyms;
4612 }
4613
4614 // Deduplicate map operands.
4615 SmallVector<Value> dimOperands, symOperands;
4616 SmallVector<AffineExpr> dimRplacements, symRepacements;
4617 if (deduplicateAndResolveOperands(parser, flatDimOperands, dimOperands,
4618 dimRplacements, AffineExprKind::DimId) ||
4619 deduplicateAndResolveOperands(parser, flatSymOperands, symOperands,
4620 symRepacements, AffineExprKind::SymbolId))
4621 return failure();
4622
4623 result.operands.append(dimOperands.begin(), dimOperands.end());
4624 result.operands.append(symOperands.begin(), symOperands.end());
4625
4626 Builder &builder = parser.getBuilder();
4627 auto flatMap = AffineMap::get(totalNumDims, totalNumSyms, flatExprs,
4628 parser.getContext());
4629 flatMap = flatMap.replaceDimsAndSymbols(
4630 dimRplacements, symRepacements, dimOperands.size(), symOperands.size());
4631
4632 result.addAttribute(mapName, AffineMapAttr::get(flatMap));
4633 result.addAttribute(groupsName, builder.getI32TensorAttr(numMapsPerGroup));
4634 return success();
4635}
4636
4637//
4638// operation ::= `affine.parallel` `(` ssa-ids `)` `=` parallel-bound
4639// `to` parallel-bound steps? region attr-dict?
4640// steps ::= `steps` `(` integer-literals `)`
4641//
4642ParseResult AffineParallelOp::parse(OpAsmParser &parser,
4643 OperationState &result) {
4644 auto &builder = parser.getBuilder();
4645 auto indexType = builder.getIndexType();
4646 SmallVector<OpAsmParser::Argument, 4> ivs;
4648 parser.parseEqual() ||
4649 parseAffineMapWithMinMax(parser, result, MinMaxKind::Max) ||
4650 parser.parseKeyword("to") ||
4651 parseAffineMapWithMinMax(parser, result, MinMaxKind::Min))
4652 return failure();
4653
4654 AffineMapAttr stepsMapAttr;
4655 NamedAttrList stepsAttrs;
4656 SmallVector<OpAsmParser::UnresolvedOperand, 4> stepsMapOperands;
4657 if (failed(parser.parseOptionalKeyword("step"))) {
4658 SmallVector<int64_t, 4> steps(ivs.size(), 1);
4659 result.addAttribute(AffineParallelOp::getStepsAttrStrName(),
4660 builder.getI64ArrayAttr(steps));
4661 } else {
4662 if (parser.parseAffineMapOfSSAIds(stepsMapOperands, stepsMapAttr,
4663 AffineParallelOp::getStepsAttrStrName(),
4664 stepsAttrs,
4666 return failure();
4667
4668 // Convert steps from an AffineMap into an I64ArrayAttr.
4669 SmallVector<int64_t, 4> steps;
4670 auto stepsMap = stepsMapAttr.getValue();
4671 for (const auto &result : stepsMap.getResults()) {
4672 auto constExpr = dyn_cast<AffineConstantExpr>(result);
4673 if (!constExpr)
4674 return parser.emitError(parser.getNameLoc(),
4675 "steps must be constant integers");
4676 steps.push_back(constExpr.getValue());
4677 }
4678 result.addAttribute(AffineParallelOp::getStepsAttrStrName(),
4679 builder.getI64ArrayAttr(steps));
4680 }
4681
4682 // Parse optional clause of the form: `reduce ("addf", "maxf")`, where the
4683 // quoted strings are a member of the enum AtomicRMWKind.
4684 SmallVector<Attribute, 4> reductions;
4685 if (succeeded(parser.parseOptionalKeyword("reduce"))) {
4686 if (parser.parseLParen())
4687 return failure();
4688 auto parseAttributes = [&]() -> ParseResult {
4689 // Parse a single quoted string via the attribute parsing, and then
4690 // verify it is a member of the enum and convert to it's integer
4691 // representation.
4692 StringAttr attrVal;
4693 NamedAttrList attrStorage;
4694 auto loc = parser.getCurrentLocation();
4695 if (parser.parseAttribute(attrVal, builder.getNoneType(), "reduce",
4696 attrStorage))
4697 return failure();
4698 std::optional<arith::AtomicRMWKind> reduction =
4699 arith::symbolizeAtomicRMWKind(attrVal.getValue());
4700 if (!reduction)
4701 return parser.emitError(loc, "invalid reduction value: ") << attrVal;
4702 reductions.push_back(
4703 builder.getI64IntegerAttr(static_cast<int64_t>(reduction.value())));
4704 // While we keep getting commas, keep parsing.
4705 return success();
4706 };
4707 if (parser.parseCommaSeparatedList(parseAttributes) || parser.parseRParen())
4708 return failure();
4709 }
4710 result.addAttribute(AffineParallelOp::getReductionsAttrStrName(),
4711 builder.getArrayAttr(reductions));
4712
4713 // Parse return types of reductions (if any)
4714 if (parser.parseOptionalArrowTypeList(result.types))
4715 return failure();
4716
4717 // Now parse the body.
4718 Region *body = result.addRegion();
4719 for (auto &iv : ivs)
4720 iv.type = indexType;
4721 if (parser.parseRegion(*body, ivs) ||
4722 parser.parseOptionalAttrDict(result.attributes))
4723 return failure();
4724
4725 // Add a terminator if none was parsed.
4726 AffineParallelOp::ensureTerminator(*body, builder, result.location);
4727 return success();
4728}
4729
4730//===----------------------------------------------------------------------===//
4731// AffineYieldOp
4732//===----------------------------------------------------------------------===//
4733
4734LogicalResult AffineYieldOp::verify() {
4735 auto *parentOp = (*this)->getParentOp();
4736 auto results = parentOp->getResults();
4737 auto operands = getOperands();
4738
4739 if (!isa<AffineParallelOp, AffineIfOp, AffineForOp>(parentOp))
4740 return emitOpError() << "only terminates affine.if/for/parallel regions";
4741 if (parentOp->getNumResults() != getNumOperands())
4742 return emitOpError() << "parent of yield must have same number of "
4743 "results as the yield operands";
4744 for (auto it : llvm::zip(results, operands)) {
4745 if (std::get<0>(it).getType() != std::get<1>(it).getType())
4746 return emitOpError() << "types mismatch between yield op and its parent";
4747 }
4748
4749 return success();
4750}
4751
4752//===----------------------------------------------------------------------===//
4753// AffineVectorLoadOp
4754//===----------------------------------------------------------------------===//
4755
4756void AffineVectorLoadOp::build(OpBuilder &builder, OperationState &result,
4757 VectorType resultType, AffineMap map,
4758 ValueRange operands,
4759 llvm::MaybeAlign alignment) {
4760 assert(operands.size() == 1 + map.getNumInputs() && "inconsistent operands");
4761 result.addOperands(operands);
4762 if (map)
4763 result.addAttribute(getMapAttrStrName(), AffineMapAttr::get(map));
4764 addAlignmentAttr(builder, result, getAlignmentAttrName(result.name),
4765 alignment);
4766 result.types.push_back(resultType);
4767}
4768
4769void AffineVectorLoadOp::build(OpBuilder &builder, OperationState &result,
4770 VectorType resultType, Value memref,
4771 AffineMap map, ValueRange mapOperands,
4772 llvm::MaybeAlign alignment) {
4773 assert(map.getNumInputs() == mapOperands.size() && "inconsistent index info");
4774 result.addOperands(memref);
4775 result.addOperands(mapOperands);
4776 result.addAttribute(getMapAttrStrName(), AffineMapAttr::get(map));
4777 addAlignmentAttr(builder, result, getAlignmentAttrName(result.name),
4778 alignment);
4779 result.types.push_back(resultType);
4780}
4781
4782void AffineVectorLoadOp::build(OpBuilder &builder, OperationState &result,
4783 VectorType resultType, Value memref,
4784 ValueRange indices, llvm::MaybeAlign alignment) {
4785 auto memrefType = llvm::cast<MemRefType>(memref.getType());
4786 int64_t rank = memrefType.getRank();
4787 // Create identity map for memrefs with at least one dimension or () -> ()
4788 // for zero-dimensional memrefs.
4789 auto map =
4790 rank ? builder.getMultiDimIdentityMap(rank) : builder.getEmptyAffineMap();
4791 build(builder, result, resultType, memref, map, indices, alignment);
4792}
4793
4794void AffineVectorLoadOp::getCanonicalizationPatterns(RewritePatternSet &results,
4795 MLIRContext *context) {
4796 results.add<SimplifyAffineOp<AffineVectorLoadOp>>(context);
4797}
4798
4799ParseResult AffineVectorLoadOp::parse(OpAsmParser &parser,
4800 OperationState &result) {
4801 auto &builder = parser.getBuilder();
4802 auto indexTy = builder.getIndexType();
4803
4804 MemRefType memrefType;
4805 VectorType resultType;
4806 OpAsmParser::UnresolvedOperand memrefInfo;
4807 AffineMapAttr mapAttr;
4808 SmallVector<OpAsmParser::UnresolvedOperand, 1> mapOperands;
4809 return failure(
4810 parser.parseOperand(memrefInfo) ||
4811 parser.parseAffineMapOfSSAIds(mapOperands, mapAttr,
4812 AffineVectorLoadOp::getMapAttrStrName(),
4813 result.attributes) ||
4814 parser.parseOptionalAttrDict(result.attributes) ||
4815 parser.parseColonType(memrefType) || parser.parseComma() ||
4816 parser.parseType(resultType) ||
4817 parser.resolveOperand(memrefInfo, memrefType, result.operands) ||
4818 parser.resolveOperands(mapOperands, indexTy, result.operands) ||
4819 parser.addTypeToList(resultType, result.types));
4820}
4821
4822void AffineVectorLoadOp::print(OpAsmPrinter &p) {
4823 p << " " << getMemRef() << '[';
4824 if (AffineMapAttr mapAttr =
4825 (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()))
4826 p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
4827 p << ']';
4828 p.printOptionalAttrDict((*this)->getAttrs(),
4829 /*elidedAttrs=*/{getMapAttrStrName()});
4830 p << " : " << getMemRefType() << ", " << getType();
4831}
4832
4833/// Verify common invariants of affine.vector_load and affine.vector_store.
4834static LogicalResult verifyVectorMemoryOp(Operation *op, MemRefType memrefType,
4835 VectorType vectorType) {
4836 // Check that memref and vector element types match.
4837 if (memrefType.getElementType() != vectorType.getElementType())
4838 return op->emitOpError(
4839 "requires memref and vector types of the same elemental type");
4840 return success();
4841}
4842
4843LogicalResult AffineVectorLoadOp::verify() {
4844 MemRefType memrefType = getMemRefType();
4846 *this, (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()),
4847 getMapOperands(), memrefType,
4848 /*numIndexOperands=*/getNumOperands() - 1)))
4849 return failure();
4850
4851 if (failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType())))
4852 return failure();
4853
4854 return success();
4855}
4856
4857//===----------------------------------------------------------------------===//
4858// AffineVectorStoreOp
4859//===----------------------------------------------------------------------===//
4860
4861void AffineVectorStoreOp::build(OpBuilder &builder, OperationState &result,
4862 Value valueToStore, Value memref, AffineMap map,
4863 ValueRange mapOperands,
4864 llvm::MaybeAlign alignment) {
4865 assert(map.getNumInputs() == mapOperands.size() && "inconsistent index info");
4866 result.addOperands(valueToStore);
4867 result.addOperands(memref);
4868 result.addOperands(mapOperands);
4869 result.addAttribute(getMapAttrStrName(), AffineMapAttr::get(map));
4870 addAlignmentAttr(builder, result, getAlignmentAttrName(result.name),
4871 alignment);
4872}
4873
4874// Use identity map.
4875void AffineVectorStoreOp::build(OpBuilder &builder, OperationState &result,
4876 Value valueToStore, Value memref,
4878 llvm::MaybeAlign alignment) {
4879 auto memrefType = llvm::cast<MemRefType>(memref.getType());
4880 int64_t rank = memrefType.getRank();
4881 // Create identity map for memrefs with at least one dimension or () -> ()
4882 // for zero-dimensional memrefs.
4883 auto map =
4884 rank ? builder.getMultiDimIdentityMap(rank) : builder.getEmptyAffineMap();
4885 build(builder, result, valueToStore, memref, map, indices, alignment);
4886}
4887void AffineVectorStoreOp::getCanonicalizationPatterns(
4888 RewritePatternSet &results, MLIRContext *context) {
4889 results.add<SimplifyAffineOp<AffineVectorStoreOp>>(context);
4890}
4891
4892ParseResult AffineVectorStoreOp::parse(OpAsmParser &parser,
4893 OperationState &result) {
4894 auto indexTy = parser.getBuilder().getIndexType();
4895
4896 MemRefType memrefType;
4897 VectorType resultType;
4898 OpAsmParser::UnresolvedOperand storeValueInfo;
4899 OpAsmParser::UnresolvedOperand memrefInfo;
4900 AffineMapAttr mapAttr;
4901 SmallVector<OpAsmParser::UnresolvedOperand, 1> mapOperands;
4902 return failure(
4903 parser.parseOperand(storeValueInfo) || parser.parseComma() ||
4904 parser.parseOperand(memrefInfo) ||
4905 parser.parseAffineMapOfSSAIds(mapOperands, mapAttr,
4906 AffineVectorStoreOp::getMapAttrStrName(),
4907 result.attributes) ||
4908 parser.parseOptionalAttrDict(result.attributes) ||
4909 parser.parseColonType(memrefType) || parser.parseComma() ||
4910 parser.parseType(resultType) ||
4911 parser.resolveOperand(storeValueInfo, resultType, result.operands) ||
4912 parser.resolveOperand(memrefInfo, memrefType, result.operands) ||
4913 parser.resolveOperands(mapOperands, indexTy, result.operands));
4914}
4915
4916void AffineVectorStoreOp::print(OpAsmPrinter &p) {
4917 p << " " << getValueToStore();
4918 p << ", " << getMemRef() << '[';
4919 if (AffineMapAttr mapAttr =
4920 (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()))
4921 p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
4922 p << ']';
4923 p.printOptionalAttrDict((*this)->getAttrs(),
4924 /*elidedAttrs=*/{getMapAttrStrName()});
4925 p << " : " << getMemRefType() << ", " << getValueToStore().getType();
4926}
4927
4928LogicalResult AffineVectorStoreOp::verify() {
4929 MemRefType memrefType = getMemRefType();
4931 *this, (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()),
4932 getMapOperands(), memrefType,
4933 /*numIndexOperands=*/getNumOperands() - 2)))
4934 return failure();
4935
4936 if (failed(verifyVectorMemoryOp(*this, memrefType, getVectorType())))
4937 return failure();
4938
4939 return success();
4940}
4941
4942//===----------------------------------------------------------------------===//
4943// DelinearizeIndexOp
4944//===----------------------------------------------------------------------===//
4945
4946void AffineDelinearizeIndexOp::build(OpBuilder &odsBuilder,
4947 OperationState &odsState,
4948 Value linearIndex, ValueRange dynamicBasis,
4949 ArrayRef<int64_t> staticBasis,
4950 bool hasOuterBound) {
4951 SmallVector<Type> returnTypes(hasOuterBound ? staticBasis.size()
4952 : staticBasis.size() + 1,
4953 linearIndex.getType());
4954 build(odsBuilder, odsState, returnTypes, linearIndex, dynamicBasis,
4955 staticBasis);
4956}
4957
4958void AffineDelinearizeIndexOp::build(OpBuilder &odsBuilder,
4959 OperationState &odsState,
4960 Value linearIndex, ValueRange basis,
4961 bool hasOuterBound) {
4962 if (hasOuterBound && !basis.empty() && basis.front() == nullptr) {
4963 hasOuterBound = false;
4964 basis = basis.drop_front();
4965 }
4966 SmallVector<Value> dynamicBasis;
4967 SmallVector<int64_t> staticBasis;
4968 dispatchIndexOpFoldResults(getAsOpFoldResult(basis), dynamicBasis,
4969 staticBasis);
4970 build(odsBuilder, odsState, linearIndex, dynamicBasis, staticBasis,
4971 hasOuterBound);
4972}
4973
4974void AffineDelinearizeIndexOp::build(OpBuilder &odsBuilder,
4975 OperationState &odsState,
4976 Value linearIndex,
4977 ArrayRef<OpFoldResult> basis,
4978 bool hasOuterBound) {
4979 if (hasOuterBound && !basis.empty() && basis.front() == OpFoldResult()) {
4980 hasOuterBound = false;
4981 basis = basis.drop_front();
4982 }
4983 SmallVector<Value> dynamicBasis;
4984 SmallVector<int64_t> staticBasis;
4985 dispatchIndexOpFoldResults(basis, dynamicBasis, staticBasis);
4986 build(odsBuilder, odsState, linearIndex, dynamicBasis, staticBasis,
4987 hasOuterBound);
4988}
4989
4990void AffineDelinearizeIndexOp::build(OpBuilder &odsBuilder,
4991 OperationState &odsState,
4992 Value linearIndex, ArrayRef<int64_t> basis,
4993 bool hasOuterBound) {
4994 build(odsBuilder, odsState, linearIndex, ValueRange{}, basis, hasOuterBound);
4995}
4996
4997LogicalResult AffineDelinearizeIndexOp::verify() {
4998 ArrayRef<int64_t> staticBasis = getStaticBasis();
4999 if (getNumResults() != staticBasis.size() &&
5000 getNumResults() != staticBasis.size() + 1)
5001 return emitOpError("should return an index for each basis element and up "
5002 "to one extra index");
5003
5004 auto dynamicMarkersCount = llvm::count_if(staticBasis, ShapedType::isDynamic);
5005 if (static_cast<size_t>(dynamicMarkersCount) != getDynamicBasis().size())
5006 return emitOpError(
5007 "mismatch between dynamic and static basis (kDynamic marker but no "
5008 "corresponding dynamic basis entry) -- this can only happen due to an "
5009 "incorrect fold/rewrite");
5010
5011 if (!llvm::all_of(staticBasis, [](int64_t v) {
5012 return v > 0 || ShapedType::isDynamic(v);
5013 }))
5014 return emitOpError("no basis element may be statically non-positive");
5015
5016 return success();
5017}
5018
5019/// Given mixed basis of affine.delinearize_index/linearize_index replace
5020/// constant SSA values with the constant integer value and return the new
5021/// static basis. In case no such candidate for replacement exists, this utility
5022/// returns std::nullopt.
5023static std::optional<SmallVector<int64_t>>
5025 MutableOperandRange mutableDynamicBasis,
5026 ArrayRef<Attribute> dynamicBasis) {
5027 uint64_t dynamicBasisIndex = 0;
5028 for (Attribute basis : dynamicBasis) {
5029 // Skip poison values: they don't have a concrete integer value, so erasing
5030 // them from the dynamic operands would create an inconsistency between
5031 // the static basis (which would still hold kDynamic) and the dynamic
5032 // operand list (which would be one element shorter).
5033 if (basis && isa<IntegerAttr>(basis)) {
5034 mutableDynamicBasis.erase(dynamicBasisIndex);
5035 } else {
5036 ++dynamicBasisIndex;
5037 }
5038 }
5039
5040 // No constant SSA value exists.
5041 if (dynamicBasisIndex == dynamicBasis.size())
5042 return std::nullopt;
5043
5044 SmallVector<int64_t> staticBasis;
5045 for (OpFoldResult basis : mixedBasis) {
5046 std::optional<int64_t> basisVal = getConstantIntValue(basis);
5047 if (!basisVal)
5048 staticBasis.push_back(ShapedType::kDynamic);
5049 else
5050 staticBasis.push_back(*basisVal);
5051 }
5052
5053 return staticBasis;
5054}
5055
5056LogicalResult
5057AffineDelinearizeIndexOp::fold(FoldAdaptor adaptor,
5058 SmallVectorImpl<OpFoldResult> &result) {
5059 std::optional<SmallVector<int64_t>> maybeStaticBasis =
5060 foldCstValueToCstAttrBasis(getMixedBasis(), getDynamicBasisMutable(),
5061 adaptor.getDynamicBasis());
5062 if (maybeStaticBasis) {
5063 setStaticBasis(*maybeStaticBasis);
5064 return success();
5065 }
5066 // If we won't be doing any division or modulo (no basis or the one basis
5067 // element is purely advisory), simply return the input value.
5068 if (getNumResults() == 1) {
5069 result.push_back(getLinearIndex());
5070 return success();
5071 }
5072
5073 if (adaptor.getLinearIndex() == nullptr)
5074 return failure();
5075
5076 if (!adaptor.getDynamicBasis().empty())
5077 return failure();
5078
5079 int64_t highPart = cast<IntegerAttr>(adaptor.getLinearIndex()).getInt();
5080 Type attrType = getLinearIndex().getType();
5081
5082 ArrayRef<int64_t> staticBasis = getStaticBasis();
5083 if (hasOuterBound())
5084 staticBasis = staticBasis.drop_front();
5085 for (int64_t modulus : llvm::reverse(staticBasis)) {
5086 result.push_back(IntegerAttr::get(attrType, llvm::mod(highPart, modulus)));
5087 highPart = llvm::divideFloorSigned(highPart, modulus);
5088 }
5089 result.push_back(IntegerAttr::get(attrType, highPart));
5090 std::reverse(result.begin(), result.end());
5091 return success();
5092}
5093
5094SmallVector<OpFoldResult> AffineDelinearizeIndexOp::getEffectiveBasis() {
5095 OpBuilder builder(getContext());
5096 if (hasOuterBound()) {
5097 if (getStaticBasis().front() == ::mlir::ShapedType::kDynamic)
5098 return getMixedValues(getStaticBasis().drop_front(),
5099 getDynamicBasis().drop_front(), builder);
5100
5101 return getMixedValues(getStaticBasis().drop_front(), getDynamicBasis(),
5102 builder);
5103 }
5104
5105 return getMixedValues(getStaticBasis(), getDynamicBasis(), builder);
5106}
5107
5108SmallVector<OpFoldResult> AffineDelinearizeIndexOp::getPaddedBasis() {
5109 SmallVector<OpFoldResult> ret = getMixedBasis();
5110 if (!hasOuterBound())
5111 ret.insert(ret.begin(), OpFoldResult());
5112 return ret;
5113}
5114
5115namespace {
5116
5117// Drops delinearization indices that correspond to unit-extent basis
5118struct DropUnitExtentBasis
5119 : public OpRewritePattern<affine::AffineDelinearizeIndexOp> {
5121
5122 LogicalResult matchAndRewrite(affine::AffineDelinearizeIndexOp delinearizeOp,
5123 PatternRewriter &rewriter) const override {
5124 SmallVector<Value> replacements(delinearizeOp->getNumResults(), nullptr);
5125 std::optional<Value> zero = std::nullopt;
5126 Location loc = delinearizeOp->getLoc();
5127 Type indexType = delinearizeOp.getLinearIndex().getType();
5128 auto getZero = [&]() -> Value {
5129 if (!zero)
5130 zero = arith::ConstantOp::create(rewriter, loc,
5131 rewriter.getZeroAttr(indexType));
5132 return zero.value();
5133 };
5134
5135 // Replace all indices corresponding to unit-extent basis with 0.
5136 // Remaining basis can be used to get a new `affine.delinearize_index` op.
5137 SmallVector<OpFoldResult> newBasis;
5138 for (auto [index, basis] :
5139 llvm::enumerate(delinearizeOp.getPaddedBasis())) {
5140 std::optional<int64_t> basisVal =
5141 basis ? getConstantIntValue(basis) : std::nullopt;
5142 if (basisVal == 1)
5143 replacements[index] = getZero();
5144 else
5145 newBasis.push_back(basis);
5146 }
5147
5148 if (newBasis.size() == delinearizeOp.getNumResults())
5149 return rewriter.notifyMatchFailure(delinearizeOp,
5150 "no unit basis elements");
5151
5152 if (!newBasis.empty()) {
5153 // Will drop the leading nullptr from `basis` if there was no outer bound.
5154 auto newDelinearizeOp = affine::AffineDelinearizeIndexOp::create(
5155 rewriter, loc, delinearizeOp.getLinearIndex(), newBasis);
5156 int newIndex = 0;
5157 // Map back the new delinearized indices to the values they replace.
5158 for (auto &replacement : replacements) {
5159 if (replacement)
5160 continue;
5161 replacement = newDelinearizeOp->getResult(newIndex++);
5162 }
5163 }
5164
5165 rewriter.replaceOp(delinearizeOp, replacements);
5166 return success();
5167 }
5168};
5169
5170/// If a `affine.delinearize_index`'s input is a `affine.linearize_index
5171/// disjoint` and the two operations end with the same basis elements,
5172/// cancel those parts of the operations out because they are inverses
5173/// of each other.
5174///
5175/// If the operations have the same basis, cancel them entirely.
5176///
5177/// The `disjoint` flag is needed on the `affine.linearize_index` because
5178/// otherwise, there is no guarantee that the inputs to the linearization are
5179/// in-bounds the way the outputs of the delinearization would be.
5180struct CancelDelinearizeOfLinearizeDisjointExactTail
5181 : public OpRewritePattern<affine::AffineDelinearizeIndexOp> {
5183
5184 LogicalResult matchAndRewrite(affine::AffineDelinearizeIndexOp delinearizeOp,
5185 PatternRewriter &rewriter) const override {
5186 auto linearizeOp = delinearizeOp.getLinearIndex()
5187 .getDefiningOp<affine::AffineLinearizeIndexOp>();
5188 if (!linearizeOp)
5189 return rewriter.notifyMatchFailure(delinearizeOp,
5190 "index doesn't come from linearize");
5191
5192 if (!linearizeOp.getDisjoint())
5193 return rewriter.notifyMatchFailure(linearizeOp, "not disjoint");
5194
5195 ValueRange linearizeIns = linearizeOp.getMultiIndex();
5196 // Note: we use the full basis so we don't lose outer bounds later.
5197 SmallVector<OpFoldResult> linearizeBasis = linearizeOp.getMixedBasis();
5198 SmallVector<OpFoldResult> delinearizeBasis = delinearizeOp.getMixedBasis();
5199 size_t numMatches = 0;
5200 for (auto [linSize, delinSize] : llvm::zip(
5201 llvm::reverse(linearizeBasis), llvm::reverse(delinearizeBasis))) {
5202 if (linSize != delinSize)
5203 break;
5204 ++numMatches;
5205 }
5206
5207 if (numMatches == 0)
5208 return rewriter.notifyMatchFailure(
5209 delinearizeOp, "final basis element doesn't match linearize");
5210
5211 // The easy case: everything lines up and the basis match sup completely.
5212 if (numMatches == linearizeBasis.size() &&
5213 numMatches == delinearizeBasis.size() &&
5214 linearizeIns.size() == delinearizeOp.getNumResults()) {
5215 rewriter.replaceOp(delinearizeOp, linearizeOp.getMultiIndex());
5216 return success();
5217 }
5218
5219 Value newLinearize = affine::AffineLinearizeIndexOp::create(
5220 rewriter, linearizeOp.getLoc(), linearizeIns.drop_back(numMatches),
5221 ArrayRef<OpFoldResult>{linearizeBasis}.drop_back(numMatches),
5222 linearizeOp.getDisjoint());
5223 auto newDelinearize = affine::AffineDelinearizeIndexOp::create(
5224 rewriter, delinearizeOp.getLoc(), newLinearize,
5225 ArrayRef<OpFoldResult>{delinearizeBasis}.drop_back(numMatches),
5226 delinearizeOp.hasOuterBound());
5227 SmallVector<Value> mergedResults(newDelinearize.getResults());
5228 mergedResults.append(linearizeIns.take_back(numMatches).begin(),
5229 linearizeIns.take_back(numMatches).end());
5230 rewriter.replaceOp(delinearizeOp, mergedResults);
5231 return success();
5232 }
5233};
5234
5235/// If the input to a delinearization is a disjoint linearization, and the
5236/// last k > 1 components of the delinearization basis multiply to the
5237/// last component of the linearization basis, break the linearization and
5238/// delinearization into two parts, peeling off the last input to linearization.
5239///
5240/// For example:
5241/// %0 = affine.linearize_index [%z, %y, %x] by (3, 2, 32) : index
5242/// %1:4 = affine.delinearize_index %0 by (2, 3, 8, 4) : index, ...
5243/// becomes
5244/// %0 = affine.linearize_index [%z, %y] by (3, 2) : index
5245/// %1:2 = affine.delinearize_index %0 by (2, 3) : index
5246/// %2:2 = affine.delinearize_index %x by (8, 4) : index
5247/// where the original %1:4 is replaced by %1:2 ++ %2:2
5248struct SplitDelinearizeSpanningLastLinearizeArg final
5249 : OpRewritePattern<affine::AffineDelinearizeIndexOp> {
5251
5252 LogicalResult matchAndRewrite(affine::AffineDelinearizeIndexOp delinearizeOp,
5253 PatternRewriter &rewriter) const override {
5254 auto linearizeOp = delinearizeOp.getLinearIndex()
5255 .getDefiningOp<affine::AffineLinearizeIndexOp>();
5256 if (!linearizeOp)
5257 return rewriter.notifyMatchFailure(delinearizeOp,
5258 "index doesn't come from linearize");
5259
5260 if (!linearizeOp.getDisjoint())
5261 return rewriter.notifyMatchFailure(linearizeOp,
5262 "linearize isn't disjoint");
5263
5264 // A linearize with no inputs has an empty basis and folds to a constant
5265 // zero; there is nothing to split, and reading its last basis element
5266 // below would be out of bounds.
5267 if (linearizeOp.getStaticBasis().empty())
5268 return rewriter.notifyMatchFailure(
5269 linearizeOp, "linearize has no basis elements (no inputs)");
5270
5271 int64_t target = linearizeOp.getStaticBasis().back();
5272 if (ShapedType::isDynamic(target))
5273 return rewriter.notifyMatchFailure(
5274 linearizeOp, "linearize ends with dynamic basis value");
5275
5276 int64_t sizeToSplit = 1;
5277 size_t elemsToSplit = 0;
5278 ArrayRef<int64_t> basis = delinearizeOp.getStaticBasis();
5279 for (int64_t basisElem : llvm::reverse(basis)) {
5280 if (ShapedType::isDynamic(basisElem))
5281 return rewriter.notifyMatchFailure(
5282 delinearizeOp, "dynamic basis element while scanning for split");
5283 sizeToSplit *= basisElem;
5284 elemsToSplit += 1;
5285
5286 if (sizeToSplit > target)
5287 return rewriter.notifyMatchFailure(delinearizeOp,
5288 "overshot last argument size");
5289 if (sizeToSplit == target)
5290 break;
5291 }
5292
5293 if (sizeToSplit < target)
5294 return rewriter.notifyMatchFailure(
5295 delinearizeOp, "product of known basis elements doesn't exceed last "
5296 "linearize argument");
5297
5298 if (elemsToSplit < 2)
5299 return rewriter.notifyMatchFailure(
5300 delinearizeOp,
5301 "need at least two elements to form the basis product");
5302
5303 Value linearizeWithoutBack = affine::AffineLinearizeIndexOp::create(
5304 rewriter, linearizeOp.getLoc(), linearizeOp.getLinearIndex().getType(),
5305 linearizeOp.getMultiIndex().drop_back(), linearizeOp.getDynamicBasis(),
5306 linearizeOp.getStaticBasis().drop_back(), linearizeOp.getDisjoint());
5307 auto delinearizeWithoutSplitPart = affine::AffineDelinearizeIndexOp::create(
5308 rewriter, delinearizeOp.getLoc(), linearizeWithoutBack,
5309 delinearizeOp.getDynamicBasis(), basis.drop_back(elemsToSplit),
5310 delinearizeOp.hasOuterBound());
5311 auto delinearizeBack = affine::AffineDelinearizeIndexOp::create(
5312 rewriter, delinearizeOp.getLoc(), linearizeOp.getMultiIndex().back(),
5313 basis.take_back(elemsToSplit), /*hasOuterBound=*/true);
5314 SmallVector<Value> results = llvm::to_vector(
5315 llvm::concat<Value>(delinearizeWithoutSplitPart.getResults(),
5316 delinearizeBack.getResults()));
5317 rewriter.replaceOp(delinearizeOp, results);
5318
5319 return success();
5320 }
5321};
5322} // namespace
5323
5324void affine::AffineDelinearizeIndexOp::getCanonicalizationPatterns(
5325 RewritePatternSet &patterns, MLIRContext *context) {
5326 patterns
5327 .insert<CancelDelinearizeOfLinearizeDisjointExactTail,
5328 DropUnitExtentBasis, SplitDelinearizeSpanningLastLinearizeArg>(
5329 context);
5330}
5331
5332//===----------------------------------------------------------------------===//
5333// LinearizeIndexOp
5334//===----------------------------------------------------------------------===//
5335
5336/// Infer the index type from a set of multi-index values. Returns the common
5337/// type (index or vector<...xindex>), or IndexType if the set is empty.
5338static Type inferIndexType(MLIRContext *ctx, ValueRange multiIndex) {
5339 if (multiIndex.empty())
5340 return IndexType::get(ctx);
5341 return multiIndex.front().getType();
5342}
5343
5344void AffineLinearizeIndexOp::build(OpBuilder &odsBuilder,
5345 OperationState &odsState,
5346 ValueRange multiIndex, ValueRange basis,
5347 bool disjoint) {
5348 if (!basis.empty() && basis.front() == Value())
5349 basis = basis.drop_front();
5350 SmallVector<Value> dynamicBasis;
5351 SmallVector<int64_t> staticBasis;
5352 dispatchIndexOpFoldResults(getAsOpFoldResult(basis), dynamicBasis,
5353 staticBasis);
5354 Type resultType = inferIndexType(odsBuilder.getContext(), multiIndex);
5355 build(odsBuilder, odsState, resultType, multiIndex, dynamicBasis, staticBasis,
5356 disjoint);
5357}
5358
5359void AffineLinearizeIndexOp::build(OpBuilder &odsBuilder,
5360 OperationState &odsState,
5361 ValueRange multiIndex,
5362 ArrayRef<OpFoldResult> basis,
5363 bool disjoint) {
5364 if (!basis.empty() && basis.front() == OpFoldResult())
5365 basis = basis.drop_front();
5366 SmallVector<Value> dynamicBasis;
5367 SmallVector<int64_t> staticBasis;
5368 dispatchIndexOpFoldResults(basis, dynamicBasis, staticBasis);
5369 Type resultType = inferIndexType(odsBuilder.getContext(), multiIndex);
5370 build(odsBuilder, odsState, resultType, multiIndex, dynamicBasis, staticBasis,
5371 disjoint);
5372}
5373
5374void AffineLinearizeIndexOp::build(OpBuilder &odsBuilder,
5375 OperationState &odsState,
5376 ValueRange multiIndex,
5377 ArrayRef<int64_t> basis, bool disjoint) {
5378 Type resultType = inferIndexType(odsBuilder.getContext(), multiIndex);
5379 build(odsBuilder, odsState, resultType, multiIndex, ValueRange{}, basis,
5380 disjoint);
5381}
5382
5383LogicalResult AffineLinearizeIndexOp::verify() {
5384 size_t numIndexes = getMultiIndex().size();
5385 size_t numBasisElems = getStaticBasis().size();
5386 if (numIndexes != numBasisElems && numIndexes != numBasisElems + 1)
5387 return emitOpError("should be passed a basis element for each index except "
5388 "possibly the first");
5389
5390 auto dynamicMarkersCount =
5391 llvm::count_if(getStaticBasis(), ShapedType::isDynamic);
5392 if (static_cast<size_t>(dynamicMarkersCount) != getDynamicBasis().size())
5393 return emitOpError(
5394 "mismatch between dynamic and static basis (kDynamic marker but no "
5395 "corresponding dynamic basis entry) -- this can only happen due to an "
5396 "incorrect fold/rewrite");
5397
5398 return success();
5399}
5400
5401OpFoldResult AffineLinearizeIndexOp::fold(FoldAdaptor adaptor) {
5402 std::optional<SmallVector<int64_t>> maybeStaticBasis =
5403 foldCstValueToCstAttrBasis(getMixedBasis(), getDynamicBasisMutable(),
5404 adaptor.getDynamicBasis());
5405 if (maybeStaticBasis) {
5406 setStaticBasis(*maybeStaticBasis);
5407 return getResult();
5408 }
5409 // No indices linearizes to zero.
5410 if (getMultiIndex().empty())
5411 return IntegerAttr::get(getResult().getType(), 0);
5412
5413 // One single index linearizes to itself.
5414 if (getMultiIndex().size() == 1)
5415 return getMultiIndex().front();
5416
5417 // Return nullptr if any multi-index attribute has not been folded to a
5418 // concrete integer (e.g. it is still a runtime value or has folded to a
5419 // non-integer attribute such as #ub.poison).
5420 if (llvm::any_of(adaptor.getMultiIndex(), [](Attribute a) {
5421 return !isa_and_nonnull<IntegerAttr>(a);
5422 }))
5423 return nullptr;
5424
5425 if (!adaptor.getDynamicBasis().empty())
5426 return nullptr;
5427
5428 int64_t result = 0;
5429 int64_t stride = 1;
5430 for (auto [length, indexAttr] :
5431 llvm::zip_first(llvm::reverse(getStaticBasis()),
5432 llvm::reverse(adaptor.getMultiIndex()))) {
5433 result = result + cast<IntegerAttr>(indexAttr).getInt() * stride;
5434 stride = stride * length;
5435 }
5436 // Handle the index element with no basis element.
5437 if (!hasOuterBound())
5438 result =
5439 result +
5440 cast<IntegerAttr>(adaptor.getMultiIndex().front()).getInt() * stride;
5441
5442 return IntegerAttr::get(getResult().getType(), result);
5443}
5444
5445SmallVector<OpFoldResult> AffineLinearizeIndexOp::getEffectiveBasis() {
5446 OpBuilder builder(getContext());
5447 if (hasOuterBound()) {
5448 if (getStaticBasis().front() == ::mlir::ShapedType::kDynamic)
5449 return getMixedValues(getStaticBasis().drop_front(),
5450 getDynamicBasis().drop_front(), builder);
5451
5452 return getMixedValues(getStaticBasis().drop_front(), getDynamicBasis(),
5453 builder);
5454 }
5455
5456 return getMixedValues(getStaticBasis(), getDynamicBasis(), builder);
5457}
5458
5459SmallVector<OpFoldResult> AffineLinearizeIndexOp::getPaddedBasis() {
5460 SmallVector<OpFoldResult> ret = getMixedBasis();
5461 if (!hasOuterBound())
5462 ret.insert(ret.begin(), OpFoldResult());
5463 return ret;
5464}
5465
5466namespace {
5467/// Rewrite `affine.linearize_index disjoint [%...a, %x, %...b] by (%...c, 1,
5468/// %...d)` to `affine.linearize_index disjoint [%...a, %...b] by (%...c,
5469/// %...d)`.
5470
5471/// Note that `disjoint` is required here, because, without it, we could have
5472/// `affine.linearize_index [%...a, %c64, %...b] by (%...c, 1, %...d)`
5473/// is a valid operation where the `%c64` cannot be trivially dropped.
5474///
5475/// Alternatively, if `%x` in the above is a known constant 0, remove it even if
5476/// the operation isn't asserted to be `disjoint`.
5477struct DropLinearizeUnitComponentsIfDisjointOrZero final
5478 : OpRewritePattern<affine::AffineLinearizeIndexOp> {
5480
5481 LogicalResult matchAndRewrite(affine::AffineLinearizeIndexOp op,
5482 PatternRewriter &rewriter) const override {
5483 ValueRange multiIndex = op.getMultiIndex();
5484 size_t numIndices = multiIndex.size();
5485 SmallVector<Value> newIndices;
5486 newIndices.reserve(numIndices);
5487 SmallVector<OpFoldResult> newBasis;
5488 newBasis.reserve(numIndices);
5489
5490 if (!op.hasOuterBound()) {
5491 newIndices.push_back(multiIndex.front());
5492 multiIndex = multiIndex.drop_front();
5493 }
5494
5495 SmallVector<OpFoldResult> basis = op.getMixedBasis();
5496 for (auto [index, basisElem] : llvm::zip_equal(multiIndex, basis)) {
5497 std::optional<int64_t> basisEntry = getConstantIntValue(basisElem);
5498 if (!basisEntry || *basisEntry != 1) {
5499 newIndices.push_back(index);
5500 newBasis.push_back(basisElem);
5501 continue;
5502 }
5503
5504 std::optional<int64_t> indexValue = getConstantIntValue(index);
5505 if (!op.getDisjoint() && (!indexValue || *indexValue != 0)) {
5506 newIndices.push_back(index);
5507 newBasis.push_back(basisElem);
5508 continue;
5509 }
5510 }
5511 if (newIndices.size() == numIndices)
5512 return rewriter.notifyMatchFailure(op,
5513 "no unit basis entries to replace");
5514
5515 if (newIndices.empty()) {
5516 rewriter.replaceOpWithNewOp<arith::ConstantOp>(
5517 op, rewriter.getZeroAttr(op.getLinearIndex().getType()));
5518 return success();
5519 }
5520 rewriter.replaceOpWithNewOp<affine::AffineLinearizeIndexOp>(
5521 op, newIndices, newBasis, op.getDisjoint());
5522 return success();
5523 }
5524};
5525
5526OpFoldResult computeProduct(Location loc, OpBuilder &builder,
5527 ArrayRef<OpFoldResult> terms) {
5528 int64_t nDynamic = 0;
5529 SmallVector<Value> dynamicPart;
5530 AffineExpr result = builder.getAffineConstantExpr(1);
5531 for (OpFoldResult term : terms) {
5532 if (!term)
5533 return term;
5534 std::optional<int64_t> maybeConst = getConstantIntValue(term);
5535 if (maybeConst) {
5536 result = result * builder.getAffineConstantExpr(*maybeConst);
5537 } else {
5538 dynamicPart.push_back(cast<Value>(term));
5539 result = result * builder.getAffineSymbolExpr(nDynamic++);
5540 }
5541 }
5542 if (auto constant = dyn_cast<AffineConstantExpr>(result))
5543 return getAsIndexOpFoldResult(builder.getContext(), constant.getValue());
5544 return AffineApplyOp::create(builder, loc, result, dynamicPart).getResult();
5545}
5546
5547/// If conseceutive outputs of a delinearize_index are linearized with the same
5548/// bounds, canonicalize away the redundant arithmetic.
5549///
5550/// That is, if we have
5551/// ```
5552/// %s:N = affine.delinearize_index %x into (...a, B1, B2, ... BK, ...b)
5553/// %t = affine.linearize_index [...c, %s#I, %s#(I + 1), ... %s#(I+K-1), ...d]
5554/// by (...e, B1, B2, ..., BK, ...f)
5555/// ```
5556///
5557/// We can rewrite this to
5558/// ```
5559/// B = B1 * B2 ... BK
5560/// %sMerged:(N-K+1) affine.delinearize_index %x into (...a, B, ...b)
5561/// %t = affine.linearize_index [...c, %s#I, ...d] by (...e, B, ...f)
5562/// ```
5563/// where we replace all results of %s unaffected by the change with results
5564/// from %sMerged.
5565///
5566/// As a special case, if all results of the delinearize are merged in this way
5567/// we can replace those usages with %x, thus cancelling the delinearization
5568/// entirely, as in
5569/// ```
5570/// %s:3 = affine.delinearize_index %x into (2, 4, 8)
5571/// %t = affine.linearize_index [%s#0, %s#1, %s#2, %c0] by (2, 4, 8, 16)
5572/// ```
5573/// becoming `%t = affine.linearize_index [%x, %c0] by (64, 16)`
5574struct CancelLinearizeOfDelinearizePortion final
5575 : OpRewritePattern<affine::AffineLinearizeIndexOp> {
5577
5578private:
5579 // Struct representing a case where the cancellation pattern
5580 // applies. A `Match` means that `length` inputs to the linearize operation
5581 // starting at `linStart` can be cancelled with `length` outputs of
5582 // `delinearize`, starting from `delinStart`.
5583 struct Match {
5584 AffineDelinearizeIndexOp delinearize;
5585 unsigned linStart = 0;
5586 unsigned delinStart = 0;
5587 unsigned length = 0;
5588 };
5589
5590public:
5591 LogicalResult matchAndRewrite(affine::AffineLinearizeIndexOp linearizeOp,
5592 PatternRewriter &rewriter) const override {
5593 SmallVector<Match> matches;
5594
5595 const SmallVector<OpFoldResult> linBasis = linearizeOp.getPaddedBasis();
5596 ArrayRef<OpFoldResult> linBasisRef = linBasis;
5597
5598 ValueRange multiIndex = linearizeOp.getMultiIndex();
5599 unsigned numLinArgs = multiIndex.size();
5600 unsigned linArgIdx = 0;
5601 // We only want to replace one run from the same delinearize op per
5602 // pattern invocation lest we run into invalidation issues.
5603 llvm::SmallPtrSet<Operation *, 2> alreadyMatchedDelinearize;
5604 while (linArgIdx < numLinArgs) {
5605 auto asResult = dyn_cast<OpResult>(multiIndex[linArgIdx]);
5606 if (!asResult) {
5607 linArgIdx++;
5608 continue;
5609 }
5610
5611 auto delinearizeOp =
5612 dyn_cast<AffineDelinearizeIndexOp>(asResult.getOwner());
5613 if (!delinearizeOp) {
5614 linArgIdx++;
5615 continue;
5616 }
5617
5618 /// Result 0 of the delinearize and argument 0 of the linearize can
5619 /// leave their maximum value unspecified. However, even if this happens
5620 /// we can still sometimes start the match process. Specifically, if
5621 /// - The argument we're matching is result 0 and argument 0 (so the
5622 /// bounds don't matter). For example,
5623 ///
5624 /// %0:2 = affine.delinearize_index %x into (8) : index, index
5625 /// %1 = affine.linearize_index [%s#0, %s#1, ...] (8, ...)
5626 /// allows cancellation
5627 /// - The delinearization doesn't specify a bound, but the linearization
5628 /// is `disjoint`, which asserts that the bound on the linearization is
5629 /// correct.
5630 unsigned delinArgIdx = asResult.getResultNumber();
5631 SmallVector<OpFoldResult> delinBasis = delinearizeOp.getPaddedBasis();
5632 OpFoldResult firstDelinBound = delinBasis[delinArgIdx];
5633 OpFoldResult firstLinBound = linBasis[linArgIdx];
5634 bool boundsMatch = firstDelinBound == firstLinBound;
5635 bool bothAtFront = linArgIdx == 0 && delinArgIdx == 0;
5636 bool knownByDisjoint =
5637 linearizeOp.getDisjoint() && delinArgIdx == 0 && !firstDelinBound;
5638 if (!boundsMatch && !bothAtFront && !knownByDisjoint) {
5639 linArgIdx++;
5640 continue;
5641 }
5642
5643 unsigned j = 1;
5644 unsigned numDelinOuts = delinearizeOp.getNumResults();
5645 for (; j + linArgIdx < numLinArgs && j + delinArgIdx < numDelinOuts;
5646 ++j) {
5647 if (multiIndex[linArgIdx + j] !=
5648 delinearizeOp.getResult(delinArgIdx + j))
5649 break;
5650 if (linBasis[linArgIdx + j] != delinBasis[delinArgIdx + j])
5651 break;
5652 }
5653 // If there're multiple matches against the same delinearize_index,
5654 // only rewrite the first one we find to prevent invalidations. The next
5655 // ones will be taken care of by subsequent pattern invocations.
5656 if (j <= 1 || !alreadyMatchedDelinearize.insert(delinearizeOp).second) {
5657 linArgIdx++;
5658 continue;
5659 }
5660 matches.push_back(Match{delinearizeOp, linArgIdx, delinArgIdx, j});
5661 linArgIdx += j;
5662 }
5663
5664 if (matches.empty())
5665 return rewriter.notifyMatchFailure(
5666 linearizeOp, "no run of delinearize outputs to deal with");
5667
5668 // Record all the delinearize replacements so we can do them after creating
5669 // the new linearization operation, since the new operation might use
5670 // outputs of something we're replacing.
5671 SmallVector<SmallVector<Value>> delinearizeReplacements;
5672
5673 SmallVector<Value> newIndex;
5674 newIndex.reserve(numLinArgs);
5675 SmallVector<OpFoldResult> newBasis;
5676 newBasis.reserve(numLinArgs);
5677 unsigned prevMatchEnd = 0;
5678 for (Match m : matches) {
5679 unsigned gap = m.linStart - prevMatchEnd;
5680 llvm::append_range(newIndex, multiIndex.slice(prevMatchEnd, gap));
5681 llvm::append_range(newBasis, linBasisRef.slice(prevMatchEnd, gap));
5682 // Update here so we don't forget this during early continues
5683 prevMatchEnd = m.linStart + m.length;
5684
5685 PatternRewriter::InsertionGuard g(rewriter);
5686 rewriter.setInsertionPoint(m.delinearize);
5687
5688 ArrayRef<OpFoldResult> basisToMerge =
5689 linBasisRef.slice(m.linStart, m.length);
5690 // We use the slice from the linearize's basis above because of the
5691 // "bounds inferred from `disjoint`" case above.
5692 OpFoldResult newSize =
5693 computeProduct(linearizeOp.getLoc(), rewriter, basisToMerge);
5694
5695 // Trivial case where we can just skip past the delinearize all together
5696 if (m.length == m.delinearize.getNumResults()) {
5697 newIndex.push_back(m.delinearize.getLinearIndex());
5698 newBasis.push_back(newSize);
5699 // Pad out set of replacements so we don't do anything with this one.
5700 delinearizeReplacements.push_back(SmallVector<Value>());
5701 continue;
5702 }
5703
5704 SmallVector<Value> newDelinResults;
5705 SmallVector<OpFoldResult> newDelinBasis = m.delinearize.getPaddedBasis();
5706 newDelinBasis.erase(newDelinBasis.begin() + m.delinStart,
5707 newDelinBasis.begin() + m.delinStart + m.length);
5708 newDelinBasis.insert(newDelinBasis.begin() + m.delinStart, newSize);
5709 auto newDelinearize = AffineDelinearizeIndexOp::create(
5710 rewriter, m.delinearize.getLoc(), m.delinearize.getLinearIndex(),
5711 newDelinBasis);
5712
5713 // Since there may be other uses of the indices we just merged together,
5714 // create a residual affine.delinearize_index that delinearizes the
5715 // merged output into its component parts.
5716 Value combinedElem = newDelinearize.getResult(m.delinStart);
5717 auto residualDelinearize = AffineDelinearizeIndexOp::create(
5718 rewriter, m.delinearize.getLoc(), combinedElem, basisToMerge);
5719
5720 // Swap all the uses of the unaffected delinearize outputs to the new
5721 // delinearization so that the old code can be removed if this
5722 // linearize_index is the only user of the merged results.
5723 llvm::append_range(newDelinResults,
5724 newDelinearize.getResults().take_front(m.delinStart));
5725 llvm::append_range(newDelinResults, residualDelinearize.getResults());
5726 llvm::append_range(
5727 newDelinResults,
5728 newDelinearize.getResults().drop_front(m.delinStart + 1));
5729
5730 delinearizeReplacements.push_back(newDelinResults);
5731 newIndex.push_back(combinedElem);
5732 newBasis.push_back(newSize);
5733 }
5734 llvm::append_range(newIndex, multiIndex.drop_front(prevMatchEnd));
5735 llvm::append_range(newBasis, linBasisRef.drop_front(prevMatchEnd));
5736 rewriter.replaceOpWithNewOp<AffineLinearizeIndexOp>(
5737 linearizeOp, newIndex, newBasis, linearizeOp.getDisjoint());
5738
5739 for (auto [m, newResults] :
5740 llvm::zip_equal(matches, delinearizeReplacements)) {
5741 if (newResults.empty())
5742 continue;
5743 rewriter.replaceOp(m.delinearize, newResults);
5744 }
5745
5746 return success();
5747 }
5748};
5749
5750/// Strip leading zero from affine.linearize_index.
5751///
5752/// `affine.linearize_index [%c0, ...a] by (%x, ...b)` can be rewritten
5753/// to `affine.linearize_index [...a] by (...b)` in all cases.
5754struct DropLinearizeLeadingZero final
5755 : OpRewritePattern<affine::AffineLinearizeIndexOp> {
5757
5758 LogicalResult matchAndRewrite(affine::AffineLinearizeIndexOp op,
5759 PatternRewriter &rewriter) const override {
5760 Value leadingIdx = op.getMultiIndex().front();
5761 if (!matchPattern(leadingIdx, m_Zero()))
5762 return failure();
5763
5764 if (op.getMultiIndex().size() == 1) {
5765 rewriter.replaceOp(op, leadingIdx);
5766 return success();
5767 }
5768
5769 SmallVector<OpFoldResult> mixedBasis = op.getMixedBasis();
5770 ArrayRef<OpFoldResult> newMixedBasis = mixedBasis;
5771 if (op.hasOuterBound())
5772 newMixedBasis = newMixedBasis.drop_front();
5773
5774 rewriter.replaceOpWithNewOp<affine::AffineLinearizeIndexOp>(
5775 op, op.getMultiIndex().drop_front(), newMixedBasis, op.getDisjoint());
5776 return success();
5777 }
5778};
5779} // namespace
5780
5781void affine::AffineLinearizeIndexOp::getCanonicalizationPatterns(
5782 RewritePatternSet &patterns, MLIRContext *context) {
5783 patterns.add<CancelLinearizeOfDelinearizePortion, DropLinearizeLeadingZero,
5784 DropLinearizeUnitComponentsIfDisjointOrZero>(context);
5785}
5786
5787//===----------------------------------------------------------------------===//
5788// TableGen'd op method definitions
5789//===----------------------------------------------------------------------===//
5790
5791#define GET_OP_CLASSES
5792#include "mlir/Dialect/Affine/IR/AffineOps.cpp.inc"
return success()
static AffineForOp buildAffineLoopFromConstants(OpBuilder &builder, Location loc, int64_t lb, int64_t ub, int64_t step, AffineForOp::BodyBuilderFn bodyBuilderFn)
Creates an affine loop from the bounds known to be constants.
static bool hasTrivialZeroTripCount(AffineForOp op)
Returns true if the affine.for has zero iterations in trivial cases.
static Type inferIndexType(MLIRContext *ctx, ValueRange multiIndex)
Infer the index type from a set of multi-index values. Returns the common type (index or vector<....
static LogicalResult verifyMemoryOpIndexing(AffineMemOpTy op, AffineMapAttr mapAttr, Operation::operand_range mapOperands, MemRefType memrefType, unsigned numIndexOperands)
Verify common indexing invariants of affine.load, affine.store, affine.vector_load and affine....
static void printAffineMinMaxOp(OpAsmPrinter &p, T op)
static bool isResultTypeMatchAtomicRMWKind(Type resultType, arith::AtomicRMWKind op)
static bool remainsLegalAfterInline(Value value, Region *src, Region *dest, const IRMapping &mapping, function_ref< bool(Value, Region *)> legalityCheck)
Checks if value known to be a legal affine dimension or symbol in src region remains legal if the ope...
Definition AffineOps.cpp:62
static void printMinMaxBound(OpAsmPrinter &p, AffineMapAttr mapAttr, DenseIntElementsAttr group, ValueRange operands, StringRef keyword)
Prints a lower(upper) bound of an affine parallel loop with max(min) conditions in it.
static OpFoldResult foldMinMaxOp(T op, ArrayRef< Attribute > operands)
Fold an affine min or max operation with the given operands.
static bool isTopLevelValueOrAbove(Value value, Region *region)
A utility function to check if a value is defined at the top level of region or is an argument of reg...
static LogicalResult canonicalizeLoopBounds(AffineForOp forOp)
Canonicalize the bounds of the given loop.
static void simplifyExprAndOperands(AffineExpr &expr, unsigned numDims, unsigned numSymbols, ArrayRef< Value > operands)
Simplify expr while exploiting information from the values in operands.
static bool isValidAffineIndexOperand(Value value, Region *region)
p<< " : "<< getMemRefType()<< ", "<< getType();}static LogicalResult verifyVectorMemoryOp(Operation *op, MemRefType memrefType, VectorType vectorType) { if(memrefType.getElementType() !=vectorType.getElementType()) return op-> emitOpError("requires memref and vector types of the same elemental type")
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
static void canonicalizeMapOrSetAndOperands(MapOrSet *mapOrSet, SmallVectorImpl< Value > *operands)
static ParseResult parseBound(bool isLower, OperationState &result, OpAsmParser &p)
Parse a for operation loop bounds.
static std::optional< SmallVector< int64_t > > foldCstValueToCstAttrBasis(ArrayRef< OpFoldResult > mixedBasis, MutableOperandRange mutableDynamicBasis, ArrayRef< Attribute > dynamicBasis)
Given mixed basis of affine.delinearize_index/linearize_index replace constant SSA values with the co...
static void canonicalizePromotedSymbols(MapOrSet *mapOrSet, SmallVectorImpl< Value > *operands)
static void simplifyMinOrMaxExprWithOperands(AffineMap &map, ArrayRef< Value > operands, bool isMax)
Simplify the expressions in map while making use of lower or upper bounds of its operands.
static ParseResult parseAffineMinMaxOp(OpAsmParser &parser, OperationState &result)
static LogicalResult replaceAffineDelinearizeIndexInverseExpression(AffineDelinearizeIndexOp delinOp, Value resultToReplace, AffineMap *map, SmallVectorImpl< Value > &dims, SmallVectorImpl< Value > &syms)
If this map contains of the expression x_1 + x_1 * C_1 + ... x_n * C_N + / ... (not necessarily in or...
static void composeSetAndOperands(IntegerSet &set, SmallVectorImpl< Value > &operands, bool composeAffineMin=false)
Compose any affine.apply ops feeding into operands of the integer set set by composing the maps of su...
static bool isMemRefSizeValidSymbol(AnyMemRefDefOp memrefDefOp, unsigned index, Region *region)
Returns true if the 'index' dimension of the memref defined by memrefDefOp is a statically shaped one...
static bool isNonNegativeBoundedBy(AffineExpr e, ArrayRef< Value > operands, int64_t k)
Check if e is known to be: 0 <= e < k.
static AffineForOp buildAffineLoopFromValues(OpBuilder &builder, Location loc, Value lb, Value ub, int64_t step, AffineForOp::BodyBuilderFn bodyBuilderFn)
Creates an affine loop from the bounds that may or may not be constants.
static void simplifyMapWithOperands(AffineMap &map, ArrayRef< Value > operands)
Simplify the map while exploiting information on the values in operands.
static void printDimAndSymbolList(Operation::operand_iterator begin, Operation::operand_iterator end, unsigned numDims, OpAsmPrinter &printer)
Prints dimension and symbol list.
static int64_t getLargestKnownDivisor(AffineExpr e, ArrayRef< Value > operands)
Returns the largest known divisor of e.
static void composeAffineMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands, bool composeAffineMin=false)
Iterate over operands and fold away all those produced by an AffineApplyOp iteratively.
static void legalizeDemotedDims(MapOrSet &mapOrSet, SmallVectorImpl< Value > &operands)
A valid affine dimension may appear as a symbol in affine.apply operations.
static OpTy makeComposedMinMax(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
static std::optional< int64_t > getUpperBound(Value iv)
Gets the constant upper bound on an affine.for iv.
static void buildAffineLoopNestImpl(OpBuilder &builder, Location loc, BoundListTy lbs, BoundListTy ubs, ArrayRef< int64_t > steps, function_ref< void(OpBuilder &, Location, ValueRange)> bodyBuilderFn, LoopCreatorTy &&loopCreatorFn)
Builds an affine loop nest, using "loopCreatorFn" to create individual loop operations.
static LogicalResult foldLoopBounds(AffineForOp forOp)
Fold the constant bounds of a loop.
return success()
static LogicalResult replaceAffineMinBoundingBoxExpression(AffineMinOp minOp, AffineExpr dimOrSym, AffineMap *map, ValueRange dims, ValueRange syms)
Assuming dimOrSym is a quantity in the apply op map map and defined by minOp = affine_min(x_1,...
static void addAlignmentAttr(OpBuilder &builder, OperationState &result, StringAttr attrName, llvm::MaybeAlign alignment)
Adds the optional alignment attribute to result, if one is given.
static SmallVector< OpFoldResult > AffineForEmptyLoopFolder(AffineForOp forOp)
Fold the empty loop.
static LogicalResult verifyDimAndSymbolIdentifiers(OpTy &op, Operation::operand_range operands, unsigned numDims)
Utility function to verify that a set of operands are valid dimension and symbol identifiers.
static OpFoldResult makeComposedFoldedMinMax(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
static bool isDimOpValidSymbol(ShapedDimOpInterface dimOp, Region *region)
Returns true if the result of the dim op is a valid symbol for region.
static bool isQTimesDPlusR(AffineExpr e, ArrayRef< Value > operands, int64_t &div, AffineExpr &quotientTimesDiv, AffineExpr &rem)
Check if expression e is of the form d*e_1 + e_2 where 0 <= e_2 < d.
static LogicalResult replaceDimOrSym(AffineMap *map, unsigned dimOrSymbolPosition, SmallVectorImpl< Value > &dims, SmallVectorImpl< Value > &syms, bool replaceAffineMin)
Replace all occurrences of AffineExpr at position pos in map by the defining AffineApplyOp expression...
static std::optional< int64_t > getLowerBound(Value iv)
Gets the constant lower bound on an iv.
static std::optional< uint64_t > getTrivialConstantTripCount(AffineForOp forOp)
Returns constant trip count in trivial cases.
static LogicalResult verifyAffineMinMaxOp(T op)
static void printBound(AffineMapAttr boundMap, Operation::operand_range boundOperands, const char *prefix, OpAsmPrinter &p)
static void shortenAddChainsContainingAll(AffineExpr e, const llvm::SmallDenseSet< AffineExpr, 4 > &exprsToRemove, AffineExpr newVal, DenseMap< AffineExpr, AffineExpr > &replacementsMap)
Recursively traverse e.
static void composeMultiResultAffineMap(AffineMap &map, SmallVectorImpl< Value > &operands, bool composeAffineMin=false)
Composes the given affine map with the given list of operands, pulling in the maps from any affine....
static LogicalResult canonicalizeMapExprAndTermOrder(AffineMap &map)
Canonicalize the result expression order of an affine map and return success if the order changed.
static Value getZero(OpBuilder &b, Location loc, Type elementType)
Get zero value for an element type.
static Value getMemRef(Operation *memOp)
Returns the memref being read/written by a memref/affine load/store op.
Definition Utils.cpp:247
lhs
static bool isLegalToInline(InlinerInterface &interface, Region *src, Region *insertRegion, bool shouldCloneInlinedRegion, IRMapping &valueMapping)
Utility to check that all of the operations within 'src' can be inlined.
static int64_t getNumElements(Type t)
Compute the total number of elements in the given type, also taking into account nested types.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getI64ArrayAttr(paddingDimensions)
b getContext())
auto load
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
static Operation::operand_range getLowerBoundOperands(AffineForOp forOp)
Definition SCFToGPU.cpp:75
static Operation::operand_range getUpperBoundOperands(AffineForOp forOp)
Definition SCFToGPU.cpp:80
static VectorType getVectorType(Type scalarTy, const VectorizationStrategy *strategy)
Returns the vector type resulting from applying the provided vectorization strategy on the scalar typ...
#define div(a, b)
#define rem(a, b)
RetTy walkPostOrder(AffineExpr expr)
Base type for affine expression.
Definition AffineExpr.h:68
AffineExpr shiftDims(unsigned numDims, unsigned shift, unsigned offset=0) const
Replace dims[offset ... numDims) by dims[offset + shift ... shift + numDims).
AffineExpr shiftSymbols(unsigned numSymbols, unsigned shift, unsigned offset=0) const
Replace symbols[offset ... numSymbols) by symbols[offset + shift ... shift + numSymbols).
AffineExpr floorDiv(uint64_t v) const
AffineExprKind getKind() const
Return the classification for this type.
int64_t getLargestKnownDivisor() const
Returns the greatest known integral divisor of this affine expression.
MLIRContext * getContext() const
AffineExpr replace(AffineExpr expr, AffineExpr replacement) const
Sparse replace method.
AffineExpr ceilDiv(uint64_t v) const
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
AffineMap getSliceMap(unsigned start, unsigned length) const
Returns the map consisting of length expressions starting from start.
MLIRContext * getContext() const
bool isFunctionOfDim(unsigned position) const
Return true if any affine expression involves AffineDimExpr position.
Definition AffineMap.h:221
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
AffineMap shiftDims(unsigned shift, unsigned offset=0) const
Replace dims[offset ... numDims) by dims[offset + shift ... shift + numDims).
Definition AffineMap.h:267
unsigned getNumSymbols() const
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
bool isFunctionOfSymbol(unsigned position) const
Return true if any affine expression involves AffineSymbolExpr position.
Definition AffineMap.h:228
unsigned getNumResults() const
static SmallVector< AffineMap, 4 > inferFromExprList(ArrayRef< ArrayRef< AffineExpr > > exprsList, MLIRContext *context)
Returns a vector of AffineMaps; each with as many results as exprs.size(), as many dims as the larges...
AffineMap replaceDimsAndSymbols(ArrayRef< AffineExpr > dimReplacements, ArrayRef< AffineExpr > symReplacements, unsigned numResultDims, unsigned numResultSyms) const
This method substitutes any uses of dimensions and symbols (e.g.
unsigned getNumInputs() const
AffineMap shiftSymbols(unsigned shift, unsigned offset=0) const
Replace symbols[offset ... numSymbols) by symbols[offset + shift ... shift + numSymbols).
Definition AffineMap.h:280
AffineExpr getResult(unsigned idx) const
AffineMap replace(AffineExpr expr, AffineExpr replacement, unsigned numResultDims, unsigned numResultSyms) const
Sparse replace method.
static AffineMap getConstantMap(int64_t val, MLIRContext *context)
Returns a single constant result affine map.
AffineMap getSubMap(ArrayRef< unsigned > resultPos) const
Returns the map consisting of the resultPos subset.
LogicalResult constantFold(ArrayRef< Attribute > operandConstants, SmallVectorImpl< Attribute > &results, bool *hasPoison=nullptr) const
Folds the results of the application of an affine map on the provided operands to a constant if possi...
@ Paren
Parens surrounding zero or more operands.
@ OptionalSquare
Square brackets supporting zero or more ops, or nothing.
virtual ParseResult parseColonTypeList(SmallVectorImpl< Type > &result)=0
Parse a colon followed by a type list, which must have at least one type.
virtual Builder & getBuilder() const =0
Return a builder which provides useful access to MLIRContext, global objects like types and attribute...
virtual ParseResult parseCommaSeparatedList(Delimiter delimiter, function_ref< ParseResult()> parseElementFn, StringRef contextMessage=StringRef())=0
Parse a list of comma-separated items with an optional delimiter.
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
virtual ParseResult parseOptionalKeyword(StringRef keyword)=0
Parse the given keyword if present.
MLIRContext * getContext() const
virtual ParseResult parseRParen()=0
Parse a ) token.
virtual InFlightDiagnostic emitError(SMLoc loc, const Twine &message={})=0
Emit a diagnostic at the specified location and return failure.
ParseResult addTypeToList(Type type, SmallVectorImpl< Type > &result)
Add the specified type to the end of the specified type list and return success.
virtual ParseResult parseOptionalRParen()=0
Parse a ) token if present.
virtual ParseResult parseLess()=0
Parse a '<' token.
virtual ParseResult parseEqual()=0
Parse a = token.
virtual ParseResult parseColonType(Type &result)=0
Parse a colon followed by a type.
virtual SMLoc getCurrentLocation()=0
Get the location of the next token and store it into the argument.
virtual SMLoc getNameLoc() const =0
Return the location of the original name token.
virtual ParseResult parseGreater()=0
Parse a '>' token.
virtual ParseResult parseLParen()=0
Parse a ( token.
virtual ParseResult parseType(Type &result)=0
Parse a type.
virtual ParseResult parseComma()=0
Parse a , token.
virtual ParseResult parseOptionalArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an optional arrow followed by a type list.
virtual ParseResult parseArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an arrow followed by a type list.
ParseResult parseKeyword(StringRef keyword)
Parse a given keyword.
virtual ParseResult parseAttribute(Attribute &result, Type type={})=0
Parse an arbitrary attribute of a given type and return it in result.
void printOptionalArrowTypeList(TypeRange &&types)
Print an optional arrow followed by a type list.
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:33
Operation & front()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
BlockArgListType getArguments()
Definition Block.h:111
DenseI32ArrayAttr getDenseI32ArrayAttr(ArrayRef< int32_t > values)
Definition Builders.cpp:171
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
AffineMap getDimIdentityMap()
Definition Builders.cpp:392
AffineMap getMultiDimIdentityMap(unsigned rank)
Definition Builders.cpp:396
AffineExpr getAffineSymbolExpr(unsigned position)
Definition Builders.cpp:377
AffineExpr getAffineConstantExpr(int64_t constant)
Definition Builders.cpp:381
DenseIntElementsAttr getI32TensorAttr(ArrayRef< int32_t > values)
Tensor-typed DenseIntElementsAttr getters.
Definition Builders.cpp:187
IntegerAttr getI64IntegerAttr(int64_t value)
Definition Builders.cpp:120
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
NoneType getNoneType()
Definition Builders.cpp:96
BoolAttr getBoolAttr(bool value)
Definition Builders.cpp:108
AffineMap getEmptyAffineMap()
Returns a zero result affine map with no dimensions or symbols: () -> ().
Definition Builders.cpp:385
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
AffineMap getConstantAffineMap(int64_t val)
Returns a single constant result affine map with 0 dimensions and 0 symbols.
Definition Builders.cpp:387
AffineMap getSymbolIdentityMap()
Definition Builders.cpp:405
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
MLIRContext * getContext() const
Definition Builders.h:56
ArrayAttr getI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:290
IndexType getIndexType()
Definition Builders.cpp:59
An attribute that represents a reference to a dense integer vector or tensor object.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookup(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:72
An integer set representing a conjunction of one or more affine equalities and inequalities.
Definition IntegerSet.h:44
unsigned getNumDims() const
static IntegerSet get(unsigned dimCount, unsigned symbolCount, ArrayRef< AffineExpr > constraints, ArrayRef< bool > eqFlags)
MLIRContext * getContext() const
unsigned getNumInputs() const
ArrayRef< AffineExpr > getConstraints() const
ArrayRef< bool > getEqFlags() const
Returns the equality bits, which specify whether each of the constraints is an equality or inequality...
unsigned getNumSymbols() const
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class provides a mutable adaptor for a range of operands.
Definition ValueRange.h:119
void erase(unsigned subStart, unsigned subLen=1)
Erase the operands within the given sub-range.
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual ParseResult parseRegion(Region &region, ArrayRef< Argument > arguments={}, bool enableNameShadowing=false)=0
Parses a region.
virtual ParseResult parseArgument(Argument &result, bool allowType=false, bool allowAttrs=false)=0
Parse a single argument with the following syntax:
ParseResult parseTrailingOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None)
Parse zero or more trailing SSA comma-separated trailing operand references with a specified surround...
virtual ParseResult parseArgumentList(SmallVectorImpl< Argument > &result, Delimiter delimiter=Delimiter::None, bool allowType=false, bool allowAttrs=false)=0
Parse zero or more arguments with a specified surrounding delimiter.
virtual ParseResult parseAffineMapOfSSAIds(SmallVectorImpl< UnresolvedOperand > &operands, Attribute &map, StringRef attrName, NamedAttrList &attrs, Delimiter delimiter=Delimiter::Square)=0
Parses an affine map attribute where dims and symbols are SSA operands.
ParseResult parseAssignmentList(SmallVectorImpl< Argument > &lhs, SmallVectorImpl< UnresolvedOperand > &rhs)
Parse a list of assignments of the form (x1 = y1, x2 = y2, ...)
virtual ParseResult resolveOperand(const UnresolvedOperand &operand, Type type, SmallVectorImpl< Value > &result)=0
Resolve an operand to an SSA value, emitting an error on failure.
ParseResult resolveOperands(Operands &&operands, Type type, SmallVectorImpl< Value > &result)
Resolve a list of operands to SSA values, emitting an error on failure, or appending the results to t...
virtual ParseResult parseOperand(UnresolvedOperand &result, bool allowResultNumber=true)=0
Parse a single SSA value operand name along with a result number if allowResultNumber is true.
virtual ParseResult parseAffineExprOfSSAIds(SmallVectorImpl< UnresolvedOperand > &dimOperands, SmallVectorImpl< UnresolvedOperand > &symbOperands, AffineExpr &expr)=0
Parses an affine expression where dims and symbols are SSA operands.
virtual ParseResult parseOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None, bool allowResultNumber=true, int requiredOperandCount=-1)=0
Parse zero or more SSA comma-separated operand references with a specified surrounding delimiter,...
This is a pure-virtual base class that exposes the asmprinter hooks necessary to implement a custom p...
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
virtual void printAffineExprOfSSAIds(AffineExpr expr, ValueRange dimOperands, ValueRange symOperands)=0
Prints an affine expression of SSA ids with SSA id names used instead of dims and symbols.
virtual void printAffineMapOfSSAIds(AffineMapAttr mapAttr, ValueRange operands)=0
Prints an affine map of SSA ids, where SSA id names are used in place of dims/symbols.
virtual void printRegion(Region &blocks, bool printEntryBlockArgs=true, bool printBlockTerminators=true, bool printEmptyBlock=false)=0
Prints a region.
virtual void printRegionArgument(BlockArgument arg, ArrayRef< NamedAttribute > argAttrs={}, bool omitType=false)=0
Print a block argument in the usual format of: ssaName : type {attr1=42} loc("here") where location p...
virtual void printOperand(Value value)=0
Print implementations for various things an operation contains.
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Definition Builders.cpp:439
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
This class represents a single result from folding an operation.
A trait of region holding operations that defines a new scope for polyhedral optimization purposes.
This class provides the API for ops that are known to be isolated from above.
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:44
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:774
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
OperandRange operand_range
Definition Operation.h:396
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:247
operand_range::iterator operand_iterator
Definition Operation.h:397
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class represents a point being branched from in the methods of the RegionBranchOpInterface.
bool isParent() const
Returns true if branching from the parent op.
RegionBranchTerminatorOpInterface getTerminatorPredecessorOrNull() const
Returns the terminator if branching from a region.
This class represents a successor of a region.
Region * getSuccessor() const
Return the given region successor.
bool isOperation() const
Return true if the successor is an operation.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
bool empty()
Definition Region.h:60
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:213
bool hasOneBlock()
Return true if this region has exactly one block.
Definition Region.h:68
RewritePatternSet & insert(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void eraseBlock(Block *block)
This method erases all operations in a block.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void finalizeOpModification(Operation *op)
This method is used to signal the end of an in-place modification of the given operation.
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
virtual void replaceUsesWithIf(Value from, Value to, function_ref< bool(OpOperand &)> functor, bool *allUsesReplaced=nullptr)
Find uses of from and replace them with to if the functor returns true.
virtual void inlineBlockBefore(Block *source, Block *dest, Block::iterator before, ValueRange argValues={})
Inline the operations of block 'source' into block 'dest' before the given position.
void mergeBlocks(Block *source, Block *dest, ValueRange argValues={})
Inline the operations of block 'source' into the end of block 'dest'.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
virtual void startOpModification(Operation *op)
This method is used to notify the rewriter that an in-place operation modification is about to happen...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class represents a specific instance of an effect.
std::vector< SmallVector< int64_t, 8 > > operandExprStack
static Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIndex() const
Definition Types.cpp:56
A variable that can be added to the constraint set as a "column".
static bool compare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
Return "true" if "lhs cmp rhs" was proven to hold.
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
type_range getType() const
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
AffineBound represents a lower or upper bound in the for operation.
Definition AffineOps.h:218
An AffineValueMap is an affine map plus its ML value operands and results for analysis purposes.
LogicalResult canonicalize()
Attempts to canonicalize the map and operands.
ArrayRef< Value > getOperands() const
AffineExpr getResult(unsigned i)
void reset(AffineMap map, ValueRange operands, ValueRange results={})
static void difference(const AffineValueMap &a, const AffineValueMap &b, AffineValueMap *res)
Return the value map that is the difference of value maps 'a' and 'b', represented as an affine map a...
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
constexpr auto RecursivelySpeculatable
Speculatability
This enum is returned from the getSpeculatability method in the ConditionallySpeculatable op interfac...
constexpr auto NotSpeculatable
void buildAffineLoopNest(OpBuilder &builder, Location loc, ArrayRef< int64_t > lbs, ArrayRef< int64_t > ubs, ArrayRef< int64_t > steps, function_ref< void(OpBuilder &, Location, ValueRange)> bodyBuilderFn=nullptr)
Builds a perfect nest of affine.for loops, i.e., each loop except the innermost one contains only ano...
AffineApplyOp makeComposedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Returns a composed AffineApplyOp by composing map and operands with other AffineApplyOps supplying th...
void extractForInductionVars(ArrayRef< AffineForOp > forInsts, SmallVectorImpl< Value > *ivs)
Extracts the induction variables from a list of AffineForOps and places them in the output argument i...
bool isValidDim(Value value)
Returns true if the given Value can be used as a dimension id in the region of the closest surroundin...
bool isAffineInductionVar(Value val)
Returns true if the provided value is the induction variable of an AffineForOp or AffineParallelOp.
SmallVector< OpFoldResult > makeComposedFoldedMultiResultAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Variant of makeComposedFoldedAffineApply suitable for multi-result maps.
OpFoldResult computeProduct(Location loc, OpBuilder &builder, ArrayRef< OpFoldResult > terms)
Return the product of terms, creating an affine.apply if any of them are non-constant values.
AffineForOp getForInductionVarOwner(Value val)
Returns the loop parent of an induction variable.
void canonicalizeMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands)
Modifies both map and operands in-place so as to:
OpFoldResult makeComposedFoldedAffineMax(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
Constructs an AffineMinOp that computes a maximum across the results of applying map to operands,...
bool isAffineForInductionVar(Value val)
Returns true if the provided value is the induction variable of an AffineForOp.
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Constructs an AffineApplyOp that applies map to operands after composing the map with the maps of any...
OpFoldResult makeComposedFoldedAffineMin(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
Constructs an AffineMinOp that computes a minimum across the results of applying map to operands,...
bool isTopLevelValue(Value value)
A utility function to check if a value is defined at the top level of an op with trait AffineScope or...
Region * getAffineAnalysisScope(Operation *op)
Returns the closest region enclosing op that is held by a non-affine operation; nullptr if there is n...
void fullyComposeAffineMapAndOperands(AffineMap *map, SmallVectorImpl< Value > *operands, bool composeAffineMin=false)
Given an affine map map and its input operands, this method composes into map, maps of AffineApplyOps...
void canonicalizeSetAndOperands(IntegerSet *set, SmallVectorImpl< Value > *operands)
Canonicalizes an integer set the same way canonicalizeMapAndOperands does for affine maps.
void extractInductionVars(ArrayRef< Operation * > affineOps, SmallVectorImpl< Value > &ivs)
Extracts the induction variables from a list of either AffineForOp or AffineParallelOp and places the...
bool isValidSymbol(Value value)
Returns true if the given value can be used as a symbol in the region of the closest surrounding op t...
AffineParallelOp getAffineParallelInductionVarOwner(Value val)
Returns true if the provided value is among the induction variables of an AffineParallelOp.
Region * getAffineScope(Operation *op)
Returns the closest region enclosing op that is held by an operation with trait AffineScope; nullptr ...
ParseResult parseDimAndSymbolList(OpAsmParser &parser, SmallVectorImpl< Value > &operands, unsigned &numDims)
Parses dimension and symbol list.
bool isAffineParallelInductionVar(Value val)
Returns true if val is the induction variable of an AffineParallelOp.
AffineMinOp makeComposedAffineMin(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands)
Returns an AffineMinOp obtained by composing map and operands with AffineApplyOps supplying those ope...
LogicalResult foldMemRefCast(Operation *op, Value inner=nullptr)
This is a common utility used for patterns of the form "someop(memref.cast) -> someop".
Definition MemRefOps.cpp:47
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
MemRefType getMemRefType(T &&t)
Convenience method to abbreviate casting getType().
Include the generated interface declarations.
AffineMap simplifyAffineMap(AffineMap map)
Simplifies an affine map by simplifying its underlying AffineExpr results.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
SmallVector< OpFoldResult > getMixedValues(ArrayRef< int64_t > staticValues, ValueRange dynamicValues, MLIRContext *context)
Return a vector of OpFoldResults with the same size a staticValues, but all elements for which Shaped...
OpFoldResult getAsIndexOpFoldResult(MLIRContext *ctx, int64_t val)
Convert int64_t to integer attributes of index type and return them as OpFoldResult.
AffineMap removeDuplicateExprs(AffineMap map)
Returns a map with the same dimension and symbol count as map, but whose results are the unique affin...
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
std::function< SmallVector< Value >( OpBuilder &b, Location loc, ArrayRef< BlockArgument > newBbArgs)> NewYieldValuesFn
A function that returns the additional yielded values during replaceWithAdditionalYields.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
std::optional< int64_t > getBoundForAffineExpr(AffineExpr expr, unsigned numDims, unsigned numSymbols, ArrayRef< std::optional< int64_t > > constLowerBounds, ArrayRef< std::optional< int64_t > > constUpperBounds, bool isUpper)
Get a lower or upper (depending on isUpper) bound for expr while using the constant lower and upper b...
SmallVector< int64_t > delinearize(int64_t linearIndex, ArrayRef< int64_t > strides)
Given the strides together with a linear index in the dimension space, return the vector-space offset...
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
bool isPure(Operation *op)
Returns true if the given operation is pure, i.e., is speculatable that does not touch memory.
AffineExprKind
Definition AffineExpr.h:40
@ CeilDiv
RHS of ceildiv is always a constant or a symbolic expression.
Definition AffineExpr.h:50
@ Mod
RHS of mod is always a constant or a symbolic expression with a positive value.
Definition AffineExpr.h:46
@ DimId
Dimensional identifier.
Definition AffineExpr.h:59
@ FloorDiv
RHS of floordiv is always a constant or a symbolic expression.
Definition AffineExpr.h:48
@ SymbolId
Symbolic identifier.
Definition AffineExpr.h:61
AffineExpr getAffineBinaryOpExpr(AffineExprKind kind, AffineExpr lhs, AffineExpr rhs)
detail::constant_int_predicate_matcher m_Zero()
Matches a constant scalar / vector splat / tensor splat integer zero.
Definition Matchers.h:442
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
AffineExpr getAffineConstantExpr(int64_t constant, MLIRContext *context)
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
AffineMap foldAttributesIntoMap(Builder &b, AffineMap map, ArrayRef< OpFoldResult > operands, SmallVector< Value > &remainingValues)
Fold all attributes among the given operands into the affine map.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
AffineExpr getAffineSymbolExpr(unsigned position, MLIRContext *context)
Canonicalize the affine map result expression order of an affine min/max operation.
LogicalResult matchAndRewrite(T affineOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(T affineOp, PatternRewriter &rewriter) const override
Remove duplicated expressions in affine min/max ops.
LogicalResult matchAndRewrite(T affineOp, PatternRewriter &rewriter) const override
Merge an affine min/max op to its consumers if its consumer is also an affine min/max op.
LogicalResult matchAndRewrite(T affineOp, PatternRewriter &rewriter) const override
This is the representation of an operand reference.
This class represents a listener that may be used to hook into various actions within an OpBuilder.
Definition Builders.h:288
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
This represents an operation in an abstracted form, suitable for use with the builder APIs.