//===- RegionUtils.cpp - Region-related transformation utilities ----------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "mlir/Transforms/RegionUtils.h" #include "mlir/Analysis/TopologicalSortUtils.h" #include "mlir/IR/Block.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/IRMapping.h" #include "mlir/IR/Operation.h" #include "mlir/IR/PatternMatch.h" #include "mlir/IR/RegionGraphTraits.h" #include "mlir/IR/Value.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Support/LogicalResult.h" #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallSet.h" #include <deque> #include <iterator> usingnamespacemlir; void mlir::replaceAllUsesInRegionWith(Value orig, Value replacement, Region ®ion) { … } void mlir::visitUsedValuesDefinedAbove( Region ®ion, Region &limit, function_ref<void(OpOperand *)> callback) { … } void mlir::visitUsedValuesDefinedAbove( MutableArrayRef<Region> regions, function_ref<void(OpOperand *)> callback) { … } void mlir::getUsedValuesDefinedAbove(Region ®ion, Region &limit, SetVector<Value> &values) { … } void mlir::getUsedValuesDefinedAbove(MutableArrayRef<Region> regions, SetVector<Value> &values) { … } //===----------------------------------------------------------------------===// // Make block isolated from above. //===----------------------------------------------------------------------===// SmallVector<Value> mlir::makeRegionIsolatedFromAbove( RewriterBase &rewriter, Region ®ion, llvm::function_ref<bool(Operation *)> cloneOperationIntoRegion) { … } //===----------------------------------------------------------------------===// // Unreachable Block Elimination //===----------------------------------------------------------------------===// /// Erase the unreachable blocks within the provided regions. Returns success /// if any blocks were erased, failure otherwise. // TODO: We could likely merge this with the DCE algorithm below. LogicalResult mlir::eraseUnreachableBlocks(RewriterBase &rewriter, MutableArrayRef<Region> regions) { … } //===----------------------------------------------------------------------===// // Dead Code Elimination //===----------------------------------------------------------------------===// namespace { /// Data structure used to track which values have already been proved live. /// /// Because Operation's can have multiple results, this data structure tracks /// liveness for both Value's and Operation's to avoid having to look through /// all Operation results when analyzing a use. /// /// This data structure essentially tracks the dataflow lattice. /// The set of values/ops proved live increases monotonically to a fixed-point. class LiveMap { … }; } // namespace static bool isUseSpeciallyKnownDead(OpOperand &use, LiveMap &liveMap) { … } static void processValue(Value value, LiveMap &liveMap) { … } static void propagateLiveness(Region ®ion, LiveMap &liveMap); static void propagateTerminatorLiveness(Operation *op, LiveMap &liveMap) { … } static void propagateLiveness(Operation *op, LiveMap &liveMap) { … } static void propagateLiveness(Region ®ion, LiveMap &liveMap) { … } static void eraseTerminatorSuccessorOperands(Operation *terminator, LiveMap &liveMap) { … } static LogicalResult deleteDeadness(RewriterBase &rewriter, MutableArrayRef<Region> regions, LiveMap &liveMap) { … } // This function performs a simple dead code elimination algorithm over the // given regions. // // The overall goal is to prove that Values are dead, which allows deleting ops // and block arguments. // // This uses an optimistic algorithm that assumes everything is dead until // proved otherwise, allowing it to delete recursively dead cycles. // // This is a simple fixed-point dataflow analysis algorithm on a lattice // {Dead,Alive}. Because liveness flows backward, we generally try to // iterate everything backward to speed up convergence to the fixed-point. This // allows for being able to delete recursively dead cycles of the use-def graph, // including block arguments. // // This function returns success if any operations or arguments were deleted, // failure otherwise. LogicalResult mlir::runRegionDCE(RewriterBase &rewriter, MutableArrayRef<Region> regions) { … } //===----------------------------------------------------------------------===// // Block Merging //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// // BlockEquivalenceData namespace { /// This class contains the information for comparing the equivalencies of two /// blocks. Blocks are considered equivalent if they contain the same operations /// in the same order. The only allowed divergence is for operands that come /// from sources outside of the parent block, i.e. the uses of values produced /// within the block must be equivalent. /// e.g., /// Equivalent: /// ^bb1(%arg0: i32) /// return %arg0, %foo : i32, i32 /// ^bb2(%arg1: i32) /// return %arg1, %bar : i32, i32 /// Not Equivalent: /// ^bb1(%arg0: i32) /// return %foo, %arg0 : i32, i32 /// ^bb2(%arg1: i32) /// return %arg1, %bar : i32, i32 struct BlockEquivalenceData { … }; } // namespace BlockEquivalenceData::BlockEquivalenceData(Block *block) : … { … } unsigned BlockEquivalenceData::getOrderOf(Value value) const { … } //===----------------------------------------------------------------------===// // BlockMergeCluster namespace { /// This class represents a cluster of blocks to be merged together. class BlockMergeCluster { … }; } // namespace LogicalResult BlockMergeCluster::addToCluster(BlockEquivalenceData &blockData) { … } /// Returns true if the predecessor terminators of the given block can not have /// their operands updated. static bool ableToUpdatePredOperands(Block *block) { … } /// Prunes the redundant list of new arguments. E.g., if we are passing an /// argument list like [x, y, z, x] this would return [x, y, z] and it would /// update the `block` (to whom the argument are passed to) accordingly. The new /// arguments are passed as arguments at the back of the block, hence we need to /// know how many `numOldArguments` were before, in order to correctly replace /// the new arguments in the block static SmallVector<SmallVector<Value, 8>, 2> pruneRedundantArguments( const SmallVector<SmallVector<Value, 8>, 2> &newArguments, RewriterBase &rewriter, unsigned numOldArguments, Block *block) { … } LogicalResult BlockMergeCluster::merge(RewriterBase &rewriter) { … } /// Identify identical blocks within the given region and merge them, inserting /// new block arguments as necessary. Returns success if any blocks were merged, /// failure otherwise. static LogicalResult mergeIdenticalBlocks(RewriterBase &rewriter, Region ®ion) { … } /// Identify identical blocks within the given regions and merge them, inserting /// new block arguments as necessary. static LogicalResult mergeIdenticalBlocks(RewriterBase &rewriter, MutableArrayRef<Region> regions) { … } /// If a block's argument is always the same across different invocations, then /// drop the argument and use the value directly inside the block static LogicalResult dropRedundantArguments(RewriterBase &rewriter, Block &block) { … } /// This optimization drops redundant argument to blocks. I.e., if a given /// argument to a block receives the same value from each of the block /// predecessors, we can remove the argument from the block and use directly the /// original value. This is a simple example: /// /// %cond = llvm.call @rand() : () -> i1 /// %val0 = llvm.mlir.constant(1 : i64) : i64 /// %val1 = llvm.mlir.constant(2 : i64) : i64 /// %val2 = llvm.mlir.constant(3 : i64) : i64 /// llvm.cond_br %cond, ^bb1(%val0 : i64, %val1 : i64), ^bb2(%val0 : i64, %val2 /// : i64) /// /// ^bb1(%arg0 : i64, %arg1 : i64): /// llvm.call @foo(%arg0, %arg1) /// /// The previous IR can be rewritten as: /// %cond = llvm.call @rand() : () -> i1 /// %val0 = llvm.mlir.constant(1 : i64) : i64 /// %val1 = llvm.mlir.constant(2 : i64) : i64 /// %val2 = llvm.mlir.constant(3 : i64) : i64 /// llvm.cond_br %cond, ^bb1(%val1 : i64), ^bb2(%val2 : i64) /// /// ^bb1(%arg0 : i64): /// llvm.call @foo(%val0, %arg0) /// static LogicalResult dropRedundantArguments(RewriterBase &rewriter, MutableArrayRef<Region> regions) { … } //===----------------------------------------------------------------------===// // Region Simplification //===----------------------------------------------------------------------===// /// Run a set of structural simplifications over the given regions. This /// includes transformations like unreachable block elimination, dead argument /// elimination, as well as some other DCE. This function returns success if any /// of the regions were simplified, failure otherwise. LogicalResult mlir::simplifyRegions(RewriterBase &rewriter, MutableArrayRef<Region> regions, bool mergeBlocks) { … }