llvm-project/mlir/lib/Analysis/SliceWalk.cpp
Matthias Springer 5f3b40ec7a
[mlir][Interfaces][NFC] Simplify and align RegionSuccessor design / API (#174945)
Simplify the design of `RegionSuccessor`. There is no need to store the
`Operation *` pointer when branching out of the region branch op (to the
parent). There is no API to even access the `Operation *` pointer.

Add a new helper function `RegionSuccessor::parent` to construct a
region successor that points to the parent. This aligns the
`RegionSuccessor` design and API with `RegionBranchPoint`:
* Both classes now have a `parent()` helper function.
`ClassName::parent()` can be used in documentation to precisely describe
the source/target of a region branch.
* Both classes now use `nullptr` internally to represent "parent".

This API change also protects against incorrect API usage: users can no
longer pass an incorrect parent op. If a region successor is not a
region of the region branch op, it *must* branch out of region branch op
itself ("parent"). However, the previous API allowed passing other
operations. There was one such API violation in a [test
case](https://github.com/llvm/llvm-project/pull/174945/files#diff-d5717e4a8d7344b2ff77762b8fa480bcfec0eeee97a86195c787d791a6217e13L71).

Also clean up the documentation to use the correct terminology (such as
"successor operands", "successor inputs") consistently.

Note: This PR effectively rolls back some changes from #161575. That PR
introduced `llvm::PointerUnion<Region *, Operation *>
successor{nullptr};`. It is unclear from the commit message why that
change was made.

Note for LLVM integration: You may have to slightly modify
`getSuccessorRegion` implementations: Replace
`RegionSuccessor(getOperation(), getOperation()->getResults())` with
`RegionSuccessor::parent(getResults())`.
2026-01-14 10:57:22 +01:00

102 lines
4.0 KiB
C++

#include "mlir/Analysis/SliceWalk.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
using namespace mlir;
WalkContinuation mlir::walkSlice(ValueRange rootValues,
WalkCallback walkCallback) {
// Search the backward slice starting from the root values.
SmallVector<Value> workList = rootValues;
llvm::SmallDenseSet<Value, 16> seenValues;
while (!workList.empty()) {
// Search the backward slice of the current value.
Value current = workList.pop_back_val();
// Skip the current value if it has already been seen.
if (!seenValues.insert(current).second)
continue;
// Call the walk callback with the current value.
WalkContinuation continuation = walkCallback(current);
if (continuation.wasInterrupted())
return continuation;
if (continuation.wasSkipped())
continue;
assert(continuation.wasAdvancedTo());
// Add the next values to the work list if the walk should continue.
workList.append(continuation.getNextValues().begin(),
continuation.getNextValues().end());
}
return WalkContinuation::skip();
}
/// Returns the predecessor branch operands that match `blockArg`, or nullopt if
/// some of the predecessor terminators do not implement the BranchOpInterface.
static std::optional<SmallVector<Value>>
getBlockPredecessorOperands(BlockArgument blockArg) {
Block *block = blockArg.getOwner();
// Search the predecessor operands for all predecessor terminators.
SmallVector<Value> predecessorOperands;
for (auto it = block->pred_begin(); it != block->pred_end(); ++it) {
Block *predecessor = *it;
auto branchOp = dyn_cast<BranchOpInterface>(predecessor->getTerminator());
if (!branchOp)
return std::nullopt;
SuccessorOperands successorOperands =
branchOp.getSuccessorOperands(it.getSuccessorIndex());
// Store the predecessor operand if the block argument matches an operand
// and is not produced by the terminator.
if (Value operand = successorOperands[blockArg.getArgNumber()])
predecessorOperands.push_back(operand);
}
return predecessorOperands;
}
std::optional<SmallVector<Value>>
mlir::getControlFlowPredecessors(Value value) {
if (OpResult opResult = dyn_cast<OpResult>(value)) {
if (auto selectOp = opResult.getDefiningOp<SelectLikeOpInterface>())
return SmallVector<Value>(
{selectOp.getTrueValue(), selectOp.getFalseValue()});
auto regionOp = opResult.getDefiningOp<RegionBranchOpInterface>();
// If the interface is not implemented, there are no control flow
// predecessors to work with.
if (!regionOp)
return std::nullopt;
// Add the control flow predecessor operands to the work list.
RegionSuccessor region = RegionSuccessor::parent(regionOp->getResults());
SmallVector<Value> predecessorOperands;
// TODO (#175168): This assumes that there are no non-successor-inputs
// in front of the op result.
regionOp.getPredecessorValues(region, opResult.getResultNumber(),
predecessorOperands);
return predecessorOperands;
}
auto blockArg = cast<BlockArgument>(value);
Block *block = blockArg.getOwner();
// Search the region predecessor operands for structured control flow.
if (block->isEntryBlock()) {
if (auto regionBranchOp =
dyn_cast<RegionBranchOpInterface>(block->getParentOp())) {
RegionSuccessor region(blockArg.getParentRegion());
SmallVector<Value> predecessorOperands;
// TODO (#175168): This assumes that there are no non-successor-inputs
// in front of the block argument.
regionBranchOp.getPredecessorValues(region, blockArg.getArgNumber(),
predecessorOperands);
return predecessorOperands;
}
// If the interface is not implemented, there are no control flow
// predecessors to work with.
return std::nullopt;
}
// Search the block predecessor operands for unstructured control flow.
return getBlockPredecessorOperands(blockArg);
}