From 052d24af2913e8e2a0183bcab8af9c8c9cad94f1 Mon Sep 17 00:00:00 2001 From: Alex Zinenko Date: Sun, 15 Nov 2020 15:25:01 +0100 Subject: [PATCH] [mlir] Introduce support for parametric side-effects The side effect infrastructure is based on the Effect and Resource class templates, instances of instantiations of which are constructed as thread-local singletons. With this scheme, it is impossible to further parameterize either of those, or the EffectInstance class that contains pointers to an Effect and Resource instances. Such a parameterization is necessary to express more detailed side effects, e.g. those of a loop or a function call with affine operations inside where it is possible to precisely specify the slices of accessed buffers. Include an additional Attribute to EffectInstance class for further parameterization. This allows to leverage the dialect-specific registration and uniquing capabilities of the attribute infrastructure without requiring Effect or Resource instantiations to be attached to a dialect themselves. Split out the generic part of the side effect Tablegen classes into a separate file to avoid generating built-in MemoryEffect interfaces when processing any .td file that includes SideEffectInterfaceBase.td. Reviewed By: rriddle Differential Revision: https://reviews.llvm.org/D91493 --- .../Interfaces/SideEffectInterfaceBase.td | 166 ++++++++++++++++++ .../mlir/Interfaces/SideEffectInterfaces.h | 20 ++- .../mlir/Interfaces/SideEffectInterfaces.td | 150 +--------------- mlir/test/IR/test-side-effects.mlir | 7 + mlir/test/lib/Dialect/Test/CMakeLists.txt | 3 + mlir/test/lib/Dialect/Test/TestDialect.cpp | 10 ++ mlir/test/lib/Dialect/Test/TestDialect.h | 3 +- mlir/test/lib/Dialect/Test/TestInterfaces.cpp | 8 + mlir/test/lib/Dialect/Test/TestInterfaces.h | 37 ++++ mlir/test/lib/Dialect/Test/TestInterfaces.td | 21 ++- mlir/test/lib/Dialect/Test/TestOps.td | 4 +- mlir/test/lib/IR/TestSideEffects.cpp | 14 ++ 12 files changed, 288 insertions(+), 155 deletions(-) create mode 100644 mlir/include/mlir/Interfaces/SideEffectInterfaceBase.td create mode 100644 mlir/test/lib/Dialect/Test/TestInterfaces.cpp create mode 100644 mlir/test/lib/Dialect/Test/TestInterfaces.h diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaceBase.td b/mlir/include/mlir/Interfaces/SideEffectInterfaceBase.td new file mode 100644 index 000000000000..41b07bcedc35 --- /dev/null +++ b/mlir/include/mlir/Interfaces/SideEffectInterfaceBase.td @@ -0,0 +1,166 @@ +//===-- SideEffectInterfaceBase.td - Side Effect Base ------*- 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 base class definitions for side effect interfaces, i.e. +// the customizable interfaces that provide information about which effects are +// applied by an operation. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_INTERFACES_SIDEEFFECTS_BASE +#define MLIR_INTERFACES_SIDEEFFECTS_BASE + +include "mlir/IR/OpBase.td" + +//===----------------------------------------------------------------------===// +// Resource Bindings +//===----------------------------------------------------------------------===// + +// A generic resource that can be attached to a general base side effect. +class Resource { + /// The resource that the associated effect is being applied to. + string name = resourceName; +} + +// An intrinsic resource that lives in the ::mlir::SideEffects namespace. +class IntrinsicResource : + Resource { +} + +// A link to the DefaultResource class. +def DefaultResource : IntrinsicResource<"DefaultResource">; +// A link to the AutomaticAllocationScopeResource class. +def AutomaticAllocationScopeResource : + IntrinsicResource<"AutomaticAllocationScopeResource">; + +//===----------------------------------------------------------------------===// +// 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<::mlir::SideEffects::EffectInstance<" + # baseEffect # ">> &":$effects) + >, + ]; + + let extraClassDeclaration = [{ + /// Collect all of the effect instances that correspond to the given + /// `Effect` and place them in 'effects'. + template void getEffects( + SmallVectorImpl<::mlir::SideEffects::EffectInstance< + }] # baseEffect # [{>> &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 true 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 true if this operation has no effects. + bool hasNoEffect() { + SmallVector<::mlir::SideEffects::EffectInstance<}] # baseEffect # [{>, 4> effects; + getEffects(effects); + return effects.empty(); + } + + /// Returns true if the given operation has no effects for this interface. + static bool hasNoEffect(Operation *op) { + if (auto interface = dyn_cast<}] # name # [{>(op)) + return interface.hasNoEffect(); + return op->hasTrait<::mlir::OpTrait::HasRecursiveSideEffects>(); + } + + /// Collect all of the effect instances that operate on the provided value + /// and place them in 'effects'. + void getEffectsOnValue(::mlir::Value value, + llvm::SmallVectorImpl<::mlir::SideEffects::EffectInstance< + }] # baseEffect # [{>> & effects) { + getEffects(effects); + llvm::erase_if(effects, [&](auto &it) { return it.getValue() != value; }); + } + + /// Collect all of the effect instances that operate on the provided + /// resource and place them in 'effects'. + void getEffectsOnValue(::mlir::SideEffects::Resource *resource, + llvm::SmallVectorImpl<::mlir::SideEffects::EffectInstance< + }] # baseEffect # [{>> & effects) { + getEffects(effects); + llvm::erase_if(effects, [&](auto &it) { + return it.getResource() != resource; + }); + } + }]; + + // 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 : OpVariableDecorator { + /// The name of the base effects class. + string baseEffectName = interface.baseEffectName; + + /// The parent interface that the effect belongs to. + string interfaceTrait = interface.trait; + + /// The cpp namespace of the interface trait. + string cppNamespace = interface.cppNamespace; + + /// The derived effect that is being applied. + string effect = effectName; + + /// The resource that the effect is being applied to. + string resource = resourceReference.name; +} + +// 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 cpp namespace of the interface trait. + string cppNamespace = parentInterface.cppNamespace; + + /// The name of the base effects class. + string baseEffectName = parentInterface.baseEffectName; + + /// The derived effects being applied. + list effects = staticEffects; +} + +#endif // MLIR_INTERFACES_SIDEEFFECTS_BASE diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h index 181d218838ff..c19f7f4f03ee 100644 --- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h +++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h @@ -131,8 +131,9 @@ struct AutomaticAllocationScopeResource /// 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. +/// applied, an optional value (either operand, result, or region entry +/// argument) that the effect is applied to, and an optional parameters +/// attribute further specifying the details of the effect. template class EffectInstance { public: EffectInstance(EffectT *effect, Resource *resource = DefaultResource::get()) @@ -140,6 +141,13 @@ public: EffectInstance(EffectT *effect, Value value, Resource *resource = DefaultResource::get()) : effect(effect), resource(resource), value(value) {} + EffectInstance(EffectT *effect, Attribute parameters, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), parameters(parameters) {} + EffectInstance(EffectT *effect, Value value, Attribute parameters, + Resource *resource = DefaultResource::get()) + : effect(effect), resource(resource), value(value), + parameters(parameters) {} /// Return the effect being applied. EffectT *getEffect() const { return effect; } @@ -151,6 +159,9 @@ public: /// Return the resource that the effect applies to. Resource *getResource() const { return resource; } + /// Return the parameters of the effect, if any. + Attribute getParameters() const { return parameters; } + private: /// The specific effect being applied. EffectT *effect; @@ -160,6 +171,11 @@ private: /// The value that the effect applies to. This is optionally null. Value value; + + /// Additional parameters of the effect instance. An attribute is used for + /// type-safe structured storage and context-based uniquing. Concrete effects + /// can use this at their convenience. This is optionally null. + Attribute parameters; }; } // namespace SideEffects diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.td b/mlir/include/mlir/Interfaces/SideEffectInterfaces.td index 4f26cb98a41b..9c4aee895c26 100644 --- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.td +++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.td @@ -1,4 +1,4 @@ -//===-- SideEffectInterfaces.td - Side Effect Interfaces ------------*- tablegen -*-===// +//===-- SideEffectInterfaces.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. @@ -14,153 +14,7 @@ #ifndef MLIR_INTERFACES_SIDEEFFECTS #define MLIR_INTERFACES_SIDEEFFECTS -include "mlir/IR/OpBase.td" - -//===----------------------------------------------------------------------===// -// Resource Bindings -//===----------------------------------------------------------------------===// - -// A generic resource that can be attached to a general base side effect. -class Resource { - /// The resource that the associated effect is being applied to. - string name = resourceName; -} - -// An intrinsic resource that lives in the ::mlir::SideEffects namespace. -class IntrinsicResource : - Resource { -} - -// A link to the DefaultResource class. -def DefaultResource : IntrinsicResource<"DefaultResource">; -// A link to the AutomaticAllocationScopeResource class. -def AutomaticAllocationScopeResource : - IntrinsicResource<"AutomaticAllocationScopeResource">; - -//===----------------------------------------------------------------------===// -// 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<::mlir::SideEffects::EffectInstance<" - # baseEffect # ">> &":$effects) - >, - ]; - - let extraClassDeclaration = [{ - /// Collect all of the effect instances that correspond to the given - /// `Effect` and place them in 'effects'. - template void getEffects( - SmallVectorImpl<::mlir::SideEffects::EffectInstance< - }] # baseEffect # [{>> &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 true 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 true if this operation has no effects. - bool hasNoEffect() { - SmallVector<::mlir::SideEffects::EffectInstance<}] # baseEffect # [{>, 4> effects; - getEffects(effects); - return effects.empty(); - } - - /// Returns true if the given operation has no effects for this interface. - static bool hasNoEffect(Operation *op) { - if (auto interface = dyn_cast<}] # name # [{>(op)) - return interface.hasNoEffect(); - return op->hasTrait<::mlir::OpTrait::HasRecursiveSideEffects>(); - } - - /// Collect all of the effect instances that operate on the provided value - /// and place them in 'effects'. - void getEffectsOnValue(::mlir::Value value, - llvm::SmallVectorImpl<::mlir::SideEffects::EffectInstance< - }] # baseEffect # [{>> & effects) { - getEffects(effects); - llvm::erase_if(effects, [&](auto &it) { return it.getValue() != value; }); - } - - /// Collect all of the effect instances that operate on the provided - /// resource and place them in 'effects'. - void getEffectsOnValue(::mlir::SideEffects::Resource *resource, - llvm::SmallVectorImpl<::mlir::SideEffects::EffectInstance< - }] # baseEffect # [{>> & effects) { - getEffects(effects); - llvm::erase_if(effects, [&](auto &it) { - return it.getResource() != resource; - }); - } - }]; - - // 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 : OpVariableDecorator { - /// The name of the base effects class. - string baseEffectName = interface.baseEffectName; - - /// The parent interface that the effect belongs to. - string interfaceTrait = interface.trait; - - /// The cpp namespace of the interface trait. - string cppNamespace = interface.cppNamespace; - - /// The derived effect that is being applied. - string effect = effectName; - - /// The resource that the effect is being applied to. - string resource = resourceReference.name; -} - -// 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 cpp namespace of the interface trait. - string cppNamespace = parentInterface.cppNamespace; - - /// The name of the base effects class. - string baseEffectName = parentInterface.baseEffectName; - - /// The derived effects being applied. - list effects = staticEffects; -} +include "mlir/Interfaces/SideEffectInterfaceBase.td" //===----------------------------------------------------------------------===// // MemoryEffects diff --git a/mlir/test/IR/test-side-effects.mlir b/mlir/test/IR/test-side-effects.mlir index 6832483ff353..ca2e32c9a768 100644 --- a/mlir/test/IR/test-side-effects.mlir +++ b/mlir/test/IR/test-side-effects.mlir @@ -18,3 +18,10 @@ %3 = "test.side_effect_op"() {effects = [ {effect="allocate", on_result, test_resource} ]} : () -> i32 + +// No _memory_ effects, but a parametric test effect. +// expected-remark@+2 {{operation has no memory effects}} +// expected-remark@+1 {{found a parametric effect with affine_map<(d0, d1) -> (d1, d0)>}} +%4 = "test.side_effect_op"() { + effect_parameter = affine_map<(i, j) -> (j, i)> +} : () -> i32 diff --git a/mlir/test/lib/Dialect/Test/CMakeLists.txt b/mlir/test/lib/Dialect/Test/CMakeLists.txt index d1d84f9ce83a..895b7755029c 100644 --- a/mlir/test/lib/Dialect/Test/CMakeLists.txt +++ b/mlir/test/lib/Dialect/Test/CMakeLists.txt @@ -7,6 +7,8 @@ set(LLVM_OPTIONAL_SOURCES set(LLVM_TARGET_DEFINITIONS TestInterfaces.td) mlir_tablegen(TestTypeInterfaces.h.inc -gen-type-interface-decls) mlir_tablegen(TestTypeInterfaces.cpp.inc -gen-type-interface-defs) +mlir_tablegen(TestOpInterfaces.h.inc -gen-op-interface-decls) +mlir_tablegen(TestOpInterfaces.cpp.inc -gen-op-interface-defs) add_public_tablegen_target(MLIRTestInterfaceIncGen) set(LLVM_TARGET_DEFINITIONS TestTypeDefs.td) @@ -29,6 +31,7 @@ add_public_tablegen_target(MLIRTestOpsIncGen) # Exclude tests from libMLIR.so add_mlir_library(MLIRTestDialect TestDialect.cpp + TestInterfaces.cpp TestPatterns.cpp TestTraits.cpp TestTypes.cpp diff --git a/mlir/test/lib/Dialect/Test/TestDialect.cpp b/mlir/test/lib/Dialect/Test/TestDialect.cpp index d445644a2435..e71fceb9fa3d 100644 --- a/mlir/test/lib/Dialect/Test/TestDialect.cpp +++ b/mlir/test/lib/Dialect/Test/TestDialect.cpp @@ -758,6 +758,15 @@ void SideEffectOp::getEffects( } } +void SideEffectOp::getEffects( + SmallVectorImpl &effects) { + auto effectsAttr = getAttrOfType("effect_parameter"); + if (!effectsAttr) + return; + + effects.emplace_back(TestEffects::Concrete::get(), effectsAttr); +} + //===----------------------------------------------------------------------===// // StringAttrPrettyNameOp //===----------------------------------------------------------------------===// @@ -911,6 +920,7 @@ void RegionIfOp::getSuccessorRegions( } #include "TestOpEnums.cpp.inc" +#include "TestOpInterfaces.cpp.inc" #include "TestOpStructs.cpp.inc" #include "TestTypeInterfaces.cpp.inc" diff --git a/mlir/test/lib/Dialect/Test/TestDialect.h b/mlir/test/lib/Dialect/Test/TestDialect.h index 84ec1fb00d59..28572ec0acaf 100644 --- a/mlir/test/lib/Dialect/Test/TestDialect.h +++ b/mlir/test/lib/Dialect/Test/TestDialect.h @@ -14,6 +14,7 @@ #ifndef MLIR_TESTDIALECT_H #define MLIR_TESTDIALECT_H +#include "TestInterfaces.h" #include "mlir/Dialect/Traits.h" #include "mlir/IR/BuiltinDialect.h" #include "mlir/IR/Dialect.h" @@ -30,7 +31,7 @@ #include "mlir/Interfaces/SideEffectInterfaces.h" #include "TestOpEnums.h.inc" - +#include "TestOpInterfaces.h.inc" #include "TestOpStructs.h.inc" #include "TestOpsDialect.h.inc" diff --git a/mlir/test/lib/Dialect/Test/TestInterfaces.cpp b/mlir/test/lib/Dialect/Test/TestInterfaces.cpp new file mode 100644 index 000000000000..64ec82ecb24f --- /dev/null +++ b/mlir/test/lib/Dialect/Test/TestInterfaces.cpp @@ -0,0 +1,8 @@ +#include "TestInterfaces.h" + +using namespace mlir; + +bool mlir::TestEffects::Effect::classof( + const mlir::SideEffects::Effect *effect) { + return isa(effect); +} diff --git a/mlir/test/lib/Dialect/Test/TestInterfaces.h b/mlir/test/lib/Dialect/Test/TestInterfaces.h new file mode 100644 index 000000000000..3239584a9332 --- /dev/null +++ b/mlir/test/lib/Dialect/Test/TestInterfaces.h @@ -0,0 +1,37 @@ +//===- TestInterfaces.h - MLIR interfaces for testing -----------*- C++ -*-===// +// +// 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 declares interfaces for the 'test' dialect that can be used for +// testing the interface infrastructure. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_TEST_LIB_DIALECT_TEST_TESTINTERFACES_H +#define MLIR_TEST_LIB_DIALECT_TEST_TESTINTERFACES_H + +#include "mlir/Interfaces/SideEffectInterfaces.h" + +namespace mlir { +namespace TestEffects { +struct Effect : public SideEffects::Effect { + using SideEffects::Effect::Effect; + + template + using Base = SideEffects::Effect::Base; + + static bool classof(const SideEffects::Effect *effect); +}; + +using EffectInstance = SideEffects::EffectInstance; + +struct Concrete : public Effect::Base {}; + +} // namespace TestEffects +} // namespace mlir + +#endif // MLIR_TEST_LIB_DIALECT_TEST_TESTINTERFACES_H diff --git a/mlir/test/lib/Dialect/Test/TestInterfaces.td b/mlir/test/lib/Dialect/Test/TestInterfaces.td index 19a779d0a81c..d4c9650e8512 100644 --- a/mlir/test/lib/Dialect/Test/TestInterfaces.td +++ b/mlir/test/lib/Dialect/Test/TestInterfaces.td @@ -6,10 +6,11 @@ // //===----------------------------------------------------------------------===// -#ifndef TEST_INTERFACES -#define TEST_INTERFACES +#ifndef MLIR_TEST_DIALECT_TEST_INTERFACES +#define MLIR_TEST_DIALECT_TEST_INTERFACES include "mlir/IR/OpBase.td" +include "mlir/Interfaces/SideEffectInterfaceBase.td" // A type interface used to test the ODS generation of type interfaces. def TestTypeInterface : TypeInterface<"TestTypeInterface"> { @@ -52,4 +53,18 @@ def TestTypeInterface : TypeInterface<"TestTypeInterface"> { }]; } -#endif // TEST_INTERFACES +def TestEffectOpInterface + : EffectOpInterfaceBase<"TestEffectOpInterface", + "::mlir::TestEffects::Effect"> { + let cppNamespace = "::mlir"; +} + +class TestEffect + : SideEffect; + +class TestEffects effects = []> + : SideEffectsTraitBase; + +def TestConcreteEffect : TestEffect<"TestEffects::Concrete">; + +#endif // MLIR_TEST_DIALECT_TEST_INTERFACES diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td index a1f3246075dd..aef5b5166ae2 100644 --- a/mlir/test/lib/Dialect/Test/TestOps.td +++ b/mlir/test/lib/Dialect/Test/TestOps.td @@ -18,6 +18,7 @@ include "mlir/Interfaces/ControlFlowInterfaces.td" include "mlir/Interfaces/CopyOpInterface.td" include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/SideEffectInterfaces.td" +include "TestInterfaces.td" def Test_Dialect : Dialect { let name = "test"; @@ -1721,7 +1722,8 @@ def FormatTypesMatchAttrOp : TEST_Op<"format_types_match_attr", [ //===----------------------------------------------------------------------===// def SideEffectOp : TEST_Op<"side_effect_op", - [DeclareOpInterfaceMethods]> { + [DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { let results = (outs AnyType:$result); } diff --git a/mlir/test/lib/IR/TestSideEffects.cpp b/mlir/test/lib/IR/TestSideEffects.cpp index 1ac9e82a7f7e..d9d6aed59215 100644 --- a/mlir/test/lib/IR/TestSideEffects.cpp +++ b/mlir/test/lib/IR/TestSideEffects.cpp @@ -47,6 +47,20 @@ struct SideEffectsPass diag << " on resource '" << instance.getResource()->getName() << "'"; } }); + + SmallVector testEffects; + module.walk([&](TestEffectOpInterface op) { + testEffects.clear(); + op.getEffects(testEffects); + + if (testEffects.empty()) + return; + + for (const TestEffects::EffectInstance &instance : testEffects) { + op.emitRemark() << "found a parametric effect with " + << instance.getParameters(); + } + }); } }; } // end anonymous namespace