[mlir] add getViewDest method to viewLikeOpInterface (#154524)

The viewLikeOpInterface abstracts the behavior of an operation view one
buffer as another. However, the current interface only includes a
"getViewSource" method and lacks a "getViewDest" method.

Previously, it was generally assumed that viewLikeOpInterface operations
would have only one return value, which was the view dest. This
assumption was broken by memref.extract_strided_metadata, and more
operations may break these silent conventions in the future. Calling
"viewLikeInterface->getResult(0)" may lead to a core dump at runtime.
Therefore, we need 'getViewDest' method to standardize our behavior.

This patch adds the getViewDest function to viewLikeOpInterface and
modifies the usage points of viewLikeOpInterface to standardize its use.
This commit is contained in:
donald chen 2025-08-21 20:09:52 +08:00 committed by GitHub
parent 9ccdadd2a5
commit 5af7263d42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 58 additions and 21 deletions

View File

@ -26,7 +26,17 @@ def ViewLikeOpInterface : OpInterface<"ViewLikeOpInterface"> {
let methods = [
InterfaceMethod<
"Returns the source buffer from which the view is created.",
"::mlir::Value", "getViewSource">
"::mlir::Value", "getViewSource">,
InterfaceMethod<
/*desc=*/[{ Returns the buffer which the view created. }],
/*retTy=*/"::mlir::Value",
/*methodName=*/"getViewDest",
/*args=*/(ins),
/*methodBody=*/"",
/*defaultImplementation=*/[{
return $_op->getResult(0);
}]
>
];
}

View File

@ -127,9 +127,12 @@ static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
Operation *op = result.getOwner();
// If this is a view, unwrap to the source.
if (ViewLikeOpInterface view = dyn_cast<ViewLikeOpInterface>(op))
if (ViewLikeOpInterface view = dyn_cast<ViewLikeOpInterface>(op)) {
if (result == view.getViewDest()) {
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,

View File

@ -190,7 +190,8 @@ static bool isEscapingMemref(Value memref, Block *block) {
// Check if this is defined to be an alias of another memref.
if (auto viewOp = dyn_cast<mlir::ViewLikeOpInterface>(defOp))
if (isEscapingMemref(viewOp.getViewSource(), block))
if (memref == viewOp.getViewDest() &&
isEscapingMemref(viewOp.getViewSource(), block))
return true;
// Any op besides allocating ops wouldn't guarantee alias freedom

View File

@ -463,8 +463,12 @@ struct SimplifyClones : public OpRewritePattern<CloneOp> {
// which otherwise could prevent removal of unnecessary allocs.
Value canonicalSource = source;
while (auto iface = dyn_cast_or_null<ViewLikeOpInterface>(
canonicalSource.getDefiningOp()))
canonicalSource.getDefiningOp())) {
if (canonicalSource != iface.getViewDest()) {
break;
}
canonicalSource = iface.getViewSource();
}
std::optional<Operation *> maybeCloneDeallocOp =
memref::findDealloc(cloneOp.getOutput());

View File

@ -37,8 +37,12 @@ using namespace mlir::bufferization;
/// Given a memref value, return the "base" value by skipping over all
/// ViewLikeOpInterface ops (if any) in the reverse use-def chain.
static Value getViewBase(Value value) {
while (auto viewLikeOp = value.getDefiningOp<ViewLikeOpInterface>())
while (auto viewLikeOp = value.getDefiningOp<ViewLikeOpInterface>()) {
if (value != viewLikeOp.getViewDest()) {
break;
}
value = viewLikeOp.getViewSource();
}
return value;
}

View File

@ -121,7 +121,7 @@ void BufferViewFlowAnalysis::build(Operation *op) {
// Add additional dependencies created by view changes to the alias list.
if (auto viewInterface = dyn_cast<ViewLikeOpInterface>(op)) {
registerDependencies(viewInterface.getViewSource(),
viewInterface->getResult(0));
viewInterface.getViewDest());
return WalkResult::advance();
}
@ -231,8 +231,12 @@ static bool isFunctionArgument(Value v) {
/// Given a memref value, return the "base" value by skipping over all
/// ViewLikeOpInterface ops (if any) in the reverse use-def chain.
static Value getViewBase(Value value) {
while (auto viewLikeOp = value.getDefiningOp<ViewLikeOpInterface>())
while (auto viewLikeOp = value.getDefiningOp<ViewLikeOpInterface>()) {
if (value != viewLikeOp.getViewDest()) {
break;
}
value = viewLikeOp.getViewSource();
}
return value;
}

View File

@ -235,8 +235,10 @@ getUnderlyingObjectSet(Value pointerValue) {
WalkContinuation walkResult = walkSlice(pointerValue, [&](Value val) {
// Attempt to advance to the source of the underlying view-like operation.
// Examples of view-like operations include GEPOp and AddrSpaceCastOp.
if (auto viewOp = val.getDefiningOp<ViewLikeOpInterface>())
if (auto viewOp = val.getDefiningOp<ViewLikeOpInterface>()) {
if (val == viewOp.getViewDest())
return WalkContinuation::advanceTo(viewOp.getViewSource());
}
// Attempt to advance to control flow predecessors.
std::optional<SmallVector<Value>> controlFlowPredecessors =

View File

@ -166,8 +166,12 @@ static bool noAliasingUseInLoop(vector::TransferReadOp transferRead,
Value source = transferRead.getBase();
// Skip view-like Ops and retrive the actual soruce Operation
while (auto srcOp = source.getDefiningOp<ViewLikeOpInterface>())
source = srcOp.getViewSource();
while (auto viewLike = source.getDefiningOp<ViewLikeOpInterface>()) {
if (viewLike.getViewDest() != source) {
break;
}
source = viewLike.getViewSource();
}
llvm::SmallVector<Operation *, 32> users(source.getUsers().begin(),
source.getUsers().end());
@ -178,7 +182,8 @@ static bool noAliasingUseInLoop(vector::TransferReadOp transferRead,
if (!processed.insert(user).second)
continue;
if (auto viewLike = dyn_cast<ViewLikeOpInterface>(user)) {
users.append(viewLike->getUsers().begin(), viewLike->getUsers().end());
Value viewDest = viewLike.getViewDest();
users.append(viewDest.getUsers().begin(), viewDest.getUsers().end());
continue;
}
if (isMemoryEffectFree(user) || isa<vector::TransferReadOp>(user))

View File

@ -959,7 +959,7 @@ class RewriteExtractAlignedPointerAsIndexOfViewLikeOp
PatternRewriter &rewriter) const override {
auto viewLikeOp =
extractOp.getSource().getDefiningOp<ViewLikeOpInterface>();
if (!viewLikeOp)
if (!viewLikeOp || extractOp.getSource() != viewLikeOp.getViewDest())
return rewriter.notifyMatchFailure(extractOp, "not a ViewLike source");
rewriter.modifyOpInPlace(extractOp, [&]() {
extractOp.getSourceMutable().assign(viewLikeOp.getViewSource());

View File

@ -210,9 +210,11 @@ MemrefValue skipFullyAliasingOperations(MemrefValue source) {
MemrefValue skipViewLikeOps(MemrefValue source) {
while (auto op = source.getDefiningOp()) {
if (auto viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
if (source == viewLike.getViewDest()) {
source = cast<MemrefValue>(viewLike.getViewSource());
continue;
}
}
return source;
}
return source;

View File

@ -98,8 +98,9 @@ void TransferOptimization::deadStoreOp(vector::TransferWriteOp write) {
// If the user has already been processed skip.
if (!processed.insert(user).second)
continue;
if (isa<ViewLikeOpInterface>(user)) {
users.append(user->getUsers().begin(), user->getUsers().end());
if (auto viewLike = dyn_cast<ViewLikeOpInterface>(user)) {
Value viewDest = viewLike.getViewDest();
users.append(viewDest.getUsers().begin(), viewDest.getUsers().end());
continue;
}
if (isMemoryEffectFree(user))
@ -182,8 +183,9 @@ void TransferOptimization::storeToLoadForwarding(vector::TransferReadOp read) {
// If the user has already been processed skip.
if (!processed.insert(user).second)
continue;
if (isa<ViewLikeOpInterface>(user)) {
users.append(user->getUsers().begin(), user->getUsers().end());
if (auto viewLike = dyn_cast<ViewLikeOpInterface>(user)) {
Value viewDest = viewLike.getViewDest();
users.append(viewDest.getUsers().begin(), viewDest.getUsers().end());
continue;
}
if (isMemoryEffectFree(user) || isa<vector::TransferReadOp>(user))