Skip to content

Conversation

vzakhari
Copy link
Contributor

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.

In this patch, I only add ViewLikeOpInterface support to few
FIR operations to demonstrate the intent. Flang's alias analysis
LIT tests are failing with this change, and I would like to get
some advices on how to proceed.

As soon as I add, ViewLikeOpInterface interface support for FIR
operations, the LocalAliasAnalysis kicks in, and starts reporting
MustAlias for some cases where Flang currently reports MayAlias.
I believe both analyses are incorrect here: they should actually
report PartialAlias for those cases.

This is what the new method isSameStart should be used for.

I would like to fix both LocalAliasAnalysis and Flang's alias analysis
to return PartialAlias in those cases, but I am not sure if
querying isSameStart is enough - see the TBD comment in
ViewLikeInterface.td.

If my understanding of PartialAlias is correct, then we can have
a partial alias even when the starting addresses of the source
and the resulting view are the same - though, I might be wrong.

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.

In this patch, I only add ViewLikeOpInterface support to few
FIR operations to demonstrate the intent. Flang's alias analysis
LIT tests are failing with this change, and I would like to get
some advices on how to proceed.

As soon as I add, ViewLikeOpInterface interface support for FIR
operations, the LocalAliasAnalysis kicks in, and starts reporting
`MustAlias` for some cases where Flang currently reports MayAlias.
I believe both analyses are incorrect here: they should actually
report `PartialAlias` for those cases.

This is what the new method `isSameStart` should be used for.

I would like to fix both LocalAliasAnalysis and Flang's alias analysis
to return `PartialAlias` in those cases, but I am not sure if
querying `isSameStart` is enough - see the TBD comment in
ViewLikeInterface.td.

If my understanding of `PartialAlias` is correct, then we can have
a partial alias even when the starting addresses of the source
and the resulting view are the same - though, I might be wrong.
@llvmbot llvmbot added mlir flang Flang issues not falling into any other category flang:fir-hlfir labels Oct 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 17, 2025

@llvm/pr-subscribers-mlir-memref

@llvm/pr-subscribers-mlir

Author: Slava Zakharin (vzakhari)

Changes

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.

In this patch, I only add ViewLikeOpInterface support to few
FIR operations to demonstrate the intent. Flang's alias analysis
LIT tests are failing with this change, and I would like to get
some advices on how to proceed.

As soon as I add, ViewLikeOpInterface interface support for FIR
operations, the LocalAliasAnalysis kicks in, and starts reporting
MustAlias for some cases where Flang currently reports MayAlias.
I believe both analyses are incorrect here: they should actually
report PartialAlias for those cases.

This is what the new method isSameStart should be used for.

I would like to fix both LocalAliasAnalysis and Flang's alias analysis
to return PartialAlias in those cases, but I am not sure if
querying isSameStart is enough - see the TBD comment in
ViewLikeInterface.td.

If my understanding of PartialAlias is correct, then we can have
a partial alias even when the starting addresses of the source
and the resulting view are the same - though, I might be wrong.


Full diff: https://github.com/llvm/llvm-project/pull/164020.diff

5 Files Affected:

  • (modified) flang/include/flang/Optimizer/Dialect/FIROps.h (+1)
  • (modified) flang/include/flang/Optimizer/Dialect/FIROps.td (+14-4)
  • (modified) flang/lib/Optimizer/Analysis/AliasAnalysis.cpp (+12-12)
  • (modified) mlir/include/mlir/Interfaces/ViewLikeInterface.td (+27-14)
  • (modified) mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp (+4)
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 fc6eedc6ed4c6..5ce7390fca0d8 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"
@@ -1730,8 +1731,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";
 
@@ -1774,9 +1776,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";
 
@@ -1828,6 +1834,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(); }
   }];
 }
 
