[Mlir-commits] [flang] [mlir] [MLIR] Implement mayHaveNonZeroOffset class (PR #231285)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sun Oct 25 15:46:21 PDT 2026


https://github.com/aokblast updated https://github.com/llvm/llvm-project/pull/231285

>From 2f3c61e3f5ce0793e5e560a79846fc2d28f9fcce Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Mon, 12 Oct 2026 15:39:38 -0500
Subject: [PATCH 1/3] [MLIR] Implement mayHaveNonZeroOffset class

Generic MLIR analysis for alias is not enough as it has no information
for an offset alias. For example, without this patch, CIR ptr_stride
ptr, 0 and ptr_stride ptr, 1 are considered as MustAlias as they uses
the same base. However, there is still a chance for us to do precise
alias analysis and declare not alias by its implementation. As a
result, we provide a mayHaveNonZeroOffset interface method and
downgrade to MayAlias to give the underlying Dialect implementation a
chance to do AliasAnalysis.
---
 .../mlir/Interfaces/ViewLikeInterface.td      | 13 +++++
 .../AliasAnalysis/LocalAliasAnalysis.cpp      | 55 ++++++++++++-------
 mlir/test/Analysis/test-alias-analysis.mlir   | 31 +++++++++++
 mlir/test/lib/Dialect/Test/CMakeLists.txt     |  1 +
 mlir/test/lib/Dialect/Test/TestOps.td         | 23 ++++++++
 5 files changed, 103 insertions(+), 20 deletions(-)

diff --git a/mlir/include/mlir/Interfaces/ViewLikeInterface.td b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
index 131c1a0d92b24c..0bbcd0401aa830 100644
--- a/mlir/include/mlir/Interfaces/ViewLikeInterface.td
+++ b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
@@ -36,6 +36,19 @@ def ViewLikeOpInterface : OpInterface<"ViewLikeOpInterface"> {
       /*defaultImplementation=*/[{
         return $_op->getResult(0);
       }]
+    >,
+    InterfaceMethod<
+      /*desc=*/[{
+        Returns true if the view may start at a different address than its
+        source.
+      }],
+      /*retTy=*/"bool",
+      /*methodName=*/"mayHaveNonZeroOffset",
+      /*args=*/(ins),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        return false;
+      }]
     >
   ];
 }
diff --git a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
index 4a8c2de8562f8b..ebefb1c490aa64 100644
--- a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
+++ b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
@@ -39,9 +39,17 @@ using namespace mlir;
 /// value.
 static constexpr unsigned maxUnderlyingValueSearchDepth = 10;
 
+namespace {
+struct UnderlyingValueWalk {
+  DenseSet<Value> visited;
+  // True if a value can be aliased by offset.
+  bool throughOffsetView = false;
+};
+} // namespace
+
 /// Given a value, collect all of the underlying values being addressed.
 static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
-                                           DenseSet<Value> &visited,
+                                           UnderlyingValueWalk &walk,
                                            SmallVectorImpl<Value> &output);
 
 /// Given a RegionBranchOpInterface operation  (`branch`), a Value`inputValue`
@@ -50,7 +58,7 @@ 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) {
+    UnderlyingValueWalk &walk, SmallVectorImpl<Value> &output) {
   LDBG() << "collectUnderlyingAddressValues2: "
          << OpWithFlags(branch.getOperation(), OpPrintingFlags().skipRegions());
   LDBG() << " with initialSuccessor " << initialSuccessor;
@@ -84,13 +92,13 @@ 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, walk, output);
   }
 }
 
 /// Given a result, collect all of the underlying values being addressed.
 static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
-                                           DenseSet<Value> &visited,
+                                           UnderlyingValueWalk &walk,
                                            SmallVectorImpl<Value> &output) {
   LDBG() << "collectUnderlyingAddressValues (OpResult): " << result;
   LDBG() << "  maxDepth: " << maxDepth;
@@ -101,8 +109,10 @@ 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();
+      if (view.mayHaveNonZeroOffset())
+        walk.throughOffsetView = true;
       return collectUnderlyingAddressValues(view.getViewSource(), maxDepth,
-                                            visited, output);
+                                            walk, output);
     }
   }
   // Check to see if we can reason about the control flow of this op.
