This CL adds a new RegionBranchTerminatorOpInterface to query information about operands that can be passed to successor regions. Similar to the BranchOpInterface, it allows to freely define the involved operands. However, in contrast to the BranchOpInterface, it expects an additional region number to distinguish between various use cases which might require different operands passed to different regions. Moreover, we added new utility functions (namely getMutableRegionBranchSuccessorOperands and getRegionBranchSuccessorOperands) to query (mutable) operand ranges for operations equiped with the ReturnLike trait and/or implementing the newly added interface. This simplifies reasoning about terminators in the scope of the nested regions. We also adjusted the SCF.ConditionOp to benefit from the newly added capabilities. Differential Revision: https://reviews.llvm.org/D105018
398 lines
16 KiB
C++
398 lines
16 KiB
C++
//===- LocalAliasAnalysis.cpp - Local stateless alias Analysis for MLIR ---===//
|
|
//
|
|
// 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/Analysis/AliasAnalysis/LocalAliasAnalysis.h"
|
|
|
|
#include "mlir/IR/FunctionSupport.h"
|
|
#include "mlir/IR/Matchers.h"
|
|
#include "mlir/Interfaces/ControlFlowInterfaces.h"
|
|
#include "mlir/Interfaces/SideEffectInterfaces.h"
|
|
#include "mlir/Interfaces/ViewLikeInterface.h"
|
|
|
|
using namespace mlir;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Underlying Address Computation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// The maximum depth that will be searched when trying to find an underlying
|
|
/// value.
|
|
static constexpr unsigned maxUnderlyingValueSearchDepth = 10;
|
|
|
|
/// Given a value, collect all of the underlying values being addressed.
|
|
static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
|
|
DenseSet<Value> &visited,
|
|
SmallVectorImpl<Value> &output);
|
|
|
|
/// Given a successor (`region`) of a RegionBranchOpInterface, collect all of
|
|
/// the underlying values being addressed by one of the successor inputs. If the
|
|
/// provided `region` is null, as per `RegionBranchOpInterface` this represents
|
|
/// the parent operation.
|
|
static void collectUnderlyingAddressValues(RegionBranchOpInterface branch,
|
|
Region *region, Value inputValue,
|
|
unsigned inputIndex,
|
|
unsigned maxDepth,
|
|
DenseSet<Value> &visited,
|
|
SmallVectorImpl<Value> &output) {
|
|
// Given the index of a region of the branch (`predIndex`), or None to
|
|
// represent the parent operation, try to return the index into the outputs of
|
|
// this region predecessor that correspond to the input values of `region`. If
|
|
// an index could not be found, None is returned instead.
|
|
auto getOperandIndexIfPred =
|
|
[&](Optional<unsigned> predIndex) -> Optional<unsigned> {
|
|
SmallVector<RegionSuccessor, 2> successors;
|
|
branch.getSuccessorRegions(predIndex, successors);
|
|
for (RegionSuccessor &successor : successors) {
|
|
if (successor.getSuccessor() != region)
|
|
continue;
|
|
// Check that the successor inputs map to the given input value.
|
|
ValueRange inputs = successor.getSuccessorInputs();
|
|
if (inputs.empty()) {
|
|
output.push_back(inputValue);
|
|
break;
|
|
}
|
|
unsigned firstInputIndex, lastInputIndex;
|
|
if (region) {
|
|
firstInputIndex = inputs[0].cast<BlockArgument>().getArgNumber();
|
|
lastInputIndex = inputs.back().cast<BlockArgument>().getArgNumber();
|
|
} else {
|
|
firstInputIndex = inputs[0].cast<OpResult>().getResultNumber();
|
|
lastInputIndex = inputs.back().cast<OpResult>().getResultNumber();
|
|
}
|
|
if (firstInputIndex > inputIndex || lastInputIndex < inputIndex) {
|
|
output.push_back(inputValue);
|
|
break;
|
|
}
|
|
return inputIndex - firstInputIndex;
|
|
}
|
|
return llvm::None;
|
|
};
|
|
|
|
// Check branches from the parent operation.
|
|
Optional<unsigned> regionIndex;
|
|
if (region) {
|
|
// Determine the actual region number from the passed region.
|
|
regionIndex = region->getRegionNumber();
|
|
if (Optional<unsigned> operandIndex =
|
|
getOperandIndexIfPred(/*predIndex=*/llvm::None)) {
|
|
collectUnderlyingAddressValues(
|
|
branch.getSuccessorEntryOperands(*regionIndex)[*operandIndex],
|
|
maxDepth, visited, output);
|
|
}
|
|
}
|
|
// Check branches from each child region.
|
|
Operation *op = branch.getOperation();
|
|
for (int i = 0, e = op->getNumRegions(); i != e; ++i) {
|
|
if (Optional<unsigned> operandIndex = getOperandIndexIfPred(i)) {
|
|
for (Block &block : op->getRegion(i)) {
|
|
Operation *term = block.getTerminator();
|
|
// Try to determine possible region-branch successor operands for the
|
|
// current region.
|
|
auto successorOperands =
|
|
getRegionBranchSuccessorOperands(term, regionIndex);
|
|
if (successorOperands) {
|
|
collectUnderlyingAddressValues((*successorOperands)[*operandIndex],
|
|
maxDepth, visited, output);
|
|
} else if (term->getNumSuccessors()) {
|
|
// Otherwise, if this terminator may exit the region we can't make
|
|
// any assumptions about which values get passed.
|
|
output.push_back(inputValue);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Given a result, collect all of the underlying values being addressed.
|
|
static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
|
|
DenseSet<Value> &visited,
|
|
SmallVectorImpl<Value> &output) {
|
|
Operation *op = result.getOwner();
|
|
|
|
// If this is a view, unwrap to the source.
|
|
if (ViewLikeOpInterface view = dyn_cast<ViewLikeOpInterface>(op))
|
|
return collectUnderlyingAddressValues(view.getViewSource(), maxDepth,
|
|
visited, output);
|
|
// Check to see if we can reason about the control flow of this op.
|
|
if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
|
|
return collectUnderlyingAddressValues(branch, /*region=*/nullptr, result,
|
|
result.getResultNumber(), maxDepth,
|
|
visited, output);
|
|
}
|
|
|
|
output.push_back(result);
|
|
}
|
|
|
|
/// Given a block argument, collect all of the underlying values being
|
|
/// addressed.
|
|
static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
|
|
DenseSet<Value> &visited,
|
|
SmallVectorImpl<Value> &output) {
|
|
Block *block = arg.getOwner();
|
|
unsigned argNumber = arg.getArgNumber();
|
|
|
|
// Handle the case of a non-entry block.
|
|
if (!block->isEntryBlock()) {
|
|
for (auto it = block->pred_begin(), e = block->pred_end(); it != e; ++it) {
|
|
auto branch = dyn_cast<BranchOpInterface>((*it)->getTerminator());
|
|
if (!branch) {
|
|
// We can't analyze the control flow, so bail out early.
|
|
output.push_back(arg);
|
|
return;
|
|
}
|
|
|
|
// Try to get the operand passed for this argument.
|
|
unsigned index = it.getSuccessorIndex();
|
|
Optional<OperandRange> operands = branch.getSuccessorOperands(index);
|
|
if (!operands) {
|
|
// We can't analyze the control flow, so bail out early.
|
|
output.push_back(arg);
|
|
return;
|
|
}
|
|
collectUnderlyingAddressValues((*operands)[argNumber], maxDepth, visited,
|
|
output);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Otherwise, check to see if we can reason about the control flow of this op.
|
|
Region *region = block->getParent();
|
|
Operation *op = region->getParentOp();
|
|
if (auto branch = dyn_cast<RegionBranchOpInterface>(op)) {
|
|
return collectUnderlyingAddressValues(branch, region, arg, argNumber,
|
|
maxDepth, visited, output);
|
|
}
|
|
|
|
// We can't reason about the underlying address of this argument.
|
|
output.push_back(arg);
|
|
}
|
|
|
|
/// Given a value, collect all of the underlying values being addressed.
|
|
static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
|
|
DenseSet<Value> &visited,
|
|
SmallVectorImpl<Value> &output) {
|
|
// Check that we don't infinitely recurse.
|
|
if (!visited.insert(value).second)
|
|
return;
|
|
if (maxDepth == 0) {
|
|
output.push_back(value);
|
|
return;
|
|
}
|
|
--maxDepth;
|
|
|
|
if (BlockArgument arg = value.dyn_cast<BlockArgument>())
|
|
return collectUnderlyingAddressValues(arg, maxDepth, visited, output);
|
|
collectUnderlyingAddressValues(value.cast<OpResult>(), maxDepth, visited,
|
|
output);
|
|
}
|
|
|
|
/// Given a value, collect all of the underlying values being addressed.
|
|
static void collectUnderlyingAddressValues(Value value,
|
|
SmallVectorImpl<Value> &output) {
|
|
DenseSet<Value> visited;
|
|
collectUnderlyingAddressValues(value, maxUnderlyingValueSearchDepth, visited,
|
|
output);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// LocalAliasAnalysis: alias
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Given a value, try to get an allocation effect attached to it. If
|
|
/// successful, `allocEffect` is populated with the effect. If an effect was
|
|
/// found, `allocScopeOp` is also specified if a parent operation of `value`
|
|
/// could be identified that bounds the scope of the allocated value; i.e. if
|
|
/// non-null it specifies the parent operation that the allocation does not
|
|
/// escape. If no scope is found, `allocScopeOp` is set to nullptr.
|
|
static LogicalResult
|
|
getAllocEffectFor(Value value, Optional<MemoryEffects::EffectInstance> &effect,
|
|
Operation *&allocScopeOp) {
|
|
// Try to get a memory effect interface for the parent operation.
|
|
Operation *op;
|
|
if (BlockArgument arg = value.dyn_cast<BlockArgument>())
|
|
op = arg.getOwner()->getParentOp();
|
|
else
|
|
op = value.cast<OpResult>().getOwner();
|
|
MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op);
|
|
if (!interface)
|
|
return failure();
|
|
|
|
// Try to find an allocation effect on the resource.
|
|
if (!(effect = interface.getEffectOnValue<MemoryEffects::Allocate>(value)))
|
|
return failure();
|
|
|
|
// If we found an allocation effect, try to find a scope for the allocation.
|
|
// If the resource of this allocation is automatically scoped, find the parent
|
|
// operation that bounds the allocation scope.
|
|
if (llvm::isa<SideEffects::AutomaticAllocationScopeResource>(
|
|
effect->getResource())) {
|
|
allocScopeOp = op->getParentWithTrait<OpTrait::AutomaticAllocationScope>();
|
|
return success();
|
|
}
|
|
|
|
// TODO: Here we could look at the users to see if the resource is either
|
|
// freed on all paths within the region, or is just not captured by anything.
|
|
// For now assume allocation scope to the function scope (we don't care if
|
|
// pointer escape outside function).
|
|
allocScopeOp = op->getParentWithTrait<OpTrait::FunctionLike>();
|
|
return success();
|
|
}
|
|
|
|
/// Given the two values, return their aliasing behavior.
|
|
static AliasResult aliasImpl(Value lhs, Value rhs) {
|
|
if (lhs == rhs)
|
|
return AliasResult::MustAlias;
|
|
Operation *lhsAllocScope = nullptr, *rhsAllocScope = nullptr;
|
|
Optional<MemoryEffects::EffectInstance> lhsAlloc, rhsAlloc;
|
|
|
|
// Handle the case where lhs is a constant.
|
|
Attribute lhsAttr, rhsAttr;
|
|
if (matchPattern(lhs, m_Constant(&lhsAttr))) {
|
|
// TODO: This is overly conservative. Two matching constants don't
|
|
// necessarily map to the same address. For example, if the two values
|
|
// correspond to different symbols that both represent a definition.
|
|
if (matchPattern(rhs, m_Constant(&rhsAttr)))
|
|
return AliasResult::MayAlias;
|
|
|
|
// Try to find an alloc effect on rhs. If an effect was found we can't
|
|
// alias, otherwise we might.
|
|
return succeeded(getAllocEffectFor(rhs, rhsAlloc, rhsAllocScope))
|
|
? AliasResult::NoAlias
|
|
: AliasResult::MayAlias;
|
|
}
|
|
// Handle the case where rhs is a constant.
|
|
if (matchPattern(rhs, m_Constant(&rhsAttr))) {
|
|
// Try to find an alloc effect on lhs. If an effect was found we can't
|
|
// alias, otherwise we might.
|
|
return succeeded(getAllocEffectFor(lhs, lhsAlloc, lhsAllocScope))
|
|
? AliasResult::NoAlias
|
|
: AliasResult::MayAlias;
|
|
}
|
|
|
|
// Otherwise, neither of the values are constant so check to see if either has
|
|
// an allocation effect.
|
|
bool lhsHasAlloc = succeeded(getAllocEffectFor(lhs, lhsAlloc, lhsAllocScope));
|
|
bool rhsHasAlloc = succeeded(getAllocEffectFor(rhs, rhsAlloc, rhsAllocScope));
|
|
if (lhsHasAlloc == rhsHasAlloc) {
|
|
// If both values have an allocation effect we know they don't alias, and if
|
|
// neither have an effect we can't make an assumptions.
|
|
return lhsHasAlloc ? AliasResult::NoAlias : AliasResult::MayAlias;
|
|
}
|
|
|
|
// When we reach this point we have one value with a known allocation effect,
|
|
// and one without. Move the one with the effect to the lhs to make the next
|
|
// checks simpler.
|
|
if (rhsHasAlloc) {
|
|
std::swap(lhs, rhs);
|
|
lhsAlloc = rhsAlloc;
|
|
lhsAllocScope = rhsAllocScope;
|
|
}
|
|
|
|
// If the effect has a scoped allocation region, check to see if the
|
|
// non-effect value is defined above that scope.
|
|
if (lhsAllocScope) {
|
|
// If the parent operation of rhs is an ancestor of the allocation scope, or
|
|
// if rhs is an entry block argument of the allocation scope we know the two
|
|
// values can't alias.
|
|
Operation *rhsParentOp = rhs.getParentRegion()->getParentOp();
|
|
if (rhsParentOp->isProperAncestor(lhsAllocScope))
|
|
return AliasResult::NoAlias;
|
|
if (rhsParentOp == lhsAllocScope) {
|
|
BlockArgument rhsArg = rhs.dyn_cast<BlockArgument>();
|
|
if (rhsArg && rhs.getParentBlock()->isEntryBlock())
|
|
return AliasResult::NoAlias;
|
|
}
|
|
}
|
|
|
|
// If we couldn't reason about the relationship between the two values,
|
|
// conservatively assume they might alias.
|
|
return AliasResult::MayAlias;
|
|
}
|
|
|
|
/// Given the two values, return their aliasing behavior.
|
|
AliasResult LocalAliasAnalysis::alias(Value lhs, Value rhs) {
|
|
if (lhs == rhs)
|
|
return AliasResult::MustAlias;
|
|
|
|
// Get the underlying values being addressed.
|
|
SmallVector<Value, 8> lhsValues, rhsValues;
|
|
collectUnderlyingAddressValues(lhs, lhsValues);
|
|
collectUnderlyingAddressValues(rhs, rhsValues);
|
|
|
|
// If we failed to collect for either of the values somehow, conservatively
|
|
// assume they may alias.
|
|
if (lhsValues.empty() || rhsValues.empty())
|
|
return AliasResult::MayAlias;
|
|
|
|
// Check the alias results against each of the underlying values.
|
|
Optional<AliasResult> result;
|
|
for (Value lhsVal : lhsValues) {
|
|
for (Value rhsVal : rhsValues) {
|
|
AliasResult nextResult = aliasImpl(lhsVal, rhsVal);
|
|
result = result ? result->merge(nextResult) : nextResult;
|
|
}
|
|
}
|
|
|
|
// We should always have a valid result here.
|
|
return *result;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// LocalAliasAnalysis: getModRef
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
ModRefResult LocalAliasAnalysis::getModRef(Operation *op, Value location) {
|
|
// Check to see if this operation relies on nested side effects.
|
|
if (op->hasTrait<OpTrait::HasRecursiveSideEffects>()) {
|
|
// TODO: To check recursive operations we need to check all of the nested
|
|
// operations, which can result in a quadratic number of queries. We should
|
|
// introduce some caching of some kind to help alleviate this, especially as
|
|
// this caching could be used in other areas of the codebase (e.g. when
|
|
// checking `wouldOpBeTriviallyDead`).
|
|
return ModRefResult::getModAndRef();
|
|
}
|
|
|
|
// Otherwise, check to see if this operation has a memory effect interface.
|
|
MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op);
|
|
if (!interface)
|
|
return ModRefResult::getModAndRef();
|
|
|
|
// Build a ModRefResult by merging the behavior of the effects of this
|
|
// operation.
|
|
SmallVector<MemoryEffects::EffectInstance> effects;
|
|
interface.getEffects(effects);
|
|
|
|
ModRefResult result = ModRefResult::getNoModRef();
|
|
for (const MemoryEffects::EffectInstance &effect : effects) {
|
|
if (isa<MemoryEffects::Allocate, MemoryEffects::Free>(effect.getEffect()))
|
|
continue;
|
|
|
|
// Check for an alias between the effect and our memory location.
|
|
// TODO: Add support for checking an alias with a symbol reference.
|
|
AliasResult aliasResult = AliasResult::MayAlias;
|
|
if (Value effectValue = effect.getValue())
|
|
aliasResult = alias(effectValue, location);
|
|
|
|
// If we don't alias, ignore this effect.
|
|
if (aliasResult.isNo())
|
|
continue;
|
|
|
|
// Merge in the corresponding mod or ref for this effect.
|
|
if (isa<MemoryEffects::Read>(effect.getEffect())) {
|
|
result = result.merge(ModRefResult::getRef());
|
|
} else {
|
|
assert(isa<MemoryEffects::Write>(effect.getEffect()));
|
|
result = result.merge(ModRefResult::getMod());
|
|
}
|
|
if (result.isModAndRef())
|
|
break;
|
|
}
|
|
return result;
|
|
}
|