1 //===- RegionUtils.cpp - Region-related transformation utilities ----------===// 2 // 3 // Copyright 2019 The MLIR Authors. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 // ============================================================================= 17 18 #include "mlir/Transforms/RegionUtils.h" 19 #include "mlir/IR/Block.h" 20 #include "mlir/IR/Operation.h" 21 #include "mlir/IR/RegionGraphTraits.h" 22 #include "mlir/IR/Value.h" 23 24 #include "llvm/ADT/DepthFirstIterator.h" 25 #include "llvm/ADT/PostOrderIterator.h" 26 #include "llvm/ADT/SmallSet.h" 27 28 using namespace mlir; 29 30 void mlir::replaceAllUsesInRegionWith(Value *orig, Value *replacement, 31 Region ®ion) { 32 for (IROperand &use : llvm::make_early_inc_range(orig->getUses())) { 33 if (region.isAncestor(use.getOwner()->getParentRegion())) 34 use.set(replacement); 35 } 36 } 37 38 void mlir::visitUsedValuesDefinedAbove( 39 Region ®ion, Region &limit, function_ref<void(OpOperand *)> callback) { 40 assert(limit.isAncestor(®ion) && 41 "expected isolation limit to be an ancestor of the given region"); 42 43 // Collect proper ancestors of `limit` upfront to avoid traversing the region 44 // tree for every value. 45 SmallPtrSet<Region *, 4> properAncestors; 46 for (auto *reg = limit.getParentRegion(); reg != nullptr; 47 reg = reg->getParentRegion()) { 48 properAncestors.insert(reg); 49 } 50 51 region.walk([callback, &properAncestors](Operation *op) { 52 for (OpOperand &operand : op->getOpOperands()) 53 // Callback on values defined in a proper ancestor of region. 54 if (properAncestors.count(operand.get()->getParentRegion())) 55 callback(&operand); 56 }); 57 } 58 59 void mlir::visitUsedValuesDefinedAbove( 60 MutableArrayRef<Region> regions, function_ref<void(OpOperand *)> callback) { 61 for (Region ®ion : regions) 62 visitUsedValuesDefinedAbove(region, region, callback); 63 } 64 65 void mlir::getUsedValuesDefinedAbove(Region ®ion, Region &limit, 66 llvm::SetVector<Value *> &values) { 67 visitUsedValuesDefinedAbove(region, limit, [&](OpOperand *operand) { 68 values.insert(operand->get()); 69 }); 70 } 71 72 void mlir::getUsedValuesDefinedAbove(MutableArrayRef<Region> regions, 73 llvm::SetVector<Value *> &values) { 74 for (Region ®ion : regions) 75 getUsedValuesDefinedAbove(region, region, values); 76 } 77 78 //===----------------------------------------------------------------------===// 79 // Unreachable Block Elimination 80 //===----------------------------------------------------------------------===// 81 82 /// Erase the unreachable blocks within the provided regions. Returns success 83 /// if any blocks were erased, failure otherwise. 84 // TODO: We could likely merge this with the DCE algorithm below. 85 static LogicalResult eraseUnreachableBlocks(MutableArrayRef<Region> regions) { 86 // Set of blocks found to be reachable within a given region. 87 llvm::df_iterator_default_set<Block *, 16> reachable; 88 // If any blocks were found to be dead. 89 bool erasedDeadBlocks = false; 90 91 SmallVector<Region *, 1> worklist; 92 worklist.reserve(regions.size()); 93 for (Region ®ion : regions) 94 worklist.push_back(®ion); 95 while (!worklist.empty()) { 96 Region *region = worklist.pop_back_val(); 97 if (region->empty()) 98 continue; 99 100 // If this is a single block region, just collect the nested regions. 101 if (std::next(region->begin()) == region->end()) { 102 for (Operation &op : region->front()) 103 for (Region ®ion : op.getRegions()) 104 worklist.push_back(®ion); 105 continue; 106 } 107 108 // Mark all reachable blocks. 109 reachable.clear(); 110 for (Block *block : depth_first_ext(®ion->front(), reachable)) 111 (void)block /* Mark all reachable blocks */; 112 113 // Collect all of the dead blocks and push the live regions onto the 114 // worklist. 115 for (Block &block : llvm::make_early_inc_range(*region)) { 116 if (!reachable.count(&block)) { 117 block.dropAllDefinedValueUses(); 118 block.erase(); 119 erasedDeadBlocks = true; 120 continue; 121 } 122 123 // Walk any regions within this block. 124 for (Operation &op : block) 125 for (Region ®ion : op.getRegions()) 126 worklist.push_back(®ion); 127 } 128 } 129 130 return success(erasedDeadBlocks); 131 } 132 133 //===----------------------------------------------------------------------===// 134 // Dead Code Elimination 135 //===----------------------------------------------------------------------===// 136 137 namespace { 138 /// Data structure used to track which values have already been proved live. 139 /// 140 /// Because Operation's can have multiple results, this data structure tracks 141 /// liveness for both Value's and Operation's to avoid having to look through 142 /// all Operation results when analyzing a use. 143 /// 144 /// This data structure essentially tracks the dataflow lattice. 145 /// The set of values/ops proved live increases monotonically to a fixed-point. 146 class LiveMap { 147 public: 148 /// Value methods. 149 bool wasProvenLive(Value *value) { return liveValues.count(value); } 150 void setProvedLive(Value *value) { 151 changed |= liveValues.insert(value).second; 152 } 153 154 /// Operation methods. 155 bool wasProvenLive(Operation *op) { return liveOps.count(op); } 156 void setProvedLive(Operation *op) { changed |= liveOps.insert(op).second; } 157 158 /// Methods for tracking if we have reached a fixed-point. 159 void resetChanged() { changed = false; } 160 bool hasChanged() { return changed; } 161 162 private: 163 bool changed = false; 164 DenseSet<Value *> liveValues; 165 DenseSet<Operation *> liveOps; 166 }; 167 } // namespace 168 169 static bool isUseSpeciallyKnownDead(OpOperand &use, LiveMap &liveMap) { 170 Operation *owner = use.getOwner(); 171 unsigned operandIndex = use.getOperandNumber(); 172 // This pass generally treats all uses of an op as live if the op itself is 173 // considered live. However, for successor operands to terminators we need a 174 // finer-grained notion where we deduce liveness for operands individually. 175 // The reason for this is easiest to think about in terms of a classical phi 176 // node based SSA IR, where each successor operand is really an operand to a 177 // *separate* phi node, rather than all operands to the branch itself as with 178 // the block argument representation that MLIR uses. 179 // 180 // And similarly, because each successor operand is really an operand to a phi 181 // node, rather than to the terminator op itself, a terminator op can't e.g. 182 // "print" the value of a successor operand. 183 if (owner->isKnownTerminator()) { 184 if (auto arg = owner->getSuccessorBlockArgument(operandIndex)) 185 return !liveMap.wasProvenLive(*arg); 186 return false; 187 } 188 return false; 189 } 190 191 static void processValue(Value *value, LiveMap &liveMap) { 192 bool provedLive = llvm::any_of(value->getUses(), [&](OpOperand &use) { 193 if (isUseSpeciallyKnownDead(use, liveMap)) 194 return false; 195 return liveMap.wasProvenLive(use.getOwner()); 196 }); 197 if (provedLive) 198 liveMap.setProvedLive(value); 199 } 200 201 static bool isOpIntrinsicallyLive(Operation *op) { 202 // This pass doesn't modify the CFG, so terminators are never deleted. 203 if (!op->isKnownNonTerminator()) 204 return true; 205 // If the op has a side effect, we treat it as live. 206 if (!op->hasNoSideEffect()) 207 return true; 208 return false; 209 } 210 211 static void propagateLiveness(Region ®ion, LiveMap &liveMap); 212 static void propagateLiveness(Operation *op, LiveMap &liveMap) { 213 // All Value's are either a block argument or an op result. 214 // We call processValue on those cases. 215 216 // Recurse on any regions the op has. 217 for (Region ®ion : op->getRegions()) 218 propagateLiveness(region, liveMap); 219 220 // Process the op itself. 221 if (isOpIntrinsicallyLive(op)) { 222 liveMap.setProvedLive(op); 223 return; 224 } 225 for (Value *value : op->getResults()) 226 processValue(value, liveMap); 227 bool provedLive = llvm::any_of(op->getResults(), [&](Value *value) { 228 return liveMap.wasProvenLive(value); 229 }); 230 if (provedLive) 231 liveMap.setProvedLive(op); 232 } 233 234 static void propagateLiveness(Region ®ion, LiveMap &liveMap) { 235 if (region.empty()) 236 return; 237 238 for (Block *block : llvm::post_order(®ion.front())) { 239 // We process block arguments after the ops in the block, to promote 240 // faster convergence to a fixed point (we try to visit uses before defs). 241 for (Operation &op : llvm::reverse(block->getOperations())) 242 propagateLiveness(&op, liveMap); 243 for (Value *value : block->getArguments()) 244 processValue(value, liveMap); 245 } 246 } 247 248 static void eraseTerminatorSuccessorOperands(Operation *terminator, 249 LiveMap &liveMap) { 250 for (unsigned succI = 0, succE = terminator->getNumSuccessors(); 251 succI < succE; succI++) { 252 // Iterating successors in reverse is not strictly needed, since we 253 // aren't erasing any successors. But it is slightly more efficient 254 // since it will promote later operands of the terminator being erased 255 // first, reducing the quadratic-ness. 256 unsigned succ = succE - succI - 1; 257 for (unsigned argI = 0, argE = terminator->getNumSuccessorOperands(succ); 258 argI < argE; argI++) { 259 // Iterating args in reverse is needed for correctness, to avoid 260 // shifting later args when earlier args are erased. 261 unsigned arg = argE - argI - 1; 262 Value *value = terminator->getSuccessor(succ)->getArgument(arg); 263 if (!liveMap.wasProvenLive(value)) { 264 terminator->eraseSuccessorOperand(succ, arg); 265 } 266 } 267 } 268 } 269 270 static LogicalResult deleteDeadness(MutableArrayRef<Region> regions, 271 LiveMap &liveMap) { 272 bool erasedAnything = false; 273 for (Region ®ion : regions) { 274 if (region.empty()) 275 continue; 276 277 // We do the deletion in an order that deletes all uses before deleting 278 // defs. 279 // MLIR's SSA structural invariants guarantee that except for block 280 // arguments, the use-def graph is acyclic, so this is possible with a 281 // single walk of ops and then a final pass to clean up block arguments. 282 // 283 // To do this, we visit ops in an order that visits domtree children 284 // before domtree parents. A CFG post-order (with reverse iteration with a 285 // block) satisfies that without needing an explicit domtree calculation. 286 for (Block *block : llvm::post_order(®ion.front())) { 287 eraseTerminatorSuccessorOperands(block->getTerminator(), liveMap); 288 for (Operation &childOp : 289 llvm::make_early_inc_range(llvm::reverse(block->getOperations()))) { 290 erasedAnything |= 291 succeeded(deleteDeadness(childOp.getRegions(), liveMap)); 292 if (!liveMap.wasProvenLive(&childOp)) { 293 erasedAnything = true; 294 childOp.erase(); 295 } 296 } 297 } 298 // Delete block arguments. 299 // The entry block has an unknown contract with their enclosing block, so 300 // skip it. 301 for (Block &block : llvm::drop_begin(region.getBlocks(), 1)) { 302 // Iterate in reverse to avoid shifting later arguments when deleting 303 // earlier arguments. 304 for (unsigned i = 0, e = block.getNumArguments(); i < e; i++) 305 if (!liveMap.wasProvenLive(block.getArgument(e - i - 1))) { 306 block.eraseArgument(e - i - 1, /*updatePredTerms=*/false); 307 erasedAnything = true; 308 } 309 } 310 } 311 return success(erasedAnything); 312 } 313 314 // This function performs a simple dead code elimination algorithm over the 315 // given regions. 316 // 317 // The overall goal is to prove that Values are dead, which allows deleting ops 318 // and block arguments. 319 // 320 // This uses an optimistic algorithm that assumes everything is dead until 321 // proved otherwise, allowing it to delete recursively dead cycles. 322 // 323 // This is a simple fixed-point dataflow analysis algorithm on a lattice 324 // {Dead,Alive}. Because liveness flows backward, we generally try to 325 // iterate everything backward to speed up convergence to the fixed-point. This 326 // allows for being able to delete recursively dead cycles of the use-def graph, 327 // including block arguments. 328 // 329 // This function returns success if any operations or arguments were deleted, 330 // failure otherwise. 331 static LogicalResult runRegionDCE(MutableArrayRef<Region> regions) { 332 assert(regions.size() == 1); 333 334 LiveMap liveMap; 335 do { 336 liveMap.resetChanged(); 337 338 for (Region ®ion : regions) 339 propagateLiveness(region, liveMap); 340 } while (liveMap.hasChanged()); 341 342 return deleteDeadness(regions, liveMap); 343 } 344 345 //===----------------------------------------------------------------------===// 346 // Region Simplification 347 //===----------------------------------------------------------------------===// 348 349 /// Run a set of structural simplifications over the given regions. This 350 /// includes transformations like unreachable block elimination, dead argument 351 /// elimination, as well as some other DCE. This function returns success if any 352 /// of the regions were simplified, failure otherwise. 353 LogicalResult mlir::simplifyRegions(MutableArrayRef<Region> regions) { 354 LogicalResult eliminatedBlocks = eraseUnreachableBlocks(regions); 355 LogicalResult eliminatedOpsOrArgs = runRegionDCE(regions); 356 return success(succeeded(eliminatedBlocks) || succeeded(eliminatedOpsOrArgs)); 357 } 358