1 //===- AffineCanonicalizationUtils.cpp - Affine Canonicalization in SCF ---===// 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 // Utility functions to canonicalize affine ops within SCF op regions. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "mlir/Dialect/SCF/Utils/AffineCanonicalizationUtils.h" 14 #include "mlir/Dialect/Affine/Analysis/AffineStructures.h" 15 #include "mlir/Dialect/Affine/IR/AffineOps.h" 16 #include "mlir/Dialect/SCF/SCF.h" 17 #include "mlir/Dialect/Utils/StaticValueUtils.h" 18 #include "mlir/IR/AffineMap.h" 19 #include "mlir/IR/Matchers.h" 20 #include "mlir/IR/PatternMatch.h" 21 #include "llvm/Support/Debug.h" 22 23 #define DEBUG_TYPE "mlir-scf-affine-utils" 24 25 using namespace mlir; 26 27 static void unpackOptionalValues(ArrayRef<Optional<Value>> source, 28 SmallVector<Value> &target) { 29 target = llvm::to_vector<4>(llvm::map_range(source, [](Optional<Value> val) { 30 return val.hasValue() ? *val : Value(); 31 })); 32 } 33 34 /// Bound an identifier `pos` in a given FlatAffineValueConstraints with 35 /// constraints drawn from an affine map. Before adding the constraint, the 36 /// dimensions/symbols of the affine map are aligned with `constraints`. 37 /// `operands` are the SSA Value operands used with the affine map. 38 /// Note: This function adds a new symbol column to the `constraints` for each 39 /// dimension/symbol that exists in the affine map but not in `constraints`. 40 static LogicalResult alignAndAddBound(FlatAffineValueConstraints &constraints, 41 FlatAffineConstraints::BoundType type, 42 unsigned pos, AffineMap map, 43 ValueRange operands) { 44 SmallVector<Value> dims, syms, newSyms; 45 unpackOptionalValues(constraints.getMaybeDimValues(), dims); 46 unpackOptionalValues(constraints.getMaybeSymbolValues(), syms); 47 48 AffineMap alignedMap = 49 alignAffineMapWithValues(map, operands, dims, syms, &newSyms); 50 for (unsigned i = syms.size(); i < newSyms.size(); ++i) 51 constraints.appendSymbolId(newSyms[i]); 52 return constraints.addBound(type, pos, alignedMap); 53 } 54 55 /// Add `val` to each result of `map`. 56 static AffineMap addConstToResults(AffineMap map, int64_t val) { 57 SmallVector<AffineExpr> newResults; 58 for (AffineExpr r : map.getResults()) 59 newResults.push_back(r + val); 60 return AffineMap::get(map.getNumDims(), map.getNumSymbols(), newResults, 61 map.getContext()); 62 } 63 64 /// This function tries to canonicalize min/max operations by proving that their 65 /// value is bounded by the same lower and upper bound. In that case, the 66 /// operation can be folded away. 67 /// 68 /// Bounds are computed by FlatAffineValueConstraints. Invariants required for 69 /// finding/proving bounds should be supplied via `constraints`. 70 /// 71 /// 1. Add dimensions for `op` and `opBound` (lower or upper bound of `op`). 72 /// 2. Compute an upper bound of `op` (in case of `isMin`) or a lower bound (in 73 /// case of `!isMin`) and bind it to `opBound`. SSA values that are used in 74 /// `op` but are not part of `constraints`, are added as extra symbols. 75 /// 3. For each result of `op`: Add result as a dimension `r_i`. Prove that: 76 /// * If `isMin`: r_i >= opBound 77 /// * If `isMax`: r_i <= opBound 78 /// If this is the case, ub(op) == lb(op). 79 /// 4. Replace `op` with `opBound`. 80 /// 81 /// In summary, the following constraints are added throughout this function. 82 /// Note: `invar` are dimensions added by the caller to express the invariants. 83 /// (Showing only the case where `isMin`.) 84 /// 85 /// invar | op | opBound | r_i | extra syms... | const | eq/ineq 86 /// ------+-------+---------+-----+---------------+-------+------------------- 87 /// (various eq./ineq. constraining `invar`, added by the caller) 88 /// ... | 0 | 0 | 0 | 0 | ... | ... 89 /// ------+-------+---------+-----+---------------+-------+------------------- 90 /// (various ineq. constraining `op` in terms of `op` operands (`invar` and 91 /// extra `op` operands "extra syms" that are not in `invar`)). 92 /// ... | -1 | 0 | 0 | ... | ... | >= 0 93 /// ------+-------+---------+-----+---------------+-------+------------------- 94 /// (set `opBound` to `op` upper bound in terms of `invar` and "extra syms") 95 /// ... | 0 | -1 | 0 | ... | ... | = 0 96 /// ------+-------+---------+-----+---------------+-------+------------------- 97 /// (for each `op` map result r_i: set r_i to corresponding map result, 98 /// prove that r_i >= minOpUb via contradiction) 99 /// ... | 0 | 0 | -1 | ... | ... | = 0 100 /// 0 | 0 | 1 | -1 | 0 | -1 | >= 0 101 /// 102 static LogicalResult 103 canonicalizeMinMaxOp(RewriterBase &rewriter, Operation *op, AffineMap map, 104 ValueRange operands, bool isMin, 105 FlatAffineValueConstraints constraints) { 106 RewriterBase::InsertionGuard guard(rewriter); 107 unsigned numResults = map.getNumResults(); 108 109 // Add a few extra dimensions. 110 unsigned dimOp = constraints.appendDimId(); // `op` 111 unsigned dimOpBound = constraints.appendDimId(); // `op` lower/upper bound 112 unsigned resultDimStart = constraints.appendDimId(/*num=*/numResults); 113 114 // Add an inequality for each result expr_i of map: 115 // isMin: op <= expr_i, !isMin: op >= expr_i 116 auto boundType = 117 isMin ? FlatAffineConstraints::UB : FlatAffineConstraints::LB; 118 // Upper bounds are exclusive, so add 1. (`affine.min` ops are inclusive.) 119 AffineMap mapLbUb = isMin ? addConstToResults(map, 1) : map; 120 if (failed( 121 alignAndAddBound(constraints, boundType, dimOp, mapLbUb, operands))) 122 return failure(); 123 124 // Try to compute a lower/upper bound for op, expressed in terms of the other 125 // `dims` and extra symbols. 126 SmallVector<AffineMap> opLb(1), opUb(1); 127 constraints.getSliceBounds(dimOp, 1, rewriter.getContext(), &opLb, &opUb); 128 AffineMap sliceBound = isMin ? opUb[0] : opLb[0]; 129 // TODO: `getSliceBounds` may return multiple bounds at the moment. This is 130 // a TODO of `getSliceBounds` and not handled here. 131 if (!sliceBound || sliceBound.getNumResults() != 1) 132 return failure(); // No or multiple bounds found. 133 // Recover the inclusive UB in the case of an `affine.min`. 134 AffineMap boundMap = isMin ? addConstToResults(sliceBound, -1) : sliceBound; 135 136 // Add an equality: Set dimOpBound to computed bound. 137 // Add back dimension for op. (Was removed by `getSliceBounds`.) 138 AffineMap alignedBoundMap = boundMap.shiftDims(/*shift=*/1, /*offset=*/dimOp); 139 if (failed(constraints.addBound(FlatAffineConstraints::EQ, dimOpBound, 140 alignedBoundMap))) 141 return failure(); 142 143 // If the constraint system is empty, there is an inconsistency. (E.g., this 144 // can happen if loop lb > ub.) 145 if (constraints.isEmpty()) 146 return failure(); 147 148 // In the case of `isMin` (`!isMin` is inversed): 149 // Prove that each result of `map` has a lower bound that is equal to (or 150 // greater than) the upper bound of `op` (`dimOpBound`). In that case, `op` 151 // can be replaced with the bound. I.e., prove that for each result 152 // expr_i (represented by dimension r_i): 153 // 154 // r_i >= opBound 155 // 156 // To prove this inequality, add its negation to the constraint set and prove 157 // that the constraint set is empty. 158 for (unsigned i = resultDimStart; i < resultDimStart + numResults; ++i) { 159 FlatAffineValueConstraints newConstr(constraints); 160 161 // Add an equality: r_i = expr_i 162 // Note: These equalities could have been added earlier and used to express 163 // minOp <= expr_i. However, then we run the risk that `getSliceBounds` 164 // computes minOpUb in terms of r_i dims, which is not desired. 165 if (failed(alignAndAddBound(newConstr, FlatAffineConstraints::EQ, i, 166 map.getSubMap({i - resultDimStart}), operands))) 167 return failure(); 168 169 // If `isMin`: Add inequality: r_i < opBound 170 // equiv.: opBound - r_i - 1 >= 0 171 // If `!isMin`: Add inequality: r_i > opBound 172 // equiv.: -opBound + r_i - 1 >= 0 173 SmallVector<int64_t> ineq(newConstr.getNumCols(), 0); 174 ineq[dimOpBound] = isMin ? 1 : -1; 175 ineq[i] = isMin ? -1 : 1; 176 ineq[newConstr.getNumCols() - 1] = -1; 177 newConstr.addInequality(ineq); 178 if (!newConstr.isEmpty()) 179 return failure(); 180 } 181 182 // Lower and upper bound of `op` are equal. Replace `minOp` with its bound. 183 AffineMap newMap = alignedBoundMap; 184 SmallVector<Value> newOperands; 185 unpackOptionalValues(constraints.getMaybeDimAndSymbolValues(), newOperands); 186 // If dims/symbols have known constant values, use those in order to simplify 187 // the affine map further. 188 for (int64_t i = 0, e = constraints.getNumIds(); i < e; ++i) { 189 // Skip unused operands and operands that are already constants. 190 if (!newOperands[i] || getConstantIntValue(newOperands[i])) 191 continue; 192 if (auto bound = constraints.getConstantBound(FlatAffineConstraints::EQ, i)) 193 newOperands[i] = 194 rewriter.create<arith::ConstantIndexOp>(op->getLoc(), *bound); 195 } 196 mlir::canonicalizeMapAndOperands(&newMap, &newOperands); 197 rewriter.setInsertionPoint(op); 198 rewriter.replaceOpWithNewOp<AffineApplyOp>(op, newMap, newOperands); 199 return success(); 200 } 201 202 static LogicalResult 203 addLoopRangeConstraints(FlatAffineValueConstraints &constraints, Value iv, 204 Value lb, Value ub, Value step, 205 RewriterBase &rewriter) { 206 // FlatAffineConstraints does not support semi-affine expressions. 207 // Therefore, only constant step values are supported. 208 auto stepInt = getConstantIntValue(step); 209 if (!stepInt) 210 return failure(); 211 212 unsigned dimIv = constraints.appendDimId(iv); 213 unsigned dimLb = constraints.appendDimId(lb); 214 unsigned dimUb = constraints.appendDimId(ub); 215 216 // If loop lower/upper bounds are constant: Add EQ constraint. 217 Optional<int64_t> lbInt = getConstantIntValue(lb); 218 Optional<int64_t> ubInt = getConstantIntValue(ub); 219 if (lbInt) 220 constraints.addBound(FlatAffineConstraints::EQ, dimLb, *lbInt); 221 if (ubInt) 222 constraints.addBound(FlatAffineConstraints::EQ, dimUb, *ubInt); 223 224 // Lower bound: iv >= lb (equiv.: iv - lb >= 0) 225 SmallVector<int64_t> ineqLb(constraints.getNumCols(), 0); 226 ineqLb[dimIv] = 1; 227 ineqLb[dimLb] = -1; 228 constraints.addInequality(ineqLb); 229 230 // Upper bound 231 AffineExpr ivUb; 232 if (lbInt && ubInt && (*lbInt + *stepInt >= *ubInt)) { 233 // The loop has at most one iteration. 234 // iv < lb + 1 235 // TODO: Try to derive this constraint by simplifying the expression in 236 // the else-branch. 237 ivUb = rewriter.getAffineDimExpr(dimLb) + 1; 238 } else { 239 // The loop may have more than one iteration. 240 // iv < lb + step * ((ub - lb - 1) floorDiv step) + 1 241 AffineExpr exprLb = lbInt ? rewriter.getAffineConstantExpr(*lbInt) 242 : rewriter.getAffineDimExpr(dimLb); 243 AffineExpr exprUb = ubInt ? rewriter.getAffineConstantExpr(*ubInt) 244 : rewriter.getAffineDimExpr(dimUb); 245 ivUb = exprLb + 1 + (*stepInt * ((exprUb - exprLb - 1).floorDiv(*stepInt))); 246 } 247 auto map = AffineMap::get( 248 /*dimCount=*/constraints.getNumDimIds(), 249 /*symbolCount=*/constraints.getNumSymbolIds(), /*result=*/ivUb); 250 251 return constraints.addBound(FlatAffineConstraints::UB, dimIv, map); 252 } 253 254 /// Canonicalize min/max operations in the context of for loops with a known 255 /// range. Call `canonicalizeMinMaxOp` and add the following constraints to 256 /// the constraint system (along with the missing dimensions): 257 /// 258 /// * iv >= lb 259 /// * iv < lb + step * ((ub - lb - 1) floorDiv step) + 1 260 /// 261 /// Note: Due to limitations of FlatAffineConstraints, only constant step sizes 262 /// are currently supported. 263 LogicalResult scf::canonicalizeMinMaxOpInLoop(RewriterBase &rewriter, 264 Operation *op, AffineMap map, 265 ValueRange operands, bool isMin, 266 LoopMatcherFn loopMatcher) { 267 FlatAffineValueConstraints constraints; 268 DenseSet<Value> allIvs; 269 270 // Find all iteration variables among `minOp`'s operands add constrain them. 271 for (Value operand : operands) { 272 // Skip duplicate ivs. 273 if (llvm::find(allIvs, operand) != allIvs.end()) 274 continue; 275 276 // If `operand` is an iteration variable: Find corresponding loop 277 // bounds and step. 278 Value iv = operand; 279 Value lb, ub, step; 280 if (failed(loopMatcher(operand, lb, ub, step))) 281 continue; 282 allIvs.insert(iv); 283 284 if (failed( 285 addLoopRangeConstraints(constraints, iv, lb, ub, step, rewriter))) 286 return failure(); 287 } 288 289 return canonicalizeMinMaxOp(rewriter, op, map, operands, isMin, constraints); 290 } 291 292 /// Try to simplify a min/max operation `op` after loop peeling. This function 293 /// can simplify min/max operations such as (ub is the previous upper bound of 294 /// the unpeeled loop): 295 /// ``` 296 /// #map = affine_map<(d0)[s0, s1] -> (s0, -d0 + s1)> 297 /// %r = affine.min #affine.min #map(%iv)[%step, %ub] 298 /// ``` 299 /// and rewrites them into (in the case the peeled loop): 300 /// ``` 301 /// %r = %step 302 /// ``` 303 /// min/max operations inside the partial iteration are rewritten in a similar 304 /// way. 305 /// 306 /// This function builds up a set of constraints, capable of proving that: 307 /// * Inside the peeled loop: min(step, ub - iv) == step 308 /// * Inside the partial iteration: min(step, ub - iv) == ub - iv 309 /// 310 /// Returns `success` if the given operation was replaced by a new operation; 311 /// `failure` otherwise. 312 /// 313 /// Note: `ub` is the previous upper bound of the loop (before peeling). 314 /// `insideLoop` must be true for min/max ops inside the loop and false for 315 /// affine.min ops inside the partial iteration. For an explanation of the other 316 /// parameters, see comment of `canonicalizeMinMaxOpInLoop`. 317 LogicalResult scf::rewritePeeledMinMaxOp(RewriterBase &rewriter, Operation *op, 318 AffineMap map, ValueRange operands, 319 bool isMin, Value iv, Value ub, 320 Value step, bool insideLoop) { 321 FlatAffineValueConstraints constraints; 322 constraints.appendDimId({iv, ub, step}); 323 if (auto constUb = getConstantIntValue(ub)) 324 constraints.addBound(FlatAffineConstraints::EQ, 1, *constUb); 325 if (auto constStep = getConstantIntValue(step)) 326 constraints.addBound(FlatAffineConstraints::EQ, 2, *constStep); 327 328 // Add loop peeling invariant. This is the main piece of knowledge that 329 // enables AffineMinOp simplification. 330 if (insideLoop) { 331 // ub - iv >= step (equiv.: -iv + ub - step + 0 >= 0) 332 // Intuitively: Inside the peeled loop, every iteration is a "full" 333 // iteration, i.e., step divides the iteration space `ub - lb` evenly. 334 constraints.addInequality({-1, 1, -1, 0}); 335 } else { 336 // ub - iv < step (equiv.: iv + -ub + step - 1 >= 0) 337 // Intuitively: `iv` is the split bound here, i.e., the iteration variable 338 // value of the very last iteration (in the unpeeled loop). At that point, 339 // there are less than `step` elements remaining. (Otherwise, the peeled 340 // loop would run for at least one more iteration.) 341 constraints.addInequality({1, -1, 1, -1}); 342 } 343 344 return canonicalizeMinMaxOp(rewriter, op, map, operands, isMin, constraints); 345 } 346