@@ -110,7 +120,7 @@ static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
     LDBG() << "  Processing region branch operation";
     return collectUnderlyingAddressValues2(
         branch, RegionSuccessor(branch.getOperation()), result,
-        result.getResultNumber(), maxDepth, visited, output);
+        result.getResultNumber(), maxDepth, walk, output);
   }
 
   LDBG() << "  Adding result to output: " << result;
@@ -120,7 +130,7 @@ static void collectUnderlyingAddressValues(OpResult result, unsigned maxDepth,
 /// Given a block argument, collect all of the underlying values being
 /// addressed.
 static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
-                                           DenseSet<Value> &visited,
+                                           UnderlyingValueWalk &walk,
                                            SmallVectorImpl<Value> &output) {
   LDBG() << "collectUnderlyingAddressValues (BlockArgument): " << arg;
   LDBG() << "  maxDepth: " << maxDepth;
@@ -154,7 +164,7 @@ static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
         return;
       }
       LDBG() << "    Processing operand from predecessor: " << operand;
-      collectUnderlyingAddressValues(operand, maxDepth, visited, output);
+      collectUnderlyingAddressValues(operand, maxDepth, walk, output);
     }
     return;
   }
@@ -174,7 +184,7 @@ static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
       if (successor.getSuccessor() == region) {
         LDBG() << "  Found matching region successor: " << successor;
         return collectUnderlyingAddressValues2(
-            branch, successor, arg, argNumber, maxDepth, visited, output);
+            branch, successor, arg, argNumber, maxDepth, walk, output);
       }
     }
     LDBG() << "  No matching region successor found, adding argument to output";
