diff --git a/mlir/docs/Rationale/SideEffectsAndSpeculation.md b/mlir/docs/Rationale/SideEffectsAndSpeculation.md --- a/mlir/docs/Rationale/SideEffectsAndSpeculation.md +++ b/mlir/docs/Rationale/SideEffectsAndSpeculation.md @@ -47,7 +47,10 @@ `longjmp`, operations that throw exceptions. Finally, a given operation may have a combination of the above implicit -behaviors. +behaviors. The combination of implicit behaviors during the execution of the +operation may be ordered. We use 'stage' to label the order of implicit +behaviors during the execution of 'op'. Implicit behaviors with a lower stage +number happen earlier than those with a higher stage number. ## Modeling @@ -76,6 +79,10 @@ 1. Does it read from or write to the heap or stack? It should probably implement `MemoryEffectsOpInterface`. +1. Does these side effects ordered? It should probably set the stage of + side effects to make analysis more accurate. +1. Does These side effects act on every single value of resource? If not, it + should set the effectOnFullRegion field with false. 1. Does it have side effects that must be preserved, like a volatile store or a syscall? It should probably implement `MemoryEffectsOpInterface` and model the effect as a read from or write to an abstract `Resource`. Please start an @@ -91,3 +98,80 @@ 1. Is your operation free of side effects and can be freely hoisted, introduced and eliminated? It should probably be marked `Pure`. (TODO: revisit this name since it has overloaded meanings in C++.) + +## Examples + +This section describes a few very simple examples that help understand how to +add side effect correctly. + +### SIMD compute operation + +If we have a SIMD backend dialect and have a "simd.abs" operation, which read +all value in source memref, calculate its absolute value and write to target +memref. + +```mlir + func.func @abs(%source : memref<10xf32>, %target : memref<10xf32>) { + simd.abs(%source, %target) : memref<10xf32> to memref<10xf32> + return + } +``` + +The abs operation reads every single value from the source resource and +then writes these values to every single value in the target resource. +Therefore, we need to specify a read side effect for the source and a write side +effect for the target. The read side effect occurs before the write side effect, +so we need to mark the read stage as earlier than the write stage. Additionally, +we need to mark these side effects as acting on every single value in the +resource. + +A typical approach is as follows: +``` mlir + def AbsOp : SIMD_Op<"abs", [...] { + ... + + let arguments = (ins Arg]>:$source, + Arg]>:$target); + + ... + } +``` + +In the above example, we added the side effect [MemReadAt<0>] to the source, +indicating abs operation reads every single value from source in stage 0. +[MemReadAt<0>] is a shorthand notation for [MemReadAt<0, true>]. We added the +side effect [MemWriteAt<0>] to the target, indicating abs operation writes on +every single value inside the target on stage 1(after read from source). + +### Load like operation + +Memref.load is a typical load like operation: +```mlir + func.func @foo(%input : memref<10xf32>, %index : index) -> f32 { + %result = memref.load %input[index] : memref<10xf32> + return %result : f32 + } +``` + +The load like operation read one value from input memref and return it. +Therefore, we needs to specify a read side effect for input memref, and mark +not every single value is used. + +A typical approach is as follows: +``` mlir + def LoadOp : MemRef_Op<"load", [...] { + ... + + let arguments = (ins Arg]>:$memref, + Variadic:$indices, + DefaultValuedOptionalAttr:$nontemporal); + + ... + } +``` + +In the above example, we added the side effect [MemReadAt<0, false>] to the +source, indicating load operation read parts of value from memref at stage 0. diff --git a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td --- a/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td +++ b/mlir/include/mlir/Dialect/MemRef/IR/MemRefOps.td @@ -1207,7 +1207,7 @@ }]; let arguments = (ins Arg:$memref, + [MemReadAt<0, false>]>:$memref, Variadic:$indices, DefaultValuedOptionalAttr:$nontemporal); let results = (outs AnyType:$result); @@ -1791,7 +1791,7 @@ let arguments = (ins AnyType:$value, Arg:$memref, + [MemWriteAt<0, false>]>:$memref, Variadic:$indices, DefaultValuedOptionalAttr:$nontemporal); diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaceBase.td b/mlir/include/mlir/Interfaces/SideEffectInterfaceBase.td --- a/mlir/include/mlir/Interfaces/SideEffectInterfaceBase.td +++ b/mlir/include/mlir/Interfaces/SideEffectInterfaceBase.td @@ -152,7 +152,8 @@ // This class is the general base side effect class. This is used by derived // effect interfaces to define their effects. class SideEffect : OpVariableDecorator { + Resource resourceReference, int effectStage, bit fullRegion> + : OpVariableDecorator { /// The name of the base effects class. string baseEffectName = interface.baseEffectName; @@ -167,6 +168,13 @@ /// The resource that the effect is being applied to. string resource = resourceReference.name; + + /// The stage of side effects, we use it to describe the sequence in which + /// effects occur. + int stage = effectStage; + + // Does this side effect act on every single value of resource. + bit effectOnFullRegion = fullRegion; } // This class is the base used for specifying effects applied to an operation. diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h --- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h +++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h @@ -139,36 +139,74 @@ class EffectInstance { public: EffectInstance(EffectT *effect, Resource *resource = DefaultResource::get()) - : effect(effect), resource(resource) {} + : effect(effect), resource(resource), stage(0), effectOnFullRegion(true) { + } + EffectInstance(EffectT *effect, int stage, bool effectOnFullRegion, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), stage(stage), + effectOnFullRegion(effectOnFullRegion) {} EffectInstance(EffectT *effect, Value value, Resource *resource = DefaultResource::get()) - : effect(effect), resource(resource), value(value) {} + : effect(effect), resource(resource), value(value), stage(0), + effectOnFullRegion(true) {} + EffectInstance(EffectT *effect, Value value, int stage, + bool effectOnFullRegion, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), value(value), stage(stage), + effectOnFullRegion(effectOnFullRegion) {} EffectInstance(EffectT *effect, SymbolRefAttr symbol, Resource *resource = DefaultResource::get()) - : effect(effect), resource(resource), value(symbol) {} + : effect(effect), resource(resource), value(symbol), stage(0), + effectOnFullRegion(true) {} + EffectInstance(EffectT *effect, SymbolRefAttr symbol, int stage, + bool effectOnFullRegion, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), value(symbol), stage(stage), + effectOnFullRegion(effectOnFullRegion) {} EffectInstance(EffectT *effect, Attribute parameters, Resource *resource = DefaultResource::get()) - : effect(effect), resource(resource), parameters(parameters) {} + : effect(effect), resource(resource), parameters(parameters), stage(0), + effectOnFullRegion(true) {} + EffectInstance(EffectT *effect, Attribute parameters, int stage, + bool effectOnFullRegion, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), parameters(parameters), + stage(stage), effectOnFullRegion(effectOnFullRegion) {} EffectInstance(EffectT *effect, Value value, Attribute parameters, Resource *resource = DefaultResource::get()) : effect(effect), resource(resource), value(value), - parameters(parameters) {} + parameters(parameters), stage(0), effectOnFullRegion(true) {} + EffectInstance(EffectT *effect, Value value, Attribute parameters, int stage, + bool effectOnFullRegion, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), value(value), + parameters(parameters), stage(stage), + effectOnFullRegion(effectOnFullRegion) {} EffectInstance(EffectT *effect, SymbolRefAttr symbol, Attribute parameters, Resource *resource = DefaultResource::get()) : effect(effect), resource(resource), value(symbol), - parameters(parameters) {} + parameters(parameters), stage(0), effectOnFullRegion(true) {} + EffectInstance(EffectT *effect, SymbolRefAttr symbol, Attribute parameters, + int stage, bool effectOnFullRegion, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), value(symbol), + parameters(parameters), stage(stage), + effectOnFullRegion(effectOnFullRegion) {} /// 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 ? llvm::dyn_cast_if_present(value) : Value(); } + Value getValue() const { + return value ? llvm::dyn_cast_if_present(value) : Value(); + } /// Return the symbol reference the effect is applied on, or nullptr if there /// isn't a known smbol being affected. SymbolRefAttr getSymbolRef() const { - return value ? llvm::dyn_cast_if_present(value) : SymbolRefAttr(); + return value ? llvm::dyn_cast_if_present(value) + : SymbolRefAttr(); } /// Return the resource that the effect applies to. @@ -177,6 +215,12 @@ /// Return the parameters of the effect, if any. Attribute getParameters() const { return parameters; } + /// Return the effect happen stage. + int getStage() const { return stage; } + + /// Return if this side effect act on every single value of resource. + bool getEffectOnFullRegion() const { return effectOnFullRegion; } + private: /// The specific effect being applied. EffectT *effect; @@ -191,6 +235,13 @@ /// type-safe structured storage and context-based uniquing. Concrete effects /// can use this at their convenience. This is optionally null. Attribute parameters; + + // The stage side effect happen. Side effect with a lower stage + // number happen earlier than those with a higher stage number + int stage; + + // Does this side effect act on every single value of resource. + bool effectOnFullRegion; }; } // namespace SideEffects diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.td b/mlir/include/mlir/Interfaces/SideEffectInterfaces.td --- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.td +++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.td @@ -34,8 +34,10 @@ } // The base class for defining specific memory effects. -class MemoryEffect - : SideEffect; +class MemoryEffect + : SideEffect; // This class represents the trait for memory effects that may be placed on // operations. @@ -48,30 +50,39 @@ // 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<"::mlir::MemoryEffects::Allocate", resource>; -def MemAlloc : MemAlloc; +class MemAlloc + : MemoryEffect<"::mlir::MemoryEffects::Allocate", resource, stage, + fullRegion>; +def MemAlloc : MemAlloc; +class MemAllocAt + : 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<"::mlir::MemoryEffects::Free", resource>; -def MemFree : MemFree; +class MemFree + : MemoryEffect<"::mlir::MemoryEffects::Free", resource, stage, fullRegion>; +def MemFree : MemFree; +class MemFreeAt + : 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<"::mlir::MemoryEffects::Read", resource>; -def MemRead : MemRead; +class MemRead + : MemoryEffect<"::mlir::MemoryEffects::Read", resource, stage, fullRegion>; +def MemRead : MemRead; +class MemReadAt + : 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<"::mlir::MemoryEffects::Write", resource>; -def MemWrite : MemWrite; +class MemWrite + : MemoryEffect<"::mlir::MemoryEffects::Write", resource, stage, fullRegion>; +def MemWrite : MemWrite; +class MemWriteAt + : MemWrite; //===----------------------------------------------------------------------===// // Effect Traits diff --git a/mlir/include/mlir/TableGen/SideEffects.h b/mlir/include/mlir/TableGen/SideEffects.h --- a/mlir/include/mlir/TableGen/SideEffects.h +++ b/mlir/include/mlir/TableGen/SideEffects.h @@ -35,6 +35,12 @@ // Return the name of the resource class. StringRef getResource() const; + // Return the stage of the effect happen. + int64_t getStage() const; + + // Return if this side effect act on every single value of resource. + bool getEffectOnfullRegion() const; + static bool classof(const Operator::VariableDecorator *var); }; diff --git a/mlir/lib/TableGen/SideEffects.cpp b/mlir/lib/TableGen/SideEffects.cpp --- a/mlir/lib/TableGen/SideEffects.cpp +++ b/mlir/lib/TableGen/SideEffects.cpp @@ -36,6 +36,12 @@ return def->getValueAsString("resource"); } +int64_t SideEffect::getStage() const { return def->getValueAsInt("stage"); } + +bool SideEffect::getEffectOnfullRegion() const { + return def->getValueAsBit("effectOnFullRegion"); +} + bool SideEffect::classof(const Operator::VariableDecorator *var) { return var->getDef().isSubClassOf("SideEffect"); } diff --git a/mlir/test/lib/Dialect/Test/TestInterfaces.td b/mlir/test/lib/Dialect/Test/TestInterfaces.td --- a/mlir/test/lib/Dialect/Test/TestInterfaces.td +++ b/mlir/test/lib/Dialect/Test/TestInterfaces.td @@ -127,7 +127,7 @@ } class TestEffect - : SideEffect; + : SideEffect; class TestEffects effects = []> : SideEffectsTraitBase; diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td --- a/mlir/test/lib/Dialect/Test/TestOps.td +++ b/mlir/test/lib/Dialect/Test/TestOps.td @@ -2407,11 +2407,11 @@ Arg, "", [MemRead]>:$optional_symbol ); - let results = (outs Res]>); + let results = (outs Res]>); } def TestEffectsOpB : TEST_Op<"op_with_effects_b", - [MemoryEffects<[MemWrite]>]>; + [MemoryEffects<[MemWrite]>]>; def TestEffectsRead : TEST_Op<"op_with_memread", [MemoryEffects<[MemRead]>]> { diff --git a/mlir/test/mlir-tblgen/op-side-effects.td b/mlir/test/mlir-tblgen/op-side-effects.td --- a/mlir/test/mlir-tblgen/op-side-effects.td +++ b/mlir/test/mlir-tblgen/op-side-effects.td @@ -13,25 +13,28 @@ def SideEffectOpA : TEST_Op<"side_effect_op_a"> { let arguments = (ins Arg, "", [MemRead]>, + Arg]>, Arg:$symbol, Arg:$flat_symbol, Arg, "", [MemRead]>:$optional_symbol ); - let results = (outs Res]>); + let results = (outs Res]>); } def SideEffectOpB : TEST_Op<"side_effect_op_b", - [MemoryEffects<[MemWrite]>]>; + [MemoryEffects<[MemWrite]>]>; // CHECK: void SideEffectOpA::getEffects // CHECK: for (::mlir::Value value : getODSOperands(0)) -// CHECK: effects.emplace_back(::mlir::MemoryEffects::Read::get(), value, ::mlir::SideEffects::DefaultResource::get()); -// CHECK: effects.emplace_back(::mlir::MemoryEffects::Read::get(), getSymbolAttr(), ::mlir::SideEffects::DefaultResource::get()); -// CHECK: effects.emplace_back(::mlir::MemoryEffects::Write::get(), getFlatSymbolAttr(), ::mlir::SideEffects::DefaultResource::get()); +// CHECK: effects.emplace_back(::mlir::MemoryEffects::Read::get(), value, 0, true, ::mlir::SideEffects::DefaultResource::get()); +// CHECK: for (::mlir::Value value : getODSOperands(1)) +// CHECK: effects.emplace_back(::mlir::MemoryEffects::Write::get(), value, 1, false, ::mlir::SideEffects::DefaultResource::get()); +// CHECK: effects.emplace_back(::mlir::MemoryEffects::Read::get(), getSymbolAttr(), 0, true, ::mlir::SideEffects::DefaultResource::get()); +// CHECK: effects.emplace_back(::mlir::MemoryEffects::Write::get(), getFlatSymbolAttr(), 0, true, ::mlir::SideEffects::DefaultResource::get()); // CHECK: if (auto symbolRef = getOptionalSymbolAttr()) -// CHECK: effects.emplace_back(::mlir::MemoryEffects::Read::get(), symbolRef, ::mlir::SideEffects::DefaultResource::get()); +// CHECK: effects.emplace_back(::mlir::MemoryEffects::Read::get(), symbolRef, 0, true, ::mlir::SideEffects::DefaultResource::get()); // CHECK: for (::mlir::Value value : getODSResults(0)) -// CHECK: effects.emplace_back(::mlir::MemoryEffects::Allocate::get(), value, CustomResource::get()); +// CHECK: effects.emplace_back(::mlir::MemoryEffects::Allocate::get(), value, 0, true, CustomResource::get()); // CHECK: void SideEffectOpB::getEffects -// CHECK: effects.emplace_back(::mlir::MemoryEffects::Write::get(), CustomResource::get()); +// CHECK: effects.emplace_back(::mlir::MemoryEffects::Write::get(), 0, true, CustomResource::get()); diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp --- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp +++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp @@ -3194,9 +3194,11 @@ // The code used to add an effect instance. // {0}: The effect class. // {1}: Optional value or symbol reference. - // {1}: The resource class. + // {2}: The side effect stage. + // {3}: Does this side effect act on every single value of resource. + // {4}: The resource class. const char *addEffectCode = - " effects.emplace_back({0}::get(), {1}{2}::get());\n"; + " effects.emplace_back({0}::get(), {1}{2}, {3}, {4}::get());\n"; for (auto &it : interfaceEffects) { // Generate the 'getEffects' method. @@ -3213,20 +3215,25 @@ for (auto &location : it.second) { StringRef effect = location.effect.getName(); StringRef resource = location.effect.getResource(); + int stage = (int)location.effect.getStage(); + bool effectOnFullRegion = (int)location.effect.getEffectOnfullRegion(); if (location.kind == EffectKind::Static) { // A static instance has no attached value. - body << llvm::formatv(addEffectCode, effect, "", resource).str(); + body << llvm::formatv(addEffectCode, effect, "", stage, + effectOnFullRegion, resource) + .str(); } else if (location.kind == EffectKind::Symbol) { // A symbol reference requires adding the proper attribute. const auto *attr = op.getArg(location.index).get(); std::string argName = op.getGetterName(attr->name); if (attr->attr.isOptional()) { body << " if (auto symbolRef = " << argName << "Attr())\n " - << llvm::formatv(addEffectCode, effect, "symbolRef, ", resource) + << llvm::formatv(addEffectCode, effect, "symbolRef, ", stage, + effectOnFullRegion, resource) .str(); } else { body << llvm::formatv(addEffectCode, effect, argName + "Attr(), ", - resource) + stage, effectOnFullRegion, resource) .str(); } } else { @@ -3234,7 +3241,9 @@ body << " for (::mlir::Value value : getODS" << (location.kind == EffectKind::Operand ? "Operands" : "Results") << "(" << location.index << "))\n " - << llvm::formatv(addEffectCode, effect, "value, ", resource).str(); + << llvm::formatv(addEffectCode, effect, "value, ", stage, + effectOnFullRegion, resource) + .str(); } } }