1 //===- Utils.cpp ---- Utilities for affine dialect transformation ---------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file implements miscellaneous transformation utilities for the Affine 10 // dialect. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "mlir/Dialect/Affine/Utils.h" 15 #include "mlir/Dialect/Affine/IR/AffineOps.h" 16 #include "mlir/IR/BlockAndValueMapping.h" 17 #include "mlir/IR/Function.h" 18 #include "mlir/IR/IntegerSet.h" 19 #include "mlir/IR/PatternMatch.h" 20 #include "llvm/Support/Debug.h" 21 #include "llvm/Support/raw_ostream.h" 22 23 using namespace mlir; 24 25 /// Promotes the `then` or the `else` block of `ifOp` (depending on whether 26 /// `elseBlock` is false or true) into `ifOp`'s containing block, and discards 27 /// the rest of the op. 28 static void promoteIfBlock(AffineIfOp ifOp, bool elseBlock) { 29 if (elseBlock) 30 assert(ifOp.hasElse() && "else block expected"); 31 32 Block *destBlock = ifOp.getOperation()->getBlock(); 33 Block *srcBlock = elseBlock ? ifOp.getElseBlock() : ifOp.getThenBlock(); 34 destBlock->getOperations().splice( 35 Block::iterator(ifOp), srcBlock->getOperations(), srcBlock->begin(), 36 std::prev(srcBlock->end())); 37 ifOp.erase(); 38 } 39 40 /// Returns the outermost affine.for/parallel op that the `ifOp` is invariant 41 /// on. The `ifOp` could be hoisted and placed right before such an operation. 42 /// This method assumes that the ifOp has been canonicalized (to be correct and 43 /// effective). 44 static Operation *getOutermostInvariantForOp(AffineIfOp ifOp) { 45 // Walk up the parents past all for op that this conditional is invariant on. 46 auto ifOperands = ifOp.getOperands(); 47 auto res = ifOp.getOperation(); 48 while (!isa<FuncOp>(res->getParentOp())) { 49 auto *parentOp = res->getParentOp(); 50 if (auto forOp = dyn_cast<AffineForOp>(parentOp)) { 51 if (llvm::is_contained(ifOperands, forOp.getInductionVar())) 52 break; 53 } else if (auto parallelOp = dyn_cast<AffineParallelOp>(parentOp)) { 54 for (auto iv : parallelOp.getIVs()) 55 if (llvm::is_contained(ifOperands, iv)) 56 break; 57 } else if (!isa<AffineIfOp>(parentOp)) { 58 // Won't walk up past anything other than affine.for/if ops. 59 break; 60 } 61 // You can always hoist up past any affine.if ops. 62 res = parentOp; 63 } 64 return res; 65 } 66 67 /// A helper for the mechanics of mlir::hoistAffineIfOp. Hoists `ifOp` just over 68 /// `hoistOverOp`. Returns the new hoisted op if any hoisting happened, 69 /// otherwise the same `ifOp`. 70 static AffineIfOp hoistAffineIfOp(AffineIfOp ifOp, Operation *hoistOverOp) { 71 // No hoisting to do. 72 if (hoistOverOp == ifOp) 73 return ifOp; 74 75 // Create the hoisted 'if' first. Then, clone the op we are hoisting over for 76 // the else block. Then drop the else block of the original 'if' in the 'then' 77 // branch while promoting its then block, and analogously drop the 'then' 78 // block of the original 'if' from the 'else' branch while promoting its else 79 // block. 80 BlockAndValueMapping operandMap; 81 OpBuilder b(hoistOverOp); 82 auto hoistedIfOp = b.create<AffineIfOp>(ifOp.getLoc(), ifOp.getIntegerSet(), 83 ifOp.getOperands(), 84 /*elseBlock=*/true); 85 86 // Create a clone of hoistOverOp to use for the else branch of the hoisted 87 // conditional. The else block may get optimized away if empty. 88 Operation *hoistOverOpClone = nullptr; 89 // We use this unique name to identify/find `ifOp`'s clone in the else 90 // version. 91 Identifier idForIfOp = b.getIdentifier("__mlir_if_hoisting"); 92 operandMap.clear(); 93 b.setInsertionPointAfter(hoistOverOp); 94 // We'll set an attribute to identify this op in a clone of this sub-tree. 95 ifOp.setAttr(idForIfOp, b.getBoolAttr(true)); 96 hoistOverOpClone = b.clone(*hoistOverOp, operandMap); 97 98 // Promote the 'then' block of the original affine.if in the then version. 99 promoteIfBlock(ifOp, /*elseBlock=*/false); 100 101 // Move the then version to the hoisted if op's 'then' block. 102 auto *thenBlock = hoistedIfOp.getThenBlock(); 103 thenBlock->getOperations().splice(thenBlock->begin(), 104 hoistOverOp->getBlock()->getOperations(), 105 Block::iterator(hoistOverOp)); 106 107 // Find the clone of the original affine.if op in the else version. 108 AffineIfOp ifCloneInElse; 109 hoistOverOpClone->walk([&](AffineIfOp ifClone) { 110 if (!ifClone.getAttr(idForIfOp)) 111 return WalkResult::advance(); 112 ifCloneInElse = ifClone; 113 return WalkResult::interrupt(); 114 }); 115 assert(ifCloneInElse && "if op clone should exist"); 116 // For the else block, promote the else block of the original 'if' if it had 117 // one; otherwise, the op itself is to be erased. 118 if (!ifCloneInElse.hasElse()) 119 ifCloneInElse.erase(); 120 else 121 promoteIfBlock(ifCloneInElse, /*elseBlock=*/true); 122 123 // Move the else version into the else block of the hoisted if op. 124 auto *elseBlock = hoistedIfOp.getElseBlock(); 125 elseBlock->getOperations().splice( 126 elseBlock->begin(), hoistOverOpClone->getBlock()->getOperations(), 127 Block::iterator(hoistOverOpClone)); 128 129 return hoistedIfOp; 130 } 131 132 // Returns success if any hoisting happened. 133 LogicalResult mlir::hoistAffineIfOp(AffineIfOp ifOp, bool *folded) { 134 // Apply canonicalization patterns and folding - this is necessary for the 135 // hoisting check to be correct (operands should be composed), and to be more 136 // effective (no unused operands). Since the pattern rewriter's folding is 137 // entangled with application of patterns, we may fold/end up erasing the op, 138 // in which case we return with `folded` being set. 139 OwningRewritePatternList patterns; 140 AffineIfOp::getCanonicalizationPatterns(patterns, ifOp.getContext()); 141 bool erased; 142 applyOpPatternsAndFold(ifOp, patterns, &erased); 143 if (erased) { 144 if (folded) 145 *folded = true; 146 return failure(); 147 } 148 if (folded) 149 *folded = false; 150 151 // The folding above should have ensured this, but the affine.if's 152 // canonicalization is missing composition of affine.applys into it. 153 assert(llvm::all_of(ifOp.getOperands(), 154 [](Value v) { 155 return isTopLevelValue(v) || isForInductionVar(v); 156 }) && 157 "operands not composed"); 158 159 // We are going hoist as high as possible. 160 // TODO: this could be customized in the future. 161 auto *hoistOverOp = getOutermostInvariantForOp(ifOp); 162 163 AffineIfOp hoistedIfOp = ::hoistAffineIfOp(ifOp, hoistOverOp); 164 // Nothing to hoist over. 165 if (hoistedIfOp == ifOp) 166 return failure(); 167 168 // Canonicalize to remove dead else blocks (happens whenever an 'if' moves up 169 // a sequence of affine.fors that are all perfectly nested). 170 applyPatternsAndFoldGreedily( 171 hoistedIfOp.getParentWithTrait<OpTrait::IsIsolatedFromAbove>(), 172 std::move(patterns)); 173 174 return success(); 175 } 176