[flang-commits] [flang] [mlir] [RFC][mlir] Resource hierarchy for MLIR Side Effects. (PR #181229)

Slava Zakharin via flang-commits flang-commits at lists.llvm.org
Mon Mar 9 09:30:01 PDT 2026


https://github.com/vzakhari updated https://github.com/llvm/llvm-project/pull/181229

>From 0995662c53bfe18944f633de3366f9af6a8ffaf7 Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Thu, 12 Feb 2026 11:45:26 -0800
Subject: [PATCH 1/2] [RFC][mlir] Memory region hierarchy for MLIR Side
 Effects.

This patch adds a `MemoryRegion` hierarchy to MLIR's side-effect infrastructure
so that effects on *disjoint* regions (e.g. addressable memory vs. abstract
runtime state) can be proven non-conflicting, unblocking CSE, LICM, and alias
analysis without per-pass special-casing.
---
 .../include/flang/Optimizer/Dialect/FIROps.h  |   6 +
 mlir/include/mlir/Dialect/OpenACC/OpenACC.h   |   9 ++
 .../mlir/Interfaces/SideEffectInterfaces.h    | 117 +++++++++++++++++-
 .../AliasAnalysis/LocalAliasAnalysis.cpp      |  12 +-
 mlir/lib/Transforms/CSE.cpp                   |  24 +++-
 .../Analysis/test-alias-analysis-modref.mlir  |  24 ++++
 mlir/test/Dialect/OpenACC/cse.mlir            |  18 +++
 mlir/test/Transforms/cse.mlir                 |  80 ++++++++++++
 mlir/test/lib/Dialect/Test/TestOpDefs.cpp     |  12 ++
 mlir/test/lib/Dialect/Test/TestOps.h          |  49 ++++++++
 10 files changed, 344 insertions(+), 7 deletions(-)
 create mode 100644 mlir/test/Dialect/OpenACC/cse.mlir

diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.h b/flang/include/flang/Optimizer/Dialect/FIROps.h
index 3d08e65524cf1..ff96b31cd060a 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.h
@@ -51,12 +51,18 @@ static constexpr llvm::StringRef getNormalizedLowerBoundAttrName() {
 struct DebuggingResource
     : public mlir::SideEffects::Resource::Base<DebuggingResource> {
   mlir::StringRef getName() final { return "DebuggingResource"; }
+  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
+    return mlir::SideEffects::NonAddressableMemory::get();
+  }
 };
 
 /// Model operations which read from/write to volatile memory
 struct VolatileMemoryResource
     : public mlir::SideEffects::Resource::Base<VolatileMemoryResource> {
   mlir::StringRef getName() final { return "VolatileMemoryResource"; }
+  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
+    return mlir::SideEffects::NonAddressableMemory::get();
+  }
 };
 
 class CoordinateIndicesAdaptor;
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
index 2f6d1ee5bdd3e..69723898e3121 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
@@ -212,16 +212,25 @@ static constexpr StringLiteral getCombinedConstructsAttrName() {
 struct RuntimeCounters
     : public mlir::SideEffects::Resource::Base<RuntimeCounters> {
   mlir::StringRef getName() final { return "AccRuntimeCounters"; }
+  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
+    return mlir::SideEffects::NonAddressableMemory::get();
+  }
 };
 
 struct ConstructResource
     : public mlir::SideEffects::Resource::Base<ConstructResource> {
   mlir::StringRef getName() final { return "AccConstructResource"; }
+  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
+    return mlir::SideEffects::NonAddressableMemory::get();
+  }
 };
 
 struct CurrentDeviceIdResource
     : public mlir::SideEffects::Resource::Base<CurrentDeviceIdResource> {
   mlir::StringRef getName() final { return "AccCurrentDeviceIdResource"; }
+  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
+    return mlir::SideEffects::NonAddressableMemory::get();
+  }
 };
 
 } // namespace acc
diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
index 9de20f0c69f1a..b6d81b3c80d45 100644
--- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
+++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
@@ -70,6 +70,91 @@ class Effect {
   TypeID id;
 };
 
+//===----------------------------------------------------------------------===//
+// Memory Regions
+//===----------------------------------------------------------------------===//
+
+/// Represents a memory region in a hierarchy. Two regions are disjoint if
+/// neither is an ancestor of the other, meaning operations scoped to disjoint
+/// regions cannot conflict.
+class MemoryRegion {
+public:
+  virtual ~MemoryRegion() = default;
+
+  /// This base class is used for derived regions that are non-parametric.
+  template <typename DerivedRegion, typename BaseRegion = MemoryRegion>
+  class Base : public BaseRegion {
+  public:
+    using BaseT = Base<DerivedRegion>;
+
+    /// Returns a unique singleton instance for the given region class.
+    static DerivedRegion *get() {
+      static DerivedRegion instance;
+      return &instance;
+    }
+
+    /// Return the unique identifier for this region class.
+    static TypeID getRegionID() { return TypeID::get<DerivedRegion>(); }
+
+    /// 'classof' used to support llvm style cast functionality.
+    static bool classof(const MemoryRegion *region) {
+      return region->getRegionID() == BaseT::getRegionID();
+    }
+
+  protected:
+    Base() : BaseRegion(BaseT::getRegionID()) {}
+  };
+
+  /// Return the unique identifier for this region.
+  TypeID getRegionID() const { return id; }
+
+  /// Return a human-readable name for this region.
+  virtual StringRef getName() const = 0;
+
+  /// Return the parent region in the hierarchy, or nullptr for the root.
+  virtual MemoryRegion *getParent() const { return nullptr; }
+
+  /// Returns true if this region is a subregion of (or equal to) another.
+  bool isSubregionOf(const MemoryRegion *other) const {
+    for (const MemoryRegion *r = this; r != nullptr; r = r->getParent())
+      if (r->getRegionID() == other->getRegionID())
+        return true;
+    return false;
+  }
+
+  /// Returns true if this region is disjoint from another region.
+  /// Two regions are disjoint if neither is an ancestor of the other.
+  bool isDisjointFrom(const MemoryRegion *other) const {
+    return !isSubregionOf(other) && !other->isSubregionOf(this);
+  }
+
+protected:
+  MemoryRegion(TypeID id) : id(id) {}
+
+private:
+  TypeID id;
+};
+
+/// Root of the memory region hierarchy. All other regions are subregions of
+/// this. An effect on AllMemory may conflict with any other effect.
+struct AllMemory : public MemoryRegion::Base<AllMemory> {
+  StringRef getName() const override { return "AllMemory"; }
+};
+
+/// Memory that can be accessed via pointers/Values. This is the default region
+/// for resources that don't specify one (conservative assumption).
+struct AddressableMemory : public MemoryRegion::Base<AddressableMemory> {
+  StringRef getName() const override { return "AddressableMemory"; }
+  MemoryRegion *getParent() const override { return AllMemory::get(); }
+};
+
+/// Memory that cannot be accessed via any pointer. Effects on
+/// NonAddressableMemory are disjoint from effects on AddressableMemory.
+struct NonAddressableMemory : public MemoryRegion::Base<NonAddressableMemory> {
+  StringRef getName() const override { return "NonAddressableMemory"; }
+  MemoryRegion *getParent() const override { return AllMemory::get(); }
+};
+
 //===----------------------------------------------------------------------===//
 // Resources
 //===----------------------------------------------------------------------===//
