blob: 782efb50aabc75b9c76a837ca49f132bd4d05591 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "iree/compiler/Dialect/VM/Analysis/ValueLiveness.h"
#include <algorithm>
#include <cstring>
#include "iree/compiler/Dialect/IREE/IR/IREETypes.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/Value.h"
namespace mlir {
namespace iree_compiler {
// static
LogicalResult ValueLiveness::annotateIR(IREE::VM::FuncOp funcOp) {
ValueLiveness liveness;
if (failed(liveness.recalculate(funcOp))) {
funcOp.emitOpError()
<< "failed to compute value liveness information for function";
return failure();
}
// Build mapping of Operations to values live during them.
// This is not needed normally so we calculate it only in this debugging case.
DenseMap<Operation *, llvm::SmallSetVector<Value, 8>> livePerOp;
for (auto &liveRange : liveness.liveRanges_) {
auto value = liveRange.getFirst();
auto &bitVector = liveRange.getSecond();
for (int opIndex : bitVector.set_bits()) {
auto *op = liveness.opsInOrder_[opIndex];
livePerOp[op].insert(value);
}
}
// Block names are their order in the function.
DenseMap<Block *, int> blockOrdinals;
for (auto &block : funcOp.getBlocks()) {
blockOrdinals[&block] = blockOrdinals.size();
}
// Gather attributes for each op before we actually add them. We do this so
// that it's easier to slice out the results for printing without also
// including the attributes we ourselves are trying to add.
Builder builder(funcOp.getContext());
DenseMap<Operation *, NamedAttributeList> livenessAttrs(livePerOp.size());
auto addValuesAttr = [&](Operation &op, StringRef attrName,
const llvm::SmallSetVector<Value, 8> &values) {
SmallVector<StringAttr, 8> valueNames;
for (auto value : values) {
std::string str;
if (auto blockArg = value.dyn_cast<BlockArgument>()) {
if (blockArg.getOwner()->isEntryBlock()) {
str = llvm::formatv("%arg{0}", blockArg.getArgNumber());
} else {
str =
llvm::formatv("%bb{0}_arg{1}", blockOrdinals[blockArg.getOwner()],
blockArg.getArgNumber());
}
} else {
llvm::raw_string_ostream os(str);
value.print(os);
str = os.str();
}
int equalsIndex = str.find(" =");
if (equalsIndex != std::string::npos) { // heh
auto results = str.substr(0, equalsIndex);
valueNames.push_back(builder.getStringAttr(results));
} else {
valueNames.push_back(builder.getStringAttr(str));
}
}
// Sort attributes by name (as SmallSetVector is unordered).
std::sort(
valueNames.begin(), valueNames.end(),
+[](const StringAttr &a, const StringAttr &b) {
return a.getValue().compare_lower(b.getValue()) < 0;
});
SmallVector<Attribute, 8> valueNameAttrs;
for (auto attr : valueNames) {
valueNameAttrs.push_back(attr);
}
livenessAttrs[&op].set(builder.getIdentifier(attrName),
builder.getArrayAttr(valueNameAttrs));
};
for (auto &block : funcOp.getBlocks()) {
auto &blockLiveness = liveness.blockLiveness_[&block];
addValuesAttr(block.front(), "block_live_in", blockLiveness.liveIn);
addValuesAttr(block.front(), "block_live", blockLiveness.live);
addValuesAttr(block.front(), "block_live_out", blockLiveness.liveOut);
addValuesAttr(block.front(), "block_defined", blockLiveness.defined);
// Add per-op live values.
for (auto &op : block.getOperations()) {
addValuesAttr(op, "live", livePerOp[&op]);
}
}
// Markup all ops with their attributes.
for (auto &opAttrs : livenessAttrs) {
for (auto nameAttr : opAttrs.getSecond().getAttrs()) {
opAttrs.getFirst()->setAttr(nameAttr.first, nameAttr.second);
}
}
return success();
}
LogicalResult ValueLiveness::recalculate(IREE::VM::FuncOp funcOp) {
opsInOrder_.clear();
opOrdering_.clear();
blockLiveness_.clear();
liveRanges_.clear();
calculateOpOrdering(funcOp);
if (failed(computeLivenessSets(funcOp))) {
return funcOp.emitError() << "failed to compute liveness sets";
}
if (failed(computeLiveIntervals(funcOp))) {
return funcOp.emitError() << "failed to compute live intervals";
}
return success();
}
void ValueLiveness::calculateOpOrdering(IREE::VM::FuncOp funcOp) {
int nextOrdinal = 0;
for (auto &block : funcOp.getBlocks()) {
for (auto &op : block.getOperations()) {
opOrdering_[&op] = nextOrdinal++;
opsInOrder_.push_back(&op);
}
}
}
LogicalResult ValueLiveness::computeInitialLivenessSets(
IREE::VM::FuncOp funcOp) {
for (auto &block : funcOp.getBlocks()) {
auto &blockSets = blockLiveness_[&block];
// Block arguments are defined within the block, technically.
for (auto blockArg : block.getArguments()) {
blockSets.defined.insert(blockArg);
}
// Add all operands as live uses and all results as definitions.
for (auto &op : block.getOperations()) {
for (auto &operand : op.getOpOperands()) {
blockSets.live.insert(operand.get());
}
for (auto result : op.getResults()) {
blockSets.defined.insert(result);
for (auto &use : result.getUses()) {
if (use.getOwner()->getBlock() != &block) {
// Value escapes this block.
blockSets.liveOut.insert(result);
}
}
}
}
}
return success();
}
LogicalResult ValueLiveness::computeLivenessSets(IREE::VM::FuncOp funcOp) {
if (failed(computeInitialLivenessSets(funcOp))) {
return failure();
}
llvm::SmallSetVector<Block *, 32> worklist;
worklist.insert(&funcOp.getBlocks().front());
// Compute live-in set for each block.
for (auto &block : funcOp.getBlocks()) {
auto &blockSets = blockLiveness_[&block];
blockSets.liveIn = blockSets.live;
blockSets.liveIn.set_union(blockSets.liveOut);
blockSets.liveIn.set_subtract(blockSets.defined);
// If there are live-ins they may need to be propagated to predecessors.
if (!blockSets.liveIn.empty()) {
worklist.insert(block.getPredecessors().begin(),
block.getPredecessors().end());
}
}
// Propagate liveness until a fixed point is reached.
while (!worklist.empty()) {
Block *block = worklist.pop_back_val();
auto &blockSets = blockLiveness_[block];
// Compute a new live-out set for the block.
auto liveOut = blockSets.liveOut;
for (Block *successor : block->getSuccessors()) {
liveOut.set_union(blockLiveness_[successor].liveIn);
}
blockSets.liveOut = liveOut;
// Compute the live-in set for the block.
auto liveIn = liveOut;
liveIn.set_union(blockSets.live);
liveIn.set_subtract(blockSets.defined);
// Propagate the liveness to predecessors if the live-in set changed.
if (blockSets.liveIn.size() != liveIn.size()) {
blockSets.liveIn = liveIn;
worklist.insert(block->getPredecessors().begin(),
block->getPredecessors().end());
}
}
return success();
}
LogicalResult ValueLiveness::computeLiveIntervals(IREE::VM::FuncOp funcOp) {
// Adds a live range for |value| from |start| to |end|.
// Both |start| and |end| must be within the same Block.
auto addLiveRange = [this](Value value, Operation *start, Operation *end) {
assert(start->getBlock() == end->getBlock());
auto &bitVector = liveRanges_[value];
bitVector.resize(opOrdering_.size());
bitVector.set(opOrdering_[start], opOrdering_[end] + 1);
};
for (auto &block : funcOp.getBlocks()) {
auto &blockSets = blockLiveness_[&block];
// Handle values that escape the block.
for (auto value : blockSets.liveOut) {
if (blockSets.liveIn.count(value)) {
// Live in and live out covers the entire block.
addLiveRange(value, &block.front(), &block.back());
} else {
// Live out but not live in implies defined in the block.
Operation *firstUse =
value.getDefiningOp() ? value.getDefiningOp() : &block.front();
addLiveRange(value, firstUse, &block.back());
}
}
// Handle values entering the block and dying within.
for (auto value : blockSets.liveIn) {
if (blockSets.liveOut.count(value)) continue;
Operation *lastUse = &block.front();
for (auto &use : value.getUses()) {
if (use.getOwner()->getBlock() != &block) continue;
if (lastUse->isBeforeInBlock(use.getOwner())) {
lastUse = use.getOwner();
}
}
addLiveRange(value, &block.front(), lastUse);
}
// Handle values defined within the block and not escaping.
for (auto value : blockSets.defined) {
if (blockSets.liveOut.count(value)) continue;
Operation *firstUse =
value.getDefiningOp() ? value.getDefiningOp() : &block.front();
Operation *lastUse = firstUse;
for (auto &use : value.getUses()) {
if (use.getOwner()->getBlock() != &block) continue;
if (lastUse->isBeforeInBlock(use.getOwner())) {
lastUse = use.getOwner();
}
}
addLiveRange(value, firstUse, lastUse);
}
}
return success();
}
ArrayRef<Value> ValueLiveness::getBlockLiveIns(Block *block) {
auto &blockSets = blockLiveness_[block];
return blockSets.liveIn.getArrayRef();
}
bool ValueLiveness::isLastValueUse(Value value, Operation *useOp) {
auto &blockSets = blockLiveness_[useOp->getBlock()];
if (blockSets.liveOut.count(value)) {
// Value is escapes the block the useOp is in so it is definitely not the
// last use.
return false;
}
int opOrdinal = opOrdering_[useOp];
auto &liveRange = liveRanges_[value];
if (!useOp->isKnownTerminator() && liveRange.test(opOrdinal + 1)) {
// The value is still live within the block after the useOp.
return false;
}
return true;
}
bool ValueLiveness::isLastValueUse(Value value, Operation *useOp,
int operandIndex) {
if (!isLastValueUse(value, useOp)) {
return false;
}
for (auto &operand : llvm::reverse(useOp->getOpOperands())) {
if (operand.get() == value) {
// Compare the queried operand index with the last use index.
return operandIndex >= operand.getOperandNumber();
}
}
llvm_unreachable("value not used by operand");
return false;
}
} // namespace iree_compiler
} // namespace mlir