1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
//===- ControlFlowSinkUtils.cpp - Code to perform control-flow sinking ----===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements utilities for control-flow sinking. Control-flow
// sinking moves operations whose only uses are in conditionally-executed blocks
// into those blocks so that they aren't executed on paths where their results
// are not needed.
//
// Control-flow sinking is not implemented on BranchOpInterface because
// sinking ops into the successors of branch operations may move ops into loops.
// It is idiomatic MLIR to perform optimizations at IR levels that readily
// provide the necessary information.
//
//===----------------------------------------------------------------------===//
#include "mlir/Transforms/ControlFlowSinkUtils.h"
#include "mlir/IR/Dominance.h"
#include "mlir/IR/Matchers.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include <vector>
#define DEBUG_TYPE "cf-sink"
using namespace mlir;
namespace {
/// A helper struct for control-flow sinking.
class Sinker {
public:
/// Create an operation sinker with given dominance info.
Sinker(function_ref<bool(Operation *, Region *)> shouldMoveIntoRegion,
function_ref<void(Operation *, Region *)> moveIntoRegion,
DominanceInfo &domInfo)
: shouldMoveIntoRegion(shouldMoveIntoRegion),
moveIntoRegion(moveIntoRegion), domInfo(domInfo) {}
/// Given a list of regions, find operations to sink and sink them. Return the
/// number of operations sunk.
size_t sinkRegions(RegionRange regions);
private:
/// Given a region and an op which dominates the region, returns true if all
/// users of the given op are dominated by the entry block of the region, and
/// thus the operation can be sunk into the region.
bool allUsersDominatedBy(Operation *op, Region *region);
/// Given a region and a top-level op (an op whose parent region is the given
/// region), determine whether the defining ops of the op's operands can be
/// sunk into the region.
///
/// Add moved ops to the work queue.
void tryToSinkPredecessors(Operation *user, Region *region,
std::vector<Operation *> &stack);
/// Iterate over all the ops in a region and try to sink their predecessors.
/// Recurse on subgraphs using a work queue.
void sinkRegion(Region *region);
/// The callback to determine whether an op should be moved in to a region.
function_ref<bool(Operation *, Region *)> shouldMoveIntoRegion;
/// The calback to move an operation into the region.
function_ref<void(Operation *, Region *)> moveIntoRegion;
/// Dominance info to determine op user dominance with respect to regions.
DominanceInfo &domInfo;
/// The number of operations sunk.
size_t numSunk = 0;
};
} // end anonymous namespace
bool Sinker::allUsersDominatedBy(Operation *op, Region *region) {
assert(region->findAncestorOpInRegion(*op) == nullptr &&
"expected op to be defined outside the region");
return llvm::all_of(op->getUsers(), [&](Operation *user) {
// The user is dominated by the region if its containing block is dominated
// by the region's entry block.
return domInfo.dominates(®ion->front(), user->getBlock());
});
}
void Sinker::tryToSinkPredecessors(Operation *user, Region *region,
std::vector<Operation *> &stack) {
LLVM_DEBUG(user->print(llvm::dbgs() << "\nContained op:\n"));
for (Value value : user->getOperands()) {
Operation *op = value.getDefiningOp();
// Ignore block arguments and ops that are already inside the region.
if (!op || op->getParentRegion() == region)
continue;
LLVM_DEBUG(op->print(llvm::dbgs() << "\nTry to sink:\n"));
// If the op's users are all in the region and it can be moved, then do so.
if (allUsersDominatedBy(op, region) && shouldMoveIntoRegion(op, region)) {
moveIntoRegion(op, region);
++numSunk;
// Add the op to the work queue.
stack.push_back(op);
}
}
}
void Sinker::sinkRegion(Region *region) {
// Initialize the work queue with all the ops in the region.
std::vector<Operation *> stack;
for (Operation &op : region->getOps())
stack.push_back(&op);
// Process all the ops depth-first. This ensures that nodes of subgraphs are
// sunk in the correct order.
while (!stack.empty()) {
Operation *op = stack.back();
stack.pop_back();
tryToSinkPredecessors(op, region, stack);
}
}
size_t Sinker::sinkRegions(RegionRange regions) {
for (Region *region : regions)
if (!region->empty())
sinkRegion(region);
return numSunk;
}
size_t mlir::controlFlowSink(
RegionRange regions, DominanceInfo &domInfo,
function_ref<bool(Operation *, Region *)> shouldMoveIntoRegion,
function_ref<void(Operation *, Region *)> moveIntoRegion) {
return Sinker(shouldMoveIntoRegion, moveIntoRegion, domInfo)
.sinkRegions(regions);
}
void mlir::getSinglyExecutedRegionsToSink(RegionBranchOpInterface branch,
SmallVectorImpl<Region *> ®ions) {
// Collect constant operands.
SmallVector<Attribute> operands(branch->getNumOperands(), Attribute());
for (auto [idx, operand] : llvm::enumerate(branch->getOperands()))
(void)matchPattern(operand, m_Constant(&operands[idx]));
// Get the invocation bounds.
SmallVector<InvocationBounds> bounds;
branch.getRegionInvocationBounds(operands, bounds);
// For a simple control-flow sink, only consider regions that are executed at
// most once.
for (auto it : llvm::zip(branch->getRegions(), bounds)) {
const InvocationBounds &bound = std::get<1>(it);
if (bound.getUpperBound() && *bound.getUpperBound() <= 1)
regions.push_back(&std::get<0>(it));
}
}
|