Files
llvm/mlir/lib/Analysis/Liveness.cpp
River Riddle ab46543ceb Resubmit: ReImplement the Value classes as value-typed objects wrapping an internal pointer storage.
This will enable future commits to reimplement the internal implementation of OpResult without needing to change all of the existing users. This is part of a chain of commits optimizing the size of operation results.

PiperOrigin-RevId: 286930047
2019-12-23 16:05:05 -08:00

374 lines
13 KiB
C++

//===- Liveness.cpp - Liveness analysis for MLIR --------------------------===//
//
// Part of the MLIR 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
//
//===----------------------------------------------------------------------===//
//
// Implementation of the liveness analysis.
//
//===----------------------------------------------------------------------===//
#include "mlir/Analysis/Liveness.h"
#include "mlir/IR/Block.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/Region.h"
#include "mlir/IR/Value.h"
#include "llvm/ADT/SetOperations.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Support/raw_ostream.h"
using namespace mlir;
/// Builds and holds block information during the construction phase.
struct BlockInfoBuilder {
using ValueSetT = Liveness::ValueSetT;
/// Constructs an empty block builder.
BlockInfoBuilder() : block(nullptr) {}
/// Fills the block builder with initial liveness information.
BlockInfoBuilder(Block *block) : block(block) {
// Mark all block arguments (phis) as defined.
for (BlockArgumentPtr argument : block->getArguments())
defValues.insert(argument);
// Check all result values and whether their uses
// are inside this block or not (see outValues).
for (Operation &operation : *block)
for (ValuePtr result : operation.getResults()) {
defValues.insert(result);
// Check whether this value will be in the outValues
// set (its uses escape this block). Due to the SSA
// properties of the program, the uses must occur after
// the definition. Therefore, we do not have to check
// additional conditions to detect an escaping value.
for (OpOperand &use : result->getUses())
if (use.getOwner()->getBlock() != block) {
outValues.insert(result);
break;
}
}
// Check all operations for used operands.
for (Operation &operation : block->getOperations())
for (ValuePtr operand : operation.getOperands()) {
// If the operand is already defined in the scope of this
// block, we can skip the value in the use set.
if (!defValues.count(operand))
useValues.insert(operand);
}
}
/// Updates live-in information of the current block.
/// To do so it uses the default liveness-computation formula:
/// newIn = use union out \ def.
/// The methods returns true, if the set has changed (newIn != in),
/// false otherwise.
bool updateLiveIn() {
ValueSetT newIn = useValues;
llvm::set_union(newIn, outValues);
llvm::set_subtract(newIn, defValues);
// It is sufficient to check the set sizes (instead of their contents)
// since the live-in set can only grow monotonically during all update
// operations.
if (newIn.size() == inValues.size())
return false;
inValues = newIn;
return true;
}
/// Updates live-out information of the current block.
/// It iterates over all successors and unifies their live-in
/// values with the current live-out values.
template <typename SourceT> void updateLiveOut(SourceT &source) {
for (Block *succ : block->getSuccessors()) {
BlockInfoBuilder &builder = source[succ];
llvm::set_union(outValues, builder.inValues);
}
}
/// The current block.
Block *block;
/// The set of all live in values.
ValueSetT inValues;
/// The set of all live out values.
ValueSetT outValues;
/// The set of all defined values.
ValueSetT defValues;
/// The set of all used values.
ValueSetT useValues;
};
/// Builds the internal liveness block mapping.
static void buildBlockMapping(MutableArrayRef<Region> regions,
DenseMap<Block *, BlockInfoBuilder> &builders) {
llvm::SetVector<Block *> toProcess;
// Initialize all block structures
for (Region &region : regions)
for (Block &block : region) {
BlockInfoBuilder &builder =
builders.try_emplace(&block, &block).first->second;
if (builder.updateLiveIn())
toProcess.insert(block.pred_begin(), block.pred_end());
}
// Propagate the in and out-value sets (fixpoint iteration)
while (!toProcess.empty()) {
Block *current = toProcess.pop_back_val();
BlockInfoBuilder &builder = builders[current];
// Update the current out values.
builder.updateLiveOut(builders);
// Compute (potentially) updated live in values.
if (builder.updateLiveIn())
toProcess.insert(current->pred_begin(), current->pred_end());
}
}
//===----------------------------------------------------------------------===//
// Liveness
//===----------------------------------------------------------------------===//
/// Creates a new Liveness analysis that computes liveness
/// information for all associated regions.
Liveness::Liveness(Operation *op) : operation(op) { build(op->getRegions()); }
/// Initializes the internal mappings.
void Liveness::build(MutableArrayRef<Region> regions) {
// Build internal block mapping.
DenseMap<Block *, BlockInfoBuilder> builders;
buildBlockMapping(regions, builders);
// Store internal block data.
for (auto &entry : builders) {
BlockInfoBuilder &builder = entry.second;
LivenessBlockInfo &info = blockMapping[entry.first];
info.block = builder.block;
info.inValues = std::move(builder.inValues);
info.outValues = std::move(builder.outValues);
}
}
/// Gets liveness info (if any) for the given value.
Liveness::OperationListT Liveness::resolveLiveness(ValuePtr value) const {
OperationListT result;
SmallPtrSet<Block *, 32> visited;
SmallVector<Block *, 8> toProcess;
// Start with the defining block
Block *currentBlock;
if (Operation *defOp = value->getDefiningOp())
currentBlock = defOp->getBlock();
else
currentBlock = value.cast<BlockArgument>()->getOwner();
toProcess.push_back(currentBlock);
visited.insert(currentBlock);
// Start with all associated blocks
for (OpOperand &use : value->getUses()) {
Block *useBlock = use.getOwner()->getBlock();
if (visited.insert(useBlock).second)
toProcess.push_back(useBlock);
}
while (!toProcess.empty()) {
// Get block and block liveness information.
Block *block = toProcess.back();
toProcess.pop_back();
const LivenessBlockInfo *blockInfo = getLiveness(block);
// Note that start and end will be in the same block.
Operation *start = blockInfo->getStartOperation(value);
Operation *end = blockInfo->getEndOperation(value, start);
result.push_back(start);
while (start != end) {
start = start->getNextNode();
result.push_back(start);
}
for (Block *successor : block->getSuccessors()) {
if (getLiveness(successor)->isLiveIn(value) &&
visited.insert(successor).second)
toProcess.push_back(successor);
}
}
return result;
}
/// Gets liveness info (if any) for the block.
const LivenessBlockInfo *Liveness::getLiveness(Block *block) const {
auto it = blockMapping.find(block);
return it == blockMapping.end() ? nullptr : &it->second;
}
/// Returns a reference to a set containing live-in values.
const Liveness::ValueSetT &Liveness::getLiveIn(Block *block) const {
return getLiveness(block)->in();
}
/// Returns a reference to a set containing live-out values.
const Liveness::ValueSetT &Liveness::getLiveOut(Block *block) const {
return getLiveness(block)->out();
}
/// Returns true if the given operation represent the last use of the
/// given value.
bool Liveness::isLastUse(ValuePtr value, Operation *operation) const {
Block *block = operation->getBlock();
const LivenessBlockInfo *blockInfo = getLiveness(block);
// The given value escapes the associated block.
if (blockInfo->isLiveOut(value))
return false;
Operation *endOperation = blockInfo->getEndOperation(value, operation);
// If the operation is a real user of `value` the first check is sufficient.
// If not, we will have to test whether the end operation is executed before
// the given operation in the block.
return endOperation == operation || endOperation->isBeforeInBlock(operation);
}
/// Dumps the liveness information in a human readable format.
void Liveness::dump() const { print(llvm::errs()); }
/// Dumps the liveness information to the given stream.
void Liveness::print(raw_ostream &os) const {
os << "// ---- Liveness -----\n";
// Builds unique block/value mappings for testing purposes.
DenseMap<Block *, size_t> blockIds;
DenseMap<Operation *, size_t> operationIds;
DenseMap<ValuePtr, size_t> valueIds;
for (Region &region : operation->getRegions())
for (Block &block : region) {
blockIds.insert({&block, blockIds.size()});
for (BlockArgumentPtr argument : block.getArguments())
valueIds.insert({argument, valueIds.size()});
for (Operation &operation : block) {
operationIds.insert({&operation, operationIds.size()});
for (ValuePtr result : operation.getResults())
valueIds.insert({result, valueIds.size()});
}
}
// Local printing helpers
auto printValueRef = [&](ValuePtr value) {
if (Operation *defOp = value->getDefiningOp())
os << "val_" << defOp->getName();
else {
auto blockArg = value.cast<BlockArgument>();
os << "arg" << blockArg->getArgNumber() << "@"
<< blockIds[blockArg->getOwner()];
}
os << " ";
};
auto printValueRefs = [&](const ValueSetT &values) {
std::vector<ValuePtr> orderedValues(values.begin(), values.end());
std::sort(orderedValues.begin(), orderedValues.end(),
[&](ValuePtr left, ValuePtr right) {
return valueIds[left] < valueIds[right];
});
for (ValuePtr value : orderedValues)
printValueRef(value);
};
// Dump information about in and out values.
for (Region &region : operation->getRegions())
for (Block &block : region) {
os << "// - Block: " << blockIds[&block] << "\n";
auto liveness = getLiveness(&block);
os << "// --- LiveIn: ";
printValueRefs(liveness->inValues);
os << "\n// --- LiveOut: ";
printValueRefs(liveness->outValues);
os << "\n";
// Print liveness intervals.
os << "// --- BeginLiveness";
for (Operation &op : block) {
if (op.getNumResults() < 1)
continue;
os << "\n";
for (ValuePtr result : op.getResults()) {
os << "// ";
printValueRef(result);
os << ":";
auto liveOperations = resolveLiveness(result);
std::sort(liveOperations.begin(), liveOperations.end(),
[&](Operation *left, Operation *right) {
return operationIds[left] < operationIds[right];
});
for (Operation *operation : liveOperations) {
os << "\n// ";
operation->print(os);
}
}
}
os << "\n// --- EndLiveness\n";
}
os << "// -------------------\n";
}
//===----------------------------------------------------------------------===//
// LivenessBlockInfo
//===----------------------------------------------------------------------===//
/// Returns true if the given value is in the live-in set.
bool LivenessBlockInfo::isLiveIn(ValuePtr value) const {
return inValues.count(value);
}
/// Returns true if the given value is in the live-out set.
bool LivenessBlockInfo::isLiveOut(ValuePtr value) const {
return outValues.count(value);
}
/// Gets the start operation for the given value
/// (must be referenced in this block).
Operation *LivenessBlockInfo::getStartOperation(ValuePtr value) const {
Operation *definingOp = value->getDefiningOp();
// The given value is either live-in or is defined
// in the scope of this block.
if (isLiveIn(value) || !definingOp)
return &block->front();
return definingOp;
}
/// Gets the end operation for the given value using the start operation
/// provided (must be referenced in this block).
Operation *LivenessBlockInfo::getEndOperation(ValuePtr value,
Operation *startOperation) const {
// The given value is either dying in this block or live-out.
if (isLiveOut(value))
return &block->back();
// Resolve the last operation (must exist by definition).
Operation *endOperation = startOperation;
for (OpOperand &use : value->getUses()) {
Operation *useOperation = use.getOwner();
// Check whether the use is in our block and after
// the current end operation.
if (useOperation->getBlock() == block &&
endOperation->isBeforeInBlock(useOperation))
endOperation = useOperation;
}
return endOperation;
}