forked from OSchip/llvm-project
Roll-forward initial liveness analysis including test cases.
Fix the usage of the map size when appending to the map with []. PiperOrigin-RevId: 284985916
This commit is contained in:
parent
984fdde269
commit
4b0198acb5
|
@ -0,0 +1,157 @@
|
|||
//===- Liveness.h - Liveness analysis for MLIR ------------------*- C++ -*-===//
|
||||
//
|
||||
// Copyright 2019 The MLIR Authors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
// =============================================================================
|
||||
//
|
||||
// This file contains an analysis for computing liveness information from a
|
||||
// given top-level operation. The current version of the analysis uses a
|
||||
// traditional algorithm to resolve detailed live-range information about all
|
||||
// values within the specified regions. It is also possible to query liveness
|
||||
// information on block level.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef MLIR_ANALYSIS_LIVENESS_H
|
||||
#define MLIR_ANALYSIS_LIVENESS_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "mlir/Support/LLVM.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
|
||||
namespace mlir {
|
||||
|
||||
class Block;
|
||||
class LivenessBlockInfo;
|
||||
class Operation;
|
||||
class Region;
|
||||
class Value;
|
||||
|
||||
/// Represents an analysis for computing liveness information from a
|
||||
/// given top-level operation. The analysis iterates over all associated
|
||||
/// regions that are attached to the given top-level operation. It
|
||||
/// computes liveness information for every value and block that are
|
||||
/// included in the mentioned regions. It relies on a fixpoint iteration
|
||||
/// to compute all live-in and live-out values of all included blocks.
|
||||
/// Sample usage:
|
||||
/// Liveness liveness(topLevelOp);
|
||||
/// auto &allInValues = liveness.getLiveIn(block);
|
||||
/// auto &allOutValues = liveness.getLiveOut(block);
|
||||
/// auto allOperationsInWhichValueIsLive = liveness.resolveLiveness(value);
|
||||
/// bool lastUse = liveness.isLastUse(value, operation);
|
||||
class Liveness {
|
||||
public:
|
||||
using OperationListT = std::vector<Operation *>;
|
||||
using BlockMapT = DenseMap<Block *, LivenessBlockInfo>;
|
||||
using ValueSetT = SmallPtrSet<Value *, 16>;
|
||||
|
||||
public:
|
||||
/// Creates a new Liveness analysis that computes liveness
|
||||
/// information for all associated regions.
|
||||
Liveness(Operation *op);
|
||||
|
||||
/// Returns the operation this analysis was constructed from.
|
||||
Operation *getOperation() const { return operation; }
|
||||
|
||||
/// Gets liveness info (if any) for the given value.
|
||||
/// This includes all operations in which the given value is live.
|
||||
/// Note that the operations in this list are not ordered and the current
|
||||
/// implementation is computationally expensive (as it iterates over all
|
||||
/// blocks in which the given value is live).
|
||||
OperationListT resolveLiveness(Value *value) const;
|
||||
|
||||
/// Gets liveness info (if any) for the block.
|
||||
const LivenessBlockInfo *getLiveness(Block *block) const;
|
||||
|
||||
/// Returns a reference to a set containing live-in values (unordered).
|
||||
const ValueSetT &getLiveIn(Block *block) const;
|
||||
|
||||
/// Returns a reference to a set containing live-out values (unordered).
|
||||
const ValueSetT &getLiveOut(Block *block) const;
|
||||
|
||||
/// Returns true if the given operation represent the last use of the
|
||||
/// given value.
|
||||
bool isLastUse(Value *value, Operation *operation) const;
|
||||
|
||||
/// Dumps the liveness information in a human readable format.
|
||||
void dump() const;
|
||||
|
||||
/// Dumps the liveness information to the given stream.
|
||||
void print(raw_ostream &os) const;
|
||||
|
||||
private:
|
||||
/// Initializes the internal mappings.
|
||||
void build(MutableArrayRef<Region> regions);
|
||||
|
||||
private:
|
||||
/// The operation this analysis was constructed from.
|
||||
Operation *operation;
|
||||
|
||||
/// Maps blocks to internal liveness information.
|
||||
BlockMapT blockMapping;
|
||||
};
|
||||
|
||||
/// This class represents liveness information on block level.
|
||||
class LivenessBlockInfo {
|
||||
public:
|
||||
/// A typedef declaration of a value set.
|
||||
using ValueSetT = Liveness::ValueSetT;
|
||||
|
||||
public:
|
||||
/// Returns the underlying block.
|
||||
Block *getBlock() const { return block; }
|
||||
|
||||
/// Returns all values that are live at the beginning
|
||||
/// of the block (unordered).
|
||||
const ValueSetT &in() const { return inValues; }
|
||||
|
||||
/// Returns all values that are live at the end
|
||||
/// of the block (unordered).
|
||||
const ValueSetT &out() const { return outValues; }
|
||||
|
||||
/// Returns true if the given value is in the live-in set.
|
||||
bool isLiveIn(Value *value) const;
|
||||
|
||||
/// Returns true if the given value is in the live-out set.
|
||||
bool isLiveOut(Value *value) const;
|
||||
|
||||
/// Gets the start operation for the given value. This is the first operation
|
||||
/// the given value is considered to be live. This could either be the start
|
||||
/// operation of the current block (in case the value is live-in) or the
|
||||
/// operation that defines the given value (must be referenced in this block).
|
||||
Operation *getStartOperation(Value *value) const;
|
||||
|
||||
/// Gets the end operation for the given value using the start operation
|
||||
/// provided (must be referenced in this block).
|
||||
Operation *getEndOperation(Value *value, Operation *startOperation) const;
|
||||
|
||||
private:
|
||||
/// The underlying block.
|
||||
Block *block;
|
||||
|
||||
/// The set of all live in values.
|
||||
ValueSetT inValues;
|
||||
|
||||
/// The set of all live out values.
|
||||
ValueSetT outValues;
|
||||
|
||||
friend class Liveness;
|
||||
};
|
||||
|
||||
} // end namespace mlir
|
||||
|
||||
#endif // MLIR_ANALYSIS_LIVENESS_H
|
|
@ -4,6 +4,7 @@ add_llvm_library(MLIRAnalysis STATIC
|
|||
CallGraph.cpp
|
||||
Dominance.cpp
|
||||
InferTypeOpInterface.cpp
|
||||
Liveness.cpp
|
||||
LoopAnalysis.cpp
|
||||
MemRefBoundCheck.cpp
|
||||
NestedMatcher.cpp
|
||||
|
|
|
@ -0,0 +1,382 @@
|
|||
//===- Liveness.cpp - Liveness analysis for MLIR --------------------------===//
|
||||
//
|
||||
// Copyright 2019 The MLIR Authors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
// =============================================================================
|
||||
//
|
||||
// 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 (BlockArgument *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 (Value *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 (Value *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 ®ion : 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(Value *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 = cast<BlockArgument>(value)->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(Value *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<Value *, size_t> valueIds;
|
||||
for (Region ®ion : operation->getRegions())
|
||||
for (Block &block : region) {
|
||||
blockIds.insert({&block, blockIds.size()});
|
||||
for (BlockArgument *argument : block.getArguments())
|
||||
valueIds.insert({argument, valueIds.size()});
|
||||
for (Operation &operation : block) {
|
||||
operationIds.insert({&operation, operationIds.size()});
|
||||
for (Value *result : operation.getResults())
|
||||
valueIds.insert({result, valueIds.size()});
|
||||
}
|
||||
}
|
||||
|
||||
// Local printing helpers
|
||||
auto printValueRef = [&](Value *value) {
|
||||
if (Operation *defOp = value->getDefiningOp())
|
||||
os << "val_" << defOp->getName();
|
||||
else {
|
||||
auto blockArg = cast<BlockArgument>(value);
|
||||
os << "arg" << blockArg->getArgNumber() << "@"
|
||||
<< blockIds[blockArg->getOwner()];
|
||||
}
|
||||
os << " ";
|
||||
};
|
||||
|
||||
auto printValueRefs = [&](const ValueSetT &values) {
|
||||
std::vector<Value *> orderedValues(values.begin(), values.end());
|
||||
std::sort(orderedValues.begin(), orderedValues.end(),
|
||||
[&](Value *left, Value *right) {
|
||||
return valueIds[left] < valueIds[right];
|
||||
});
|
||||
for (Value *value : orderedValues)
|
||||
printValueRef(value);
|
||||
};
|
||||
|
||||
// Dump information about in and out values.
|
||||
for (Region ®ion : 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 (Value *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(Value *value) const {
|
||||
return inValues.count(value);
|
||||
}
|
||||
|
||||
/// Returns true if the given value is in the live-out set.
|
||||
bool LivenessBlockInfo::isLiveOut(Value *value) const {
|
||||
return outValues.count(value);
|
||||
}
|
||||
|
||||
/// Gets the start operation for the given value
|
||||
/// (must be referenced in this block).
|
||||
Operation *LivenessBlockInfo::getStartOperation(Value *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(Value *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;
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
// RUN: mlir-opt %s -test-print-liveness -split-input-file 2>&1 | FileCheck %s --dump-input-on-failure
|
||||
|
||||
// CHECK-LABEL: Testing : func_empty
|
||||
func @func_empty() {
|
||||
// CHECK: Block: 0
|
||||
// CHECK-NEXT: LiveIn:{{ *$}}
|
||||
// CHECK-NEXT: LiveOut:{{ *$}}
|
||||
// CHECK-NEXT: BeginLiveness
|
||||
// CHECK-NEXT: EndLiveness
|
||||
return
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// CHECK-LABEL: Testing : func_simpleBranch
|
||||
func @func_simpleBranch(%arg0: i32, %arg1 : i32) -> i32 {
|
||||
// CHECK: Block: 0
|
||||
// CHECK-NEXT: LiveIn:{{ *$}}
|
||||
// CHECK-NEXT: LiveOut: arg0@0 arg1@0
|
||||
// CHECK-NEXT: BeginLiveness
|
||||
// CHECK-NEXT: EndLiveness
|
||||
br ^exit
|
||||
^exit:
|
||||
// CHECK: Block: 1
|
||||
// CHECK-NEXT: LiveIn: arg0@0 arg1@0
|
||||
// CHECK-NEXT: LiveOut:{{ *$}}
|
||||
// CHECK-NEXT: BeginLiveness
|
||||
// CHECK: val_std.addi
|
||||
// CHECK-NEXT: %0 = addi
|
||||
// CHECK-NEXT: return
|
||||
// CHECK-NEXT: EndLiveness
|
||||
%result = addi %arg0, %arg1 : i32
|
||||
return %result : i32
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// CHECK-LABEL: Testing : func_condBranch
|
||||
func @func_condBranch(%cond : i1, %arg1: i32, %arg2 : i32) -> i32 {
|
||||
// CHECK: Block: 0
|
||||
// CHECK-NEXT: LiveIn:{{ *$}}
|
||||
// CHECK-NEXT: LiveOut: arg1@0 arg2@0
|
||||
// CHECK-NEXT: BeginLiveness
|
||||
// CHECK-NEXT: EndLiveness
|
||||
cond_br %cond, ^bb1, ^bb2
|
||||
^bb1:
|
||||
// CHECK: Block: 1
|
||||
// CHECK-NEXT: LiveIn: arg1@0 arg2@0
|
||||
// CHECK-NEXT: LiveOut: arg1@0 arg2@0
|
||||
br ^exit
|
||||
^bb2:
|
||||
// CHECK: Block: 2
|
||||
// CHECK-NEXT: LiveIn: arg1@0 arg2@0
|
||||
// CHECK-NEXT: LiveOut: arg1@0 arg2@0
|
||||
br ^exit
|
||||
^exit:
|
||||
// CHECK: Block: 3
|
||||
// CHECK-NEXT: LiveIn: arg1@0 arg2@0
|
||||
// CHECK-NEXT: LiveOut:{{ *$}}
|
||||
// CHECK-NEXT: BeginLiveness
|
||||
// CHECK: val_std.addi
|
||||
// CHECK-NEXT: %0 = addi
|
||||
// CHECK-NEXT: return
|
||||
// CHECK-NEXT: EndLiveness
|
||||
%result = addi %arg1, %arg2 : i32
|
||||
return %result : i32
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// CHECK-LABEL: Testing : func_loop
|
||||
func @func_loop(%arg0 : i32, %arg1 : i32) -> i32 {
|
||||
// CHECK: Block: 0
|
||||
// CHECK-NEXT: LiveIn:{{ *$}}
|
||||
// CHECK-NEXT: LiveOut: arg1@0
|
||||
%const0 = constant 0 : i32
|
||||
br ^loopHeader(%const0, %arg0 : i32, i32)
|
||||
^loopHeader(%counter : i32, %i : i32):
|
||||
// CHECK: Block: 1
|
||||
// CHECK-NEXT: LiveIn: arg1@0
|
||||
// CHECK-NEXT: LiveOut: arg1@0 arg0@1
|
||||
// CHECK-NEXT: BeginLiveness
|
||||
// CHECK-NEXT: val_std.cmpi
|
||||
// CHECK-NEXT: %2 = cmpi
|
||||
// CHECK-NEXT: cond_br
|
||||
// CHECK-NEXT: EndLiveness
|
||||
%lessThan = cmpi "slt", %counter, %arg1 : i32
|
||||
cond_br %lessThan, ^loopBody(%i : i32), ^exit(%i : i32)
|
||||
^loopBody(%val : i32):
|
||||
// CHECK: Block: 2
|
||||
// CHECK-NEXT: LiveIn: arg1@0 arg0@1
|
||||
// CHECK-NEXT: LiveOut: arg1@0
|
||||
// CHECK-NEXT: BeginLiveness
|
||||
// CHECK-NEXT: val_std.constant
|
||||
// CHECK-NEXT: %c
|
||||
// CHECK-NEXT: %4 = addi
|
||||
// CHECK-NEXT: %5 = addi
|
||||
// CHECK-NEXT: val_std.addi
|
||||
// CHECK-NEXT: %4 = addi
|
||||
// CHECK-NEXT: %5 = addi
|
||||
// CHECK-NEXT: br
|
||||
// CHECK: EndLiveness
|
||||
%const1 = constant 1 : i32
|
||||
%inc = addi %val, %const1 : i32
|
||||
%inc2 = addi %counter, %const1 : i32
|
||||
br ^loopHeader(%inc, %inc2 : i32, i32)
|
||||
^exit(%sum : i32):
|
||||
// CHECK: Block: 3
|
||||
// CHECK-NEXT: LiveIn: arg1@0
|
||||
// CHECK-NEXT: LiveOut:{{ *$}}
|
||||
%result = addi %sum, %arg1 : i32
|
||||
return %result : i32
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// CHECK-LABEL: Testing : func_ranges
|
||||
func @func_ranges(%cond : i1, %arg1 : i32, %arg2 : i32, %arg3 : i32) -> i32 {
|
||||
// CHECK: Block: 0
|
||||
// CHECK-NEXT: LiveIn:{{ *$}}
|
||||
// CHECK-NEXT: LiveOut: arg2@0 val_std.muli val_std.addi
|
||||
// CHECK-NEXT: BeginLiveness
|
||||
// CHECK-NEXT: val_std.addi
|
||||
// CHECK-NEXT: %0 = addi
|
||||
// CHECK-NEXT: %c
|
||||
// CHECK-NEXT: %1 = addi
|
||||
// CHECK-NEXT: %2 = addi
|
||||
// CHECK-NEXT: %3 = muli
|
||||
// CHECK-NEXT: val_std.constant
|
||||
// CHECK-NEXT: %c
|
||||
// CHECK-NEXT: %1 = addi
|
||||
// CHECK-NEXT: %2 = addi
|
||||
// CHECK-NEXT: %3 = muli
|
||||
// CHECK-NEXT: %4 = muli
|
||||
// CHECK-NEXT: %5 = addi
|
||||
// CHECK-NEXT: val_std.addi
|
||||
// CHECK-NEXT: %1 = addi
|
||||
// CHECK-NEXT: %2 = addi
|
||||
// CHECK-NEXT: %3 = muli
|
||||
// CHECK-NEXT: val_std.addi
|
||||
// CHECK-NEXT %2 = addi
|
||||
// CHECK-NEXT %3 = muli
|
||||
// CHECK-NEXT %4 = muli
|
||||
// CHECK: val_std.muli
|
||||
// CHECK-NEXT: %3 = muli
|
||||
// CHECK-NEXT: %4 = muli
|
||||
// CHECK-NEXT: val_std.muli
|
||||
// CHECK-NEXT: %4 = muli
|
||||
// CHECK-NEXT: %5 = addi
|
||||
// CHECK-NEXT: cond_br
|
||||
// CHECK-NEXT: %c
|
||||
// CHECK-NEXT: %6 = muli
|
||||
// CHECK-NEXT: %7 = muli
|
||||
// CHECK-NEXT: %8 = addi
|
||||
// CHECK-NEXT: val_std.addi
|
||||
// CHECK-NEXT: %5 = addi
|
||||
// CHECK-NEXT: cond_br
|
||||
// CHECK-NEXT: %7
|
||||
// CHECK: EndLiveness
|
||||
%0 = addi %arg1, %arg2 : i32
|
||||
%const1 = constant 1 : i32
|
||||
%1 = addi %const1, %arg2 : i32
|
||||
%2 = addi %const1, %arg3 : i32
|
||||
%3 = muli %0, %1 : i32
|
||||
%4 = muli %3, %2 : i32
|
||||
%5 = addi %4, %const1 : i32
|
||||
cond_br %cond, ^bb1, ^bb2
|
||||
|
||||
^bb1:
|
||||
// CHECK: Block: 1
|
||||
// CHECK-NEXT: LiveIn: arg2@0 val_std.muli
|
||||
// CHECK-NEXT: LiveOut: arg2@0
|
||||
%const4 = constant 4 : i32
|
||||
%6 = muli %4, %const4 : i32
|
||||
br ^exit(%6 : i32)
|
||||
|
||||
^bb2:
|
||||
// CHECK: Block: 2
|
||||
// CHECK-NEXT: LiveIn: arg2@0 val_std.muli val_std.addi
|
||||
// CHECK-NEXT: LiveOut: arg2@0
|
||||
%7 = muli %4, %5 : i32
|
||||
%8 = addi %4, %arg2 : i32
|
||||
br ^exit(%8 : i32)
|
||||
|
||||
^exit(%sum : i32):
|
||||
// CHECK: Block: 3
|
||||
// CHECK-NEXT: LiveIn: arg2@0
|
||||
// CHECK-NEXT: LiveOut:{{ *$}}
|
||||
%result = addi %sum, %arg2 : i32
|
||||
return %result : i32
|
||||
}
|
|
@ -4,6 +4,7 @@ add_llvm_library(MLIRTestTransforms
|
|||
TestLoopFusion.cpp
|
||||
TestInlining.cpp
|
||||
TestLinalgTransforms.cpp
|
||||
TestLiveness.cpp
|
||||
TestLoopMapping.cpp
|
||||
TestLoopParametricTiling.cpp
|
||||
TestOpaqueLoc.cpp
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
//===- TestLiveness.cpp - Test liveness construction and information
|
||||
//-------===//
|
||||
//
|
||||
// Copyright 2019 The MLIR Authors.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// http://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.
|
||||
// =============================================================================
|
||||
//
|
||||
// This file contains test passes for constructing and resolving liveness
|
||||
// information.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "mlir/Analysis/Liveness.h"
|
||||
#include "mlir/Pass/Pass.h"
|
||||
|
||||
using namespace mlir;
|
||||
|
||||
namespace {
|
||||
|
||||
struct TestLivenessPass : public FunctionPass<TestLivenessPass> {
|
||||
void runOnFunction() override {
|
||||
llvm::errs() << "Testing : " << getFunction().getName() << "\n";
|
||||
getAnalysis<Liveness>().print(llvm::errs());
|
||||
}
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
static PassRegistration<TestLivenessPass>
|
||||
pass("test-print-liveness",
|
||||
"Print the contents of a constructed liveness information.");
|
Loading…
Reference in New Issue