[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:
parent
2c1594a258
commit
48e6adc97e
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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"}
|
||||
}
|
||||
|
||||
18
mlir/test/Dialect/OpenACC/cse.mlir
Normal file
18
mlir/test/Dialect/OpenACC/cse.mlir
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"))
|
||||
|
||||
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
123
mlir/unittests/Interfaces/SideEffectInterfacesTest.cpp
Normal file
123
mlir/unittests/Interfaces/SideEffectInterfacesTest.cpp
Normal 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()));
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user