[Mlir-commits] [mlir] [mlir][memref][llvm] Infer llvm alias scopes attrs from `memref.distinct_objects` (PR #160512)

Ivan Butygin llvmlistbot at llvm.org
Wed Sep 24 05:13:56 PDT 2025


https://github.com/Hardcode84 created https://github.com/llvm/llvm-project/pull/160512

None

>From 7a513df00552310c078f38d2d1a8aa20e9cf03e6 Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Thu, 4 Sep 2025 17:25:26 +0200
Subject: [PATCH 1/6] [mlir][memref] Introduce `memref.distinct_objects` op

---
 .../mlir/Dialect/MemRef/IR/MemRefOps.td       | 39 ++++++++++++++-
 .../Conversion/MemRefToLLVM/MemRefToLLVM.cpp  | 49 +++++++++++++++++--
 mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp      | 19 +++++++
 .../MemRefToLLVM/memref-to-llvm.mlir          | 19 +++++++
 mlir/test/Dialect/MemRef/ops.mlir             |  9 ++++
 5 files changed, 130 insertions(+), 5 deletions(-)

diff --git a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
index 671cc05e963b4..933fb87de30ab 100644
--- a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
+++ b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
@@ -153,7 +153,7 @@ def AssumeAlignmentOp : MemRef_Op<"assume_alignment", [
       The `assume_alignment` operation takes a memref and an integer alignment
       value. It returns a new SSA value of the same memref type, but associated
       with the assumption that the underlying buffer is aligned to the given
-      alignment. 
+      alignment.
 
       If the buffer isn't aligned to the given alignment, its result is poison.
       This operation doesn't affect the semantics of a program where the
@@ -168,7 +168,7 @@ def AssumeAlignmentOp : MemRef_Op<"assume_alignment", [
   let assemblyFormat = "$memref `,` $alignment attr-dict `:` type($memref)";
   let extraClassDeclaration = [{
     MemRefType getType() { return ::llvm::cast<MemRefType>(getResult().getType()); }
-    
+
     Value getViewSource() { return getMemref(); }
   }];
 
@@ -176,6 +176,41 @@ def AssumeAlignmentOp : MemRef_Op<"assume_alignment", [
   let hasFolder = 1;
 }
 
+//===----------------------------------------------------------------------===//
+// DistinctObjectsOp
+//===----------------------------------------------------------------------===//
+
+def DistinctObjectsOp : MemRef_Op<"distinct_objects", [
+      Pure,
+      DeclareOpInterfaceMethods<InferTypeOpInterface>
+      // ViewLikeOpInterface TODO: ViewLikeOpInterface only supports a single argument
+    ]> {
+  let summary = "assumption that acesses to specific memrefs will never alias";
+  let description = [{
+      The `distinct_objects` operation takes a list of memrefs and returns a list of
+      memrefs of the same types, with the additional assumption that accesses to
+      these memrefs will never alias with each other. This means that loads and
+      stores to different memrefs in the list can be safely reordered.
+
+      If the memrefs do alias, the behavior is undefined. This operation doesn't
+      affect the semantics of a program where the non-aliasing assumption holds
+      true. It is intended for optimization purposes, allowing the compiler to
+      generate more efficient code based on the non-aliasing assumption. The
+      optimization is best-effort.
+
+      Example:
+
+      ```mlir
+      %1, %2 = memref.distinct_objects %a, %b : memref<?xf32>, memref<?xf32>
+      ```
+  }];
+  let arguments = (ins Variadic<AnyMemRef>:$operands);
+  let results = (outs Variadic<AnyMemRef>:$results);
+
+  let assemblyFormat = "$operands attr-dict `:` type($operands)";
+  let hasVerifier = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // AllocOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
index 262e0e7a30c63..571e5000b3f51 100644
--- a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
+++ b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
@@ -465,6 +465,48 @@ struct AssumeAlignmentOpLowering
   }
 };
 
+struct DistinctObjectsOpLowering
+    : public ConvertOpToLLVMPattern<memref::DistinctObjectsOp> {
+  using ConvertOpToLLVMPattern<
+      memref::DistinctObjectsOp>::ConvertOpToLLVMPattern;
+  explicit DistinctObjectsOpLowering(const LLVMTypeConverter &converter)
+      : ConvertOpToLLVMPattern<memref::DistinctObjectsOp>(converter) {}
+
+  LogicalResult
+  matchAndRewrite(memref::DistinctObjectsOp op, OpAdaptor adaptor,
+                  ConversionPatternRewriter &rewriter) const override {
+    ValueRange operands = adaptor.getOperands();
+    if (operands.empty()) {
+      rewriter.eraseOp(op);
+      return success();
+    }
+    Location loc = op.getLoc();
+    SmallVector<Value> ptrs;
+    for (auto [origOperand, newOperand] :
+         llvm::zip_equal(op.getOperands(), operands)) {
+      auto memrefType = cast<MemRefType>(origOperand.getType());
+      Value ptr = getStridedElementPtr(rewriter, loc, memrefType, newOperand,
+                                       /*indices=*/{});
+      ptrs.push_back(ptr);
+    }
+
+    auto cond =
+        LLVM::ConstantOp::create(rewriter, loc, rewriter.getI1Type(), 1);
+    // Generate separate_storage assumptions for each pair of pointers.
+    for (auto i : llvm::seq<size_t>(ptrs.size() - 1)) {
+      for (auto j : llvm::seq<size_t>(i + 1, ptrs.size())) {
+        Value ptr1 = ptrs[i];
+        Value ptr2 = ptrs[j];
+        LLVM::AssumeOp::create(rewriter, loc, cond,
+                               LLVM::AssumeSeparateStorageTag{}, ptr1, ptr2);
+      }
+    }
+
+    rewriter.replaceOp(op, operands);
+    return success();
+  }
+};
+
 // A `dealloc` is converted into a call to `free` on the underlying data buffer.
 // The memref descriptor being an SSA value, there is no need to clean it up
 // in any way.
@@ -1997,22 +2039,23 @@ void mlir::populateFinalizeMemRefToLLVMConversionPatterns(
   patterns.add<
       AllocaOpLowering,
       AllocaScopeOpLowering,
-      AtomicRMWOpLowering,
       AssumeAlignmentOpLowering,
+      AtomicRMWOpLowering,
       ConvertExtractAlignedPointerAsIndex,
       DimOpLowering,
+      DistinctObjectsOpLowering,
       ExtractStridedMetadataOpLowering,
       GenericAtomicRMWOpLowering,
       GetGlobalMemrefOpLowering,
       LoadOpLowering,
       MemRefCastOpLowering,
-      MemorySpaceCastOpLowering,
       MemRefReinterpretCastOpLowering,
       MemRefReshapeOpLowering,
+      MemorySpaceCastOpLowering,
       PrefetchOpLowering,
       RankOpLowering,
-      ReassociatingReshapeOpConversion<memref::ExpandShapeOp>,
       ReassociatingReshapeOpConversion<memref::CollapseShapeOp>,
+      ReassociatingReshapeOpConversion<memref::ExpandShapeOp>,
       StoreOpLowering,
       SubViewOpLowering,
       TransposeOpLowering,
diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
index 5d15d5f6e3de4..62fb57f24a870 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
@@ -542,6 +542,25 @@ OpFoldResult AssumeAlignmentOp::fold(FoldAdaptor adaptor) {
   return getMemref();
 }
 
+//===----------------------------------------------------------------------===//
+// DistinctObjectsOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult DistinctObjectsOp::verify() {
+  if (getOperandTypes() != getResultTypes())
+    return emitOpError("operand types and result types must match");
+  return success();
+}
+
+LogicalResult DistinctObjectsOp::inferReturnTypes(
+    MLIRContext * /*context*/, std::optional<Location> /*location*/,
+    ValueRange operands, DictionaryAttr /*attributes*/,
+    OpaqueProperties /*properties*/, RegionRange /*regions*/,
+    SmallVectorImpl<Type> &inferredReturnTypes) {
+  llvm::copy(operands.getTypes(), std::back_inserter(inferredReturnTypes));
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // CastOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir b/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
index 45b1a1f1ca40c..3eb8df093af10 100644
--- a/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
+++ b/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
@@ -195,6 +195,25 @@ func.func @assume_alignment(%0 : memref<4x4xf16>) {
 
 // -----
 
+// ALL-LABEL: func @distinct_objects
+//  ALL-SAME:   (%[[ARG0:.*]]: memref<?xf16>, %[[ARG1:.*]]: memref<?xf32>, %[[ARG2:.*]]: memref<?xf64>)
+func.func @distinct_objects(%arg0: memref<?xf16>, %arg1: memref<?xf32>, %arg2: memref<?xf64>) -> (memref<?xf16>, memref<?xf32>, memref<?xf64>) {
+//   ALL-DAG:   %[[CAST_0:.*]] = builtin.unrealized_conversion_cast %[[ARG0]] : memref<?xf16> to !llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>
+//   ALL-DAG:   %[[CAST_1:.*]] = builtin.unrealized_conversion_cast %[[ARG1]] : memref<?xf32> to !llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>
+//   ALL-DAG:   %[[CAST_2:.*]] = builtin.unrealized_conversion_cast %[[ARG2]] : memref<?xf64> to !llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>
+//       ALL:   %[[PTR_0:.*]] = llvm.extractvalue %[[CAST_0]][1] : !llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>
+//       ALL:   %[[PTR_1:.*]] = llvm.extractvalue %[[CAST_1]][1] : !llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>
+//       ALL:   %[[PTR_2:.*]] = llvm.extractvalue %[[CAST_2]][1] : !llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>
+//       ALL:   %[[TRUE:.*]] = llvm.mlir.constant(true) : i1
+//       ALL:   llvm.intr.assume %[[TRUE]] ["separate_storage"(%[[PTR_0]], %[[PTR_1]] : !llvm.ptr, !llvm.ptr)] : i1
+//       ALL:   llvm.intr.assume %[[TRUE]] ["separate_storage"(%[[PTR_0]], %[[PTR_2]] : !llvm.ptr, !llvm.ptr)] : i1
+//       ALL:   llvm.intr.assume %[[TRUE]] ["separate_storage"(%[[PTR_1]], %[[PTR_2]] : !llvm.ptr, !llvm.ptr)] : i1
+  %1, %2, %3 = memref.distinct_objects %arg0, %arg1, %arg2 : memref<?xf16>, memref<?xf32>, memref<?xf64>
+  return %1, %2, %3 : memref<?xf16>, memref<?xf32>, memref<?xf64>
+}
+
+// -----
+
 // CHECK-LABEL: func @assume_alignment_w_offset
 // CHECK-INTERFACE-LABEL: func @assume_alignment_w_offset
 func.func @assume_alignment_w_offset(%0 : memref<4x4xf16, strided<[?, ?], offset: ?>>) {
diff --git a/mlir/test/Dialect/MemRef/ops.mlir b/mlir/test/Dialect/MemRef/ops.mlir
index 6c2298a3f8acb..a90c9505a8405 100644
--- a/mlir/test/Dialect/MemRef/ops.mlir
+++ b/mlir/test/Dialect/MemRef/ops.mlir
@@ -302,6 +302,15 @@ func.func @assume_alignment(%0: memref<4x4xf16>) {
   return
 }
 
+// CHECK-LABEL: func @distinct_objects
+// CHECK-SAME: (%[[ARG0:.*]]: memref<?xf16>, %[[ARG1:.*]]: memref<?xf32>, %[[ARG2:.*]]: memref<?xf64>)
+func.func @distinct_objects(%arg0: memref<?xf16>, %arg1: memref<?xf32>, %arg2: memref<?xf64>) -> (memref<?xf16>, memref<?xf32>, memref<?xf64>) {
+  // CHECK:  %[[RES:.*]]:3 = memref.distinct_objects %[[ARG0]], %[[ARG1]], %[[ARG2]] : memref<?xf16>, memref<?xf32>, memref<?xf64>
+  %1, %2, %3 = memref.distinct_objects %arg0, %arg1, %arg2 : memref<?xf16>, memref<?xf32>, memref<?xf64>
+  // CHECK:  return %[[RES]]#0, %[[RES]]#1, %[[RES]]#2 : memref<?xf16>, memref<?xf32>, memref<?xf64>
+  return %1, %2, %3 : memref<?xf16>, memref<?xf32>, memref<?xf64>
+}
+
 // CHECK-LABEL: func @expand_collapse_shape_static
 func.func @expand_collapse_shape_static(
     %arg0: memref<3x4x5xf32>,

>From 1e24829dbc19f9d3afbb8d11cd1d9a334c9c2c80 Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Wed, 10 Sep 2025 17:25:38 +0200
Subject: [PATCH 2/6] verifier test

---
 mlir/test/Dialect/MemRef/invalid.mlir | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/mlir/test/Dialect/MemRef/invalid.mlir b/mlir/test/Dialect/MemRef/invalid.mlir
index 3f96d907632b7..67951f8ef0765 100644
--- a/mlir/test/Dialect/MemRef/invalid.mlir
+++ b/mlir/test/Dialect/MemRef/invalid.mlir
@@ -1169,3 +1169,11 @@ func.func @expand_shape_invalid_output_shape(
       into memref<2x15x20xf32, strided<[60000, 4000, 2], offset: 100>>
   return
 }
+
+// -----
+
+func.func @Invalid_distinct_objects(%arg0: memref<?xf32>, %arg1: memref<?xi32>) -> (memref<?xi32>, memref<?xf32>) {
+  // expected-error @+1 {{operand types and result types must match}}
+  %0, %1 = "memref.distinct_objects"(%arg0, %arg1) : (memref<?xf32>, memref<?xi32>) -> (memref<?xi32>, memref<?xf32>)
+  return %0, %1 : memref<?xi32>, memref<?xf32>
+}

>From 4b943e82d1727e00512c35fb0034beeab7346e1e Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Tue, 23 Sep 2025 23:13:13 +0200
Subject: [PATCH 3/6] comments, reject 0-poerand version

---
 .../mlir/Dialect/MemRef/IR/MemRefOps.td       | 20 +++++++++----------
 .../Conversion/MemRefToLLVM/MemRefToLLVM.cpp  |  6 ++++--
 mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp      |  4 ++++
 .../MemRefToLLVM/memref-to-llvm.mlir          | 11 ++++++++++
 mlir/test/Dialect/MemRef/invalid.mlir         | 10 +++++++++-
 5 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
index 933fb87de30ab..f75e311645426 100644
--- a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
+++ b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
@@ -187,16 +187,16 @@ def DistinctObjectsOp : MemRef_Op<"distinct_objects", [
     ]> {
   let summary = "assumption that acesses to specific memrefs will never alias";
   let description = [{
-      The `distinct_objects` operation takes a list of memrefs and returns a list of
-      memrefs of the same types, with the additional assumption that accesses to
-      these memrefs will never alias with each other. This means that loads and
-      stores to different memrefs in the list can be safely reordered.
-
-      If the memrefs do alias, the behavior is undefined. This operation doesn't
-      affect the semantics of a program where the non-aliasing assumption holds
-      true. It is intended for optimization purposes, allowing the compiler to
-      generate more efficient code based on the non-aliasing assumption. The
-      optimization is best-effort.
+      The `distinct_objects` operation takes a list of memrefs and returns the same
+      memrefs, with the additional assumption that accesses to them will never
+      alias with each other. This means that loads and stores to different
+      memrefs in the list can be safely reordered.
+
+      If the memrefs do alias, the load/store behavior is undefined. This
+      operation doesn't affect the semantics of a valid program. It is
+      intended for optimization purposes, allowing the compiler to generate more
+      efficient code based on the non-aliasing assumption. The optimization is
+      best-effort.
 
       Example:
 
diff --git a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
index 571e5000b3f51..64270726f4a01 100644
--- a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
+++ b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
@@ -476,10 +476,12 @@ struct DistinctObjectsOpLowering
   matchAndRewrite(memref::DistinctObjectsOp op, OpAdaptor adaptor,
                   ConversionPatternRewriter &rewriter) const override {
     ValueRange operands = adaptor.getOperands();
-    if (operands.empty()) {
-      rewriter.eraseOp(op);
+    if (operands.size() <= 1) {
+      // Fast path.
+      rewriter.replaceOp(op, operands);
       return success();
     }
+
     Location loc = op.getLoc();
     SmallVector<Value> ptrs;
     for (auto [origOperand, newOperand] :
diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
index 62fb57f24a870..0bca922b0c804 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
@@ -549,6 +549,10 @@ OpFoldResult AssumeAlignmentOp::fold(FoldAdaptor adaptor) {
 LogicalResult DistinctObjectsOp::verify() {
   if (getOperandTypes() != getResultTypes())
     return emitOpError("operand types and result types must match");
+
+  if (getOperandTypes().empty())
+    return emitOpError("expected at least one operand");
+
   return success();
 }
 
diff --git a/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir b/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
index 3eb8df093af10..0cbe064572911 100644
--- a/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
+++ b/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
@@ -214,6 +214,17 @@ func.func @distinct_objects(%arg0: memref<?xf16>, %arg1: memref<?xf32>, %arg2: m
 
 // -----
 
+// ALL-LABEL: func @distinct_objects_noop
+//  ALL-SAME:   (%[[ARG0:.*]]: memref<?xf16>)
+func.func @distinct_objects_noop(%arg0: memref<?xf16>) -> memref<?xf16> {
+// 1-operand version is noop
+//  ALL-NEXT:   return %[[ARG0]]
+  %1 = memref.distinct_objects %arg0 : memref<?xf16>
+  return %1 : memref<?xf16>
+}
+
+// -----
+
 // CHECK-LABEL: func @assume_alignment_w_offset
 // CHECK-INTERFACE-LABEL: func @assume_alignment_w_offset
 func.func @assume_alignment_w_offset(%0 : memref<4x4xf16, strided<[?, ?], offset: ?>>) {
diff --git a/mlir/test/Dialect/MemRef/invalid.mlir b/mlir/test/Dialect/MemRef/invalid.mlir
index 67951f8ef0765..5ff292058ccc1 100644
--- a/mlir/test/Dialect/MemRef/invalid.mlir
+++ b/mlir/test/Dialect/MemRef/invalid.mlir
@@ -1172,8 +1172,16 @@ func.func @expand_shape_invalid_output_shape(
 
 // -----
 
-func.func @Invalid_distinct_objects(%arg0: memref<?xf32>, %arg1: memref<?xi32>) -> (memref<?xi32>, memref<?xf32>) {
+func.func @distinct_objects_types_mismatch(%arg0: memref<?xf32>, %arg1: memref<?xi32>) -> (memref<?xi32>, memref<?xf32>) {
   // expected-error @+1 {{operand types and result types must match}}
   %0, %1 = "memref.distinct_objects"(%arg0, %arg1) : (memref<?xf32>, memref<?xi32>) -> (memref<?xi32>, memref<?xf32>)
   return %0, %1 : memref<?xi32>, memref<?xf32>
 }
+
+// -----
+
+func.func @distinct_objects_0_operands() {
+  // expected-error @+1 {{expected at least one operand}}
+  "memref.distinct_objects"() : () -> ()
+  return
+}

>From 98af92170063138588f98758bf8e8a75b24ae74d Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Wed, 24 Sep 2025 12:34:15 +0200
Subject: [PATCH 4/6] LocalAliasAnalysis

---
 .../mlir/Dialect/MemRef/IR/MemRefOps.td       |  1 +
 .../mlir/Interfaces/ViewLikeInterface.td      | 40 +++++++++++++++++++
 .../AliasAnalysis/LocalAliasAnalysis.cpp      | 30 ++++++++++++++
 mlir/test/Analysis/test-alias-analysis.mlir   | 16 ++++++++
 4 files changed, 87 insertions(+)

diff --git a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
index f75e311645426..b4900720c6f74 100644
--- a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
+++ b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td
@@ -182,6 +182,7 @@ def AssumeAlignmentOp : MemRef_Op<"assume_alignment", [
 
 def DistinctObjectsOp : MemRef_Op<"distinct_objects", [
       Pure,
+      DistinctObjectsInterface,
       DeclareOpInterfaceMethods<InferTypeOpInterface>
       // ViewLikeOpInterface TODO: ViewLikeOpInterface only supports a single argument
     ]> {
diff --git a/mlir/include/mlir/Interfaces/ViewLikeInterface.td b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
index ed213bfdae337..2c3adb4a7fd6a 100644
--- a/mlir/include/mlir/Interfaces/ViewLikeInterface.td
+++ b/mlir/include/mlir/Interfaces/ViewLikeInterface.td
@@ -414,4 +414,44 @@ def OffsetSizeAndStrideOpInterface : OpInterface<"OffsetSizeAndStrideOpInterface
   }];
 }
 
+def DistinctObjectsInterface : OpInterface<"DistinctObjectsInterface"> {
+  let description = [{
+    This intefaces indicates that pointer-like objects (such as memrefs) returned
+    from this operation will never alias with each other. This provides a
+    guarantee to optimization passes that accesses through different results
+    of this operation can be safely reordered, as they will never reference
+    overlapping memory locations.
+
+    Operations with this interface take multiple pointer-like operands
+    and return the same operands with additional non-aliasing guarantees.
+    If the access to the results of this operation aliases at runtime, the
+    behavior of such access is undefined.
+  }];
+  let cppNamespace = "::mlir";
+
+  let methods = [
+    InterfaceMethod<
+      /*desc=*/[{ Return input pointer-like objects. }],
+      /*retTy=*/"::mlir::ValueRange",
+      /*methodName=*/"getDistinctOperands",
+      /*args=*/(ins),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        return $_op->getOperands();
+      }]
+    >,
+    InterfaceMethod<
+      /*desc=*/[{ Return result pointer-like objects. }],
+      /*retTy=*/"::mlir::ValueRange",
+      /*methodName=*/"getDistinctResults",
+      /*args=*/(ins),
+      /*methodBody=*/"",
+      /*defaultImplementation=*/[{
+        return $_op->getResults();
+      }]
+    >
+  ];
+}
+
+
 #endif // MLIR_INTERFACES_VIEWLIKEINTERFACE
diff --git a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
index 8062b474539fd..15686fc6d4031 100644
--- a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
+++ b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
@@ -258,6 +258,33 @@ getAllocEffectFor(Value value,
   return success();
 }
 
+static Value getDistinctObjectsOperand(DistinctObjectsInterface op,
+                                       Value value) {
+  unsigned argNumber = cast<OpResult>(value).getResultNumber();
+  return op.getDistinctOperands()[argNumber];
+}
+
+static std::optional<AliasResult> checkDistinctObjects(Value lhs, Value rhs) {
+  // We should already checked that lhs and rhs are different.
+  assert(lhs != rhs && "lhs and rhs must be different");
+
+  // Result and corresponding operand must alias.
+  auto lhsOp = lhs.getDefiningOp<DistinctObjectsInterface>();
+  if (lhsOp && getDistinctObjectsOperand(lhsOp, lhs) == rhs)
+    return AliasResult::MustAlias;
+
+  auto rhsOp = rhs.getDefiningOp<DistinctObjectsInterface>();
+  if (rhsOp && getDistinctObjectsOperand(rhsOp, rhs) == lhs)
+    return AliasResult::MustAlias;
+
+  // If two different values come from the same `DistinctObjects` operation,
+  // they don't alias.
+  if (lhsOp && lhsOp == rhsOp)
+    return AliasResult::NoAlias;
+
+  return std::nullopt;
+}
+
 /// Given the two values, return their aliasing behavior.
 AliasResult LocalAliasAnalysis::aliasImpl(Value lhs, Value rhs) {
   if (lhs == rhs)
@@ -289,6 +316,9 @@ AliasResult LocalAliasAnalysis::aliasImpl(Value lhs, Value rhs) {
                : AliasResult::MayAlias;
   }
 
+  if (std::optional<AliasResult> result = checkDistinctObjects(lhs, rhs))
+    return *result;
+
   // Otherwise, neither of the values are constant so check to see if either has
   // an allocation effect.
   bool lhsHasAlloc = succeeded(getAllocEffectFor(lhs, lhsAlloc, lhsAllocScope));
diff --git a/mlir/test/Analysis/test-alias-analysis.mlir b/mlir/test/Analysis/test-alias-analysis.mlir
index 8cbee61c78b45..d71adee05c7a3 100644
--- a/mlir/test/Analysis/test-alias-analysis.mlir
+++ b/mlir/test/Analysis/test-alias-analysis.mlir
@@ -256,3 +256,19 @@ func.func @constants(%arg: memref<2xf32>) attributes {test.ptr = "func"} {
 
   return
 }
+
+// -----
+
+// CHECK-LABEL: Testing : "distinct_objects"
+// CHECK-DAG: func.region0#0 <-> func.region0#1: MayAlias
+
+// CHECK-DAG: distinct#0 <-> distinct#1: NoAlias
+// CHECK-DAG: distinct#0 <-> func.region0#0: MustAlias
+// CHECK-DAG: distinct#1 <-> func.region0#0: MayAlias
+// CHECK-DAG: distinct#0 <-> func.region0#1: MayAlias
+// CHECK-DAG: distinct#1 <-> func.region0#1: MustAlias
+
+func.func @distinct_objects(%arg: memref<?xf32>, %arg1: memref<?xf32>) attributes {test.ptr = "func"} {
+  %0, %1 = memref.distinct_objects %arg, %arg1 {test.ptr = "distinct"} : memref<?xf32>, memref<?xf32>
+  return
+}

>From f492b57daf45a9ddbd9cb12366a3e3840a8e29d1 Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Wed, 24 Sep 2025 13:53:58 +0200
Subject: [PATCH 5/6] infer alias scopes

---
 .../LLVMIR/Transforms/InferAliasScopeAttrs.h  |  22 ++++
 .../mlir/Dialect/LLVMIR/Transforms/Passes.td  |  16 +++
 .../Dialect/LLVMIR/Transforms/CMakeLists.txt  |   1 +
 .../Transforms/InferAliasScopeAttrs.cpp       | 112 ++++++++++++++++++
 .../Dialect/LLVMIR/infer-alias-attrs.mlir     |  14 +++
 5 files changed, 165 insertions(+)
 create mode 100644 mlir/include/mlir/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.h
 create mode 100644 mlir/lib/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.cpp
 create mode 100644 mlir/test/Dialect/LLVMIR/infer-alias-attrs.mlir

diff --git a/mlir/include/mlir/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.h b/mlir/include/mlir/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.h
new file mode 100644
index 0000000000000..e0ec027d95b4d
--- /dev/null
+++ b/mlir/include/mlir/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.h
@@ -0,0 +1,22 @@
+//==- InferAliasScopeAttrs.h - Infer LLVM alias scope attributes -*- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_DIALECT_LLVMIR_TRANSFORMS_INFERALIASSCOPEATTRS_H
+#define MLIR_DIALECT_LLVMIR_TRANSFORMS_INFERALIASSCOPEATTRS_H
+
+#include <memory>
+
+namespace mlir {
+class Pass;
+namespace LLVM {
+#define GEN_PASS_DECL_LLVMINFERALIASSCOPEATTRIBUTES
+#include "mlir/Dialect/LLVMIR/Transforms/Passes.h.inc"
+} // namespace LLVM
+} // namespace mlir
+
+#endif // MLIR_DIALECT_LLVMIR_TRANSFORMS_INFERALIASSCOPEATTRS_H
diff --git a/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td b/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td
index 961909d5c8d27..62cdca9062c2a 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td
@@ -73,4 +73,20 @@ def DIScopeForLLVMFuncOpPass : Pass<"ensure-debug-info-scope-on-llvm-func", "::m
   ];
 }
 
+def LLVMInferAliasScopeAttributes
+    : InterfacePass<"llvm-infer-alias-scopes-attrs", "::mlir::FunctionOpInterface"> {
+  let summary = "Infer alias scopes for load/store ops";
+  let description = [{
+    This pass infers LLVM alias scope attributes for other dialect load and store
+    operations based on alias analysis information. It analyzes memory operations
+    within functions to determine which operations may or may not alias with each
+    other, and annotates them with appropriate LLVM alias scope metadata.
+
+    Alias scopes on other dialect ops are not robust enough against code
+    transformations (e.g. inlining) so this pass is intended to be run just
+    before *-to-llvm conversion.
+  }];
+  let dependentDialects = ["LLVM::LLVMDialect"];
+}
+
 #endif // MLIR_DIALECT_LLVMIR_TRANSFORMS_PASSES
diff --git a/mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt b/mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt
index d4ff0955c5d0e..c263f4292c338 100644
--- a/mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt
@@ -3,6 +3,7 @@ add_mlir_dialect_library(MLIRLLVMIRTransforms
   DIExpressionLegalization.cpp
   DIExpressionRewriter.cpp
   DIScopeForLLVMFuncOp.cpp
+  InferAliasScopeAttrs.cpp
   InlinerInterfaceImpl.cpp
   LegalizeForExport.cpp
   OptimizeForNVVM.cpp
diff --git a/mlir/lib/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.cpp b/mlir/lib/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.cpp
new file mode 100644
index 0000000000000..06bf380eea386
--- /dev/null
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.cpp
@@ -0,0 +1,112 @@
+//===- InferAliasScopeAttrs.cpp - Infer LLVM alias scope attributes -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Dialect/LLVMIR/Transforms/InferAliasScopeAttrs.h"
+
+#include "mlir/Analysis/AliasAnalysis.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Pass/Pass.h"
+
+namespace mlir {
+namespace LLVM {
+#define GEN_PASS_DEF_LLVMINFERALIASSCOPEATTRIBUTES
+#include "mlir/Dialect/LLVMIR/Transforms/Passes.h.inc"
+} // namespace LLVM
+} // namespace mlir
+
+using namespace mlir;
+
+static Value getBasePtr(Operation *op) {
+  // TODO: we need a common interface to get the base ptr.
+  if (auto loadOp = dyn_cast<memref::LoadOp>(op))
+    return loadOp.getMemRef();
+
+  if (auto storeOp = dyn_cast<memref::StoreOp>(op))
+    return storeOp.getMemRef();
+
+  return nullptr;
+}
+
+namespace {
+
+struct LLVMInferAliasScopeAttrs
+    : public LLVM::impl::LLVMInferAliasScopeAttributesBase<
+          LLVMInferAliasScopeAttrs> {
+  void runOnOperation() override {
+    SmallVector<Operation *> memOps;
+    getOperation().walk([&](MemoryEffectOpInterface op) {
+      if ((op.hasEffect<MemoryEffects::Read>() ||
+           op.hasEffect<MemoryEffects::Write>()) &&
+          getBasePtr(op))
+        memOps.emplace_back(op);
+    });
+
+    if (memOps.empty())
+      return markAllAnalysesPreserved();
+
+    auto &aliasAnalysis = getAnalysis<AliasAnalysis>();
+    MLIRContext *ctx = &getContext();
+
+    LLVM::AliasScopeDomainAttr domain;
+    llvm::SmallDenseMap<Operation *, Attribute> aliasScopes;
+    auto getScope = [&](Operation *op) -> LLVM::AliasScopeAttr {
+      if (!domain)
+        domain = LLVM::AliasScopeDomainAttr::get(ctx);
+
+      auto scope =
+          cast_if_present<LLVM::AliasScopeAttr>(aliasScopes.lookup(op));
+      if (scope)
+        return scope;
+
+      scope = LLVM::AliasScopeAttr::get(domain);
+      aliasScopes[op] = scope;
+      return scope;
+    };
+
+    DenseMap<Operation *, llvm::SmallSetVector<Attribute, 4>> noaliasScopes;
+
+    // TODO: This is quadratic in the number of memOps, can we do better?
+    for (Operation *op : memOps) {
+      for (Operation *otherOp : memOps) {
+        if (op == otherOp)
+          continue;
+
+        Value basePtr = getBasePtr(op);
+        assert(basePtr && "Expected base ptr");
+        Value otherBasePtr = getBasePtr(otherOp);
+        assert(otherBasePtr && "Expected base ptr");
+        if (!aliasAnalysis.alias(basePtr, otherBasePtr).isNo())
+          continue;
+
+        noaliasScopes[op].insert(getScope(otherOp));
+      }
+    }
+
+    if (noaliasScopes.empty())
+      return markAllAnalysesPreserved();
+
+    auto aliasScopesName =
+        StringAttr::get(ctx, LLVM::LLVMDialect::getAliasScopesAttrName());
+    auto noaliasName =
+        StringAttr::get(ctx, LLVM::LLVMDialect::getNoAliasAttrName());
+
+    // We are intentionally using discardable attributes here because those are
+    // generally not robust against codegen transformations (e.g. inlining) and
+    // this pass is intended to be run just before *-to-llvm conversion.
+    for (Operation *op : memOps) {
+      if (auto aliasScope = aliasScopes.lookup(op))
+        op->setAttr(aliasScopesName, ArrayAttr::get(ctx, {aliasScope}));
+
+      auto it = noaliasScopes.find(op);
+      if (it != noaliasScopes.end())
+        op->setAttr(noaliasName, ArrayAttr::get(ctx, it->second.getArrayRef()));
+    }
+  }
+};
+} // namespace
diff --git a/mlir/test/Dialect/LLVMIR/infer-alias-attrs.mlir b/mlir/test/Dialect/LLVMIR/infer-alias-attrs.mlir
new file mode 100644
index 0000000000000..91a83a88989bf
--- /dev/null
+++ b/mlir/test/Dialect/LLVMIR/infer-alias-attrs.mlir
@@ -0,0 +1,14 @@
+// RUN: mlir-opt -pass-pipeline='builtin.module(func.func(llvm-infer-alias-scopes-attrs))' %s | FileCheck %s
+
+// CHECK-LABEL:  distinct_objects
+func.func @distinct_objects(%arg0: memref<?xf32>, %arg1: memref<?xf32>, %arg2: memref<?xf32>) {
+  %c0 = arith.constant 0 : index
+  %1, %2, %3 = memref.distinct_objects %arg0, %arg1, %arg2 : memref<?xf32>, memref<?xf32>, memref<?xf32>
+//       CHECK:    memref.load {{.*}} {alias_scopes = [#[[SCOPE0:.*]]], llvm.noalias = [#[[SCOPE1:.*]], #[[SCOPE2:.*]]]}
+//       CHECK:    memref.store {{.*}} {alias_scopes = [#[[SCOPE1]]], llvm.noalias = [#[[SCOPE0]], #[[SCOPE2]]]} : memref<?xf32>
+//       CHECK:    memref.store {{.*}} {alias_scopes = [#[[SCOPE2]]], llvm.noalias = [#[[SCOPE0]], #[[SCOPE1]]]} : memref<?xf32>
+  %4 = memref.load %1[%c0] : memref<?xf32>
+  memref.store %4, %2[%c0] : memref<?xf32>
+  memref.store %4, %3[%c0] : memref<?xf32>
+  return
+}

>From aceff3733a3a851be37d92d328857cb24517b321 Mon Sep 17 00:00:00 2001
From: Ivan Butygin <ivan.butygin at gmail.com>
Date: Wed, 24 Sep 2025 14:11:51 +0200
Subject: [PATCH 6/6] copy attrs

---
 .../Conversion/MemRefToLLVM/MemRefToLLVM.cpp  | 34 ++++++++++++++++---
 .../MemRefToLLVM/memref-to-llvm.mlir          | 16 +++++++++
 2 files changed, 46 insertions(+), 4 deletions(-)

diff --git a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
index 64270726f4a01..a4ac07ace4172 100644
--- a/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
+++ b/mlir/lib/Conversion/MemRefToLLVM/MemRefToLLVM.cpp
@@ -922,6 +922,18 @@ struct GetGlobalMemrefOpLowering
   }
 };
 
+static SmallVector<std::pair<StringAttr, Attribute>>
+copyImportantAttrs(Operation *op) {
+  SmallVector<std::pair<StringAttr, Attribute>> attrs;
+  for (StringRef attrName : {LLVM::LLVMDialect::getAliasScopesAttrName(),
+                             LLVM::LLVMDialect::getNoAliasAttrName()}) {
+    auto nameAttr = StringAttr::get(op->getContext(), attrName);
+    if (auto attr = op->getAttr(nameAttr))
+      attrs.emplace_back(nameAttr, attr);
+  }
+  return attrs;
+}
+
 // Load operation is lowered to obtaining a pointer to the indexed element
 // and loading it.
 struct LoadOpLowering : public LoadStoreOpLowering<memref::LoadOp> {
@@ -932,15 +944,22 @@ struct LoadOpLowering : public LoadStoreOpLowering<memref::LoadOp> {
                   ConversionPatternRewriter &rewriter) const override {
     auto type = loadOp.getMemRefType();
 
+    SmallVector<std::pair<StringAttr, Attribute>> importantAttrs =
+        copyImportantAttrs(loadOp);
+
     // Per memref.load spec, the indices must be in-bounds:
     // 0 <= idx < dim_size, and additionally all offsets are non-negative,
     // hence inbounds and nuw are used when lowering to llvm.getelementptr.
     Value dataPtr = getStridedElementPtr(rewriter, loadOp.getLoc(), type,
                                          adaptor.getMemref(),
                                          adaptor.getIndices(), kNoWrapFlags);
-    rewriter.replaceOpWithNewOp<LLVM::LoadOp>(
+    auto newOp = rewriter.replaceOpWithNewOp<LLVM::LoadOp>(
         loadOp, typeConverter->convertType(type.getElementType()), dataPtr,
         loadOp.getAlignment().value_or(0), false, loadOp.getNontemporal());
+
+    for (auto [nameAttr, attr] : importantAttrs)
+      newOp->setAttr(nameAttr, attr);
+
     return success();
   }
 };
@@ -955,15 +974,22 @@ struct StoreOpLowering : public LoadStoreOpLowering<memref::StoreOp> {
                   ConversionPatternRewriter &rewriter) const override {
     auto type = op.getMemRefType();
 
+    SmallVector<std::pair<StringAttr, Attribute>> importantAttrs =
+        copyImportantAttrs(op);
+
     // Per memref.store spec, the indices must be in-bounds:
     // 0 <= idx < dim_size, and additionally all offsets are non-negative,
     // hence inbounds and nuw are used when lowering to llvm.getelementptr.
     Value dataPtr =
         getStridedElementPtr(rewriter, op.getLoc(), type, adaptor.getMemref(),
                              adaptor.getIndices(), kNoWrapFlags);
-    rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(), dataPtr,
-                                               op.getAlignment().value_or(0),
-                                               false, op.getNontemporal());
+    auto newOp = rewriter.replaceOpWithNewOp<LLVM::StoreOp>(
+        op, adaptor.getValue(), dataPtr, op.getAlignment().value_or(0), false,
+        op.getNontemporal());
+
+    for (auto [nameAttr, attr] : importantAttrs)
+      newOp->setAttr(nameAttr, attr);
+
     return success();
   }
 };
diff --git a/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir b/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
index 0cbe064572911..102943ee7ffb1 100644
--- a/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
+++ b/mlir/test/Conversion/MemRefToLLVM/memref-to-llvm.mlir
@@ -820,6 +820,22 @@ func.func @store_with_alignment(%arg0 : memref<32xf32>, %arg1 : f32, %arg2 : ind
 
 // -----
 
+#alias_scope_domain = #llvm.alias_scope_domain<id = distinct[0]<>>
+#alias_scope0 = #llvm.alias_scope<id = distinct[1]<>, domain = #alias_scope_domain>
+#alias_scope1 = #llvm.alias_scope<id = distinct[2]<>, domain = #alias_scope_domain>
+
+// ALL-LABEL: func @load_store_alias_attrs
+func.func @load_store_alias_attrs(%arg0: memref<?xf32>, %arg1: memref<?xf32>) {
+// ALL:   llvm.load {{.*}} {alias_scopes = [#[[SCOPE0:.*]]], llvm.noalias = [#[[SCOPE1:.*]]]}
+// ALL:   llvm.store {{.*}} {alias_scopes = [#[[SCOPE1]]], llvm.noalias = [#[[SCOPE0]]]} : f32, !llvm.ptr
+  %c0 = arith.constant 0 : index
+  %0 = memref.load %arg0[%c0] {alias_scopes = [#alias_scope0], llvm.noalias = [#alias_scope1]} : memref<?xf32>
+  memref.store %0, %arg1[%c0] {alias_scopes = [#alias_scope1], llvm.noalias = [#alias_scope0]} : memref<?xf32>
+  func.return
+}
+
+// -----
+
 // Ensure unconvertable memory space not cause a crash
 
 // CHECK-LABEL: @alloca_unconvertable_memory_space



More information about the Mlir-commits mailing list