@@ -110,6 +195,13 @@ class Resource {
   /// Return a string name of the resource.
   virtual StringRef getName() = 0;
 
+  /// Return the memory region this resource belongs to. Defaults to
+  /// AddressableMemory (conservative: assumes pointer-based access is
+  /// possible). Override in derived resources to specify a different region.
+  virtual MemoryRegion *getMemoryRegion() const {
+    return AddressableMemory::get();
+  }
+
 protected:
   Resource(TypeID id) : id(id) {}
 
@@ -152,7 +244,9 @@ class EffectInstance {
   EffectInstance(EffectT *effect, T value,
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(value), stage(0),
-        effectOnFullRegion(false) {}
+        effectOnFullRegion(false) {
+    checkNonAddressableValue();
+  }
   template <typename T,
             std::enable_if_t<
                 llvm::is_one_of<T, OpOperand *, OpResult, BlockArgument>::value,
@@ -160,7 +254,9 @@ class EffectInstance {
   EffectInstance(EffectT *effect, T value, int stage, bool effectOnFullRegion,
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(value), stage(stage),
-        effectOnFullRegion(effectOnFullRegion) {}
+        effectOnFullRegion(effectOnFullRegion) {
+    checkNonAddressableValue();
+  }
   EffectInstance(EffectT *effect, SymbolRefAttr symbol,
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(symbol), stage(0),
@@ -186,7 +282,9 @@ class EffectInstance {
   EffectInstance(EffectT *effect, T value, Attribute parameters,
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(value),
-        parameters(parameters), stage(0), effectOnFullRegion(false) {}
+        parameters(parameters), stage(0), effectOnFullRegion(false) {
+    checkNonAddressableValue();
+  }
   template <typename T,
             std::enable_if_t<
                 llvm::is_one_of<T, OpOperand *, OpResult, BlockArgument>::value,
@@ -196,7 +294,9 @@ class EffectInstance {
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(value),
         parameters(parameters), stage(stage),
-        effectOnFullRegion(effectOnFullRegion) {}
+        effectOnFullRegion(effectOnFullRegion) {
+    checkNonAddressableValue();
+  }
   EffectInstance(EffectT *effect, SymbolRefAttr symbol, Attribute parameters,
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(symbol),
@@ -256,6 +356,15 @@ class EffectInstance {
   bool getEffectOnFullRegion() const { return effectOnFullRegion; }
 
 private:
+  /// Check that a Value is not associated with a non-addressable resource.
+  void checkNonAddressableValue() {
+    if (resource &&
+        resource->getMemoryRegion()->isSubregionOf(NonAddressableMemory::get()))
+      llvm::report_fatal_error("EffectInstance: non-addressable resource '" +
+                               resource->getName() +
+                               "' cannot have an associated Value");
+  }
+
   /// The specific effect being applied.
   EffectT *effect;
 
diff --git a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
index 5a4679ef31422..c72c386a7ffa2 100644
--- a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
+++ b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
@@ -525,7 +525,17 @@ ModRefResult LocalAliasAnalysis::getModRef(Operation *op, Value location) {
                  : aliasResult.isNo() ? "NoAlias"
                                       : "MayAlias");
     } else {
-      LDBG() << "    No effect value, assuming MayAlias";
+      // If the effect's resource is in a memory region disjoint from
+      // AddressableMemory, it cannot alias a location accessible
+      // through a pointer.
+      auto *effectRegion = effect.getResource()->getMemoryRegion();
+      if (effectRegion->isDisjointFrom(SideEffects::AddressableMemory::get())) {
+        LDBG() << "    Effect on non-addressable region '"
+               << effectRegion->getName() << "', skipping (NoAlias)";
+        aliasResult = AliasResult::NoAlias;
+      } else {
+        LDBG() << "    No effect value, assuming MayAlias";
+      }
     }
 
     // If we don't alias, ignore this effect.
diff --git a/mlir/lib/Transforms/CSE.cpp b/mlir/lib/Transforms/CSE.cpp
index 8eaac308755fd..90d0a895c8ff9 100644
--- a/mlir/lib/Transforms/CSE.cpp
+++ b/mlir/lib/Transforms/CSE.cpp
@@ -181,6 +181,18 @@ bool CSEDriver::hasOtherSideEffectingOpInBetween(Operation *fromOp,
          "expected read effect on fromOp");
   assert(hasEffect<MemoryEffects::Read>(toOp) &&
          "expected read effect on toOp");
+
+  // Collect the memory regions of fromOp's read effects. A write can only
+  // block CSE if its resource region is not disjoint from one of these.
+  SmallPtrSet<SideEffects::MemoryRegion *, 1> readRegions;
+  if (auto memOp = dyn_cast<MemoryEffectOpInterface>(fromOp)) {
+    SmallVector<MemoryEffects::EffectInstance> fromEffects;
+    memOp.getEffects(fromEffects);
+    for (const auto &e : fromEffects)
+      if (isa<MemoryEffects::Read>(e.getEffect()))
+        readRegions.insert(e.getResource()->getMemoryRegion());
+  }
+
   Operation *nextOp = fromOp->getNextNode();
   auto result =
       memEffectsCache.try_emplace(fromOp, std::make_pair(fromOp, nullptr));
@@ -210,8 +222,16 @@ bool CSEDriver::hasOtherSideEffectingOpInBetween(Operation *fromOp,
 
     for (const MemoryEffects::EffectInstance &effect : *effects) {
       if (isa<MemoryEffects::Write>(effect.getEffect())) {
-        result.first->second = {nextOp, MemoryEffects::Write::get()};
-        return true;
+        // A write on a memory region disjoint from all read regions cannot
+        // conflict with the reads being CSE'd.
+        auto *writeRegion = effect.getResource()->getMemoryRegion();
+        bool canConflict = llvm::any_of(readRegions, [&](auto *readRegion) {
+          return !writeRegion->isDisjointFrom(readRegion);
+        });
+        if (canConflict) {
+          result.first->second = {nextOp, MemoryEffects::Write::get()};
+          return true;
+        }
       }
     }
     nextOp = nextOp->getNextNode();
diff --git a/mlir/test/Analysis/test-alias-analysis-modref.mlir b/mlir/test/Analysis/test-alias-analysis-modref.mlir
index eee8ae9049cfa..bdc2e30d307d5 100644
--- a/mlir/test/Analysis/test-alias-analysis-modref.mlir
+++ b/mlir/test/Analysis/test-alias-analysis-modref.mlir
@@ -65,3 +65,27 @@ func.func @unknown(%arg0: memref<i32>) attributes {test.ptr = "func"} {
   "foo.op"() {test.ptr = "unknown"} : () -> ()
   return
 }
+
+// -----
+
+// An op writing to a non-addressable resource cannot modify or reference
+// a Value-based (addressable) memory location → NoModRef.
+// CHECK-LABEL: Testing : "nonaddressable_write"
+// CHECK-DAG: side_effect_op -> func.region0#0: NoModRef
+// CHECK-DAG: return -> func.region0#0: NoModRef
+func.func @nonaddressable_write(%arg: memref<2xf32>) attributes {test.ptr = "func"} {
+  "test.side_effect_op"() {effects = [{effect="write", test_nonaddressable_resource}], test.ptr = "side_effect_op"} : () -> i32
+  return {test.ptr = "return"}
+}
+
+// -----
+
+// An op writing to an addressable resource (DefaultResource) without an
+// associated Value → MayAlias → Mod.
+// CHECK-LABEL: Testing : "addressable_write"
+// CHECK-DAG: side_effect_op -> func.region0#0: Mod
+// CHECK-DAG: return -> func.region0#0: NoModRef
+func.func @addressable_write(%arg: memref<2xf32>) attributes {test.ptr = "func"} {
+  "test.side_effect_op"() {effects = [{effect="write"}], test.ptr = "side_effect_op"} : () -> i32
+  return {test.ptr = "return"}
+}
diff --git a/mlir/test/Dialect/OpenACC/cse.mlir b/mlir/test/Dialect/OpenACC/cse.mlir
new file mode 100644
index 0000000000000..97bd1302b966b
--- /dev/null
+++ b/mlir/test/Dialect/OpenACC/cse.mlir
@@ -0,0 +1,18 @@
+// RUN: mlir-opt %s -pass-pipeline='builtin.module(func.func(cse))' | FileCheck %s
+
+// Verify that acc.set (which writes CurrentDeviceIdResource, a non-addressable
+// resource) does not block CSE of identical memref.load operations (which
+// read DefaultResource, an addressable resource). The two resource regions are
+// disjoint, so the write cannot conflict with the loads.
+
+// CHECK-LABEL: @cse_across_acc_set
+func.func @cse_across_acc_set(%a: memref<10xf32>, %i: index) -> (f32, f32) {
+  %v1 = memref.load %a[%i] : memref<10xf32>
+  %c42 = arith.constant 42 : i32
+  acc.set device_num(%c42 : i32)
+  %v2 = memref.load %a[%i] : memref<10xf32>
+  // CHECK: %[[V:.*]] = memref.load
+  // CHECK-NOT: memref.load
+  // CHECK: return %[[V]], %[[V]] : f32, f32
+  return %v1, %v2 : f32, f32
+}
diff --git a/mlir/test/Transforms/cse.mlir b/mlir/test/Transforms/cse.mlir
index b447094874d01..1a150daa8e28d 100644
--- a/mlir/test/Transforms/cse.mlir
+++ b/mlir/test/Transforms/cse.mlir
@@ -573,3 +573,83 @@ func.func @cse_recursive_effects_failure() -> (i32, i32, i32) {
   %2 = "test.op_with_memread"() : () -> (i32)
   return %0, %2, %1 : i32, i32, i32
 }
+
+// -----
+
+/// Check that a write on a non-addressable resource does not block CSE of
+/// reads on the default (addressable) resource, because the regions are
+/// disjoint.
+// CHECK-LABEL: @cse_non_addressable_write_does_not_block
+func.func @cse_non_addressable_write_does_not_block() -> i32 {
+  // CHECK-NEXT: %[[V:.*]] = "test.op_with_memread"() : () -> i32
+  %0 = "test.op_with_memread"() : () -> (i32)
+  "test.side_effect_op"() {effects = [{effect="write", test_nonaddressable_resource}]} : () -> i32
+  %1 = "test.op_with_memread"() : () -> (i32)
+  // CHECK-NEXT: %{{.*}} = "test.side_effect_op"()
+  // CHECK-NEXT: %{{.*}} = arith.addi %[[V]], %[[V]] : i32
+  %2 = arith.addi %0, %1 : i32
+  return %2 : i32
+}
+
+// -----
+
+/// Check that consecutive reads on the same non-addressable resource are CSE'd
+/// when there is no intervening write.
+// CHECK-LABEL: @cse_reads_on_same_nonaddressable_resource
+func.func @cse_reads_on_same_nonaddressable_resource() -> i32 {
+  // CHECK-NEXT: %[[V:.*]] = "test.side_effect_op"()
+  %0 = "test.side_effect_op"() {effects = [{effect="read", test_nonaddressable_resource}]} : () -> i32
+  %1 = "test.side_effect_op"() {effects = [{effect="read", test_nonaddressable_resource}]} : () -> i32
+  // CHECK-NEXT: %{{.*}} = arith.addi %[[V]], %[[V]] : i32
+  %2 = arith.addi %0, %1 : i32
+  return %2 : i32
+}
+
+// -----
+
+/// Check that a write to the same non-addressable region blocks CSE of reads
+/// on that region (same region → not disjoint → write may conflict).
+// CHECK-LABEL: @cse_write_same_nonaddressable_region_blocks
+func.func @cse_write_same_nonaddressable_region_blocks() -> i32 {
+  // CHECK-NEXT: %[[V0:.*]] = "test.side_effect_op"(){{.*}}"read"
+  %0 = "test.side_effect_op"() {effects = [{effect="read", test_nonaddressable_resource}]} : () -> i32
+  "test.side_effect_op"() {effects = [{effect="write", test_nonaddressable_resource}]} : () -> i32
+  // CHECK: %[[V1:.*]] = "test.side_effect_op"(){{.*}}"read"
+  %1 = "test.side_effect_op"() {effects = [{effect="read", test_nonaddressable_resource}]} : () -> i32
+  // CHECK: %{{.*}} = arith.addi %[[V0]], %[[V1]] : i32
+  %2 = arith.addi %0, %1 : i32
+  return %2 : i32
+}
+
+// -----
+
+/// Check that a write to a disjoint non-addressable sub-region does NOT block
+/// CSE of reads on a different non-addressable sub-region (sibling regions
+/// under NonAddressableMemory are disjoint).
+// CHECK-LABEL: @cse_write_disjoint_nonaddressable_subregion_allows
+func.func @cse_write_disjoint_nonaddressable_subregion_allows() -> i32 {
+  // CHECK-NEXT: %[[V:.*]] = "test.side_effect_op"()
+  %0 = "test.side_effect_op"() {effects = [{effect="read", test_nonaddressable_resource_a}]} : () -> i32
+  "test.side_effect_op"() {effects = [{effect="write", test_nonaddressable_resource_b}]} : () -> i32
+  %1 = "test.side_effect_op"() {effects = [{effect="read", test_nonaddressable_resource_a}]} : () -> i32
+  // CHECK-NEXT: %{{.*}} = "test.side_effect_op"()
+  // CHECK-NEXT: %{{.*}} = arith.addi %[[V]], %[[V]] : i32
+  %2 = arith.addi %0, %1 : i32
+  return %2 : i32
+}
+
+// -----
+
+/// Check that a write on an addressable custom resource still blocks CSE of
+/// reads on the default (addressable) resource (regression guard).
+// CHECK-LABEL: @cse_addressable_custom_resource_write_blocks
+func.func @cse_addressable_custom_resource_write_blocks() -> i32 {
+  // CHECK-NEXT: %[[V0:.*]] = "test.op_with_memread"() : () -> i32
+  %0 = "test.op_with_memread"() : () -> (i32)
+  "test.side_effect_op"() {effects = [{effect="write", test_resource}]} : () -> i32
+  // CHECK: %[[V1:.*]] = "test.op_with_memread"() : () -> i32
+  %1 = "test.op_with_memread"() : () -> (i32)
+  // CHECK: %{{.*}} = arith.addi %[[V0]], %[[V1]] : i32
+  %2 = arith.addi %0, %1 : i32
+  return %2 : i32
+}
diff --git a/mlir/test/lib/Dialect/Test/TestOpDefs.cpp b/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
index d2826cc42b270..0889ea0aace98 100644
--- a/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
+++ b/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
@@ -479,6 +479,12 @@ void SideEffectOp::getEffects(
     SideEffects::Resource *resource = SideEffects::DefaultResource::get();
     if (effectElement.get("test_resource"))
       resource = TestResource::get();
+    else if (effectElement.get("test_nonaddressable_resource"))
+      resource = TestNonAddressableResource::get();
+    else if (effectElement.get("test_nonaddressable_resource_a"))
+      resource = TestNonAddressableResourceA::get();
+    else if (effectElement.get("test_nonaddressable_resource_b"))
+      resource = TestNonAddressableResourceB::get();
 
     // Check for a result to affect.
     if (effectElement.get("on_result"))
@@ -518,6 +524,12 @@ void SideEffectWithRegionOp::getEffects(
     SideEffects::Resource *resource = SideEffects::DefaultResource::get();
     if (effectElement.get("test_resource"))
       resource = TestResource::get();
+    else if (effectElement.get("test_nonaddressable_resource"))
+      resource = TestNonAddressableResource::get();
+    else if (effectElement.get("test_nonaddressable_resource_a"))
+      resource = TestNonAddressableResourceA::get();
+    else if (effectElement.get("test_nonaddressable_resource_b"))
+      resource = TestNonAddressableResourceB::get();
 
     // Check for a result to affect.
     if (effectElement.get("on_result"))
diff --git a/mlir/test/lib/Dialect/Test/TestOps.h b/mlir/test/lib/Dialect/Test/TestOps.h
index 679274346fb13..8b39699beb70a 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.h
+++ b/mlir/test/lib/Dialect/Test/TestOps.h
@@ -56,6 +56,55 @@ struct TestResource : public mlir::SideEffects::Resource::Base<TestResource> {
   llvm::StringRef getName() final { return "<Test>"; }
 };
 
+/// A test resource in NonAddressableMemory (disjoint from AddressableMemory).
+struct TestNonAddressableResource
+    : public mlir::SideEffects::Resource::Base<TestNonAddressableResource> {
+  llvm::StringRef getName() final { return "<TestNonAddressable>"; }
+  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
+    return mlir::SideEffects::NonAddressableMemory::get();
+  }
+};
+
+/// Two disjoint sub-regions under NonAddressableMemory for testing sibling
+/// disjointness within non-addressable memory.
+struct TestNonAddressableSubRegionA
+    : public mlir::SideEffects::MemoryRegion::Base<
+          TestNonAddressableSubRegionA> {
+  llvm::StringRef getName() const override {
+    return "TestNonAddressableSubRegionA";
+  }
+  mlir::SideEffects::MemoryRegion *getParent() const override {
+    return mlir::SideEffects::NonAddressableMemory::get();
+  }
+};
+
+struct TestNonAddressableSubRegionB
+    : public mlir::SideEffects::MemoryRegion::Base<
+          TestNonAddressableSubRegionB> {
+  llvm::StringRef getName() const override {
+    return "TestNonAddressableSubRegionB";
+  }
+  mlir::SideEffects::MemoryRegion *getParent() const override {
+    return mlir::SideEffects::NonAddressableMemory::get();
+  }
+};
+
+struct TestNonAddressableResourceA
+    : public mlir::SideEffects::Resource::Base<TestNonAddressableResourceA> {
+  llvm::StringRef getName() final { return "<TestNonAddressableA>"; }
+  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
+    return TestNonAddressableSubRegionA::get();
+  }
+};
+
+struct TestNonAddressableResourceB
+    : public mlir::SideEffects::Resource::Base<TestNonAddressableResourceB> {
+  llvm::StringRef getName() final { return "<TestNonAddressableB>"; }
+  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
+    return TestNonAddressableSubRegionB::get();
+  }
+};
+
 //===----------------------------------------------------------------------===//
 // PropertiesWithCustomPrint
 //===----------------------------------------------------------------------===//

>From ccfd82bee68344cb096a2043fb669c1197ffc323 Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Fri, 20 Feb 2026 12:21:06 -0800
Subject: [PATCH 2/2] =?UTF-8?q?Addressed=20review=20comments:=20*=20No=20s?=
 =?UTF-8?q?eparate=20MemoryRegion=20=E2=80=94=20The=20MemoryRegion=20tree?=
 =?UTF-8?q?=20(AllMemory,=20AddressableMemory,=20et.c)=20and=20Resource::g?=
 =?UTF-8?q?etMemoryRegion()=20are=20removed.=20Only=20the=20Resource=20hie?=
 =?UTF-8?q?rarchy=20remains,=20with=20getParent(),=20isSubresourceOf(),=20?=
 =?UTF-8?q?and=20isDisjointFrom()=20on=20Resource.=20CSE=20and=20alias=20a?=
 =?UTF-8?q?nalysis=20use=20resource=20disjointness=20instead=20of=20region?=
 =?UTF-8?q?=20disjointness.=20*=20Addressability=20on=20Resource=20?=
 =?UTF-8?q?=E2=80=94=20"Non-addressable"=20is=20a=20property=20of=20a=20re?=
 =?UTF-8?q?source:=20Resource::isAddressable()=20(default=20true).=20Diale?=
 =?UTF-8?q?cts=20that=20were=20"in=20NonAddressableMemory"=20now=20overrid?=
 =?UTF-8?q?e=20isAddressable()=20to=20false=20and=20no=20longer=20implemen?=
 =?UTF-8?q?t=20getMemoryRegion().=20EffectInstance=20still=20forbids=20att?=
 =?UTF-8?q?aching=20a=20Value=20to=20a=20resource=20disjoint=20from=20Defa?=
 =?UTF-8?q?ultResource;=20an=20addressable=20resource=20may=20not=20have?=
 =?UTF-8?q?=20a=20non-addressable=20parent=20(via=20verifyImmediateParentA?=
 =?UTF-8?q?ddressability()=20under=20EXPENSIVE=5FCHECKS).=20*=20Added=20su?=
 =?UTF-8?q?pport=20for=20isa/cast=20(note=20that=20cast<DefaultResource>(A?=
 =?UTF-8?q?utomaticAllocationScopeResource::get())=20still=20returns=20Aut?=
 =?UTF-8?q?omaticAllocationScopeResource::get()=20singleton).=20*=20Docs?=
 =?UTF-8?q?=20and=20tests=20=E2=80=94=20Scope=20and=20limitations=20are=20?=
 =?UTF-8?q?documented=20(see=20header=20and=20SideEffectsAndSpeculation.md?=
 =?UTF-8?q?).=20New=20unit=20test=20for=20Resource=20isa/cast=20and=20hier?=
 =?UTF-8?q?archy.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../include/flang/Optimizer/Dialect/FIROps.h  |  12 +-
 .../Rationale/SideEffectsAndSpeculation.md    |  16 ++
 mlir/include/mlir/Dialect/OpenACC/OpenACC.h   |  18 +-
 .../Interfaces/TransformInterfaces.h          |   4 +-
 .../mlir/Interfaces/SideEffectInterfaces.h    | 191 ++++++++----------
 .../AliasAnalysis/LocalAliasAnalysis.cpp      |  12 +-
 mlir/lib/Transforms/CSE.cpp                   |  16 +-
 .../Analysis/test-alias-analysis-modref.mlir  |   8 +-
 mlir/test/Dialect/OpenACC/cse.mlir            |   8 +-
 mlir/test/Transforms/cse.mlir                 |  28 ++-
 mlir/test/lib/Dialect/Test/TestOpDefs.cpp     |   2 +-
 mlir/test/lib/Dialect/Test/TestOps.h          |  71 ++++---
 mlir/unittests/Interfaces/CMakeLists.txt      |   2 +
 .../Interfaces/SideEffectInterfacesTest.cpp   | 123 +++++++++++
 14 files changed, 308 insertions(+), 203 deletions(-)
 create mode 100644 mlir/unittests/Interfaces/SideEffectInterfacesTest.cpp

diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.h b/flang/include/flang/Optimizer/Dialect/FIROps.h
index ff96b31cd060a..9d771b1e60604 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.h
@@ -50,19 +50,15 @@ static constexpr llvm::StringRef getNormalizedLowerBoundAttrName() {
 /// Model operations which affect global debugging information
 struct DebuggingResource
     : public mlir::SideEffects::Resource::Base<DebuggingResource> {
-  mlir::StringRef getName() final { return "DebuggingResource"; }
-  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
-    return mlir::SideEffects::NonAddressableMemory::get();
-  }
+  mlir::StringRef getName() const final { return "DebuggingResource"; }
+  bool isAddressable() const override { return false; }
 };
 
 /// Model operations which read from/write to volatile memory
 struct VolatileMemoryResource
     : public mlir::SideEffects::Resource::Base<VolatileMemoryResource> {
-  mlir::StringRef getName() final { return "VolatileMemoryResource"; }
-  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
-    return mlir::SideEffects::NonAddressableMemory::get();
-  }
+  mlir::StringRef getName() const final { return "VolatileMemoryResource"; }
+  bool isAddressable() const override { return false; }
 };
 
 class CoordinateIndicesAdaptor;
diff --git a/mlir/docs/Rationale/SideEffectsAndSpeculation.md b/mlir/docs/Rationale/SideEffectsAndSpeculation.md
index 4d9021a356dfe..f2e9e12f6cfc9 100644
--- a/mlir/docs/Rationale/SideEffectsAndSpeculation.md
+++ b/mlir/docs/Rationale/SideEffectsAndSpeculation.md
@@ -75,6 +75,22 @@ memory effects they have and whether they are speculatable.
 We don't have proper modeling yet to fully capture non-local control flow
 semantics.
 
+### Resource hierarchy and scope
+
+Each effect is applied to a `Resource`. Resources form a hierarchy: each
+resource may have a parent (`getParent()`), and the API provides
+`isSubresourceOf()` and `isDisjointFrom()` so that passes can determine whether
+two effects may conflict (e.g. CSE and alias analysis use disjointness to allow
+more optimizations). A resource may be *addressable* (`isAddressable()`), meaning
+effects on it can alias pointer-based memory; non-addressable resources (e.g.
+runtime state) do not alias with any value-based memory location. The
+canonical definition and API live in `mlir/Interfaces/SideEffectInterfaces.h`.
+
+**Scope and limitations.** This mechanism is deliberately *not* intended for
+fine-grained regions with specific addresses or sizes, or for alias classes /
+offset-based disambiguation. Those concerns are out of scope for the resource
+hierarchy and should be handled by alias analysis or other mechanisms.
+
 When adding a new op, ask:
 
 1. Does it read from or write to the heap or stack? It should probably implement
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
index 69723898e3121..13eda0486f98f 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
@@ -211,26 +211,20 @@ static constexpr StringLiteral getCombinedConstructsAttrName() {
 
 struct RuntimeCounters
     : public mlir::SideEffects::Resource::Base<RuntimeCounters> {
-  mlir::StringRef getName() final { return "AccRuntimeCounters"; }
-  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
-    return mlir::SideEffects::NonAddressableMemory::get();
-  }
+  mlir::StringRef getName() const final { return "AccRuntimeCounters"; }
+  bool isAddressable() const override { return false; }
 };
 
 struct ConstructResource
     : public mlir::SideEffects::Resource::Base<ConstructResource> {
-  mlir::StringRef getName() final { return "AccConstructResource"; }
-  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
-    return mlir::SideEffects::NonAddressableMemory::get();
-  }
+  mlir::StringRef getName() const final { return "AccConstructResource"; }
+  bool isAddressable() const override { return false; }
 };
 
 struct CurrentDeviceIdResource
     : public mlir::SideEffects::Resource::Base<CurrentDeviceIdResource> {
-  mlir::StringRef getName() final { return "AccCurrentDeviceIdResource"; }
-  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
-    return mlir::SideEffects::NonAddressableMemory::get();
-  }
+  mlir::StringRef getName() const final { return "AccCurrentDeviceIdResource"; }
+  bool isAddressable() const override { return false; }
 };
 
 } // namespace acc
diff --git a/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h b/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h
index 8b0517a84a675..c2ceaba1128a3 100644
--- a/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h
+++ b/mlir/include/mlir/Dialect/Transform/Interfaces/TransformInterfaces.h
@@ -1256,7 +1256,7 @@ class TransformEachOpTrait
 // as CSE/DCE to work.
 struct TransformMappingResource
     : public SideEffects::Resource::Base<TransformMappingResource> {
-  StringRef getName() override { return "transform.mapping"; }
+  StringRef getName() const override { return "transform.mapping"; }
 };
 
 /// Side effect resource corresponding to the Payload IR itself. Only Read and
@@ -1267,7 +1267,7 @@ struct TransformMappingResource
 /// while still allowing the reordering of those that only access it.
 struct PayloadIRResource
     : public SideEffects::Resource::Base<PayloadIRResource> {
-  StringRef getName() override { return "transform.payload_ir"; }
+  StringRef getName() const override { return "transform.payload_ir"; }
 };
 
 /// Populates `effects` with the memory effects indicating the operation on the
diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
index b6d81b3c80d45..97f860565b183 100644
--- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
+++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h
@@ -15,6 +15,7 @@
 #define MLIR_INTERFACES_SIDEEFFECTINTERFACES_H
 
 #include "mlir/IR/OpDefinition.h"
+#include "llvm/ADT/Twine.h"
 
 namespace mlir {
 namespace SideEffects {
@@ -70,97 +71,20 @@ class Effect {
   TypeID id;
 };
 
-//===----------------------------------------------------------------------===//
-// Memory Regions
-//===----------------------------------------------------------------------===//
-
-/// Represents a memory region in a hierarchy. Two regions are disjoint if
-/// neither is an ancestor of the other, meaning operations scoped to disjoint
-/// regions cannot conflict.
-class MemoryRegion {
-public:
-  virtual ~MemoryRegion() = default;
-
-  /// This base class is used for derived regions that are non-parametric.
-  template <typename DerivedRegion, typename BaseRegion = MemoryRegion>
-  class Base : public BaseRegion {
-  public:
-    using BaseT = Base<DerivedRegion>;
-
-    /// Returns a unique singleton instance for the given region class.
-    static DerivedRegion *get() {
-      static DerivedRegion instance;
-      return &instance;
-    }
-
-    /// Return the unique identifier for this region class.
-    static TypeID getRegionID() { return TypeID::get<DerivedRegion>(); }
-
-    /// 'classof' used to support llvm style cast functionality.
-    static bool classof(const MemoryRegion *region) {
-      return region->getRegionID() == BaseT::getRegionID();
-    }
-
-  protected:
-    Base() : BaseRegion(BaseT::getRegionID()) {}
-  };
-
-  /// Return the unique identifier for this region.
-  TypeID getRegionID() const { return id; }
-
-  /// Return a human-readable name for this region.
-  virtual StringRef getName() const = 0;
-
-  /// Return the parent region in the hierarchy, or nullptr for the root.
-  virtual MemoryRegion *getParent() const { return nullptr; }
-
-  /// Returns true if this region is a subregion of (or equal to) another.
-  bool isSubregionOf(const MemoryRegion *other) const {
-    for (const MemoryRegion *r = this; r != nullptr; r = r->getParent())
-      if (r->getRegionID() == other->getRegionID())
-        return true;
-    return false;
-  }
-
-  /// Returns true if this region is disjoint from another region.
-  /// Two regions are disjoint if neither is an ancestor of the other.
-  bool isDisjointFrom(const MemoryRegion *other) const {
-    return !isSubregionOf(other) && !other->isSubregionOf(this);
-  }
-
-protected:
-  MemoryRegion(TypeID id) : id(id) {}
-
-private:
-  TypeID id;
-};
-
-/// Root of the memory region hierarchy. All other regions are subregions of
-/// this. An effect on AllMemory may conflict with any other effect.
-struct AllMemory : public MemoryRegion::Base<AllMemory> {
-  StringRef getName() const override { return "AllMemory"; }
-};
-
-/// Memory that can be accessed via pointers/Values. This is the default region
-/// for resources that don't specify one (conservative assumption).
-struct AddressableMemory : public MemoryRegion::Base<AddressableMemory> {
-  StringRef getName() const override { return "AddressableMemory"; }
-  MemoryRegion *getParent() const override { return AllMemory::get(); }
-};
-
-/// Memory that cannot be accessed via any pointer. Effects on
-/// NonAddressableMemory are disjoint from effects on AddressableMemory.
-struct NonAddressableMemory : public MemoryRegion::Base<NonAddressableMemory> {
-  StringRef getName() const override { return "NonAddressableMemory"; }
-  MemoryRegion *getParent() const override { return AllMemory::get(); }
-};
-
 //===----------------------------------------------------------------------===//
 // Resources
 //===----------------------------------------------------------------------===//
 
 /// This class represents a specific resource that an effect applies to. This
-/// class represents an abstract interface for a given resource.
+/// class represents an abstract interface for a given resource. Resources
+/// form a hierarchy via getParent(); disjointness (isDisjointFrom) is used to
+/// determine whether effects can conflict.
+///
+/// Scope: The resource hierarchy is for disjointness of *abstract* resources
+/// (e.g. addressable memory vs. runtime state). It is deliberately *not*
+/// intended for fine-grained regions with specific addresses/sizes, or for
+/// alias classes / offset-based disambiguation; such concerns are out of scope
+/// and should be handled by alias analysis or other mechanisms.
 class Resource {
 public:
   virtual ~Resource() = default;
@@ -169,7 +93,9 @@ class Resource {
   template <typename DerivedResource, typename BaseResource = Resource>
   class Base : public BaseResource {
   public:
-    using BaseT = Base<DerivedResource>;
+    /// Use the current instantiation so get()/getResourceID() refer to this
+    /// hierarchy's singleton, not Base<DerivedResource, Resource>'s.
+    using BaseT = Base<DerivedResource, BaseResource>;
 
     /// Returns a unique instance for the given effect class.
     static DerivedResource *get() {
@@ -180,46 +106,92 @@ class Resource {
     /// Return the unique identifier for the base resource class.
     static TypeID getResourceID() { return TypeID::get<DerivedResource>(); }
 
-    /// 'classof' used to support llvm style cast functionality.
+    /// 'classof' used to support llvm style cast functionality. Returns true
+    /// iff the resource is the same as or a descendant of this resource type
+    /// in the hierarchy (so isa/cast work for ancestor checks).
     static bool classof(const Resource *resource) {
-      return resource->getResourceID() == BaseT::getResourceID();
+      return resource->isSubresourceOf(BaseT::get());
     }
 
   protected:
-    Base() : BaseResource(BaseT::getResourceID()){};
+    Base() : BaseResource(BaseT::getResourceID()) {}
+    /// Constructor for use when this type is used as a parent (BaseResource);
+    /// allows the derived resource to pass its TypeID so the hierarchy is
+    /// correct.
+    Base(TypeID id) : BaseResource(id) {}
   };
 
   /// Return the unique identifier for the base resource class.
   TypeID getResourceID() const { return id; }
 
   /// Return a string name of the resource.
-  virtual StringRef getName() = 0;
+  virtual StringRef getName() const = 0;
+
+  /// Return the parent resource in the hierarchy, or nullptr for a root.
+  virtual Resource *getParent() const { return nullptr; }
+
+  /// Returns true if this resource is addressable (effects on it can alias
+  /// pointer-based memory). Default is true.
+  virtual bool isAddressable() const { return true; }
 
-  /// Return the memory region this resource belongs to. Defaults to
-  /// AddressableMemory (conservative: assumes pointer-based access is
-  /// possible). Override in derived resources to specify a different region.
-  virtual MemoryRegion *getMemoryRegion() const {
-    return AddressableMemory::get();
+  /// Returns true if this resource is a subresource of (or equal to) another.
+  bool isSubresourceOf(const Resource *other) const {
+    for (const Resource *r = this; r != nullptr; r = r->getParent()) {
+#ifdef EXPENSIVE_CHECKS
+      r->verifyImmediateParentAddressability();
+#endif // EXPENSIVE_CHECKS
+      if (r == other)
+        return true;
+    }
+    return false;
+  }
+
+  /// Returns true if this resource is disjoint from another. Two resources are
+  /// disjoint if neither is an ancestor of the other.
+  bool isDisjointFrom(const Resource *other) const {
+    return !isSubresourceOf(other) && !other->isSubresourceOf(this);
   }
 
 protected:
   Resource(TypeID id) : id(id) {}
 
 private:
+#ifdef EXPENSIVE_CHECKS
+  /// Verifies the single-link invariant: an addressable resource must not have
+  /// a non-addressable parent. Used from isSubresourceOf() under
+  /// EXPENSIVE_CHECKS so the invariant is checked when the hierarchy is
+  /// traversed.
+  void verifyImmediateParentAddressability() const {
+    Resource *parent = getParent();
+    if (parent && isAddressable() && !parent->isAddressable())
+      llvm::report_fatal_error(
+          llvm::Twine("Resource '") + getName() +
+          "' is addressable but has non-addressable parent '" +
+          parent->getName() + "'");
+  }
+#endif // EXPENSIVE_CHECKS
+
   /// The id of the derived resource class.
   TypeID id;
 };
 
 /// A conservative default resource kind.
 struct DefaultResource : public Resource::Base<DefaultResource> {
-  StringRef getName() final { return "<Default>"; }
+  DefaultResource() = default;
+  StringRef getName() const override { return "<Default>"; }
+
+protected:
+  /// For use when this type is the parent of another resource; allows the
+  /// derived resource to pass its TypeID so the hierarchy is correct.
+  DefaultResource(TypeID id) : Base(id) {}
 };
 
 /// An automatic allocation-scope resource that is valid in the context of a
 /// parent AutomaticAllocationScope trait.
 struct AutomaticAllocationScopeResource
-    : public Resource::Base<AutomaticAllocationScopeResource> {
-  StringRef getName() final { return "AutomaticAllocationScope"; }
+    : public Resource::Base<AutomaticAllocationScopeResource, DefaultResource> {
+  StringRef getName() const final { return "AutomaticAllocationScope"; }
+  Resource *getParent() const override { return DefaultResource::get(); }
 };
 
 /// This class represents a specific instance of an effect. It contains the
@@ -245,7 +217,7 @@ class EffectInstance {
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(value), stage(0),
         effectOnFullRegion(false) {
-    checkNonAddressableValue();
+    checkResourceAllowsValue();
   }
   template <typename T,
             std::enable_if_t<
@@ -255,7 +227,7 @@ class EffectInstance {
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(value), stage(stage),
         effectOnFullRegion(effectOnFullRegion) {
-    checkNonAddressableValue();
+    checkResourceAllowsValue();
   }
   EffectInstance(EffectT *effect, SymbolRefAttr symbol,
                  Resource *resource = DefaultResource::get())
@@ -283,7 +255,7 @@ class EffectInstance {
                  Resource *resource = DefaultResource::get())
       : effect(effect), resource(resource), value(value),
         parameters(parameters), stage(0), effectOnFullRegion(false) {
-    checkNonAddressableValue();
+    checkResourceAllowsValue();
   }
   template <typename T,
             std::enable_if_t<
@@ -295,7 +267,7 @@ class EffectInstance {
       : effect(effect), resource(resource), value(value),
         parameters(parameters), stage(stage),
         effectOnFullRegion(effectOnFullRegion) {
-    checkNonAddressableValue();
+    checkResourceAllowsValue();
   }
   EffectInstance(EffectT *effect, SymbolRefAttr symbol, Attribute parameters,
                  Resource *resource = DefaultResource::get())
@@ -356,13 +328,12 @@ class EffectInstance {
   bool getEffectOnFullRegion() const { return effectOnFullRegion; }
 
 private:
-  /// Check that a Value is not associated with a non-addressable resource.
-  void checkNonAddressableValue() {
-    if (resource &&
-        resource->getMemoryRegion()->isSubregionOf(NonAddressableMemory::get()))
-      llvm::report_fatal_error("EffectInstance: non-addressable resource '" +
-                               resource->getName() +
-                               "' cannot have an associated Value");
+  /// Effect on a non-addressable resource cannot have an associated Value.
+  void checkResourceAllowsValue() {
+    if (value && resource && !resource->isAddressable())
+      llvm::report_fatal_error(
+          llvm::Twine("EffectInstance: resource '") + resource->getName() +
+          "' is non-addressable and cannot have an associated Value");
   }
 
   /// The specific effect being applied.
diff --git a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
index c72c386a7ffa2..11123af49774b 100644
--- a/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
+++ b/mlir/lib/Analysis/AliasAnalysis/LocalAliasAnalysis.cpp
@@ -525,13 +525,11 @@ ModRefResult LocalAliasAnalysis::getModRef(Operation *op, Value location) {
                  : aliasResult.isNo() ? "NoAlias"
                                       : "MayAlias");
     } else {
-      // If the effect's resource is in a memory region disjoint from
-      // AddressableMemory, it cannot alias a location accessible
-      // through a pointer.
-      auto *effectRegion = effect.getResource()->getMemoryRegion();
-      if (effectRegion->isDisjointFrom(SideEffects::AddressableMemory::get())) {
-        LDBG() << "    Effect on non-addressable region '"
-               << effectRegion->getName() << "', skipping (NoAlias)";
+      // An effect on a non-addressable resource cannot affect a pointer-based
+      // location.
+      if (!effect.getResource()->isAddressable()) {
+        LDBG() << "    Effect on non-addressable resource '"
+               << effect.getResource()->getName() << "', skipping (NoAlias)";
         aliasResult = AliasResult::NoAlias;
       } else {
         LDBG() << "    No effect value, assuming MayAlias";
diff --git a/mlir/lib/Transforms/CSE.cpp b/mlir/lib/Transforms/CSE.cpp
index 90d0a895c8ff9..e04b7399cfb44 100644
--- a/mlir/lib/Transforms/CSE.cpp
+++ b/mlir/lib/Transforms/CSE.cpp
@@ -182,15 +182,15 @@ bool CSEDriver::hasOtherSideEffectingOpInBetween(Operation *fromOp,
   assert(hasEffect<MemoryEffects::Read>(toOp) &&
          "expected read effect on toOp");
 
-  // Collect the memory regions of fromOp's read effects. A write can only
-  // block CSE if its resource region is not disjoint from one of these.
-  SmallPtrSet<SideEffects::MemoryRegion *, 1> readRegions;
+  // Collect the resources of fromOp's read effects. A write can only block
+  // CSE if its resource is not disjoint from one of these.
+  SmallPtrSet<SideEffects::Resource *, 1> readResources;
   if (auto memOp = dyn_cast<MemoryEffectOpInterface>(fromOp)) {
     SmallVector<MemoryEffects::EffectInstance> fromEffects;
     memOp.getEffects(fromEffects);
     for (const auto &e : fromEffects)
       if (isa<MemoryEffects::Read>(e.getEffect()))
-        readRegions.insert(e.getResource()->getMemoryRegion());
+        readResources.insert(e.getResource());
   }
 
   Operation *nextOp = fromOp->getNextNode();
@@ -222,11 +222,11 @@ bool CSEDriver::hasOtherSideEffectingOpInBetween(Operation *fromOp,
 
     for (const MemoryEffects::EffectInstance &effect : *effects) {
       if (isa<MemoryEffects::Write>(effect.getEffect())) {
-        // A write on a memory region disjoint from all read regions cannot
+        // A write on a resource disjoint from all read resources cannot
         // conflict with the reads being CSE'd.
-        auto *writeRegion = effect.getResource()->getMemoryRegion();
-        bool canConflict = llvm::any_of(readRegions, [&](auto *readRegion) {
-          return !writeRegion->isDisjointFrom(readRegion);
+        auto *writeResource = effect.getResource();
+        bool canConflict = llvm::any_of(readResources, [&](auto *readResource) {
+          return !writeResource->isDisjointFrom(readResource);
         });
         if (canConflict) {
           result.first->second = {nextOp, MemoryEffects::Write::get()};
diff --git a/mlir/test/Analysis/test-alias-analysis-modref.mlir b/mlir/test/Analysis/test-alias-analysis-modref.mlir
index bdc2e30d307d5..b82f789f4d77d 100644
--- a/mlir/test/Analysis/test-alias-analysis-modref.mlir
+++ b/mlir/test/Analysis/test-alias-analysis-modref.mlir
@@ -68,8 +68,8 @@ func.func @unknown(%arg0: memref<i32>) attributes {test.ptr = "func"} {
 
 // -----
 
-// An op writing to a non-addressable resource cannot modify or reference
-// a Value-based (addressable) memory location → NoModRef.
+// An op writing to a non-addressable resource cannot modify or reference a
+// Value-based memory location -> NoModRef.
 // CHECK-LABEL: Testing : "nonaddressable_write"
 // CHECK-DAG: side_effect_op -> func.region0#0: NoModRef
 // CHECK-DAG: return -> func.region0#0: NoModRef
@@ -80,8 +80,8 @@ func.func @nonaddressable_write(%arg: memref<2xf32>) attributes {test.ptr = "fun
 
 // -----
 
-// An op writing to an addressable resource (DefaultResource) without an
-// associated Value → MayAlias → Mod.
+// An op writing to an addressable resource without an associated Value ->
+// MayAlias -> Mod.
 // CHECK-LABEL: Testing : "addressable_write"
 // CHECK-DAG: side_effect_op -> func.region0#0: Mod
 // CHECK-DAG: return -> func.region0#0: NoModRef
diff --git a/mlir/test/Dialect/OpenACC/cse.mlir b/mlir/test/Dialect/OpenACC/cse.mlir
index 97bd1302b966b..7f3a11eb21e09 100644
--- a/mlir/test/Dialect/OpenACC/cse.mlir
+++ b/mlir/test/Dialect/OpenACC/cse.mlir
@@ -1,9 +1,9 @@
 // RUN: mlir-opt %s -pass-pipeline='builtin.module(func.func(cse))' | FileCheck %s
 
-// Verify that acc.set (which writes CurrentDeviceIdResource, a non-addressable
-// resource) does not block CSE of identical memref.load operations (which
-// read DefaultResource, an addressable resource). The two resource regions are
-// disjoint, so the write cannot conflict with the loads.
+// Verify that acc.set (which writes CurrentDeviceIdResource, a root disjoint
+// from DefaultResource) does not block CSE of identical memref.load operations
+// (which read DefaultResource). The two resources are disjoint, so the write
+// cannot conflict with the loads.
 
 // CHECK-LABEL: @cse_across_acc_set
 func.func @cse_across_acc_set(%a: memref<10xf32>, %i: index) -> (f32, f32) {
diff --git a/mlir/test/Transforms/cse.mlir b/mlir/test/Transforms/cse.mlir
index 1a150daa8e28d..14cdcdeffd1ec 100644
--- a/mlir/test/Transforms/cse.mlir
+++ b/mlir/test/Transforms/cse.mlir
@@ -576,9 +576,8 @@ func.func @cse_recursive_effects_failure() -> (i32, i32, i32) {
 
 // -----
 
-/// Check that a write on a non-addressable resource does not block CSE of
-/// reads on the default (addressable) resource, because the regions are
-/// disjoint.
+/// Check that a write on a resource disjoint from DefaultResource does not
+/// block CSE of reads on DefaultResource, because the resources are disjoint.
 // CHECK-LABEL: @cse_non_addressable_write_does_not_block
 func.func @cse_non_addressable_write_does_not_block() -> i32 {
   // CHECK-NEXT: %[[V:.*]] = "test.op_with_memread"() : () -> i32
@@ -593,7 +592,7 @@ func.func @cse_non_addressable_write_does_not_block() -> i32 {
 
 // -----
 
-/// Check that consecutive reads on the same non-addressable resource are CSE'd
+/// Check that consecutive reads on the same resource (disjoint from DefaultResource) are CSE'd
 /// when there is no intervening write.
 // CHECK-LABEL: @cse_reads_on_same_nonaddressable_resource
 func.func @cse_reads_on_same_nonaddressable_resource() -> i32 {
@@ -607,10 +606,10 @@ func.func @cse_reads_on_same_nonaddressable_resource() -> i32 {
 
 // -----
 
-/// Check that a write to the same non-addressable region blocks CSE of reads
-/// on that region (same region → not disjoint → write may conflict).
-// CHECK-LABEL: @cse_write_same_nonaddressable_region_blocks
-func.func @cse_write_same_nonaddressable_region_blocks() -> i32 {
+/// Check that a write to the same resource blocks CSE of reads
+/// on that resource (same resource -> not disjoint -> write may conflict).
+// CHECK-LABEL: @cse_write_same_nonaddressable_resource_blocks
+func.func @cse_write_same_nonaddressable_resource_blocks() -> i32 {
   // CHECK-NEXT: %[[V0:.*]] = "test.side_effect_op"(){{.*}}"read"
   %0 = "test.side_effect_op"() {effects = [{effect="read", test_nonaddressable_resource}]} : () -> i32
   "test.side_effect_op"() {effects = [{effect="write", test_nonaddressable_resource}]} : () -> i32
@@ -623,11 +622,10 @@ func.func @cse_write_same_nonaddressable_region_blocks() -> i32 {
 
 // -----
 
-/// Check that a write to a disjoint non-addressable sub-region does NOT block
-/// CSE of reads on a different non-addressable sub-region (sibling regions
-/// under NonAddressableMemory are disjoint).
-// CHECK-LABEL: @cse_write_disjoint_nonaddressable_subregion_allows
-func.func @cse_write_disjoint_nonaddressable_subregion_allows() -> i32 {
+/// Check that a write to a resource disjoint from the read resource does NOT
+/// block CSE (sibling resources under different roots are disjoint).
+// CHECK-LABEL: @cse_write_disjoint_nonaddressable_subresource_allows
+func.func @cse_write_disjoint_nonaddressable_subresource_allows() -> i32 {
   // CHECK-NEXT: %[[V:.*]] = "test.side_effect_op"()
   %0 = "test.side_effect_op"() {effects = [{effect="read", test_nonaddressable_resource_a}]} : () -> i32
   "test.side_effect_op"() {effects = [{effect="write", test_nonaddressable_resource_b}]} : () -> i32
@@ -640,8 +638,8 @@ func.func @cse_write_disjoint_nonaddressable_subregion_allows() -> i32 {
 
 // -----
 
-/// Check that a write on an addressable custom resource still blocks CSE of
-/// reads on the default (addressable) resource (regression guard).
+/// Check that a write on a resource under DefaultResource still blocks CSE of
+/// reads on DefaultResource (regression guard).
 // CHECK-LABEL: @cse_addressable_custom_resource_write_blocks
 func.func @cse_addressable_custom_resource_write_blocks() -> i32 {
   // CHECK-NEXT: %[[V0:.*]] = "test.op_with_memread"() : () -> i32
diff --git a/mlir/test/lib/Dialect/Test/TestOpDefs.cpp b/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
index 0889ea0aace98..d11b563849ad7 100644
--- a/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
+++ b/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
@@ -452,7 +452,7 @@ namespace {
 struct TestResource : public SideEffects::Resource::Base<TestResource> {
   MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestResource)
 
-  StringRef getName() final { return "<Test>"; }
+  StringRef getName() const final { return "<Test>"; }
 };
 } // namespace
 
diff --git a/mlir/test/lib/Dialect/Test/TestOps.h b/mlir/test/lib/Dialect/Test/TestOps.h
index 8b39699beb70a..b4cc2cd6cf569 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.h
+++ b/mlir/test/lib/Dialect/Test/TestOps.h
@@ -51,57 +51,64 @@ class TestDialect;
 // TestResource
 //===----------------------------------------------------------------------===//
 
-/// A test resource for side effects.
-struct TestResource : public mlir::SideEffects::Resource::Base<TestResource> {
-  llvm::StringRef getName() final { return "<Test>"; }
+/// A test resource for side effects (under DefaultResource).
+struct TestResource : public mlir::SideEffects::Resource::Base<
+                          TestResource, mlir::SideEffects::DefaultResource> {
+  llvm::StringRef getName() const final { return "<Test>"; }
+  mlir::SideEffects::Resource *getParent() const override {
+    return mlir::SideEffects::DefaultResource::get();
+  }
 };
 
-/// A test resource in NonAddressableMemory (disjoint from AddressableMemory).
+/// A test resource that is a root (disjoint from DefaultResource).
 struct TestNonAddressableResource
     : public mlir::SideEffects::Resource::Base<TestNonAddressableResource> {
-  llvm::StringRef getName() final { return "<TestNonAddressable>"; }
-  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
-    return mlir::SideEffects::NonAddressableMemory::get();
-  }
+  llvm::StringRef getName() const final { return "<TestNonAddressable>"; }
+  bool isAddressable() const override { return false; }
 };
 
-/// Two disjoint sub-regions under NonAddressableMemory for testing sibling
-/// disjointness within non-addressable memory.
-struct TestNonAddressableSubRegionA
-    : public mlir::SideEffects::MemoryRegion::Base<
-          TestNonAddressableSubRegionA> {
+/// Two disjoint sub-resources (roots) for testing sibling disjointness.
+struct TestNonAddressableSubResourceA
+    : public mlir::SideEffects::Resource::Base<TestNonAddressableSubResourceA> {
+  TestNonAddressableSubResourceA() = default;
   llvm::StringRef getName() const override {
-    return "TestNonAddressableSubRegionA";
-  }
-  mlir::SideEffects::MemoryRegion *getParent() const override {
-    return mlir::SideEffects::NonAddressableMemory::get();
+    return "TestNonAddressableSubResourceA";
   }
+  bool isAddressable() const override { return false; }
+
+protected:
+  TestNonAddressableSubResourceA(mlir::TypeID id) : Base(id) {}
 };
 
-struct TestNonAddressableSubRegionB
-    : public mlir::SideEffects::MemoryRegion::Base<
-          TestNonAddressableSubRegionB> {
+struct TestNonAddressableSubResourceB
+    : public mlir::SideEffects::Resource::Base<TestNonAddressableSubResourceB> {
+  TestNonAddressableSubResourceB() = default;
   llvm::StringRef getName() const override {
-    return "TestNonAddressableSubRegionB";
-  }
-  mlir::SideEffects::MemoryRegion *getParent() const override {
-    return mlir::SideEffects::NonAddressableMemory::get();
+    return "TestNonAddressableSubResourceB";
   }
+  bool isAddressable() const override { return false; }
+
+protected:
+  TestNonAddressableSubResourceB(mlir::TypeID id) : Base(id) {}
 };
 
 struct TestNonAddressableResourceA
-    : public mlir::SideEffects::Resource::Base<TestNonAddressableResourceA> {
-  llvm::StringRef getName() final { return "<TestNonAddressableA>"; }
-  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
-    return TestNonAddressableSubRegionA::get();
+    : public mlir::SideEffects::Resource::Base<TestNonAddressableResourceA,
+                                               TestNonAddressableSubResourceA> {
+  llvm::StringRef getName() const final { return "<TestNonAddressableA>"; }
+  bool isAddressable() const override { return false; }
+  mlir::SideEffects::Resource *getParent() const override {
+    return TestNonAddressableSubResourceA::get();
   }
 };
 
 struct TestNonAddressableResourceB
-    : public mlir::SideEffects::Resource::Base<TestNonAddressableResourceB> {
-  llvm::StringRef getName() final { return "<TestNonAddressableB>"; }
-  mlir::SideEffects::MemoryRegion *getMemoryRegion() const override {
-    return TestNonAddressableSubRegionB::get();
+    : public mlir::SideEffects::Resource::Base<TestNonAddressableResourceB,
+                                               TestNonAddressableSubResourceB> {
+  llvm::StringRef getName() const final { return "<TestNonAddressableB>"; }
+  bool isAddressable() const override { return false; }
+  mlir::SideEffects::Resource *getParent() const override {
+    return TestNonAddressableSubResourceB::get();
   }
 };
 
diff --git a/mlir/unittests/Interfaces/CMakeLists.txt b/mlir/unittests/Interfaces/CMakeLists.txt
index f40864d175629..7cb37323fcff2 100644
--- a/mlir/unittests/Interfaces/CMakeLists.txt
+++ b/mlir/unittests/Interfaces/CMakeLists.txt
@@ -2,6 +2,7 @@ add_mlir_unittest(MLIRInterfacesTests
   ControlFlowInterfacesTest.cpp
   DataLayoutInterfacesTest.cpp
   InferIntRangeInterfaceTest.cpp
+  SideEffectInterfacesTest.cpp
   InferTypeOpInterfaceTest.cpp
 )
 
@@ -15,4 +16,5 @@ mlir_target_link_libraries(MLIRInterfacesTests
   MLIRInferIntRangeInterface
   MLIRInferTypeOpInterface
   MLIRParser
+  MLIRSideEffectInterfaces
 )
diff --git a/mlir/unittests/Interfaces/SideEffectInterfacesTest.cpp b/mlir/unittests/Interfaces/SideEffectInterfacesTest.cpp
new file mode 100644
index 0000000000000..2f7f7a889ccab
--- /dev/null
+++ b/mlir/unittests/Interfaces/SideEffectInterfacesTest.cpp
@@ -0,0 +1,123 @@
+//===- SideEffectInterfacesTest.cpp - Unit tests for Resource hierarchy ---===//
+//
+// 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/Interfaces/SideEffectInterfaces.h"
+#include "mlir/Support/TypeID.h"
+#include "llvm/Support/Casting.h"
+#include <gtest/gtest.h>
+
+using namespace mlir;
+using namespace mlir::SideEffects;
+
+namespace {
+
+/// Custom resource hierarchy (root -> child -> grandchild) for testing.
+struct TestRootResource : public Resource::Base<TestRootResource> {
+  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestRootResource)
+  TestRootResource() = default;
+  StringRef getName() const override { return "TestRoot"; }
+
+protected:
+  TestRootResource(TypeID id) : Base(id) {}
+};
+
+struct TestChildResource
+    : public Resource::Base<TestChildResource, TestRootResource> {
+  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestChildResource)
+  TestChildResource() = default;
+  StringRef getName() const override { return "TestChild"; }
+  Resource *getParent() const override { return TestRootResource::get(); }
+
+protected:
+  TestChildResource(TypeID id) : Base(id) {}
+};
+
+struct TestGrandchildResource
+    : public Resource::Base<TestGrandchildResource, TestChildResource> {
+  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestGrandchildResource)
+  StringRef getName() const override { return "TestGrandchild"; }
+  Resource *getParent() const override { return TestChildResource::get(); }
+};
+
+} // namespace
+
+TEST(SideEffectResourceTest, BuiltInHierarchyIsaCast) {
+  EXPECT_TRUE(isa<DefaultResource>(DefaultResource::get()));
+  EXPECT_TRUE(isa<DefaultResource>(AutomaticAllocationScopeResource::get()));
+  EXPECT_FALSE(isa<AutomaticAllocationScopeResource>(DefaultResource::get()));
+
+  // Each type has its own singleton; cast yields the child object as parent*
+  EXPECT_NE(cast<DefaultResource>(AutomaticAllocationScopeResource::get()),
+            nullptr);
+  EXPECT_TRUE(isa<DefaultResource>(
+      cast<DefaultResource>(AutomaticAllocationScopeResource::get())));
+
+  EXPECT_NE(dyn_cast<DefaultResource>(AutomaticAllocationScopeResource::get()),
+            nullptr);
+  EXPECT_EQ(dyn_cast<AutomaticAllocationScopeResource>(DefaultResource::get()),
+            nullptr);
+}
+
+TEST(SideEffectResourceTest, CustomHierarchyIsaCast) {
+  // Root and child
+  EXPECT_TRUE(isa<TestRootResource>(TestRootResource::get()));
+  EXPECT_TRUE(isa<TestRootResource>(TestChildResource::get()));
+  EXPECT_FALSE(isa<TestChildResource>(TestRootResource::get()));
+  EXPECT_NE(cast<TestRootResource>(TestChildResource::get()), nullptr);
+
+  // Grandchild isa root, child, and self
+  EXPECT_TRUE(isa<TestRootResource>(TestGrandchildResource::get()));
+  EXPECT_TRUE(isa<TestChildResource>(TestGrandchildResource::get()));
+  EXPECT_TRUE(isa<TestGrandchildResource>(TestGrandchildResource::get()));
+
+  // Root/child are not isa grandchild
+  EXPECT_FALSE(isa<TestGrandchildResource>(TestRootResource::get()));
+  EXPECT_FALSE(isa<TestGrandchildResource>(TestChildResource::get()));
+
+  // Cast grandchild to root and child (each type has its own singleton)
+  EXPECT_NE(cast<TestRootResource>(TestGrandchildResource::get()), nullptr);
+  EXPECT_NE(cast<TestChildResource>(TestGrandchildResource::get()), nullptr);
+
+  // dyn_cast
+  EXPECT_EQ(dyn_cast<TestGrandchildResource>(TestRootResource::get()), nullptr);
+  EXPECT_NE(dyn_cast<TestRootResource>(TestGrandchildResource::get()), nullptr);
+
+  // getParent chain
+  EXPECT_EQ(TestGrandchildResource::get()->getParent(),
+            TestChildResource::get());
+  EXPECT_EQ(TestChildResource::get()->getParent(), TestRootResource::get());
+  EXPECT_EQ(TestRootResource::get()->getParent(), nullptr);
+
+  // isSubresourceOf
+  EXPECT_TRUE(
+      TestGrandchildResource::get()->isSubresourceOf(TestRootResource::get()));
+  EXPECT_TRUE(
+      TestGrandchildResource::get()->isSubresourceOf(TestChildResource::get()));
+  EXPECT_FALSE(
+      TestRootResource::get()->isSubresourceOf(TestGrandchildResource::get()));
+
+  // Custom hierarchy disjoint from DefaultResource
+  EXPECT_TRUE(TestRootResource::get()->isDisjointFrom(DefaultResource::get()));
+  EXPECT_FALSE(isa<DefaultResource>(TestRootResource::get()));
+  EXPECT_FALSE(isa<DefaultResource>(TestChildResource::get()));
+  EXPECT_FALSE(isa<DefaultResource>(TestGrandchildResource::get()));
+  EXPECT_FALSE(isa<TestRootResource>(DefaultResource::get()));
+  EXPECT_EQ(dyn_cast<DefaultResource>(TestGrandchildResource::get()), nullptr);
+  EXPECT_EQ(dyn_cast<TestRootResource>(DefaultResource::get()), nullptr);
+}
+
+TEST(SideEffectResourceTest, DisjointnessAndGetParent) {
+  EXPECT_EQ(DefaultResource::get()->getParent(), nullptr);
+  EXPECT_EQ(AutomaticAllocationScopeResource::get()->getParent(),
+            DefaultResource::get());
+  EXPECT_TRUE(DefaultResource::get()->isDisjointFrom(TestRootResource::get()));
+  EXPECT_TRUE(
+      TestChildResource::get()->isSubresourceOf(TestRootResource::get()));
+  EXPECT_FALSE(
+      TestRootResource::get()->isSubresourceOf(TestChildResource::get()));
+}



More information about the flang-commits mailing list