diff --git a/mlir/include/mlir/IR/CMakeLists.txt b/mlir/include/mlir/IR/CMakeLists.txt --- a/mlir/include/mlir/IR/CMakeLists.txt +++ b/mlir/include/mlir/IR/CMakeLists.txt @@ -2,3 +2,8 @@ mlir_tablegen(OpAsmInterface.h.inc -gen-op-interface-decls) mlir_tablegen(OpAsmInterface.cpp.inc -gen-op-interface-defs) add_public_tablegen_target(MLIROpAsmInterfacesIncGen) + +set(LLVM_TARGET_DEFINITIONS SideEffects.td) +mlir_tablegen(SideEffectInterfaces.h.inc -gen-op-interface-decls) +mlir_tablegen(SideEffectInterfaces.cpp.inc -gen-op-interface-defs) +add_public_tablegen_target(MLIRSideEffectOpInterfacesIncGen) diff --git a/mlir/include/mlir/IR/OpBase.td b/mlir/include/mlir/IR/OpBase.td --- a/mlir/include/mlir/IR/OpBase.td +++ b/mlir/include/mlir/IR/OpBase.td @@ -1410,6 +1410,8 @@ def ResultsAreFloatLike : NativeOpTrait<"ResultsAreFloatLike">; // Op has no side effect. def NoSideEffect : NativeOpTrait<"HasNoSideEffect">; +// Op has recursively computed side effects. +def RecursiveSideEffects : NativeOpTrait<"HasRecursiveSideEffects">; // Op has the same operand type. def SameTypeOperands : NativeOpTrait<"SameTypeOperands">; // Op has same shape for all operands. diff --git a/mlir/include/mlir/IR/OpDefinition.h b/mlir/include/mlir/IR/OpDefinition.h --- a/mlir/include/mlir/IR/OpDefinition.h +++ b/mlir/include/mlir/IR/OpDefinition.h @@ -746,16 +746,6 @@ } }; -/// This class adds property that the operation has no side effects. -template -class HasNoSideEffect : public TraitBase { -public: - static AbstractOperation::OperationProperties getTraitProperties() { - return static_cast( - OperationProperty::NoSideEffect); - } -}; - /// This class verifies that all operands of the specified op have a float type, /// a vector thereof, or a tensor thereof. template @@ -1198,6 +1188,222 @@ Concept *impl; }; +//===----------------------------------------------------------------------===// +// Operation Side-Effect Modeling +//===----------------------------------------------------------------------===// + +// TODO(riverriddle) This should be in its own file in a proper +// traits/interfaces directory. Move it there when we have one. + +namespace SideEffects { +//===--------------------------------------------------------------------===// +// Effects + +/// This class represents a base class for a specific effect type. +class Effect { +public: + /// This base class is used for derived effects that are non-parametric. + template + class Base : public BaseEffect { + public: + using BaseT = Base; + + /// Return the unique identifier for the base effects class. + static ClassID *getEffectID() { return ClassID::getID(); } + + /// 'classof' used to support llvm style cast functionality. + static bool classof(const Effect *effect) { + return effect->getEffectID() == BaseT::getEffectID(); + } + + /// Returns a unique instance for the derived effect class. + static DerivedEffect *get() { + return BaseEffect::template get(); + } + using BaseEffect::get; + + protected: + Base() : BaseEffect(BaseT::getEffectID()){}; + }; + + /// Return the unique identifier for the base effects class. + ClassID *getEffectID() const { return id; } + + /// Returns a unique instance for the given effect class. + template static DerivedEffect *get() { + static_assert(std::is_base_of_v, + "expected DerivedEffect to inherit from Effect"); + + static DerivedEffect instance; + return &instance; + } + +protected: + Effect(ClassID *id) : id(id) {} + +private: + /// The id of the derived effect class. + ClassID *id; +}; + +//===--------------------------------------------------------------------===// +// Resources + +/// This class represents a specific resource that an effect applies to. This +/// class represents an abstract interface for a given resource. +class Resource { +public: + virtual ~Resource() {} + + /// This base class is used for derived effects that are non-parametric. + template + class Base : public BaseResource { + public: + using BaseT = Base; + + /// Returns a unique instance for the given effect class. + static DerivedResource *get() { + static DerivedResource instance; + return &instance; + } + + /// Return the unique identifier for the base resource class. + static ClassID *getResourceID() { + return ClassID::getID(); + } + + /// 'classof' used to support llvm style cast functionality. + static bool classof(const Resource *resource) { + return resource->getResourceID() == BaseT::getResourceID(); + } + + protected: + Base() : BaseResource(BaseT::getResourceID()){}; + }; + + /// Return the unique identifier for the base resource class. + ClassID *getResourceID() const { return id; } + + /// Return a string name of the resource. + virtual StringRef getName() = 0; + +protected: + Resource(ClassID *id) : id(id) {} + +private: + /// The id of the derived resource class. + ClassID *id; +}; + +/// A conservative default resource kind. +struct DefaultResource : public Resource::Base { + StringRef getName() final { return ""; } +}; + +/// This class represents a specific instance of an effect. It contains the +/// effect being applied, a resource that corresponds to where the effect is +/// applied, and an optional value(either operand, result, or region entry +/// argument) that the effect is applied to. +template class EffectInstance { +public: + EffectInstance(EffectT *effect, Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource) {} + EffectInstance(EffectT *effect, Value value, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), value(value) {} + + /// Return the effect being applied. + EffectT *getEffect() const { return effect; } + + /// Return the value the effect is applied on, or nullptr if there isn't a + /// known value being affected. + Value getValue() const { return value; } + + /// Return the resource that the effect applies to. + Resource *getResource() const { return resource; } + +private: + /// The specific effect being applied. + EffectT *effect; + + /// The resource that the given value resides in. + Resource *resource; + + /// The value that the effect applies to. This is optionally null. + Value value; +}; +} // namespace SideEffects + +//===----------------------------------------------------------------------===// +// SideEffect Traits + +namespace OpTrait { +/// This trait indicates that an operation never has side effects. +template +class HasNoSideEffect : public TraitBase { +public: + static AbstractOperation::OperationProperties getTraitProperties() { + return static_cast( + OperationProperty::NoSideEffect); + } +}; +/// This trait indicates that the side effects of an operation includes the +/// effects of operations nested within its regions. If the operation has no +/// derived effects interfaces, the operation itself can be assumed to have no +/// side effects. +template +class HasRecursiveSideEffects + : public TraitBase {}; +} // namespace OpTrait + +//===----------------------------------------------------------------------===// +// Operation Memory-Effect Modeling +//===----------------------------------------------------------------------===// + +namespace MemoryEffects { +/// This class represents the base class used for memory effects. +struct Effect : public SideEffects::Effect { + using SideEffects::Effect::Effect; + + /// A base class for memory effects that provides helper utilities. + template + using Base = SideEffects::Effect::Base; + + static bool classof(const SideEffects::Effect *effect); +}; +using EffectInstance = SideEffects::EffectInstance; + +/// The following effect indicates that the operation allocates from some +/// resource. An 'allocate' effect implies only allocation of the resource, and +/// not any visible mutation or dereference. +struct Allocate : public Effect::Base {}; + +/// The following effect indicates that the operation frees some resource that +/// has been allocated. An 'allocate' effect implies only de-allocation of the +/// resource, and not any visible allocation, mutation or dereference. +struct Free : public Effect::Base {}; + +/// The following effect indicates that the operation reads from some resource. +/// A 'read' effect implies only dereferencing of the resource, and not any +/// visible mutation. +struct Read : public Effect::Base {}; + +/// The following effect indicates that the operation writes to some resource. A +/// 'write' effect implies only mutating a resource, and not any visible +/// dereference or read. +struct Write : public Effect::Base {}; +}; // namespace MemoryEffects + +//===----------------------------------------------------------------------===// +// SideEffect Interfaces + +/// Include the definitions of the side effect interfaces. +#include "mlir/IR/SideEffectInterfaces.h.inc" + +//===----------------------------------------------------------------------===// +// Common Operation Folders/Parsers/Printers +//===----------------------------------------------------------------------===// + // These functions are out-of-line implementations of the methods in UnaryOp and // BinaryOp, which avoids them being template instantiated/duplicated. namespace impl { diff --git a/mlir/include/mlir/IR/SideEffects.td b/mlir/include/mlir/IR/SideEffects.td new file mode 100644 --- /dev/null +++ b/mlir/include/mlir/IR/SideEffects.td @@ -0,0 +1,191 @@ +//===-- SideEffects.td - Side Effect Interfaces ------------*- tablegen -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file contains a set of interfaces that can be used to define information +// about what effects are applied by an operation. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_IR_SIDEEFFECTS +#define MLIR_IR_SIDEEFFECTS + +include "mlir/IR/OpBase.td" + +//===----------------------------------------------------------------------===// +// EffectOpInterface +//===----------------------------------------------------------------------===// + +// A base interface used to query information about the side effects applied to +// an operation. This template class takes the name of the derived interface +// class, as well as the name of the base effect class. +class EffectOpInterfaceBase + : OpInterface { + let methods = [ + InterfaceMethod<[{ + Collects all of the operation's effects into `effects`. + }], + "void", "getEffects", + (ins "SmallVectorImpl> &":$effects) + >, + InterfaceMethod<[{ + Collects all of the operation's effects into `effects`. + }], + "void", "getEffectsOnValue", + (ins "Value":$value, + "SmallVectorImpl> &":$effects), [{ + op.getEffects(effects); + llvm::erase_if(effects, [&](auto &it) { + return it.getValue() != value; + }); + }] + >, + InterfaceMethod<[{ + Collects all of the effects that are exhibited by this operation on the + given resource and place them in 'effects'. + }], + "void", "getEffectsOnResource", + (ins "SideEffects::Resource *":$resource, + "SmallVectorImpl> &":$effects), [{ + op.getEffects(effects); + llvm::erase_if(effects, [&](auto &it) { + return it.getResource() != resource; + }); + }] + > + ]; + + let extraClassDeclaration = [{ + /// Collect all of the effect instances that correspond to the given + /// `Effect` and place them in 'effects'. + template void getEffects( + SmallVectorImpl> &effects) { + getEffects(effects); + llvm::erase_if(effects, [&](auto &it) { + return !llvm::isa(it.getEffect()); + }); + } + + /// Returns true if this operation exhibits the given effect. + template bool hasEffect() { + SmallVector, 4> effects; + getEffects(effects); + return llvm::any_of(effects, [](const auto &it) { + return llvm::isa(it.getEffect()); + }); + } + + /// Returns if this operation only has the given effect. + template bool onlyHasEffect() { + SmallVector, 4> effects; + getEffects(effects); + return !effects.empty() && llvm::all_of(effects, [](const auto &it) { + return isa(it.getEffect()); + }); + } + + /// Returns if this operation has no effects. + bool hasNoEffect() { + SmallVector, 4> effects; + getEffects(effects); + return effects.empty(); + } + }]; + + // The base effect name of this interface. + string baseEffectName = baseEffect; +} + +// This class is the general base side effect class. This is used by derived +// effect interfaces to define their effects. +class SideEffect { + /// The parent interface that the effect belongs to. + string interfaceTrait = interface.trait; + + /// The name of the base effects class. + string baseEffect = interface.baseEffectName; + + /// The derived effect that is being applied. + string effect = effectName; + + /// The resource that the effect is being applied to. + string resource = resourceName; +} + +// This class is the base used for specifying effects applied to an operation. +class SideEffectsTraitBase staticEffects> + : OpInterfaceTrait<""> { + /// The name of the interface trait to use. + let trait = parentInterface.trait; + + /// The derived effects being applied. + list effects = staticEffects; +} + +//===----------------------------------------------------------------------===// +// MemoryEffects +//===----------------------------------------------------------------------===// + +// This def represents the definition for the memory effects interface. Users +// should generally not use this directly, and should instead use +// `MemoryEffects`. +def MemoryEffectsOpInterface + : EffectOpInterfaceBase<"MemoryEffectOpInterface", + "MemoryEffects::Effect"> { + let description = [{ + An interface used to query information about the memory effects applied by + an operation. + }]; +} + +// The base class for defining specific memory effects. +class MemoryEffect + : SideEffect; + +// This class represents the trait for memory effects that may be placed on +// operations. +class MemoryEffects effects = []> + : SideEffectsTraitBase; + +//===----------------------------------------------------------------------===// +// Effects + +// The following effect indicates that the operation allocates from some +// resource. An 'allocate' effect implies only allocation of the resource, and +// not any visible mutation or dereference. +class MemAlloc + : MemoryEffect<"MemoryEffects::Allocate", resourceName>; +def MemAlloc : MemAlloc<"">; + +// The following effect indicates that the operation frees some resource that +// has been allocated. A 'free' effect implies only de-allocation of the +// resource, and not any visible allocation, mutation or dereference. +class MemFree + : MemoryEffect<"MemoryEffects::Free", resourceName>; +def MemFree : MemFree<"">; + +// The following effect indicates that the operation reads from some +// resource. A 'read' effect implies only dereferencing of the resource, and +// not any visible mutation. +class MemRead + : MemoryEffect<"MemoryEffects::Read", resourceName>; +def MemRead : MemRead<"">; + +// The following effect indicates that the operation writes to some +// resource. A 'write' effect implies only mutating a resource, and not any +// visible dereference or read. +class MemWrite + : MemoryEffect<"MemoryEffects::Write", resourceName>; +def MemWrite : MemWrite<"">; + +#endif // MLIR_IR_SIDEEFFECTS diff --git a/mlir/lib/IR/CMakeLists.txt b/mlir/lib/IR/CMakeLists.txt --- a/mlir/lib/IR/CMakeLists.txt +++ b/mlir/lib/IR/CMakeLists.txt @@ -5,5 +5,11 @@ ADDITIONAL_HEADER_DIRS ${MLIR_MAIN_INCLUDE_DIR}/mlir/IR ) -add_dependencies(MLIRIR MLIRCallOpInterfacesIncGen MLIROpAsmInterfacesIncGen MLIRSupport LLVMSupport) +add_dependencies(MLIRIR + MLIRCallOpInterfacesIncGen + MLIROpAsmInterfacesIncGen + MLIRSideEffectOpInterfacesIncGen + MLIRSupport + LLVMSupport + ) target_link_libraries(MLIRIR MLIRSupport LLVMSupport) diff --git a/mlir/lib/IR/Operation.cpp b/mlir/lib/IR/Operation.cpp --- a/mlir/lib/IR/Operation.cpp +++ b/mlir/lib/IR/Operation.cpp @@ -1056,6 +1056,17 @@ return verifyValueSizeAttr(op, attrName, /*isOperand=*/false); } +//===----------------------------------------------------------------------===// +// SideEffect Interfaces + +/// Include the definitions of the side effect interfaces. +#include "mlir/IR/SideEffectInterfaces.cpp.inc" + +bool MemoryEffects::Effect::classof(const SideEffects::Effect *effect) { + return isa(effect) || isa(effect) || isa(effect) || + isa(effect); +} + //===----------------------------------------------------------------------===// // BinaryOp implementation //===----------------------------------------------------------------------===// diff --git a/mlir/test/IR/test-side-effects.mlir b/mlir/test/IR/test-side-effects.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/IR/test-side-effects.mlir @@ -0,0 +1,20 @@ +// RUN: mlir-opt %s -test-side-effects -verify-diagnostics + +// expected-remark@+1 {{operation has no memory effects}} +%0 = "test.side_effect_op"() {} : () -> i32 + +// expected-remark@+2 {{found an instance of 'read' on resource ''}} +// expected-remark@+1 {{found an instance of 'free' on resource ''}} +%1 = "test.side_effect_op"() {effects = [ + {effect="read"}, {effect="free"} +]} : () -> i32 + +// expected-remark@+1 {{found an instance of 'write' on resource ''}} +%2 = "test.side_effect_op"() {effects = [ + {effect="write", test_resource} +]} : () -> i32 + +// expected-remark@+1 {{found an instance of 'allocate' on a value, on resource ''}} +%3 = "test.side_effect_op"() {effects = [ + {effect="allocate", on_result, test_resource} +]} : () -> i32 diff --git a/mlir/test/lib/IR/CMakeLists.txt b/mlir/test/lib/IR/CMakeLists.txt --- a/mlir/test/lib/IR/CMakeLists.txt +++ b/mlir/test/lib/IR/CMakeLists.txt @@ -1,6 +1,7 @@ add_llvm_library(MLIRTestIR TestFunc.cpp TestMatchers.cpp + TestSideEffects.cpp TestSymbolUses.cpp ADDITIONAL_HEADER_DIRS diff --git a/mlir/test/lib/IR/TestSideEffects.cpp b/mlir/test/lib/IR/TestSideEffects.cpp new file mode 100644 --- /dev/null +++ b/mlir/test/lib/IR/TestSideEffects.cpp @@ -0,0 +1,58 @@ +//===- TestSidEffects.cpp - Pass to test side effects ---------------------===// +// +// 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 "TestDialect.h" +#include "mlir/Pass/Pass.h" + +using namespace mlir; + +namespace { +struct SideEffectsPass : public ModulePass { + void runOnModule() override { + auto module = getModule(); + + // Walk operations detecting side effects. + SmallVector effects; + module.walk([&](MemoryEffectOpInterface op) { + effects.clear(); + op.getEffects(effects); + + // Check to see if this operation has any memory effects. + if (effects.empty()) { + op.emitRemark() << "operation has no memory effects"; + return; + } + + for (MemoryEffects::EffectInstance instance : effects) { + auto diag = op.emitRemark() << "found an instance of "; + + if (isa(instance.getEffect())) + diag << "'allocate'"; + else if (isa(instance.getEffect())) + diag << "'free'"; + else if (isa(instance.getEffect())) + diag << "'read'"; + else if (isa(instance.getEffect())) + diag << "'write'"; + + if (instance.getValue()) + diag << " on a value,"; + + diag << " on resource '" << instance.getResource()->getName() << "'"; + } + }); + } +}; +} // end anonymous namespace + +namespace mlir { +void registerSideEffectTestPasses() { + PassRegistration("test-side-effects", + "Test side effects interfaces"); +} +} // namespace mlir diff --git a/mlir/test/lib/TestDialect/TestDialect.cpp b/mlir/test/lib/TestDialect/TestDialect.cpp --- a/mlir/test/lib/TestDialect/TestDialect.cpp +++ b/mlir/test/lib/TestDialect/TestDialect.cpp @@ -333,6 +333,56 @@ return success(); } +//===----------------------------------------------------------------------===// +// Test SideEffect interfaces +//===----------------------------------------------------------------------===// + +namespace { +/// A test resource for side effects. +struct TestResource : public SideEffects::Resource::Base { + StringRef getName() final { return ""; } +}; +} // end anonymous namespace + +void SideEffectOp::getEffects( + SmallVectorImpl &effects) { + // Check for an effects attribute on the op instance. + ArrayAttr effectsAttr = getAttrOfType("effects"); + if (!effectsAttr) + return; + + // If there is one, it is an array of dictionary attributes that hold + // information on the effects of this operation. + for (Attribute element : effectsAttr) { + DictionaryAttr effectElement = element.cast(); + + // Get the specific memory effect. + MemoryEffects::Effect *effect = + llvm::StringSwitch( + effectElement.get("effect").cast().getValue()) + .Case("allocate", MemoryEffects::Allocate::get()) + .Case("free", MemoryEffects::Free::get()) + .Case("read", MemoryEffects::Read::get()) + .Case("write", MemoryEffects::Write::get()); + + // Check for a result to affect. + Value value; + if (effectElement.get("on_result")) + value = getResult(); + + // Check for a non-default resource to use. + SideEffects::Resource *resource = SideEffects::DefaultResource::get(); + if (effectElement.get("test_resource")) + resource = TestResource::get(); + + effects.emplace_back(effect, value, resource); + } +} + +//===----------------------------------------------------------------------===// +// Dialect Registration +//===----------------------------------------------------------------------===// + // Static initialization for Test dialect registration. static mlir::DialectRegistration testDialect; diff --git a/mlir/test/lib/TestDialect/TestOps.td b/mlir/test/lib/TestDialect/TestOps.td --- a/mlir/test/lib/TestDialect/TestOps.td +++ b/mlir/test/lib/TestDialect/TestOps.td @@ -9,8 +9,8 @@ #ifndef TEST_OPS #define TEST_OPS -include "mlir/IR/OpBase.td" include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/SideEffects.td" include "mlir/Analysis/CallInterfaces.td" include "mlir/Analysis/InferTypeOpInterface.td" @@ -1144,4 +1144,13 @@ let assemblyFormat = "$targets attr-dict"; } +//===----------------------------------------------------------------------===// +// Test SideEffects +//===----------------------------------------------------------------------===// + +def SideEffectOp : TEST_Op<"side_effect_op", + [DeclareOpInterfaceMethods]> { + let results = (outs AnyType:$result); +} + #endif // TEST_OPS diff --git a/mlir/tools/mlir-opt/mlir-opt.cpp b/mlir/tools/mlir-opt/mlir-opt.cpp --- a/mlir/tools/mlir-opt/mlir-opt.cpp +++ b/mlir/tools/mlir-opt/mlir-opt.cpp @@ -33,6 +33,7 @@ void registerPassManagerTestPass(); void registerPatternsTestPass(); void registerPrintOpAvailabilityPass(); +void registerSideEffectTestPasses(); void registerSimpleParametricTilingPass(); void registerSymbolTestPasses(); void registerTestAffineDataCopyPass(); @@ -86,6 +87,7 @@ registerPassManagerTestPass(); registerPatternsTestPass(); registerPrintOpAvailabilityPass(); + registerSideEffectTestPasses(); registerSimpleParametricTilingPass(); registerSymbolTestPasses(); registerTestAffineDataCopyPass();