@@ -2792,7 +2799,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 = [{
@@ -2830,6 +2838,8 @@ 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; }
   }];
   let hasCanonicalizer = 1;
 }
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 73ddd1ff80126..f3470ecaa0ffc 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -559,10 +559,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))
+            followingData = true;
+          if (!op.isSameStart())
+            approximateSource = true;
         })
         .Case<fir::PackArrayOp>([&](auto op) {
           // The packed array is not distinguishable from the original
@@ -578,15 +587,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/Interfaces/ViewLikeInterface.td b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
index 131c1a0d92b24..5903623572b6e 100644
--- a/mlir/include/mlir/Interfaces/ViewLikeInterface.td
+++ b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
@@ -23,21 +23,34 @@ 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<
+           // TBD: should it be generalized into isPartialView that will
+           // return true in any of these cases?
+           //   1. Resulting view is a slice view of the source.
+           //   2. Resulting view's start does not match the source's start.
+           //   3. Resulting view's end does not match the source's end.
+           /*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;
+      }]>];
 }
 
 def OffsetSizeAndStrideOpInterface : OpInterface<"OffsetSizeAndStrideOpInterface"> {
diff --git a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
index a84d10d5d609d..b0ec97bd9f264 100644
--- a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
+++ b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
@@ -129,6 +129,10 @@ static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
   // If this is a view, unwrap to the source.
   if (ViewLikeOpInterface view = dyn_cast<ViewLikeOpInterface>(op)) {
     if (result == view.getViewDest()) {
+      // TODO: if view.isSameStart() returns false here,
+      // we have to make sure that further analysis may return
+      // PartialAlias for all underlying addresses we are going
+      // to collect from this point up the def-use.
       return collectUnderlyingAddressValues(view.getViewSource(), maxDepth,
                                             visited, output);
     }

@llvmbot
Copy link
Member

llvmbot commented Oct 17, 2025

@llvm/pr-subscribers-flang-fir-hlfir

Author: Slava Zakharin (vzakhari)

Changes

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.

In this patch, I only add ViewLikeOpInterface support to few
FIR operations to demonstrate the intent. Flang's alias analysis
LIT tests are failing with this change, and I would like to get
some advices on how to proceed.

As soon as I add, ViewLikeOpInterface interface support for FIR
operations, the LocalAliasAnalysis kicks in, and starts reporting
MustAlias for some cases where Flang currently reports MayAlias.
I believe both analyses are incorrect here: they should actually
report PartialAlias for those cases.

This is what the new method isSameStart should be used for.

I would like to fix both LocalAliasAnalysis and Flang's alias analysis
to return PartialAlias in those cases, but I am not sure if
querying isSameStart is enough - see the TBD comment in
ViewLikeInterface.td.

If my understanding of PartialAlias is correct, then we can have
a partial alias even when the starting addresses of the source
and the resulting view are the same - though, I might be wrong.


Full diff: https://github.com/llvm/llvm-project/pull/164020.diff

5 Files Affected:

  • (modified) flang/include/flang/Optimizer/Dialect/FIROps.h (+1)
  • (modified) flang/include/flang/Optimizer/Dialect/FIROps.td (+14-4)
  • (modified) flang/lib/Optimizer/Analysis/AliasAnalysis.cpp (+12-12)
  • (modified) mlir/include/mlir/Interfaces/ViewLikeInterface.td (+27-14)
  • (modified) mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp (+4)
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 fc6eedc6ed4c6..5ce7390fca0d8 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"
@@ -1730,8 +1731,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";
 
@@ -1774,9 +1776,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";
 
@@ -1828,6 +1834,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(); }
   }];
 }
 
@@ -2792,7 +2799,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 = [{
@@ -2830,6 +2838,8 @@ 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; }
   }];
   let hasCanonicalizer = 1;
 }
diff --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 73ddd1ff80126..f3470ecaa0ffc 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -559,10 +559,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))
+            followingData = true;
+          if (!op.isSameStart())
+            approximateSource = true;
         })
         .Case<fir::PackArrayOp>([&](auto op) {
           // The packed array is not distinguishable from the original
@@ -578,15 +587,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/Interfaces/ViewLikeInterface.td b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
index 131c1a0d92b24..5903623572b6e 100644
--- a/mlir/include/mlir/Interfaces/ViewLikeInterface.td
+++ b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
@@ -23,21 +23,34 @@ 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<
+           // TBD: should it be generalized into isPartialView that will
+           // return true in any of these cases?
+           //   1. Resulting view is a slice view of the source.
+           //   2. Resulting view's start does not match the source's start.
+           //   3. Resulting view's end does not match the source's end.
+           /*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;
+      }]>];
 }
 
 def OffsetSizeAndStrideOpInterface : OpInterface<"OffsetSizeAndStrideOpInterface"> {
diff --git a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
index a84d10d5d609d..b0ec97bd9f264 100644
--- a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
+++ b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
@@ -129,6 +129,10 @@ static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
   // If this is a view, unwrap to the source.
   if (ViewLikeOpInterface view = dyn_cast<ViewLikeOpInterface>(op)) {
     if (result == view.getViewDest()) {
+      // TODO: if view.isSameStart() returns false here,
+      // we have to make sure that further analysis may return
+      // PartialAlias for all underlying addresses we are going
+      // to collect from this point up the def-use.
       return collectUnderlyingAddressValues(view.getViewSource(), maxDepth,
                                             visited, output);
     }

Copy link
Contributor

@jeanPerier jeanPerier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to me to add the ViewLikeOpInterface to these FIR operations, thanks Slava.

If my understanding of PartialAlias is correct, then we can have a partial alias even when the starting addresses of the source and the resulting view are the same - though, I might be wrong.

That is also my reading of LLVM/MLIR documentation, although I have little experience with how it is used in practice.

@razvanlupusoru
Copy link
Contributor

razvanlupusoru commented Oct 20, 2025

Hi Slava! Thank you for your proposal! I agree with your points:

  • Partial overlaps should not report MustAlias. However, I can see that if an implementation tries to find the "parent" and then compares just the parent, then this is the expected result but not the correct one.
  • I agree that isSameStart is likely not adequate (eg trying to determine aliasing between a base object and the field at offset 0).

Core issue: ViewLikeOpInterface is overloaded

Looking at the MemRef dialect, ViewLikeOpInterface is currently used for two fundamentally different categories of operations:

Category 1: Complete alias (same entity, different type):

  • memref.cast - "converts a memref from one type to an equivalent type" - the entire source aliases with the entire result
  • memref.assume_alignment - adds alignment information, complete alias
  • memref.memory_space_cast - changes memory space, complete alias
  • memref.reshape - changes shape layout, complete alias

Category 2: Partial overlap (subset of entity):

  • memref.subview - "represents a reduced-size view of the original memref" - only a subset of the source aliases with the result
  • memref.view - "extracts an N-D contiguous memref" from a 1-D buffer at a specified byte offset - only a subset of the source (starting at the offset) aliases with the result

Thus, when LocalAliasAnalysis::collectUnderlyingAddressValues unwraps ViewLikeOpInterface, it treats memref.subview (partial) the same as memref.cast (complete), causing the MustAlias issue you observed.

Proposed solution:

I suggest introducing a SubviewLikeOpInterface (or PartialViewOpInterface) to distinguish operations that create partial views from those that merely change types. This would allow:

  1. LocalAliasAnalysis and FIR AliasAnalysis to correctly handle partial overlaps (PartialAlias)
  2. Complete view operations to continue being optimized as full aliases (MustAlias)

This would make ViewLikeOpInterface match my own intuition that it represents the same original entity. memref.subview, memref.view and similar operations would implement the new interface, while memref.cast etc. would remain with ViewLikeOpInterface.

Operations like fir.coordinate_of, fir.array_coor, and hlfir.designate could then implement SubviewLikeOpInterface, enabling correct alias analysis for Fortran derived types and arrays.

What do you think?

@vzakhari
Copy link
Contributor Author

Thank you for the comments, Jean and Razvan.

Regarding SubviewLikeOpInterface, I think the same operation may present a complete view and a partial view depending on its operands' values. This is why I would prefer the have a single interface and let the operations' implementations decide whether it is complete or partial view depending on the operands.

I thought a bit more about this, and I think that for aliasing analysis we should only care about the beginning of the input and result buffers. For example, if the initial view is (ptr1, size1), and the resulting view is (ptr1, size2), then we should actually report MustAlias. The fact that the size1 may be bigger than size2 should not affect the alias analysis.

Maybe, it is worth having the following two methods in the ViewLikeOpInterface:

  1. isSameStart - same definition as in this PR.
  2. isCompleteView - returns true iff isSameStart is true and the views have the same size.

@razvanlupusoru
Copy link
Contributor

I thought a bit more about this, and I think that for aliasing analysis we should only care about the beginning of the input and result buffers. For example, if the initial view is (ptr1, size1), and the resulting view is (ptr1, size2), then we should actually report MustAlias. The fact that the size1 may be bigger than size2 should not affect the alias analysis.

Interesting - I double-checked against LLVM and its documentation for MustAlias does NOT require the objects to be the same size: https://llvm.org/docs/AliasAnalysis.html#must-may-and-no-alias-responses
So I see your point about MustAlias being technically valid here.

However, I would like to point out that even LLVM's implementations for alias analysis are not strict about this. For example, a quick search revealed at least one spot where the same base address returns PartialAlias if one of the accesses does not cover the whole object:
https://github.com/llvm/llvm-project/blob/main/llvm/lib/Analysis/BasicAliasAnalysis.cpp#L1858

To me, it makes more sense to return PartialAlias when sizes are provably not equivalent, but I think either approach is acceptable as long as it is clearly documented.

Regarding your suggestion to capture all of this in ViewLikeOpInterface instead of adding SubviewLikeOpInterface: I'm fine with that approach since it still captures the information I currently need with the addition of isCompleteView. I'm hoping to use this functionality in OpenACC support to ensure proper device mapping order.

Thank you for your proposal, Slava!

@vzakhari
Copy link
Contributor Author

However, I would like to point out that even LLVM's implementations for alias analysis are not strict about this. For example, a quick search revealed at least one spot where the same base address returns PartialAlias if one of the accesses does not cover the whole object:
https://github.com/llvm/llvm-project/blob/main/llvm/lib/Analysis/BasicAliasAnalysis.cpp#L1858

Thanks for the example! I am not sure if the code actually implies "the same base address returns PartialAlias". I believe in this code, V1 and V2 cannot be provent to be the same (otherwise, the alias analysis would have returned earlier), but they do access the same underlying object - in this case, if one access is known to access the whole object, then PartialAlias is returned.

in more cases. There are still cases where LocalAliasAnalysis
incorrectly returns MustAlias (see mlir/test/Analysis/test-alias-analysis.mlir).

Also see a NOTE in flang/test/Analysis/AliasAnalysis/ptr-component.fir,
where combining LocalAliasAnalysis result (PartialAlias) and FIR AliasAnalysis
result (NoAlias) results in PartialAlias, which is more conservative than
the previous NoAlias result. It is unclear how to address this case,
given the merging rules for multiple alias analyses' results.
@vzakhari
Copy link
Contributor Author

Here is what I have in the latest commit:

Fixed LocalAliasAnalysis to return PartialAlias instead of MustAlias
in more cases. There are still cases where LocalAliasAnalysis
incorrectly returns MustAlias (see mlir/test/Analysis/test-alias-analysis.mlir).

Also see a NOTE in flang/test/Analysis/AliasAnalysis/ptr-component.fir,
where combining LocalAliasAnalysis result (PartialAlias) and FIR AliasAnalysis
result (NoAlias) results in PartialAlias, which is more conservative than
the previous NoAlias result. It is unclear how to address this case,
given the merging rules for multiple alias analyses' results.

I tried not to change the current LocalAliasAnalysis implementation too much. There are still cases where it incorrectly reports MustAlias instead of PartialAlias. Moreover, the order of visiting affects whether MustAlias or PartialAlias is returned.

@River707 do you have any comments?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:fir-hlfir flang Flang issues not falling into any other category mlir:memref mlir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants