[RFC][mlir] Resource hierarchy for MLIR Side Effects. (#181229)

This patch allows creating a hierarchy of `SideEffects::Resource`s by adding
a virtual `getParent()` method, so that effects on *disjoint* resources
can be proven non-conflicting. It also adds virtual `isAddressable()` method
that represents a property of a resource to be addressable via a pointer
value. The non-addressable resources may not be affected via any pointer.
This is unblocking CSE, LICM and alias analysis without per-pass
special-casing.

RFC:
https://discourse.llvm.org/t/rfc-mlir-memory-region-hierarchy-for-mlir-side-effects/89811
This commit is contained in:
Slava Zakharin 2026-03-09 13:12:49 -07:00 committed by GitHub
parent 2c1594a258
commit 48e6adc97e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 469 additions and 27 deletions

View File

@ -50,13 +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::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::StringRef getName() const final { return "VolatileMemoryResource"; }
bool isAddressable() const override { return false; }
};
class CoordinateIndicesAdaptor;

View File

@ -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

View File

@ -212,17 +212,20 @@ static constexpr StringLiteral getCombinedConstructsAttrName() {
struct RuntimeCounters
: public mlir::SideEffects::Resource::Base<RuntimeCounters> {
mlir::StringRef getName() final { return "AccRuntimeCounters"; }
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::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::StringRef getName() const final { return "AccCurrentDeviceIdResource"; }
bool isAddressable() const override { return false; }
};
} // namespace acc

View File

@ -1256,7 +1256,7 @@ public:
// 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

View File

@ -15,6 +15,7 @@
#define MLIR_INTERFACES_SIDEEFFECTINTERFACES_H
#include "mlir/IR/OpDefinition.h"
#include "llvm/ADT/Twine.h"
namespace mlir {
namespace SideEffects {
@ -75,7 +76,15 @@ private:
//===----------------------------------------------------------------------===//
/// 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;
@ -84,7 +93,9 @@ public:
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() {
@ -95,39 +106,92 @@ public:
/// 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; }
/// 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
@ -152,7 +216,9 @@ public:
EffectInstance(EffectT *effect, T value,
Resource *resource = DefaultResource::get())
: effect(effect), resource(resource), value(value), stage(0),
effectOnFullRegion(false) {}
effectOnFullRegion(false) {
checkResourceAllowsValue();
}
template <typename T,
std::enable_if_t<
llvm::is_one_of<T, OpOperand *, OpResult, BlockArgument>::value,
@ -160,7 +226,9 @@ public:
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) {
checkResourceAllowsValue();
}
EffectInstance(EffectT *effect, SymbolRefAttr symbol,
Resource *resource = DefaultResource::get())
: effect(effect), resource(resource), value(symbol), stage(0),
@ -186,7 +254,9 @@ public:
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) {
checkResourceAllowsValue();
}
template <typename T,
std::enable_if_t<
llvm::is_one_of<T, OpOperand *, OpResult, BlockArgument>::value,
@ -196,7 +266,9 @@ public:
Resource *resource = DefaultResource::get())
: effect(effect), resource(resource), value(value),
parameters(parameters), stage(stage),
effectOnFullRegion(effectOnFullRegion) {}
effectOnFullRegion(effectOnFullRegion) {
checkResourceAllowsValue();
}
EffectInstance(EffectT *effect, SymbolRefAttr symbol, Attribute parameters,
Resource *resource = DefaultResource::get())
: effect(effect), resource(resource), value(symbol),
@ -256,6 +328,14 @@ public:
bool getEffectOnFullRegion() const { return effectOnFullRegion; }
private:
/// 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.
EffectT *effect;

View File

@ -525,7 +525,15 @@ ModRefResult LocalAliasAnalysis::getModRef(Operation *op, Value location) {
: aliasResult.isNo() ? "NoAlias"
: "MayAlias");
} else {
LDBG() << " No effect value, assuming MayAlias";
// 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";
}
}
// If we don't alias, ignore this effect.

View File

@ -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 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()))
readResources.insert(e.getResource());
}
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 resource disjoint from all read resources cannot
// conflict with the reads being CSE'd.
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()};
return true;
}
}
}
nextOp = nextOp->getNextNode();

View File

@ -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 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 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"}
}

View File

@ -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 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) {
%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
}

View File

@ -573,3 +573,81 @@ 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 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
%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 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 {
// 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 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
// 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 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
%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 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
%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
}

View File

@ -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
@ -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"))

View File

@ -51,9 +51,65 @@ 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 that is a root (disjoint from DefaultResource).
struct TestNonAddressableResource
: public mlir::SideEffects::Resource::Base<TestNonAddressableResource> {
llvm::StringRef getName() const final { return "<TestNonAddressable>"; }
bool isAddressable() const override { return false; }
};
/// 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 "TestNonAddressableSubResourceA";
}
bool isAddressable() const override { return false; }
protected:
TestNonAddressableSubResourceA(mlir::TypeID id) : Base(id) {}
};
struct TestNonAddressableSubResourceB
: public mlir::SideEffects::Resource::Base<TestNonAddressableSubResourceB> {
TestNonAddressableSubResourceB() = default;
llvm::StringRef getName() const override {
return "TestNonAddressableSubResourceB";
}
bool isAddressable() const override { return false; }
protected:
TestNonAddressableSubResourceB(mlir::TypeID id) : Base(id) {}
};
struct TestNonAddressableResourceA
: 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,
TestNonAddressableSubResourceB> {
llvm::StringRef getName() const final { return "<TestNonAddressableB>"; }
bool isAddressable() const override { return false; }
mlir::SideEffects::Resource *getParent() const override {
return TestNonAddressableSubResourceB::get();
}
};
//===----------------------------------------------------------------------===//

View File

@ -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
)

View File

@ -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()));
}