[Mlir-commits] [flang] [mlir] [RFC][mlir] ViewLikeOpInterface method for detecting partial views. (PR #164020)
Slava Zakharin
llvmlistbot at llvm.org
Wed Oct 29 17:52:52 PDT 2025
https://github.com/vzakhari updated https://github.com/llvm/llvm-project/pull/164020
>From 1826c30838d156463ad5829ae34258c349746633 Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Wed, 29 Oct 2025 16:51:50 -0700
Subject: [PATCH 1/2] [RFC][mlir] ViewLikeOpInterface method for detecting
partial views.
I am trying to simplify the implementation of Flang's AliasAnalysis
by using ViewLikeOpInterface for some FIR dialect operations.
Currently, Flang's AliasAnalysis implementation explicitly recognizes
specific set of operations, which is inconvenient.
ViewLikeOpInterface may also be used in other parts of Flang.
This patch adds isKnownToHaveSameStart() method to detect
cases where the input and resulting views may be different
by an offset, in which case FIR alias analysis must not return
`MustAlias`.
New method isKnownToBeCompleteView() returns true iff
the resulting view is a complete view of the input view.
This method will be used by the following patches for OpenACC
support in Flang.
This patch also modifies LocalAliasAnalysis to stop returning
`MustAlias` in cases where both `MustAlias` and `PartialAlias`
are possible - we will return `MayAlias` now, which is conservatively
correct.
---
.../include/flang/Optimizer/Dialect/FIROps.h | 1 +
.../include/flang/Optimizer/Dialect/FIROps.td | 19 ++-
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 28 +++--
mlir/include/mlir/Analysis/AliasAnalysis.h | 7 +-
.../mlir/Dialect/MemRef/IR/MemRefOps.td | 10 +-
.../mlir/Interfaces/ViewLikeInterface.td | 52 +++++---
.../AliasAnalysis/LocalAliasAnalysis.cpp | 108 ++++++++++------
mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp | 16 +++
.../test-alias-analysis-extending.mlir | 9 ++
mlir/test/Analysis/test-alias-analysis.mlir | 118 ++++++++++++++++++
10 files changed, 295 insertions(+), 73 deletions(-)
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.h b/flang/include/flang/Optimizer/Dialect/FIROps.h
index 62ef8b4b502f2..4651f2bb8038e 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.h
@@ -20,6 +20,7 @@
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
#include "mlir/Interfaces/LoopLikeInterface.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
+#include "mlir/Interfaces/ViewLikeInterface.h"
namespace fir {
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 58a317cf5d691..f83e3e990d315 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -17,6 +17,7 @@
include "mlir/Dialect/Arith/IR/ArithBase.td"
include "mlir/Dialect/Arith/IR/ArithOpsInterfaces.td"
include "mlir/Dialect/LLVMIR/LLVMAttrDefs.td"
+include "mlir/Interfaces/ViewLikeInterface.td"
include "flang/Optimizer/Dialect/CUF/Attributes/CUFAttr.td"
include "flang/Optimizer/Dialect/FIRDialect.td"
include "flang/Optimizer/Dialect/FIRTypes.td"
@@ -1765,8 +1766,9 @@ def fir_ArrayMergeStoreOp : fir_Op<"array_merge_store",
// Record and array type operations
//===----------------------------------------------------------------------===//
-def fir_ArrayCoorOp : fir_Op<"array_coor",
- [NoMemoryEffect, AttrSizedOperandSegments]> {
+def fir_ArrayCoorOp
+ : fir_Op<"array_coor", [NoMemoryEffect, AttrSizedOperandSegments,
+ ViewLikeOpInterface]> {
let summary = "Find the coordinate of an element of an array";
@@ -1809,9 +1811,13 @@ def fir_ArrayCoorOp : fir_Op<"array_coor",
let hasVerifier = 1;
let hasCanonicalizer = 1;
+ let extraClassDeclaration = [{
+ mlir::Value getViewSource() { return getMemref(); }
+ }];
}
-def fir_CoordinateOp : fir_Op<"coordinate_of", [NoMemoryEffect]> {
+def fir_CoordinateOp
+ : fir_Op<"coordinate_of", [NoMemoryEffect, ViewLikeOpInterface]> {
let summary = "Finds the coordinate (location) of a value in memory";
@@ -1863,6 +1869,7 @@ def fir_CoordinateOp : fir_Op<"coordinate_of", [NoMemoryEffect]> {
let extraClassDeclaration = [{
constexpr static int32_t kDynamicIndex = std::numeric_limits<int32_t>::min();
CoordinateIndicesAdaptor getIndices();
+ mlir::Value getViewSource() { return getRef(); }
}];
}
@@ -2828,7 +2835,8 @@ def fir_VolatileCastOp : fir_SimpleOneResultOp<"volatile_cast", [Pure]> {
let hasFolder = 1;
}
-def fir_ConvertOp : fir_SimpleOneResultOp<"convert", [NoMemoryEffect]> {
+def fir_ConvertOp
+ : fir_SimpleOneResultOp<"convert", [NoMemoryEffect, ViewLikeOpInterface]> {
let summary = "encapsulates all Fortran entity type conversions";
let description = [{
@@ -2866,6 +2874,9 @@ def fir_ConvertOp : fir_SimpleOneResultOp<"convert", [NoMemoryEffect]> {
static bool isPointerCompatible(mlir::Type ty);
static bool canBeConverted(mlir::Type inType, mlir::Type outType);
static bool areVectorsCompatible(mlir::Type inTy, mlir::Type outTy);
+ mlir::Value getViewSource() { return getValue(); }
+ bool isSameStart() { return true; }
+ bool isCompleteView() { return true; }
}];
let hasCanonicalizer = 1;
}
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 73ddd1ff80126..62b18e5a7258c 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -212,6 +212,10 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
if (lhsSrc.origin == rhsSrc.origin) {
LLVM_DEBUG(llvm::dbgs()
<< " aliasing because same source kind and origin\n");
+ // TODO: we should return PartialAlias here. Need to decide
+ // if returning PartialAlias for fir.pack_array is okay;
+ // if not, then we need to handle it specially to still return
+ // MayAlias.
if (approximateSource)
return AliasResult::MayAlias;
return AliasResult::MustAlias;
@@ -559,10 +563,19 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
type = SourceKind::Allocate;
breakFromLoop = true;
})
- .Case<fir::ConvertOp>([&](auto op) {
- // Skip ConvertOp's and track further through the operand.
- v = op->getOperand(0);
+ .Case<mlir::ViewLikeOpInterface>([&](auto op) {
+ if (isPointerReference(ty))
+ attributes.set(Attribute::Pointer);
+ v = op.getViewSource();
defOp = v.getDefiningOp();
+ // If the source is a box, and the result is not a box,
+ // then this is one of the box "unpacking" operations,
+ // so we should set followingData.
+ if (mlir::isa<fir::BaseBoxType>(v.getType()) &&
+ !mlir::isa<fir::BaseBoxType>(ty))
+ followBoxData = true;
+ if (!op.isSameStart())
+ approximateSource = true;
})
.Case<fir::PackArrayOp>([&](auto op) {
// The packed array is not distinguishable from the original
@@ -578,15 +591,6 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
if (mlir::isa<fir::BaseBoxType>(v.getType()))
followBoxData = true;
})
- .Case<fir::ArrayCoorOp, fir::CoordinateOp>([&](auto op) {
- if (isPointerReference(ty))
- attributes.set(Attribute::Pointer);
- v = op->getOperand(0);
- defOp = v.getDefiningOp();
- if (mlir::isa<fir::BaseBoxType>(v.getType()))
- followBoxData = true;
- approximateSource = true;
- })
.Case<fir::EmboxOp, fir::ReboxOp>([&](auto op) {
if (followBoxData) {
v = op->getOperand(0);
diff --git a/mlir/include/mlir/Analysis/AliasAnalysis.h b/mlir/include/mlir/Analysis/AliasAnalysis.h
index 5beae79ad1a03..d5528a0ceb181 100644
--- a/mlir/include/mlir/Analysis/AliasAnalysis.h
+++ b/mlir/include/mlir/Analysis/AliasAnalysis.h
@@ -35,9 +35,12 @@ class AliasResult {
/// The two locations may or may not alias. This is the least precise
/// result.
MayAlias,
- /// The two locations alias, but only due to a partial overlap.
+ /// The two locations overlap in some way, regardless of whether
+ /// they start at the same address or not.
PartialAlias,
- /// The two locations precisely alias each other.
+ /// The two locations precisely alias each other, meaning that
+ /// they always start at exactly the same location.
+ /// This result does not imply that the pointers compare equal.
MustAlias,
};
diff --git a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
index b39207fc30dd7..e7a4dd545f6ba 100644
--- a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
+++ b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
@@ -2288,7 +2288,7 @@ def SubViewOp : MemRef_OpWithOffsetSizesAndStrides<"subview", [
CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>
];
- let extraClassDeclaration = extraBaseClassDeclaration # [{
+ let extraClassDeclaration = extraBaseClassDeclaration#[{
/// Returns the type of the base memref operand.
MemRefType getSourceType() {
return ::llvm::cast<MemRefType>(getSource().getType());
@@ -2350,6 +2350,10 @@ def SubViewOp : MemRef_OpWithOffsetSizesAndStrides<"subview", [
/// If the shape of `value` cannot be rank-reduced to `desiredShape`, fail.
static FailureOr<Value> rankReduceIfNeeded(
OpBuilder &b, Location loc, Value value, ArrayRef<int64_t> desiredShape);
+
+ /// Return true iff the input and the resulting views start
+ /// at the same address.
+ bool isSameStart();
}];
let hasCanonicalizer = 1;
@@ -2459,6 +2463,10 @@ def MemRef_ViewOp : MemRef_Op<"view", [
operand_range getDynamicSizes() {
return {getSizes().begin(), getSizes().end()};
}
+
+ /// Return true iff the input and the resulting views start
+ /// at the same address.
+ bool isSameStart();
}];
let assemblyFormat = [{
diff --git a/mlir/include/mlir/Interfaces/ViewLikeInterface.td b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
index 131c1a0d92b24..8d71d50a5c155 100644
--- a/mlir/include/mlir/Interfaces/ViewLikeInterface.td
+++ b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
@@ -23,21 +23,45 @@ def ViewLikeOpInterface : OpInterface<"ViewLikeOpInterface"> {
}];
let cppNamespace = "::mlir";
- let methods = [
- InterfaceMethod<
- "Returns the source buffer from which the view is created.",
- "::mlir::Value", "getViewSource">,
- InterfaceMethod<
- /*desc=*/[{ Returns the buffer which the view created. }],
- /*retTy=*/"::mlir::Value",
- /*methodName=*/"getViewDest",
- /*args=*/(ins),
- /*methodBody=*/"",
- /*defaultImplementation=*/[{
+ let methods =
+ [InterfaceMethod<
+ "Returns the source buffer from which the view is created.",
+ "::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);
- }]
- >
- ];
+ }]>,
+ InterfaceMethod<
+ /*desc=*/
+ [{ Returns true iff the source buffer and the resulting view start at the same "address". }],
+ /*retTy=*/"bool",
+ /*methodName=*/"isSameStart",
+ /*args=*/(ins),
+ /*methodBody=*/"",
+ /*defaultImplementation=*/[{
+ return false;
+ }]>,
+ InterfaceMethod<
+ /*desc=*/
+ [{ Returns true iff the resulting view is a complete view of the source buffer, i.e. isSameStart() is true and the result has the same total size and is not a subview of the input. }],
+ /*retTy=*/"bool",
+ /*methodName=*/"isCompleteView",
+ /*args=*/(ins),
+ /*methodBody=*/"",
+ /*defaultImplementation=*/[{
+ return false;
+ }]>];
+
+ let verify = [{
+ auto concreteOp = ::mlir::cast<ConcreteOp>($_op);
+ // isCompleteView() == true implies isSameStart() == true.
+ return !concreteOp.isCompleteView() || concreteOp.isSameStart() ? ::mlir::success() : ::mlir::failure();
+ }];
}
def OffsetSizeAndStrideOpInterface : OpInterface<"OffsetSizeAndStrideOpInterface"> {
diff --git a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
index 24cb123e51877..fe2c5e0d585fd 100644
--- a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
+++ b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
@@ -40,9 +40,10 @@ using namespace mlir;
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);
+static void
+collectUnderlyingAddressValues(Value value, unsigned maxDepth,
+ DenseMap<Value, bool> &visited, bool maybeOffset,
+ SmallVectorImpl<std::pair<Value, bool>> &output);
/// Given a RegionBranchOpInterface operation (`branch`), a Value`inputValue`
/// which is an input for the provided successor (`initialSuccessor`), try to
@@ -50,7 +51,8 @@ static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
static void collectUnderlyingAddressValues2(
RegionBranchOpInterface branch, RegionSuccessor initialSuccessor,
Value inputValue, unsigned inputIndex, unsigned maxDepth,
- DenseSet<Value> &visited, SmallVectorImpl<Value> &output) {
+ DenseMap<Value, bool> &visited, bool maybeOffset,
+ SmallVectorImpl<std::pair<Value, bool>> &output) {
LDBG() << "collectUnderlyingAddressValues2: "
<< OpWithFlags(branch.getOperation(), OpPrintingFlags().skipRegions());
LDBG() << " with initialSuccessor " << initialSuccessor;
@@ -60,7 +62,7 @@ static void collectUnderlyingAddressValues2(
ValueRange inputs = initialSuccessor.getSuccessorInputs();
if (inputs.empty()) {
LDBG() << " input is empty, enqueue value";
- output.push_back(inputValue);
+ output.push_back({inputValue, maybeOffset});
return;
}
unsigned firstInputIndex, lastInputIndex;
@@ -75,7 +77,7 @@ static void collectUnderlyingAddressValues2(
LDBG() << " !! Input index " << inputIndex << " out of range "
<< firstInputIndex << " to " << lastInputIndex
<< ", adding input value to output";
- output.push_back(inputValue);
+ output.push_back({inputValue, maybeOffset});
return;
}
SmallVector<Value> predecessorValues;
@@ -84,14 +86,15 @@ static void collectUnderlyingAddressValues2(
LDBG() << " Found " << predecessorValues.size() << " predecessor values";
for (Value predecessorValue : predecessorValues) {
LDBG() << " Processing predecessor value: " << predecessorValue;
- collectUnderlyingAddressValues(predecessorValue, maxDepth, visited, output);
+ collectUnderlyingAddressValues(predecessorValue, maxDepth, visited,
+ maybeOffset, output);
}
}
/// Given a result, collect all of the underlying values being addressed.
-static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
- DenseSet<Value> &visited,
- SmallVectorImpl<Value> &output) {
+static void collectUnderlyingAddressValues(
+ OpResult result, unsigned maxDepth, DenseMap<Value, bool> &visited,
+ bool maybeOffset, SmallVectorImpl<std::pair<Value, bool>> &output) {
LDBG() << "collectUnderlyingAddressValues (OpResult): " << result;
LDBG() << " maxDepth: " << maxDepth;
@@ -101,8 +104,8 @@ static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
if (ViewLikeOpInterface view = dyn_cast<ViewLikeOpInterface>(op)) {
if (result == view.getViewDest()) {
LDBG() << " Unwrapping view to source: " << view.getViewSource();
- return collectUnderlyingAddressValues(view.getViewSource(), maxDepth,
- visited, output);
+ return collectUnderlyingAddressValues(
+ view.getViewSource(), maxDepth, visited, !view.isSameStart(), output);
}
}
// Check to see if we can reason about the control flow of this op.
@@ -110,18 +113,18 @@ static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
LDBG() << " Processing region branch operation";
return collectUnderlyingAddressValues2(
branch, RegionSuccessor(op, op->getResults()), result,
- result.getResultNumber(), maxDepth, visited, output);
+ result.getResultNumber(), maxDepth, visited, maybeOffset, output);
}
LDBG() << " Adding result to output: " << result;
- output.push_back(result);
+ output.push_back({result, maybeOffset});
}
/// 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) {
+static void collectUnderlyingAddressValues(
+ BlockArgument arg, unsigned maxDepth, DenseMap<Value, bool> &visited,
+ bool maybeOffset, SmallVectorImpl<std::pair<Value, bool>> &output) {
LDBG() << "collectUnderlyingAddressValues (BlockArgument): " << arg;
LDBG() << " maxDepth: " << maxDepth;
LDBG() << " argNumber: " << arg.getArgNumber();
@@ -140,7 +143,7 @@ static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
if (!branch) {
LDBG() << " Cannot analyze control flow, adding argument to output";
// We can't analyze the control flow, so bail out early.
- output.push_back(arg);
+ output.push_back({arg, maybeOffset});
return;
}
@@ -150,11 +153,12 @@ static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
if (!operand) {
LDBG() << " No operand found for argument, adding to output";
// We can't analyze the control flow, so bail out early.
- output.push_back(arg);
+ output.push_back({arg, maybeOffset});
return;
}
LDBG() << " Processing operand from predecessor: " << operand;
- collectUnderlyingAddressValues(operand, maxDepth, visited, output);
+ collectUnderlyingAddressValues(operand, maxDepth, visited, maybeOffset,
+ output);
}
return;
}
@@ -183,54 +187,67 @@ static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
if (!found) {
LDBG()
<< " No matching region successor found, adding argument to output";
- output.push_back(arg);
+ output.push_back({arg, maybeOffset});
return;
}
- return collectUnderlyingAddressValues2(
- branch, regionSuccessor, arg, argNumber, maxDepth, visited, output);
+ return collectUnderlyingAddressValues2(branch, regionSuccessor, arg,
+ argNumber, maxDepth, visited,
+ maybeOffset, output);
}
LDBG()
<< " Cannot reason about underlying address, adding argument to output";
// We can't reason about the underlying address of this argument.
- output.push_back(arg);
+ output.push_back({arg, maybeOffset});
}
/// Given a value, collect all of the underlying values being addressed.
-static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
- DenseSet<Value> &visited,
- SmallVectorImpl<Value> &output) {
+static void collectUnderlyingAddressValues(
+ Value value, unsigned maxDepth, DenseMap<Value, bool> &visited,
+ bool maybeOffset, SmallVectorImpl<std::pair<Value, bool>> &output) {
LDBG() << "collectUnderlyingAddressValues: " << value;
LDBG() << " maxDepth: " << maxDepth;
// Check that we don't infinitely recurse.
- if (!visited.insert(value).second) {
- LDBG() << " Value already visited, skipping";
- return;
+ auto it = visited.find(value);
+ if (it != visited.end()) {
+ // If maybeOffset is true, we have to propagate it up
+ // the operands chain. If the value has already been visited
+ // with a false maybeOffset, we have to visit it again.
+ if (!maybeOffset || it->second) {
+ LDBG() << " Value already visited, skipping";
+ return;
+ }
+
+ LDBG() << " Revisiting value due to potential offset";
+ it->second = true;
+ } else {
+ visited.try_emplace(value, maybeOffset);
}
if (maxDepth == 0) {
LDBG() << " Max depth reached, adding value to output";
- output.push_back(value);
+ output.push_back({value, maybeOffset});
return;
}
--maxDepth;
if (BlockArgument arg = dyn_cast<BlockArgument>(value)) {
LDBG() << " Processing as BlockArgument";
- return collectUnderlyingAddressValues(arg, maxDepth, visited, output);
+ return collectUnderlyingAddressValues(arg, maxDepth, visited, maybeOffset,
+ output);
}
LDBG() << " Processing as OpResult";
collectUnderlyingAddressValues(cast<OpResult>(value), maxDepth, visited,
- output);
+ maybeOffset, output);
}
/// Given a value, collect all of the underlying values being addressed.
-static void collectUnderlyingAddressValues(Value value,
- SmallVectorImpl<Value> &output) {
+static void collectUnderlyingAddressValues(
+ Value value, SmallVectorImpl<std::pair<Value, bool>> &output) {
LDBG() << "collectUnderlyingAddressValues: " << value;
- DenseSet<Value> visited;
+ DenseMap<Value, bool> visited;
collectUnderlyingAddressValues(value, maxUnderlyingValueSearchDepth, visited,
- output);
+ /*maybeOffset=*/false, output);
LDBG() << " Collected " << output.size() << " underlying values";
}
@@ -446,7 +463,7 @@ AliasResult LocalAliasAnalysis::alias(Value lhs, Value rhs) {
}
// Get the underlying values being addressed.
- SmallVector<Value, 8> lhsValues, rhsValues;
+ SmallVector<std::pair<Value, bool>, 8> lhsValues, rhsValues;
collectUnderlyingAddressValues(lhs, lhsValues);
collectUnderlyingAddressValues(rhs, rhsValues);
@@ -462,14 +479,25 @@ AliasResult LocalAliasAnalysis::alias(Value lhs, Value rhs) {
// Check the alias results against each of the underlying values.
std::optional<AliasResult> result;
- for (Value lhsVal : lhsValues) {
- for (Value rhsVal : rhsValues) {
+ for (auto [lhsVal, lhsPartial] : lhsValues) {
+ for (auto [rhsVal, rhsPartial] : rhsValues) {
LDBG() << " Checking underlying values: " << lhsVal << " vs " << rhsVal;
AliasResult nextResult = aliasImpl(lhsVal, rhsVal);
LDBG() << " Result: "
<< (nextResult == AliasResult::MustAlias ? "MustAlias"
: nextResult == AliasResult::NoAlias ? "NoAlias"
: "MayAlias");
+ // If the original lhs/rhs may be an offsetted access
+ // of the memory objects pointed to by lhsVal/rhsVal,
+ // we'd better turn MustAlias results into MayAlias.
+ // It is possible that MustAlias and PartialAlias
+ // are both dynamically possible for these two references,
+ // so MayAlias should be conservatively correct.
+ if (nextResult == AliasResult::MustAlias && (lhsPartial || rhsPartial)) {
+ nextResult = AliasResult::MayAlias;
+ LDBG() << " Adjusted result (due to potentially partial overlap): "
+ "MayAlias";
+ }
result = result ? result->merge(nextResult) : nextResult;
}
}
diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
index 1c21a2f270da6..673d173ea8655 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
@@ -3039,6 +3039,15 @@ void SubViewOp::build(OpBuilder &b, OperationState &result, Value source,
/// For ViewLikeOpInterface.
Value SubViewOp::getViewSource() { return getSource(); }
+bool SubViewOp::isSameStart() {
+ // TODO: we can recognize simple constant operands.
+ if (!getOffsets().empty())
+ return false;
+ ArrayRef<int64_t> staticOffsets = getStaticOffsets();
+ return llvm::all_of(staticOffsets,
+ [](int64_t offset) { return offset == 0; });
+}
+
/// Return true if `t1` and `t2` have equal offsets (both dynamic or of same
/// static value).
static bool haveCompatibleOffsets(MemRefType t1, MemRefType t2) {
@@ -3678,6 +3687,13 @@ LogicalResult ViewOp::verify() {
Value ViewOp::getViewSource() { return getSource(); }
+bool ViewOp::isSameStart() {
+ IntegerAttr offsetAttr;
+ if (matchPattern(getByteShift(), m_Constant(&offsetAttr)))
+ return offsetAttr.getValue().isZero();
+ return false;
+}
+
OpFoldResult ViewOp::fold(FoldAdaptor adaptor) {
MemRefType sourceMemrefType = getSource().getType();
MemRefType resultMemrefType = getResult().getType();
diff --git a/mlir/test/Analysis/test-alias-analysis-extending.mlir b/mlir/test/Analysis/test-alias-analysis-extending.mlir
index 20f2a7a3de171..9730c28bd6a1f 100644
--- a/mlir/test/Analysis/test-alias-analysis-extending.mlir
+++ b/mlir/test/Analysis/test-alias-analysis-extending.mlir
@@ -6,10 +6,19 @@
// CHECK-DAG: view1#0 <-> view2#0: NoAlias
// CHECK-DAG: view1#0 <-> func.region0#0: MustAlias
// CHECK-DAG: view1#0 <-> func.region0#1: NoAlias
+// CHECK-DAG: view1#0 <-> view3#0: MayAlias
+// CHECK-DAG: view1#0 <-> view4#0: NoAlias
// CHECK-DAG: view2#0 <-> func.region0#0: NoAlias
// CHECK-DAG: view2#0 <-> func.region0#1: MustAlias
+// CHECK-DAG: view2#0 <-> view3#0: NoAlias
+// CHECK-DAG: view2#0 <-> view4#0: MayAlias
+// CHECK-DAG: view3#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: view3#0 <-> func.region0#1: NoAlias
+// CHECK-DAG: view3#0 <-> view4#0: NoAlias
func.func @restrict(%arg: memref<?xf32>, %arg1: memref<?xf32> {local_alias_analysis.restrict}) attributes {test.ptr = "func"} {
%0 = memref.subview %arg[0][2][1] {test.ptr = "view1"} : memref<?xf32> to memref<2xf32>
%1 = memref.subview %arg1[0][2][1] {test.ptr = "view2"} : memref<?xf32> to memref<2xf32>
+ %2 = memref.subview %arg[1][2][1] {test.ptr = "view3"} : memref<?xf32> to memref<2xf32, strided<[1], offset: 1>>
+ %3 = memref.subview %arg1[1][2][1] {test.ptr = "view4"} : memref<?xf32> to memref<2xf32, strided<[1], offset: 1>>
return
}
diff --git a/mlir/test/Analysis/test-alias-analysis.mlir b/mlir/test/Analysis/test-alias-analysis.mlir
index d71adee05c7a3..83565149e1ca5 100644
--- a/mlir/test/Analysis/test-alias-analysis.mlir
+++ b/mlir/test/Analysis/test-alias-analysis.mlir
@@ -272,3 +272,121 @@ func.func @distinct_objects(%arg: memref<?xf32>, %arg1: memref<?xf32>) attribute
%0, %1 = memref.distinct_objects %arg, %arg1 {test.ptr = "distinct"} : memref<?xf32>, memref<?xf32>
return
}
+
+// -----
+
+// CHECK-LABEL: Testing : "view_like_offset"
+
+// The current LocalAliasAnalysis algortithm is quite conservative
+// about differentiating PartialAlias and MustAlias, and it often
+// returns MayAlias for PartialAlias/MustAlias situations.
+
+// These are the PartialAlias cases (reported as MayAlias),
+// where LocalAliasAnalysis used to incorrectly return MustAlias:
+// CHECK-DAG: view_with_offset#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: complete_view1#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: complete_view2#0 <-> func.region0#0: MayAlias
+
+// These are the MustAlias cases, where MayAlias is returned currently:
+// CHECK-DAG: view_with_offset#0 <-> complete_view1#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> complete_view2#0: MayAlias
+// CHECK-DAG: complete_view1#0 <-> complete_view2#0: MayAlias
+func.func @view_like_offset(%arg: memref<?xi8>) attributes {test.ptr = "func"} {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %view_with_offset = memref.view %arg[%c1][] {test.ptr = "view_with_offset"} : memref<?xi8> to memref<8xi8>
+ %complete_view1 = memref.view %view_with_offset[%c0][] {test.ptr = "complete_view1"} : memref<8xi8> to memref<8xi8>
+ %complete_view2 = memref.view %view_with_offset[%c0][] {test.ptr = "complete_view2"} : memref<8xi8> to memref<8xi8>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: Testing : "view_like_offset_with_cfg1"
+
+// These are the PartialAlias cases (reported as MayAlias),
+// where LocalAliasAnalysis used to incorrectly return MustAlias:
+// CHECK-DAG: complete_view_if#0 <-> view_with_offset#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> if_view#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> complete_view1#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> complete_view2#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> func.region0#0: MayAlias
+
+// These are correctly classified as MustAlias:
+// CHECK-DAG: complete_view_if#0 <-> func.region0#0: MustAlias
+
+// TODO: these must be MustAlias:
+// CHECK-DAG: if_view#0 <-> complete_view1#0: MayAlias
+// CHECK-DAG: if_view#0 <-> complete_view2#0: MayAlias
+// CHECK-DAG: complete_view1#0 <-> complete_view2#0: MayAlias
+
+// These should be MayAlias, because the references are either
+// MustAlias or PartialAlias depending on the runtime condition:
+// CHECK-DAG: complete_view_if#0 <-> if_view#0: MayAlias
+// CHECK-DAG: complete_view_if#0 <-> complete_view1#0: MayAlias
+// CHECK-DAG: complete_view_if#0 <-> complete_view2#0: MayAlias
+// CHECK-DAG: if_view#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: complete_view1#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: complete_view2#0 <-> func.region0#0: MayAlias
+func.func @view_like_offset_with_cfg1(%arg: memref<?xi8>, %cond: i1) attributes {test.ptr = "func"} {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %0 = scf.if %cond -> (memref<8xi8>) {
+ %complete_view_if = memref.view %arg[%c0][] {test.ptr = "complete_view_if"} : memref<?xi8> to memref<8xi8>
+ scf.yield %complete_view_if : memref<8xi8>
+ } else {
+ %view_with_offset = memref.view %arg[%c1][] {test.ptr = "view_with_offset"} : memref<?xi8> to memref<8xi8>
+ scf.yield %view_with_offset : memref<8xi8>
+ } {test.ptr = "if_view"}
+ %complete_view1 = memref.view %0[%c0][] {test.ptr = "complete_view1"} : memref<8xi8> to memref<8xi8>
+ %complete_view2 = memref.view %0[%c0][] {test.ptr = "complete_view2"} : memref<8xi8> to memref<8xi8>
+ return
+}
+
+// -----
+
+// CHECK-LABEL: Testing : "view_like_offset_with_cfg2"
+
+// This test is a different version of view_like_offset_with_cfg1:
+// the then and else clauses are swapped, and this affects
+// the visiting order. The two tests check that the visiting
+// order does not matter.
+
+// These are the PartialAlias cases (reported as MayAlias),
+// where LocalAliasAnalysis used to incorrectly return MustAlias:
+// CHECK-DAG: view_with_offset#0 <-> complete_view_if#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> if_view#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> complete_view1#0: MayAlias
+// CHECK-DAG: if_view#0 <-> func.region0#0: MayAlias
+
+// These are correctly classified as MustAlias:
+// CHECK-DAG: complete_view_if#0 <-> func.region0#0: MustAlias
+
+// TODO: these must be MustAlias:
+// CHECK-DAG: if_view#0 <-> complete_view1#0: MayAlias
+// CHECK-DAG: if_view#0 <-> complete_view2#0: MayAlias
+// CHECK-DAG: complete_view1#0 <-> complete_view2#0: MayAlias
+
+// These should be MayAlias, because the references are either
+// MustAlias or PartialAlias depending on the runtime condition:
+// CHECK-DAG: complete_view_if#0 <-> if_view#0: MayAlias
+// CHECK-DAG: complete_view_if#0 <-> complete_view1#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> complete_view2#0: MayAlias
+// CHECK-DAG: complete_view_if#0 <-> complete_view2#0: MayAlias
+// CHECK-DAG: view_with_offset#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: complete_view1#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: complete_view2#0 <-> func.region0#0: MayAlias
+func.func @view_like_offset_with_cfg2(%arg: memref<?xi8>, %cond: i1) attributes {test.ptr = "func"} {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %0 = scf.if %cond -> (memref<8xi8>) {
+ %view_with_offset = memref.view %arg[%c1][] {test.ptr = "view_with_offset"} : memref<?xi8> to memref<8xi8>
+ scf.yield %view_with_offset : memref<8xi8>
+ } else {
+ %complete_view_if = memref.view %arg[%c0][] {test.ptr = "complete_view_if"} : memref<?xi8> to memref<8xi8>
+ scf.yield %complete_view_if : memref<8xi8>
+ } {test.ptr = "if_view"}
+ %complete_view1 = memref.view %0[%c0][] {test.ptr = "complete_view1"} : memref<8xi8> to memref<8xi8>
+ %complete_view2 = memref.view %0[%c0][] {test.ptr = "complete_view2"} : memref<8xi8> to memref<8xi8>
+ return
+}
>From afed03e69fa2179cf46fdaa77f6d91c3cc56e80f Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Wed, 29 Oct 2025 17:05:16 -0700
Subject: [PATCH 2/2] Renamed the methods to clearly indicate what their result
means.
---
.../include/flang/Optimizer/Dialect/FIROps.td | 4 ++--
.../lib/Optimizer/Analysis/AliasAnalysis.cpp | 2 +-
.../mlir/Dialect/MemRef/IR/MemRefOps.td | 12 +++++------
.../mlir/Interfaces/ViewLikeInterface.td | 20 +++++++++++++------
.../AliasAnalysis/LocalAliasAnalysis.cpp | 3 ++-
mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp | 4 ++--
6 files changed, 27 insertions(+), 18 deletions(-)
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index f83e3e990d315..6e2d665379da1 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -2875,8 +2875,8 @@ def fir_ConvertOp
static bool canBeConverted(mlir::Type inType, mlir::Type outType);
static bool areVectorsCompatible(mlir::Type inTy, mlir::Type outTy);
mlir::Value getViewSource() { return getValue(); }
- bool isSameStart() { return true; }
- bool isCompleteView() { return true; }
+ bool isKnownToHaveSameStart() { return true; }
+ bool isKnownToBeCompleteView() { return true; }
}];
let hasCanonicalizer = 1;
}
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 62b18e5a7258c..7a3ae87a4a0c1 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -574,7 +574,7 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
if (mlir::isa<fir::BaseBoxType>(v.getType()) &&
!mlir::isa<fir::BaseBoxType>(ty))
followBoxData = true;
- if (!op.isSameStart())
+ if (!op.isKnownToHaveSameStart())
approximateSource = true;
})
.Case<fir::PackArrayOp>([&](auto op) {
diff --git a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
index e7a4dd545f6ba..b1dec5efd81a0 100644
--- a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
+++ b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
@@ -2351,9 +2351,9 @@ def SubViewOp : MemRef_OpWithOffsetSizesAndStrides<"subview", [
static FailureOr<Value> rankReduceIfNeeded(
OpBuilder &b, Location loc, Value value, ArrayRef<int64_t> desiredShape);
- /// Return true iff the input and the resulting views start
- /// at the same address.
- bool isSameStart();
+ /// Return true iff the input and the resulting views
+ /// are known to start at the same address.
+ bool isKnownToHaveSameStart();
}];
let hasCanonicalizer = 1;
@@ -2464,9 +2464,9 @@ def MemRef_ViewOp : MemRef_Op<"view", [
return {getSizes().begin(), getSizes().end()};
}
- /// Return true iff the input and the resulting views start
- /// at the same address.
- bool isSameStart();
+ /// Return true iff the input and the resulting views
+ /// are known to start at the same address.
+ bool isKnownToHaveSameStart();
}];
let assemblyFormat = [{
diff --git a/mlir/include/mlir/Interfaces/ViewLikeInterface.td b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
index 8d71d50a5c155..fa6984fd56193 100644
--- a/mlir/include/mlir/Interfaces/ViewLikeInterface.td
+++ b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
@@ -38,9 +38,12 @@ def ViewLikeOpInterface : OpInterface<"ViewLikeOpInterface"> {
}]>,
InterfaceMethod<
/*desc=*/
- [{ Returns true iff the source buffer and the resulting view start at the same "address". }],
+ [{
+ Returns true iff the source buffer and the resulting view
+ are known to start at the same "address".
+ }],
/*retTy=*/"bool",
- /*methodName=*/"isSameStart",
+ /*methodName=*/"isKnownToHaveSameStart",
/*args=*/(ins),
/*methodBody=*/"",
/*defaultImplementation=*/[{
@@ -48,9 +51,14 @@ def ViewLikeOpInterface : OpInterface<"ViewLikeOpInterface"> {
}]>,
InterfaceMethod<
/*desc=*/
- [{ Returns true iff the resulting view is a complete view of the source buffer, i.e. isSameStart() is true and the result has the same total size and is not a subview of the input. }],
+ [{
+ Returns true iff the resulting view is known to be
+ a complete view of the source buffer, i.e.
+ isKnownToHaveSameStart() is true and the result
+ has the same total size and is not a subview of the input.
+ }],
/*retTy=*/"bool",
- /*methodName=*/"isCompleteView",
+ /*methodName=*/"isKnownToBeCompleteView",
/*args=*/(ins),
/*methodBody=*/"",
/*defaultImplementation=*/[{
@@ -59,8 +67,8 @@ def ViewLikeOpInterface : OpInterface<"ViewLikeOpInterface"> {
let verify = [{
auto concreteOp = ::mlir::cast<ConcreteOp>($_op);
- // isCompleteView() == true implies isSameStart() == true.
- return !concreteOp.isCompleteView() || concreteOp.isSameStart() ? ::mlir::success() : ::mlir::failure();
+ // isKnownToBeCompleteView() == true implies isKnownToHaveSameStart() == true.
+ return !concreteOp.isKnownToBeCompleteView() || concreteOp.isKnownToHaveSameStart() ? ::mlir::success() : ::mlir::failure();
}];
}
diff --git a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
index fe2c5e0d585fd..5cdee7a0dcd67 100644
--- a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
+++ b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
@@ -105,7 +105,8 @@ static void collectUnderlyingAddressValues(
if (result == view.getViewDest()) {
LDBG() << " Unwrapping view to source: " << view.getViewSource();
return collectUnderlyingAddressValues(
- view.getViewSource(), maxDepth, visited, !view.isSameStart(), output);
+ view.getViewSource(), maxDepth, visited,
+ !view.isKnownToHaveSameStart(), output);
}
}
// Check to see if we can reason about the control flow of this op.
diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
index 673d173ea8655..47fac4837e477 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
@@ -3039,7 +3039,7 @@ void SubViewOp::build(OpBuilder &b, OperationState &result, Value source,
/// For ViewLikeOpInterface.
Value SubViewOp::getViewSource() { return getSource(); }
-bool SubViewOp::isSameStart() {
+bool SubViewOp::isKnownToHaveSameStart() {
// TODO: we can recognize simple constant operands.
if (!getOffsets().empty())
return false;
@@ -3687,7 +3687,7 @@ LogicalResult ViewOp::verify() {
Value ViewOp::getViewSource() { return getSource(); }
-bool ViewOp::isSameStart() {
+bool ViewOp::isKnownToHaveSameStart() {
IntegerAttr offsetAttr;
if (matchPattern(getByteShift(), m_Constant(&offsetAttr)))
return offsetAttr.getValue().isZero();
More information about the Mlir-commits
mailing list