@@ -190,13 +200,13 @@ static void collectUnderlyingAddressValues(BlockArgument arg, unsigned maxDepth,
 
 /// Given a value, collect all of the underlying values being addressed.
 static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
-                                           DenseSet<Value> &visited,
+                                           UnderlyingValueWalk &walk,
                                            SmallVectorImpl<Value> &output) {
   LDBG() << "collectUnderlyingAddressValues: " << value;
   LDBG() << "  maxDepth: " << maxDepth;
 
   // Check that we don't infinitely recurse.
-  if (!visited.insert(value).second) {
+  if (!walk.visited.insert(value).second) {
     LDBG() << "  Value already visited, skipping";
     return;
   }
@@ -209,21 +219,23 @@ static void collectUnderlyingAddressValues(Value value, unsigned maxDepth,
 
   if (BlockArgument arg = dyn_cast<BlockArgument>(value)) {
     LDBG() << "  Processing as BlockArgument";
-    return collectUnderlyingAddressValues(arg, maxDepth, visited, output);
+    return collectUnderlyingAddressValues(arg, maxDepth, walk, output);
   }
   LDBG() << "  Processing as OpResult";
-  collectUnderlyingAddressValues(cast<OpResult>(value), maxDepth, visited,
-                                 output);
+  collectUnderlyingAddressValues(cast<OpResult>(value), maxDepth, walk, output);
 }
 
-/// Given a value, collect all of the underlying values being addressed.
-static void collectUnderlyingAddressValues(Value value,
+/// Given a value, collect all of the underlying values being addressed. Returns
+/// true if the walk looked through a view that may start at an offset from its
+/// source.
+static bool collectUnderlyingAddressValues(Value value,
                                            SmallVectorImpl<Value> &output) {
   LDBG() << "collectUnderlyingAddressValues: " << value;
-  DenseSet<Value> visited;
-  collectUnderlyingAddressValues(value, maxUnderlyingValueSearchDepth, visited,
+  UnderlyingValueWalk walk;
+  collectUnderlyingAddressValues(value, maxUnderlyingValueSearchDepth, walk,
                                  output);
   LDBG() << "  Collected " << output.size() << " underlying values";
+  return walk.throughOffsetView;
 }
 
 //===----------------------------------------------------------------------===//
@@ -439,8 +451,8 @@ AliasResult LocalAliasAnalysis::alias(Value lhs, Value rhs) {
 
   // Get the underlying values being addressed.
   SmallVector<Value, 8> lhsValues, rhsValues;
-  collectUnderlyingAddressValues(lhs, lhsValues);
-  collectUnderlyingAddressValues(rhs, rhsValues);
+  bool throughOffsetView = collectUnderlyingAddressValues(lhs, lhsValues);
+  throughOffsetView |= collectUnderlyingAddressValues(rhs, rhsValues);
 
   LDBG() << "  lhs underlying values: " << lhsValues.size();
   LDBG() << "  rhs underlying values: " << rhsValues.size();
@@ -458,6 +470,9 @@ AliasResult LocalAliasAnalysis::alias(Value lhs, Value rhs) {
     for (Value rhsVal : rhsValues) {
       LDBG() << "  Checking underlying values: " << lhsVal << " vs " << rhsVal;
       AliasResult nextResult = aliasImpl(lhsVal, rhsVal);
+      // Downgrade to MayAlias to give impl a chance to do alias anaylasis
+      if (nextResult.isMust() && throughOffsetView)
+        nextResult = AliasResult::MayAlias;
       LDBG() << "  Result: "
              << (nextResult == AliasResult::MustAlias ? "MustAlias"
                  : nextResult == AliasResult::NoAlias ? "NoAlias"
diff --git a/mlir/test/Analysis/test-alias-analysis.mlir b/mlir/test/Analysis/test-alias-analysis.mlir
index d71adee05c7a36..a786705bbd80c9 100644
--- a/mlir/test/Analysis/test-alias-analysis.mlir
+++ b/mlir/test/Analysis/test-alias-analysis.mlir
@@ -234,6 +234,37 @@ func.func @view_like(%arg: memref<2xf32>, %size: index) attributes {test.ptr = "
 
 // -----
 
+// Two addresses traced back to the same value only must alias if no view on
+// the way may have moved them away from it.
+
+// CHECK-LABEL: Testing : "view_like_offset"
+// CHECK-DAG: alloca_1#0 <-> alloca_2#0: NoAlias
+// CHECK-DAG: alloca_1#0 <-> view_1#0: MayAlias
+// CHECK-DAG: alloca_1#0 <-> view_2#0: MayAlias
+// CHECK-DAG: alloca_1#0 <-> view_3#0: NoAlias
+// CHECK-DAG: alloca_1#0 <-> zero_view#0: MustAlias
+// CHECK-DAG: alloca_2#0 <-> view_1#0: NoAlias
+// CHECK-DAG: alloca_2#0 <-> view_2#0: NoAlias
+// CHECK-DAG: alloca_2#0 <-> view_3#0: MayAlias
+// CHECK-DAG: alloca_2#0 <-> zero_view#0: NoAlias
+// CHECK-DAG: view_1#0 <-> view_2#0: MayAlias
+// CHECK-DAG: view_1#0 <-> view_3#0: NoAlias
+// CHECK-DAG: view_1#0 <-> zero_view#0: MayAlias
+// CHECK-DAG: view_2#0 <-> view_3#0: NoAlias
+// CHECK-DAG: view_2#0 <-> zero_view#0: MayAlias
+// CHECK-DAG: view_3#0 <-> zero_view#0: NoAlias
+func.func @view_like_offset() {
+  %0 = memref.alloca() {test.ptr = "alloca_1"} : memref<8xf32>
+  %1 = memref.alloca() {test.ptr = "alloca_2"} : memref<8xf32>
+  %2 = test.offset_view %0 {test.ptr = "view_1"} : memref<8xf32> -> memref<4xf32>
+  %3 = test.offset_view %0 {test.ptr = "view_2"} : memref<8xf32> -> memref<4xf32>
+  %4 = test.offset_view %1 {test.ptr = "view_3"} : memref<8xf32> -> memref<4xf32>
+  %5 = test.offset_view %0 zero_offset {test.ptr = "zero_view"} : memref<8xf32> -> memref<4xf32>
+  return
+}
+
+// -----
+
 // CHECK-LABEL: Testing : "constants"
 // CHECK-DAG: alloc_1#0 <-> constant_1#0: NoAlias
 // CHECK-DAG: alloc_1#0 <-> constant_2#0: NoAlias
diff --git a/mlir/test/lib/Dialect/Test/CMakeLists.txt b/mlir/test/lib/Dialect/Test/CMakeLists.txt
index 4c4721193322b6..00c98546662e3b 100644
--- a/mlir/test/lib/Dialect/Test/CMakeLists.txt
+++ b/mlir/test/lib/Dialect/Test/CMakeLists.txt
@@ -96,6 +96,7 @@ mlir_target_link_libraries(MLIRTestDialect PUBLIC
   MLIRTransformUtils
   MLIRTransforms
   MLIRValueBoundsOpInterface
+  MLIRViewLikeInterface
   MLIRBufferizationDialect
 )
 
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index a6b05b55804b7e..3da8f14a617148 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -32,6 +32,7 @@ include "mlir/Interfaces/MemorySlotInterfaces.td"
 include "mlir/Interfaces/SideEffectInterfaces.td"
 include "mlir/Interfaces/TilingInterface.td"
 include "mlir/Interfaces/ValueBoundsOpInterface.td"
+include "mlir/Interfaces/ViewLikeInterface.td"
 include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.td"
 include "mlir/Dialect/Bufferization/IR/BufferizationTypeInterfaces.td"
 
@@ -3466,6 +3467,28 @@ def TestDefaultAttrPrintOp : TEST_Op<"default_value_print"> {
   let assemblyFormat = "prop-dict attr-dict $operand";
 }
 
+//===----------------------------------------------------------------------===//
+// Test ViewLikeOpInterface
+//===----------------------------------------------------------------------===//
+
+def TestOffsetViewOp : TEST_Op<"offset_view", [Pure, ViewLikeOpInterface]> {
+  let summary = "view that may start at an offset from its source";
+  let description = [{
+    A view of `source` that may start at an offset from it, like a pointer to a
+    struct member. With `zero_offset`, the view is known to start at `source`.
+  }];
+  let arguments = (ins AnyMemRef:$source, UnitAttr:$zero_offset);
+  let results = (outs AnyMemRef:$result);
+  let assemblyFormat = [{
+    $source (`zero_offset` $zero_offset^)? attr-dict `:` type($source) `->`
+    type($result)
+  }];
+  let extraClassDeclaration = [{
+    ::mlir::Value getViewSource() { return getSource(); }
+    bool mayHaveNonZeroOffset() { return !getZeroOffset(); }
+  }];
+}
+
 //===----------------------------------------------------------------------===//
 // Test Ops with effects
 //===----------------------------------------------------------------------===//

>From 98f7b354db8d2429f2cc5ce1a6f3130c22b120f3 Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Sun, 25 Oct 2026 17:26:05 -0500
Subject: [PATCH 2/3] fixup! [MLIR] Implement mayHaveNonZeroOffset class

---
 mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td   |  6 +++--
 .../mlir/Dialect/MemRef/IR/MemRefOps.td       | 14 ++++++++---
 mlir/include/mlir/Dialect/Ptr/IR/PtrOps.td    |  3 +++
 .../mlir/Dialect/Vector/IR/VectorOps.td       |  1 +
 .../mlir/Interfaces/ViewLikeInterface.td      |  2 +-
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp    | 10 ++++++++
 mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp      | 11 +++++++++
 mlir/lib/Dialect/Ptr/IR/PtrDialect.cpp        |  4 ++++
 mlir/test/Analysis/test-alias-analysis.mlir   | 24 +++++++++++++++++++
 9 files changed, 69 insertions(+), 6 deletions(-)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index b09cdf9000d1e1..421ed0ccfbea40 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -295,7 +295,8 @@ def LLVM_GEPOp : LLVM_Op<"getelementptr", [Pure,
     DeclareOpInterfaceMethods<PromotableOpInterface>,
     DeclareOpInterfaceMethods<SafeMemorySlotAccessOpInterface>,
     DeclareOpInterfaceMethods<DestructurableAccessorOpInterface>,
-    DeclareOpInterfaceMethods<ViewLikeOpInterface>
+    DeclareOpInterfaceMethods<ViewLikeOpInterface,
+        ["mayHaveNonZeroOffset"]>
     ]> {
   let arguments = (ins LLVM_ScalarOrVectorOf<LLVM_AnyPointer>:$base,
                    Variadic<LLVM_ScalarOrVectorOf<AnySignlessInteger>>:$dynamicIndices,
@@ -650,7 +651,8 @@ def LLVM_AddrSpaceCastOp : LLVM_CastOp<"addrspacecast", "AddrSpaceCast",
     LLVM_ScalarOrVectorOf<LLVM_AnyPointer>,
     LLVM_ScalarOrVectorOf<LLVM_AnyPointer>,
     [DeclareOpInterfaceMethods<PromotableOpInterface>,
-     DeclareOpInterfaceMethods<ViewLikeOpInterface>]> {
+     DeclareOpInterfaceMethods<ViewLikeOpInterface,
+        ["mayHaveNonZeroOffset"]>]> {
   let hasFolder = 1;
 }
 def LLVM_IntToPtrOp : LLVM_DereferenceableCastOp<"inttoptr", "IntToPtr",
diff --git a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
index 86e466c46c2285..942a849b0f3bd6 100644
--- a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
+++ b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
@@ -178,6 +178,7 @@ def AssumeAlignmentOp : MemRef_Op<"assume_alignment", [
     MemRefType getType() { return ::llvm::cast<MemRefType>(getResult().getType()); }
 
     Value getViewSource() { return getMemref(); }
+    bool mayHaveNonZeroOffset() { return false; }
   }];
 
   let hasVerifier = 1;
@@ -568,6 +569,7 @@ def MemRef_CastOp : MemRef_Op<"cast", [
     static bool canFoldIntoConsumerOp(CastOp castOp);
 
     Value getViewSource() { return getSource(); }
+    bool mayHaveNonZeroOffset() { return false; }
   }];
 
   let hasFolder = 1;
@@ -1419,6 +1421,7 @@ def MemRef_MemorySpaceCastOp : MemRef_Op<"memory_space_cast", [
 
   let extraClassDeclaration = [{
     Value getViewSource() { return getSource(); }
+    bool mayHaveNonZeroOffset() { return false; }
 
     //===------------------------------------------------------------------===//
     // MemorySpaceCastConsumerOpInterface
@@ -1785,6 +1788,7 @@ def MemRef_ReshapeOp: MemRef_Op<"reshape", [
 
   let extraClassDeclaration = [{
     Value getViewSource() { return getSource(); }
+    bool mayHaveNonZeroOffset() { return false; }
   }];
 
   let assemblyFormat = [{
@@ -1822,6 +1826,7 @@ class MemRef_ReassociativeReshapeOp<string mnemonic, list<Trait> traits = []> :
     MemRefType getResultType() { return ::llvm::cast<MemRefType>(getResult().getType()); }
 
     Value getViewSource() { return getSrc(); }
+    bool mayHaveNonZeroOffset() { return false; }
   }];
 
   let hasFolder = 1;
@@ -2156,7 +2161,8 @@ def SubViewOp : MemRef_OpWithOffsetSizesAndStrides<"subview", [
     DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
     DeclareOpInterfaceMethods<InferStridedMetadataOpInterface>,
     DeclareOpInterfaceMethods<MemorySpaceCastConsumerOpInterface>,
-    DeclareOpInterfaceMethods<ViewLikeOpInterface>,
+    DeclareOpInterfaceMethods<ViewLikeOpInterface,
+        ["mayHaveNonZeroOffset"]>,
     AttrSizedOperandSegments,
     OffsetSizeAndStrideOpInterface,
     Pure
@@ -2434,7 +2440,8 @@ def SubViewOp : MemRef_OpWithOffsetSizesAndStrides<"subview", [
 def MemRef_TransposeOp : MemRef_Op<"transpose", [
     DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
     DeclareOpInterfaceMethods<MemorySpaceCastConsumerOpInterface>,
-    DeclareOpInterfaceMethods<ViewLikeOpInterface>,
+    DeclareOpInterfaceMethods<ViewLikeOpInterface,
+        ["mayHaveNonZeroOffset"]>,
     Pure]>,
     Arguments<(ins AnyStridedMemRef:$in, AffineMapAttr:$permutation)>,
     Results<(outs AnyStridedMemRef)> {
@@ -2471,7 +2478,8 @@ def MemRef_TransposeOp : MemRef_Op<"transpose", [
 def MemRef_ViewOp : MemRef_Op<"view", [
     DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
     DeclareOpInterfaceMethods<MemorySpaceCastConsumerOpInterface>,
-    DeclareOpInterfaceMethods<ViewLikeOpInterface>,
+    DeclareOpInterfaceMethods<ViewLikeOpInterface,
+        ["mayHaveNonZeroOffset"]>,
     Pure]> {
   let summary = "memref view operation";
   let description = [{
diff --git a/mlir/include/mlir/Dialect/Ptr/IR/PtrOps.td b/mlir/include/mlir/Dialect/Ptr/IR/PtrOps.td
index e14f64330c2949..8e242c645e56f0 100644
--- a/mlir/include/mlir/Dialect/Ptr/IR/PtrOps.td
+++ b/mlir/include/mlir/Dialect/Ptr/IR/PtrOps.td
@@ -410,6 +410,9 @@ def Ptr_PtrAddOp : Pointer_Op<"ptr_add", [
     /// `ViewLikeOp::getViewSource` method.
     Value getViewSource() { return getBase(); }
 
+    /// `ViewLikeOp::mayHaveNonZeroOffset` method.
+    bool mayHaveNonZeroOffset();
+
     /// Returns the ptr type of the operation.
     ptr::PtrType getPtrType();
   }];
diff --git a/mlir/include/mlir/Dialect/Vector/IR/VectorOps.td b/mlir/include/mlir/Dialect/Vector/IR/VectorOps.td
index 83fe3165b88540..c373235521cf1d 100644
--- a/mlir/include/mlir/Dialect/Vector/IR/VectorOps.td
+++ b/mlir/include/mlir/Dialect/Vector/IR/VectorOps.td
@@ -2621,6 +2621,7 @@ def Vector_TypeCastOp :
     }
     // Implement ViewLikeOpInterface.
     Value getViewSource() { return getMemref(); }
+    bool mayHaveNonZeroOffset() { return false; }
   }];
 
   let assemblyFormat = [{
diff --git a/mlir/include/mlir/Interfaces/ViewLikeInterface.td b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
index 0bbcd0401aa830..420c1b30e6506f 100644
--- a/mlir/include/mlir/Interfaces/ViewLikeInterface.td
+++ b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
@@ -47,7 +47,7 @@ def ViewLikeOpInterface : OpInterface<"ViewLikeOpInterface"> {
       /*args=*/(ins),
       /*methodBody=*/"",
       /*defaultImplementation=*/[{
-        return false;
+        return true;
       }]
     >
   ];
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index be5ea6df0caba7..b170db52a7b188 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -4105,6 +4105,8 @@ OpFoldResult LLVM::AddrSpaceCastOp::fold(FoldAdaptor adaptor) {
 
 Value LLVM::AddrSpaceCastOp::getViewSource() { return getArg(); }
 
+bool LLVM::AddrSpaceCastOp::mayHaveNonZeroOffset() { return false; }
+
 //===----------------------------------------------------------------------===//
 // Folder for LLVM::GEPOp
 //===----------------------------------------------------------------------===//
@@ -4161,6 +4163,14 @@ OpFoldResult LLVM::GEPOp::fold(FoldAdaptor adaptor) {
 
 Value LLVM::GEPOp::getViewSource() { return getBase(); }
 
+bool LLVM::GEPOp::mayHaveNonZeroOffset() {
+  return llvm::any_of(getIndices(), [](auto index) {
+    if (auto value = llvm::dyn_cast<Value>(index))
+      return !matchPattern(value, m_Zero());
+    return !llvm::cast<IntegerAttr>(index).getValue().isZero();
+  });
+}
+
 //===----------------------------------------------------------------------===//
 // ShlOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
index 245b2ba6babf04..2e50308799ed82 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
@@ -3311,6 +3311,11 @@ void SubViewOp::build(OpBuilder &b, OperationState &result, Value source,
 /// For ViewLikeOpInterface.
 Value SubViewOp::getViewSource() { return getSource(); }
 
+bool SubViewOp::mayHaveNonZeroOffset() {
+  return llvm::any_of(getStaticOffsets(),
+                      [](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) {
@@ -3837,6 +3842,8 @@ static MemRefType inferTransposeResultType(MemRefType memRefType,
 
 Value TransposeOp::getViewSource() { return getIn(); }
 
+bool TransposeOp::mayHaveNonZeroOffset() { return false; }
+
 void TransposeOp::build(OpBuilder &b, OperationState &result, Value in,
                         AffineMapAttr permutation,
                         ArrayRef<NamedAttribute> attrs) {
@@ -3954,6 +3961,10 @@ LogicalResult ViewOp::verify() {
 
 Value ViewOp::getViewSource() { return getSource(); }
 
+bool ViewOp::mayHaveNonZeroOffset() {
+  return !matchPattern(getByteShift(), m_Zero());
+}
+
 OpFoldResult ViewOp::fold(FoldAdaptor adaptor) {
   MemRefType sourceMemrefType = getSource().getType();
   MemRefType resultMemrefType = getResult().getType();
diff --git a/mlir/lib/Dialect/Ptr/IR/PtrDialect.cpp b/mlir/lib/Dialect/Ptr/IR/PtrDialect.cpp
index 444197d12097f9..d0907b5fdfaace 100644
--- a/mlir/lib/Dialect/Ptr/IR/PtrDialect.cpp
+++ b/mlir/lib/Dialect/Ptr/IR/PtrDialect.cpp
@@ -353,6 +353,10 @@ OpFoldResult PtrAddOp::fold(FoldAdaptor adaptor) {
   return nullptr;
 }
 
+bool PtrAddOp::mayHaveNonZeroOffset() {
+  return !matchPattern(getOffset(), m_Zero());
+}
+
 LogicalResult PtrAddOp::inferReturnTypes(
     MLIRContext *context, std::optional<Location> location, ValueRange operands,
     DictionaryAttr attributes, PropertyRef properties, RegionRange regions,
diff --git a/mlir/test/Analysis/test-alias-analysis.mlir b/mlir/test/Analysis/test-alias-analysis.mlir
index a786705bbd80c9..ab430e69b63b7e 100644
--- a/mlir/test/Analysis/test-alias-analysis.mlir
+++ b/mlir/test/Analysis/test-alias-analysis.mlir
@@ -265,6 +265,30 @@ func.func @view_like_offset() {
 
 // -----
 
+// GEPs keep MustAlias with their base only when all indices are zero.
+
+// CHECK-LABEL: Testing : "gep_offset"
+// CHECK-DAG: alloca_1#0 <-> alloca_2#0: NoAlias
+// CHECK-DAG: alloca_1#0 <-> gep_zero#0: MustAlias
+// CHECK-DAG: alloca_1#0 <-> gep_one#0: MayAlias
+// CHECK-DAG: alloca_1#0 <-> gep_dyn#0: MayAlias
+// CHECK-DAG: alloca_1#0 <-> gep_other#0: NoAlias
+// CHECK-DAG: gep_zero#0 <-> gep_one#0: MayAlias
+// CHECK-DAG: gep_one#0 <-> gep_dyn#0: MayAlias
+// CHECK-DAG: gep_one#0 <-> gep_other#0: NoAlias
+func.func @gep_offset(%idx: i64) {
+  %c1 = llvm.mlir.constant(1 : i32) : i32
+  %0 = llvm.alloca %c1 x !llvm.array<4 x i32> {test.ptr = "alloca_1"} : (i32) -> !llvm.ptr
+  %1 = llvm.alloca %c1 x !llvm.array<4 x i32> {test.ptr = "alloca_2"} : (i32) -> !llvm.ptr
+  %2 = llvm.getelementptr %0[0, 0] {test.ptr = "gep_zero"} : (!llvm.ptr) -> !llvm.ptr, !llvm.array<4 x i32>
+  %3 = llvm.getelementptr %0[0, 1] {test.ptr = "gep_one"} : (!llvm.ptr) -> !llvm.ptr, !llvm.array<4 x i32>
+  %4 = llvm.getelementptr %0[%idx] {test.ptr = "gep_dyn"} : (!llvm.ptr, i64) -> !llvm.ptr, i32
+  %5 = llvm.getelementptr %1[0, 1] {test.ptr = "gep_other"} : (!llvm.ptr) -> !llvm.ptr, !llvm.array<4 x i32>
+  return
+}
+
+// -----
+
 // CHECK-LABEL: Testing : "constants"
 // CHECK-DAG: alloc_1#0 <-> constant_1#0: NoAlias
 // CHECK-DAG: alloc_1#0 <-> constant_2#0: NoAlias

>From 96b428ec4aa21ea95b843fe60de234cf24c737f0 Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Sun, 25 Oct 2026 17:45:07 -0500
Subject: [PATCH 3/3] fixup! [MLIR] Implement mayHaveNonZeroOffset class

---
 flang/include/flang/Optimizer/Dialect/FIROps.td | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 7fc12430d0663f..176740d9dbf35f 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -2751,6 +2751,7 @@ def fir_VolatileCastOp
   let extraClassDeclaration = [{
     // ViewLikeOpInterface methods:
     mlir::Value getViewSource() { return getValue(); }
+    bool mayHaveNonZeroOffset() { return false; }
 
     // FortranObjectViewOpInterface methods:
     mlir::Value getViewSource(mlir::OpResult) { return getValue(); }
@@ -2806,6 +2807,7 @@ def fir_ConvertOp
 
     // ViewLikeOpInterface methods:
     mlir::Value getViewSource() { return getValue(); }
+    bool mayHaveNonZeroOffset() { return false; }
 
     // FortranObjectViewOpInterface methods:
     mlir::Value getViewSource(mlir::OpResult) { return getValue(); }



More information about the Mlir-commits mailing list