2019-05-23 15:11:19 -07:00
|
|
|
//===- TestDialect.cpp - MLIR Dialect for Testing -------------------------===//
|
|
|
|
//
|
2020-01-26 03:58:30 +00:00
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
2019-12-23 09:35:36 -08:00
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
2019-05-23 15:11:19 -07:00
|
|
|
//
|
2019-12-23 09:35:36 -08:00
|
|
|
//===----------------------------------------------------------------------===//
|
2019-05-23 15:11:19 -07:00
|
|
|
|
|
|
|
#include "TestDialect.h"
|
2021-03-03 16:37:32 -08:00
|
|
|
#include "TestAttributes.h"
|
2021-06-02 07:00:19 +08:00
|
|
|
#include "TestInterfaces.h"
|
2020-06-30 15:42:52 -07:00
|
|
|
#include "TestTypes.h"
|
2021-10-12 23:14:57 +00:00
|
|
|
#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
|
2021-03-11 14:33:44 +01:00
|
|
|
#include "mlir/Dialect/DLTI/DLTI.h"
|
2020-02-28 08:37:09 -08:00
|
|
|
#include "mlir/Dialect/StandardOps/IR/Ops.h"
|
2021-06-16 22:12:16 -07:00
|
|
|
#include "mlir/Dialect/Tensor/IR/Tensor.h"
|
2020-11-19 10:43:12 -08:00
|
|
|
#include "mlir/IR/BuiltinOps.h"
|
2020-06-30 15:42:52 -07:00
|
|
|
#include "mlir/IR/DialectImplementation.h"
|
2019-05-23 15:11:19 -07:00
|
|
|
#include "mlir/IR/PatternMatch.h"
|
2019-07-20 03:03:45 -07:00
|
|
|
#include "mlir/IR/TypeUtilities.h"
|
2021-06-02 07:00:19 +08:00
|
|
|
#include "mlir/Reducer/ReductionPatternInterface.h"
|
2019-09-01 20:06:42 -07:00
|
|
|
#include "mlir/Transforms/FoldUtils.h"
|
2019-09-05 12:23:45 -07:00
|
|
|
#include "mlir/Transforms/InliningUtils.h"
|
2019-11-25 11:29:21 -08:00
|
|
|
#include "llvm/ADT/StringSwitch.h"
|
2019-05-23 15:11:19 -07:00
|
|
|
|
2020-09-24 11:54:46 -07:00
|
|
|
// Include this before the using namespace lines below to
|
|
|
|
// test that we don't have namespace dependencies.
|
2021-06-28 22:54:11 +00:00
|
|
|
#include "TestOpsDialect.cpp.inc"
|
|
|
|
|
2020-09-24 11:54:46 -07:00
|
|
|
using namespace mlir;
|
|
|
|
using namespace test;
|
|
|
|
|
|
|
|
void test::registerTestDialect(DialectRegistry ®istry) {
|
Separate the Registration from Loading dialects in the Context
This changes the behavior of constructing MLIRContext to no longer load globally
registered dialects on construction. Instead Dialects are only loaded explicitly
on demand:
- the Parser is lazily loading Dialects in the context as it encounters them
during parsing. This is the only purpose for registering dialects and not load
them in the context.
- Passes are expected to declare the dialects they will create entity from
(Operations, Attributes, or Types), and the PassManager is loading Dialects into
the Context when starting a pipeline.
This changes simplifies the configuration of the registration: a compiler only
need to load the dialect for the IR it will emit, and the optimizer is
self-contained and load the required Dialects. For example in the Toy tutorial,
the compiler only needs to load the Toy dialect in the Context, all the others
(linalg, affine, std, LLVM, ...) are automatically loaded depending on the
optimization pipeline enabled.
To adjust to this change, stop using the existing dialect registration: the
global registry will be removed soon.
1) For passes, you need to override the method:
virtual void getDependentDialects(DialectRegistry ®istry) const {}
and registery on the provided registry any dialect that this pass can produce.
Passes defined in TableGen can provide this list in the dependentDialects list
field.
2) For dialects, on construction you can register dependent dialects using the
provided MLIRContext: `context.getOrLoadDialect<DialectName>()`
This is useful if a dialect may canonicalize or have interfaces involving
another dialect.
3) For loading IR, dialect that can be in the input file must be explicitly
registered with the context. `MlirOptMain()` is taking an explicit registry for
this purpose. See how the standalone-opt.cpp example is setup:
mlir::DialectRegistry registry;
registry.insert<mlir::standalone::StandaloneDialect>();
registry.insert<mlir::StandardOpsDialect>();
Only operations from these two dialects can be in the input file. To include all
of the dialects in MLIR Core, you can populate the registry this way:
mlir::registerAllDialects(registry);
4) For `mlir-translate` callback, as well as frontend, Dialects can be loaded in
the context before emitting the IR: context.getOrLoadDialect<ToyDialect>()
Differential Revision: https://reviews.llvm.org/D85622
2020-08-18 20:01:19 +00:00
|
|
|
registry.insert<TestDialect>();
|
|
|
|
}
|
|
|
|
|
2019-09-01 20:06:42 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// TestDialect Interfaces
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
2019-11-20 10:19:01 -08:00
|
|
|
|
2021-03-11 23:58:02 +00:00
|
|
|
/// Testing the correctness of some traits.
|
|
|
|
static_assert(
|
|
|
|
llvm::is_detected<OpTrait::has_implicit_terminator_t,
|
|
|
|
SingleBlockImplicitTerminatorOp>::value,
|
|
|
|
"has_implicit_terminator_t does not match SingleBlockImplicitTerminatorOp");
|
|
|
|
static_assert(OpTrait::hasSingleBlockImplicitTerminator<
|
|
|
|
SingleBlockImplicitTerminatorOp>::value,
|
|
|
|
"hasSingleBlockImplicitTerminator does not match "
|
|
|
|
"SingleBlockImplicitTerminatorOp");
|
|
|
|
|
2019-11-20 10:19:01 -08:00
|
|
|
// Test support for interacting with the AsmPrinter.
|
|
|
|
struct TestOpAsmInterface : public OpAsmDialectInterface {
|
|
|
|
using OpAsmDialectInterface::OpAsmDialectInterface;
|
|
|
|
|
2021-08-03 17:23:31 +03:00
|
|
|
AliasResult getAlias(Attribute attr, raw_ostream &os) const final {
|
2020-10-30 00:30:59 -07:00
|
|
|
StringAttr strAttr = attr.dyn_cast<StringAttr>();
|
|
|
|
if (!strAttr)
|
2021-08-03 17:23:31 +03:00
|
|
|
return AliasResult::NoAlias;
|
2020-10-30 00:30:59 -07:00
|
|
|
|
|
|
|
// Check the contents of the string attribute to see what the test alias
|
|
|
|
// should be named.
|
|
|
|
Optional<StringRef> aliasName =
|
|
|
|
StringSwitch<Optional<StringRef>>(strAttr.getValue())
|
|
|
|
.Case("alias_test:dot_in_name", StringRef("test.alias"))
|
|
|
|
.Case("alias_test:trailing_digit", StringRef("test_alias0"))
|
|
|
|
.Case("alias_test:prefixed_digit", StringRef("0_test_alias"))
|
|
|
|
.Case("alias_test:sanitize_conflict_a",
|
|
|
|
StringRef("test_alias_conflict0"))
|
|
|
|
.Case("alias_test:sanitize_conflict_b",
|
|
|
|
StringRef("test_alias_conflict0_"))
|
2021-07-07 18:07:51 +03:00
|
|
|
.Case("alias_test:tensor_encoding", StringRef("test_encoding"))
|
2020-10-30 00:30:59 -07:00
|
|
|
.Default(llvm::None);
|
|
|
|
if (!aliasName)
|
2021-08-03 17:23:31 +03:00
|
|
|
return AliasResult::NoAlias;
|
2020-10-30 00:30:59 -07:00
|
|
|
|
|
|
|
os << *aliasName;
|
2021-08-03 17:23:31 +03:00
|
|
|
return AliasResult::FinalAlias;
|
|
|
|
}
|
|
|
|
|
|
|
|
AliasResult getAlias(Type type, raw_ostream &os) const final {
|
|
|
|
if (auto tupleType = type.dyn_cast<TupleType>()) {
|
|
|
|
if (tupleType.size() > 0 &&
|
|
|
|
llvm::all_of(tupleType.getTypes(), [](Type elemType) {
|
|
|
|
return elemType.isa<SimpleAType>();
|
|
|
|
})) {
|
|
|
|
os << "test_tuple";
|
|
|
|
return AliasResult::FinalAlias;
|
|
|
|
}
|
|
|
|
}
|
2021-10-11 14:52:06 +03:00
|
|
|
if (auto intType = type.dyn_cast<TestIntegerType>()) {
|
|
|
|
if (intType.getSignedness() ==
|
|
|
|
TestIntegerType::SignednessSemantics::Unsigned &&
|
|
|
|
intType.getWidth() == 8) {
|
|
|
|
os << "test_ui8";
|
|
|
|
return AliasResult::FinalAlias;
|
|
|
|
}
|
|
|
|
}
|
2021-08-03 17:23:31 +03:00
|
|
|
return AliasResult::NoAlias;
|
2020-10-30 00:30:59 -07:00
|
|
|
}
|
|
|
|
|
2019-11-20 10:19:01 -08:00
|
|
|
void getAsmResultNames(Operation *op,
|
|
|
|
OpAsmSetValueNameFn setNameFn) const final {
|
|
|
|
if (auto asmOp = dyn_cast<AsmDialectInterfaceOp>(op))
|
|
|
|
setNameFn(asmOp, "result");
|
|
|
|
}
|
2019-12-19 22:15:31 -08:00
|
|
|
|
|
|
|
void getAsmBlockArgumentNames(Block *block,
|
|
|
|
OpAsmSetValueNameFn setNameFn) const final {
|
|
|
|
auto op = block->getParentOp();
|
|
|
|
auto arrayAttr = op->getAttrOfType<ArrayAttr>("arg_names");
|
|
|
|
if (!arrayAttr)
|
|
|
|
return;
|
|
|
|
auto args = block->getArguments();
|
|
|
|
auto e = std::min(arrayAttr.size(), args.size());
|
|
|
|
for (unsigned i = 0; i < e; ++i) {
|
2020-02-14 22:54:18 -08:00
|
|
|
if (auto strAttr = arrayAttr[i].dyn_cast<StringAttr>())
|
2019-12-19 22:15:31 -08:00
|
|
|
setNameFn(args[i], strAttr.getValue());
|
|
|
|
}
|
|
|
|
}
|
2019-11-20 10:19:01 -08:00
|
|
|
};
|
|
|
|
|
2020-08-12 22:45:16 +00:00
|
|
|
struct TestDialectFoldInterface : public DialectFoldInterface {
|
|
|
|
using DialectFoldInterface::DialectFoldInterface;
|
2019-09-01 20:06:42 -07:00
|
|
|
|
|
|
|
/// Registered hook to check if the given region, which is attached to an
|
|
|
|
/// operation that is *not* isolated from above, should be used when
|
|
|
|
/// materializing constants.
|
2019-09-07 18:56:39 -07:00
|
|
|
bool shouldMaterializeInto(Region *region) const final {
|
2019-09-01 20:06:42 -07:00
|
|
|
// If this is a one region operation, then insert into it.
|
|
|
|
return isa<OneRegionOp>(region->getParentOp());
|
|
|
|
}
|
|
|
|
};
|
2019-09-05 12:23:45 -07:00
|
|
|
|
|
|
|
/// This class defines the interface for handling inlining with standard
|
|
|
|
/// operations.
|
|
|
|
struct TestInlinerInterface : public DialectInlinerInterface {
|
|
|
|
using DialectInlinerInterface::DialectInlinerInterface;
|
|
|
|
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
// Analysis Hooks
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
|
2020-10-28 21:48:48 -07:00
|
|
|
bool isLegalToInline(Operation *call, Operation *callable,
|
|
|
|
bool wouldBeCloned) const final {
|
2020-10-28 21:48:38 -07:00
|
|
|
// Don't allow inlining calls that are marked `noinline`.
|
|
|
|
return !call->hasAttr("noinline");
|
|
|
|
}
|
2020-10-28 21:48:48 -07:00
|
|
|
bool isLegalToInline(Region *, Region *, bool,
|
|
|
|
BlockAndValueMapping &) const final {
|
2019-10-03 23:04:56 -07:00
|
|
|
// Inlining into test dialect regions is legal.
|
|
|
|
return true;
|
|
|
|
}
|
2020-10-28 21:48:48 -07:00
|
|
|
bool isLegalToInline(Operation *, Region *, bool,
|
2019-09-05 12:23:45 -07:00
|
|
|
BlockAndValueMapping &) const final {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-10-03 23:10:25 -07:00
|
|
|
bool shouldAnalyzeRecursively(Operation *op) const final {
|
2019-09-05 12:23:45 -07:00
|
|
|
// Analyze recursively if this is not a functional region operation, it
|
|
|
|
// froms a separate functional scope.
|
|
|
|
return !isa<FunctionalRegionOp>(op);
|
|
|
|
}
|
|
|
|
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
// Transformation Hooks
|
|
|
|
//===--------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
/// Handle the given inlined terminator by replacing it with a new operation
|
|
|
|
/// as necessary.
|
|
|
|
void handleTerminator(Operation *op,
|
2019-12-23 14:45:01 -08:00
|
|
|
ArrayRef<Value> valuesToRepl) const final {
|
2019-09-05 12:23:45 -07:00
|
|
|
// Only handle "test.return" here.
|
|
|
|
auto returnOp = dyn_cast<TestReturnOp>(op);
|
|
|
|
if (!returnOp)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Replace the values directly with the return operands.
|
|
|
|
assert(returnOp.getNumOperands() == valuesToRepl.size());
|
|
|
|
for (const auto &it : llvm::enumerate(returnOp.getOperands()))
|
2020-01-11 08:54:04 -08:00
|
|
|
valuesToRepl[it.index()].replaceAllUsesWith(it.value());
|
2019-09-05 12:23:45 -07:00
|
|
|
}
|
2019-10-03 23:10:25 -07:00
|
|
|
|
|
|
|
/// Attempt to materialize a conversion for a type mismatch between a call
|
|
|
|
/// from this dialect, and a callable region. This method should generate an
|
|
|
|
/// operation that takes 'input' as the only operand, and produces a single
|
|
|
|
/// result of 'resultType'. If a conversion can not be generated, nullptr
|
|
|
|
/// should be returned.
|
2019-12-23 14:45:01 -08:00
|
|
|
Operation *materializeCallConversion(OpBuilder &builder, Value input,
|
2019-10-03 23:10:25 -07:00
|
|
|
Type resultType,
|
|
|
|
Location conversionLoc) const final {
|
|
|
|
// Only allow conversion for i16/i32 types.
|
2020-01-10 14:48:24 -05:00
|
|
|
if (!(resultType.isSignlessInteger(16) ||
|
|
|
|
resultType.isSignlessInteger(32)) ||
|
|
|
|
!(input.getType().isSignlessInteger(16) ||
|
|
|
|
input.getType().isSignlessInteger(32)))
|
2019-10-03 23:10:25 -07:00
|
|
|
return nullptr;
|
|
|
|
return builder.create<TestCastOp>(conversionLoc, resultType, input);
|
|
|
|
}
|
2021-06-16 12:53:21 -07:00
|
|
|
|
|
|
|
void processInlinedCallBlocks(
|
|
|
|
Operation *call,
|
|
|
|
iterator_range<Region::iterator> inlinedBlocks) const final {
|
|
|
|
if (!isa<ConversionCallOp>(call))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Set attributed on all ops in the inlined blocks.
|
|
|
|
for (Block &block : inlinedBlocks) {
|
|
|
|
block.walk([&](Operation *op) {
|
|
|
|
op->setAttr("inlined_conversion", UnitAttr::get(call->getContext()));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2019-09-05 12:23:45 -07:00
|
|
|
};
|
2021-06-02 07:00:19 +08:00
|
|
|
|
|
|
|
struct TestReductionPatternInterface : public DialectReductionPatternInterface {
|
|
|
|
public:
|
|
|
|
TestReductionPatternInterface(Dialect *dialect)
|
|
|
|
: DialectReductionPatternInterface(dialect) {}
|
|
|
|
|
2021-09-26 22:01:19 +00:00
|
|
|
void populateReductionPatterns(RewritePatternSet &patterns) const final {
|
2021-06-02 07:00:19 +08:00
|
|
|
populateTestReductionPatterns(patterns);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-12-07 18:27:58 +00:00
|
|
|
} // namespace
|
2019-09-01 20:06:42 -07:00
|
|
|
|
2019-05-23 15:11:19 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// TestDialect
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2021-03-24 08:25:25 +00:00
|
|
|
static void testSideEffectOpGetEffect(
|
|
|
|
Operation *op,
|
|
|
|
SmallVectorImpl<SideEffects::EffectInstance<TestEffects::Effect>> &effects);
|
|
|
|
|
|
|
|
// This is the implementation of a dialect fallback for `TestEffectOpInterface`.
|
|
|
|
struct TestOpEffectInterfaceFallback
|
|
|
|
: public TestEffectOpInterface::FallbackModel<
|
|
|
|
TestOpEffectInterfaceFallback> {
|
|
|
|
static bool classof(Operation *op) {
|
|
|
|
bool isSupportedOp =
|
|
|
|
op->getName().getStringRef() == "test.unregistered_side_effect_op";
|
|
|
|
assert(isSupportedOp && "Unexpected dispatch");
|
|
|
|
return isSupportedOp;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
getEffects(Operation *op,
|
|
|
|
SmallVectorImpl<SideEffects::EffectInstance<TestEffects::Effect>>
|
|
|
|
&effects) const {
|
|
|
|
testSideEffectOpGetEffect(op, effects);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-07 02:41:44 +00:00
|
|
|
void TestDialect::initialize() {
|
2021-03-11 11:24:43 -08:00
|
|
|
registerAttributes();
|
|
|
|
registerTypes();
|
2019-05-23 15:11:19 -07:00
|
|
|
addOperations<
|
|
|
|
#define GET_OP_LIST
|
|
|
|
#include "TestOps.cpp.inc"
|
|
|
|
>();
|
2020-08-12 22:45:16 +00:00
|
|
|
addInterfaces<TestOpAsmInterface, TestDialectFoldInterface,
|
2021-06-02 07:00:19 +08:00
|
|
|
TestInlinerInterface, TestReductionPatternInterface>();
|
2019-06-19 13:58:31 -07:00
|
|
|
allowUnknownOperations();
|
2021-03-24 08:25:25 +00:00
|
|
|
|
|
|
|
// Instantiate our fallback op interface that we'll use on specific
|
|
|
|
// unregistered op.
|
|
|
|
fallbackEffectOpInterfaces = new TestOpEffectInterfaceFallback;
|
|
|
|
}
|
|
|
|
TestDialect::~TestDialect() {
|
|
|
|
delete static_cast<TestOpEffectInterfaceFallback *>(
|
|
|
|
fallbackEffectOpInterfaces);
|
2019-05-23 15:11:19 -07:00
|
|
|
}
|
|
|
|
|
2020-12-10 14:13:37 -08:00
|
|
|
Operation *TestDialect::materializeConstant(OpBuilder &builder, Attribute value,
|
|
|
|
Type type, Location loc) {
|
|
|
|
return builder.create<TestOpConstant>(loc, type, value);
|
|
|
|
}
|
|
|
|
|
2021-03-24 08:25:25 +00:00
|
|
|
void *TestDialect::getRegisteredInterfaceForOp(TypeID typeID,
|
|
|
|
OperationName opName) {
|
|
|
|
if (opName.getIdentifier() == "test.unregistered_side_effect_op" &&
|
|
|
|
typeID == TypeID::get<TestEffectOpInterface>())
|
|
|
|
return fallbackEffectOpInterfaces;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-11-12 11:57:47 -08:00
|
|
|
LogicalResult TestDialect::verifyOperationAttribute(Operation *op,
|
|
|
|
NamedAttribute namedAttr) {
|
2021-11-18 05:23:32 +00:00
|
|
|
if (namedAttr.getName() == "test.invalid_attr")
|
2019-11-12 11:57:47 -08:00
|
|
|
return op->emitError() << "invalid to use 'test.invalid_attr'";
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
2019-10-18 16:02:56 -07:00
|
|
|
LogicalResult TestDialect::verifyRegionArgAttribute(Operation *op,
|
|
|
|
unsigned regionIndex,
|
|
|
|
unsigned argIndex,
|
|
|
|
NamedAttribute namedAttr) {
|
2021-11-18 05:23:32 +00:00
|
|
|
if (namedAttr.getName() == "test.invalid_attr")
|
2019-10-18 16:02:56 -07:00
|
|
|
return op->emitError() << "invalid to use 'test.invalid_attr'";
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
|
|
|
LogicalResult
|
|
|
|
TestDialect::verifyRegionResultAttribute(Operation *op, unsigned regionIndex,
|
|
|
|
unsigned resultIndex,
|
|
|
|
NamedAttribute namedAttr) {
|
2021-11-18 05:23:32 +00:00
|
|
|
if (namedAttr.getName() == "test.invalid_attr")
|
2019-10-18 16:02:56 -07:00
|
|
|
return op->emitError() << "invalid to use 'test.invalid_attr'";
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
2021-03-23 00:33:03 +00:00
|
|
|
Optional<Dialect::ParseOpHook>
|
|
|
|
TestDialect::getParseOperationHook(StringRef opName) const {
|
|
|
|
if (opName == "test.dialect_custom_printer") {
|
|
|
|
return ParseOpHook{[](OpAsmParser &parser, OperationState &state) {
|
|
|
|
return parser.parseKeyword("custom_format");
|
|
|
|
}};
|
|
|
|
}
|
2021-12-10 00:47:48 +00:00
|
|
|
if (opName == "test.dialect_custom_format_fallback") {
|
|
|
|
return ParseOpHook{[](OpAsmParser &parser, OperationState &state) {
|
|
|
|
return parser.parseKeyword("custom_format_fallback");
|
|
|
|
}};
|
|
|
|
}
|
2021-03-23 00:33:03 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2021-08-28 03:02:55 +00:00
|
|
|
llvm::unique_function<void(Operation *, OpAsmPrinter &)>
|
|
|
|
TestDialect::getOperationPrinter(Operation *op) const {
|
2021-03-23 00:33:03 +00:00
|
|
|
StringRef opName = op->getName().getStringRef();
|
|
|
|
if (opName == "test.dialect_custom_printer") {
|
2021-08-28 03:02:55 +00:00
|
|
|
return [](Operation *op, OpAsmPrinter &printer) {
|
2021-08-28 03:03:15 +00:00
|
|
|
printer.getStream() << " custom_format";
|
2021-08-28 03:02:55 +00:00
|
|
|
};
|
2021-03-23 00:33:03 +00:00
|
|
|
}
|
2021-12-10 00:47:48 +00:00
|
|
|
if (opName == "test.dialect_custom_format_fallback") {
|
|
|
|
return [](Operation *op, OpAsmPrinter &printer) {
|
|
|
|
printer.getStream() << " custom_format_fallback";
|
|
|
|
};
|
|
|
|
}
|
2021-08-28 03:02:55 +00:00
|
|
|
return {};
|
2021-03-23 00:33:03 +00:00
|
|
|
}
|
|
|
|
|
2020-03-05 12:40:23 -08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// TestBranchOp
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2020-04-29 16:09:43 -07:00
|
|
|
Optional<MutableOperandRange>
|
|
|
|
TestBranchOp::getMutableSuccessorOperands(unsigned index) {
|
2020-03-05 12:40:23 -08:00
|
|
|
assert(index == 0 && "invalid successor index");
|
2021-10-20 07:08:36 -07:00
|
|
|
return getTargetOperandsMutable();
|
2020-03-05 12:40:23 -08:00
|
|
|
}
|
|
|
|
|
2021-05-27 17:26:45 +09:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// TestDialectCanonicalizerOp
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
static LogicalResult
|
|
|
|
dialectCanonicalizationPattern(TestDialectCanonicalizerOp op,
|
|
|
|
PatternRewriter &rewriter) {
|
2021-10-12 23:14:57 +00:00
|
|
|
rewriter.replaceOpWithNewOp<arith::ConstantOp>(
|
|
|
|
op, rewriter.getI32IntegerAttr(42));
|
2021-05-27 17:26:45 +09:00
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TestDialect::getCanonicalizationPatterns(
|
|
|
|
RewritePatternSet &results) const {
|
|
|
|
results.add(&dialectCanonicalizationPattern);
|
|
|
|
}
|
|
|
|
|
2020-06-17 13:13:48 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// TestFoldToCallOp
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
struct FoldToCallOpPattern : public OpRewritePattern<FoldToCallOp> {
|
|
|
|
using OpRewritePattern<FoldToCallOp>::OpRewritePattern;
|
|
|
|
|
|
|
|
LogicalResult matchAndRewrite(FoldToCallOp op,
|
|
|
|
PatternRewriter &rewriter) const override {
|
2021-10-20 07:08:36 -07:00
|
|
|
rewriter.replaceOpWithNewOp<CallOp>(op, TypeRange(), op.getCalleeAttr(),
|
2020-06-17 13:13:48 -07:00
|
|
|
ValueRange());
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
};
|
2021-12-07 18:27:58 +00:00
|
|
|
} // namespace
|
2020-06-17 13:13:48 -07:00
|
|
|
|
2021-03-22 16:58:34 -07:00
|
|
|
void FoldToCallOp::getCanonicalizationPatterns(RewritePatternSet &results,
|
|
|
|
MLIRContext *context) {
|
|
|
|
results.add<FoldToCallOpPattern>(context);
|
2020-06-17 13:13:48 -07:00
|
|
|
}
|
|
|
|
|
2020-08-31 12:33:36 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test Format* operations
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Parsing
|
|
|
|
|
|
|
|
static ParseResult parseCustomDirectiveOperands(
|
|
|
|
OpAsmParser &parser, OpAsmParser::OperandType &operand,
|
|
|
|
Optional<OpAsmParser::OperandType> &optOperand,
|
|
|
|
SmallVectorImpl<OpAsmParser::OperandType> &varOperands) {
|
|
|
|
if (parser.parseOperand(operand))
|
|
|
|
return failure();
|
|
|
|
if (succeeded(parser.parseOptionalComma())) {
|
|
|
|
optOperand.emplace();
|
|
|
|
if (parser.parseOperand(*optOperand))
|
|
|
|
return failure();
|
|
|
|
}
|
|
|
|
if (parser.parseArrow() || parser.parseLParen() ||
|
|
|
|
parser.parseOperandList(varOperands) || parser.parseRParen())
|
|
|
|
return failure();
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
static ParseResult
|
|
|
|
parseCustomDirectiveResults(OpAsmParser &parser, Type &operandType,
|
|
|
|
Type &optOperandType,
|
|
|
|
SmallVectorImpl<Type> &varOperandTypes) {
|
|
|
|
if (parser.parseColon())
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
if (parser.parseType(operandType))
|
|
|
|
return failure();
|
|
|
|
if (succeeded(parser.parseOptionalComma())) {
|
|
|
|
if (parser.parseType(optOperandType))
|
|
|
|
return failure();
|
|
|
|
}
|
|
|
|
if (parser.parseArrow() || parser.parseLParen() ||
|
|
|
|
parser.parseTypeList(varOperandTypes) || parser.parseRParen())
|
|
|
|
return failure();
|
|
|
|
return success();
|
|
|
|
}
|
2020-09-18 06:13:25 -04:00
|
|
|
static ParseResult
|
|
|
|
parseCustomDirectiveWithTypeRefs(OpAsmParser &parser, Type operandType,
|
|
|
|
Type optOperandType,
|
|
|
|
const SmallVectorImpl<Type> &varOperandTypes) {
|
|
|
|
if (parser.parseKeyword("type_refs_capture"))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
Type operandType2, optOperandType2;
|
|
|
|
SmallVector<Type, 1> varOperandTypes2;
|
|
|
|
if (parseCustomDirectiveResults(parser, operandType2, optOperandType2,
|
|
|
|
varOperandTypes2))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
if (operandType != operandType2 || optOperandType != optOperandType2 ||
|
|
|
|
varOperandTypes != varOperandTypes2)
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
return success();
|
|
|
|
}
|
2020-08-31 12:33:36 -07:00
|
|
|
static ParseResult parseCustomDirectiveOperandsAndTypes(
|
|
|
|
OpAsmParser &parser, OpAsmParser::OperandType &operand,
|
|
|
|
Optional<OpAsmParser::OperandType> &optOperand,
|
|
|
|
SmallVectorImpl<OpAsmParser::OperandType> &varOperands, Type &operandType,
|
|
|
|
Type &optOperandType, SmallVectorImpl<Type> &varOperandTypes) {
|
|
|
|
if (parseCustomDirectiveOperands(parser, operand, optOperand, varOperands) ||
|
|
|
|
parseCustomDirectiveResults(parser, operandType, optOperandType,
|
|
|
|
varOperandTypes))
|
|
|
|
return failure();
|
|
|
|
return success();
|
|
|
|
}
|
2020-08-31 12:33:55 -07:00
|
|
|
static ParseResult parseCustomDirectiveRegions(
|
|
|
|
OpAsmParser &parser, Region ®ion,
|
|
|
|
SmallVectorImpl<std::unique_ptr<Region>> &varRegions) {
|
|
|
|
if (parser.parseRegion(region))
|
|
|
|
return failure();
|
|
|
|
if (failed(parser.parseOptionalComma()))
|
|
|
|
return success();
|
|
|
|
std::unique_ptr<Region> varRegion = std::make_unique<Region>();
|
|
|
|
if (parser.parseRegion(*varRegion))
|
|
|
|
return failure();
|
|
|
|
varRegions.emplace_back(std::move(varRegion));
|
|
|
|
return success();
|
|
|
|
}
|
2020-08-31 12:33:36 -07:00
|
|
|
static ParseResult
|
|
|
|
parseCustomDirectiveSuccessors(OpAsmParser &parser, Block *&successor,
|
|
|
|
SmallVectorImpl<Block *> &varSuccessors) {
|
|
|
|
if (parser.parseSuccessor(successor))
|
|
|
|
return failure();
|
|
|
|
if (failed(parser.parseOptionalComma()))
|
|
|
|
return success();
|
|
|
|
Block *varSuccessor;
|
|
|
|
if (parser.parseSuccessor(varSuccessor))
|
|
|
|
return failure();
|
|
|
|
varSuccessors.append(2, varSuccessor);
|
|
|
|
return success();
|
|
|
|
}
|
2020-09-23 18:01:39 +00:00
|
|
|
static ParseResult parseCustomDirectiveAttributes(OpAsmParser &parser,
|
|
|
|
IntegerAttr &attr,
|
|
|
|
IntegerAttr &optAttr) {
|
|
|
|
if (parser.parseAttribute(attr))
|
|
|
|
return failure();
|
|
|
|
if (succeeded(parser.parseOptionalComma())) {
|
|
|
|
if (parser.parseAttribute(optAttr))
|
|
|
|
return failure();
|
|
|
|
}
|
|
|
|
return success();
|
|
|
|
}
|
2020-08-31 12:33:36 -07:00
|
|
|
|
2020-10-28 01:01:44 +00:00
|
|
|
static ParseResult parseCustomDirectiveAttrDict(OpAsmParser &parser,
|
|
|
|
NamedAttrList &attrs) {
|
|
|
|
return parser.parseOptionalAttrDict(attrs);
|
|
|
|
}
|
2021-02-09 14:32:15 -08:00
|
|
|
static ParseResult parseCustomDirectiveOptionalOperandRef(
|
|
|
|
OpAsmParser &parser, Optional<OpAsmParser::OperandType> &optOperand) {
|
|
|
|
int64_t operandCount = 0;
|
|
|
|
if (parser.parseInteger(operandCount))
|
|
|
|
return failure();
|
|
|
|
bool expectedOptionalOperand = operandCount == 0;
|
|
|
|
return success(expectedOptionalOperand != optOperand.hasValue());
|
|
|
|
}
|
2020-10-28 01:01:44 +00:00
|
|
|
|
2020-08-31 12:33:36 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Printing
|
|
|
|
|
2020-10-28 01:01:44 +00:00
|
|
|
static void printCustomDirectiveOperands(OpAsmPrinter &printer, Operation *,
|
|
|
|
Value operand, Value optOperand,
|
2020-08-31 12:33:36 -07:00
|
|
|
OperandRange varOperands) {
|
|
|
|
printer << operand;
|
|
|
|
if (optOperand)
|
|
|
|
printer << ", " << optOperand;
|
|
|
|
printer << " -> (" << varOperands << ")";
|
|
|
|
}
|
2020-10-28 01:01:44 +00:00
|
|
|
static void printCustomDirectiveResults(OpAsmPrinter &printer, Operation *,
|
|
|
|
Type operandType, Type optOperandType,
|
2020-08-31 12:33:36 -07:00
|
|
|
TypeRange varOperandTypes) {
|
|
|
|
printer << " : " << operandType;
|
|
|
|
if (optOperandType)
|
|
|
|
printer << ", " << optOperandType;
|
|
|
|
printer << " -> (" << varOperandTypes << ")";
|
|
|
|
}
|
2020-09-18 06:13:25 -04:00
|
|
|
static void printCustomDirectiveWithTypeRefs(OpAsmPrinter &printer,
|
2020-10-28 01:01:44 +00:00
|
|
|
Operation *op, Type operandType,
|
2020-09-18 06:13:25 -04:00
|
|
|
Type optOperandType,
|
|
|
|
TypeRange varOperandTypes) {
|
|
|
|
printer << " type_refs_capture ";
|
2020-10-28 01:01:44 +00:00
|
|
|
printCustomDirectiveResults(printer, op, operandType, optOperandType,
|
2020-09-18 06:13:25 -04:00
|
|
|
varOperandTypes);
|
|
|
|
}
|
2020-10-28 01:01:44 +00:00
|
|
|
static void printCustomDirectiveOperandsAndTypes(
|
|
|
|
OpAsmPrinter &printer, Operation *op, Value operand, Value optOperand,
|
|
|
|
OperandRange varOperands, Type operandType, Type optOperandType,
|
|
|
|
TypeRange varOperandTypes) {
|
|
|
|
printCustomDirectiveOperands(printer, op, operand, optOperand, varOperands);
|
|
|
|
printCustomDirectiveResults(printer, op, operandType, optOperandType,
|
2020-08-31 12:33:36 -07:00
|
|
|
varOperandTypes);
|
|
|
|
}
|
2020-10-28 01:01:44 +00:00
|
|
|
static void printCustomDirectiveRegions(OpAsmPrinter &printer, Operation *,
|
|
|
|
Region ®ion,
|
2020-08-31 12:33:55 -07:00
|
|
|
MutableArrayRef<Region> varRegions) {
|
|
|
|
printer.printRegion(region);
|
|
|
|
if (!varRegions.empty()) {
|
|
|
|
printer << ", ";
|
|
|
|
for (Region ®ion : varRegions)
|
|
|
|
printer.printRegion(region);
|
|
|
|
}
|
|
|
|
}
|
2020-10-28 01:01:44 +00:00
|
|
|
static void printCustomDirectiveSuccessors(OpAsmPrinter &printer, Operation *,
|
2020-08-31 12:33:36 -07:00
|
|
|
Block *successor,
|
|
|
|
SuccessorRange varSuccessors) {
|
|
|
|
printer << successor;
|
|
|
|
if (!varSuccessors.empty())
|
|
|
|
printer << ", " << varSuccessors.front();
|
|
|
|
}
|
2020-10-28 01:01:44 +00:00
|
|
|
static void printCustomDirectiveAttributes(OpAsmPrinter &printer, Operation *,
|
2020-09-23 18:01:39 +00:00
|
|
|
Attribute attribute,
|
|
|
|
Attribute optAttribute) {
|
|
|
|
printer << attribute;
|
|
|
|
if (optAttribute)
|
|
|
|
printer << ", " << optAttribute;
|
|
|
|
}
|
2020-08-31 12:33:36 -07:00
|
|
|
|
2020-10-28 01:01:44 +00:00
|
|
|
static void printCustomDirectiveAttrDict(OpAsmPrinter &printer, Operation *op,
|
2020-12-17 17:10:12 -08:00
|
|
|
DictionaryAttr attrs) {
|
|
|
|
printer.printOptionalAttrDict(attrs.getValue());
|
2020-10-28 01:01:44 +00:00
|
|
|
}
|
2021-02-09 14:32:15 -08:00
|
|
|
|
|
|
|
static void printCustomDirectiveOptionalOperandRef(OpAsmPrinter &printer,
|
|
|
|
Operation *op,
|
|
|
|
Value optOperand) {
|
|
|
|
printer << (optOperand ? "1" : "0");
|
|
|
|
}
|
|
|
|
|
2019-08-19 15:26:43 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test IsolatedRegionOp - parse passthrough region arguments.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2019-09-20 11:36:49 -07:00
|
|
|
static ParseResult parseIsolatedRegionOp(OpAsmParser &parser,
|
2019-09-20 19:47:05 -07:00
|
|
|
OperationState &result) {
|
2019-08-19 15:26:43 -07:00
|
|
|
OpAsmParser::OperandType argInfo;
|
2019-09-20 11:36:49 -07:00
|
|
|
Type argType = parser.getBuilder().getIndexType();
|
2019-08-19 15:26:43 -07:00
|
|
|
|
|
|
|
// Parse the input operand.
|
2019-09-20 11:36:49 -07:00
|
|
|
if (parser.parseOperand(argInfo) ||
|
2019-09-20 19:47:05 -07:00
|
|
|
parser.resolveOperand(argInfo, argType, result.operands))
|
2019-08-19 15:26:43 -07:00
|
|
|
return failure();
|
|
|
|
|
|
|
|
// Parse the body region, and reuse the operand info as the argument info.
|
2019-09-20 19:47:05 -07:00
|
|
|
Region *body = result.addRegion();
|
2019-09-20 11:36:49 -07:00
|
|
|
return parser.parseRegion(*body, argInfo, argType,
|
|
|
|
/*enableNameShadowing=*/true);
|
2019-08-19 15:26:43 -07:00
|
|
|
}
|
|
|
|
|
2019-09-20 20:43:02 -07:00
|
|
|
static void print(OpAsmPrinter &p, IsolatedRegionOp op) {
|
|
|
|
p << "test.isolated_region ";
|
|
|
|
p.printOperand(op.getOperand());
|
2021-10-20 07:08:36 -07:00
|
|
|
p.shadowRegionArgs(op.getRegion(), op.getOperand());
|
|
|
|
p.printRegion(op.getRegion(), /*printEntryBlockArgs=*/false);
|
2019-08-23 10:35:24 -07:00
|
|
|
}
|
|
|
|
|
[MLIR] Add RegionKindInterface
Some dialects have semantics which is not well represented by common
SSA structures with dominance constraints. This patch allows
operations to declare the 'kind' of their contained regions.
Currently, two kinds are allowed: "SSACFG" and "Graph". The only
difference between them at the moment is that SSACFG regions are
required to have dominance, while Graph regions are not required to
have dominance. The intention is that this Interface would be
generated by ODS for existing operations, although this has not yet
been implemented. Presumably, if someone were interested in code
generation, we might also have a "CFG" dialect, which defines control
flow, but does not require SSA.
The new behavior is mostly identical to the previous behavior, since
registered operations without a RegionKindInterface are assumed to
contain SSACFG regions. However, the behavior has changed for
unregistered operations. Previously, these were checked for
dominance, however the new behavior allows dominance violations, in
order to allow the processing of unregistered dialects with Graph
regions. One implication of this is that regions in unregistered
operations with more than one op are no longer CSE'd (since it
requires dominance info).
I've also reorganized the LangRef documentation to remove assertions
about "sequential execution", "SSA Values", and "Dominance". Instead,
the core IR is simply "ordered" (i.e. totally ordered) and consists of
"Values". I've also clarified some things about how control flow
passes between blocks in an SSACFG region. Control Flow must enter a
region at the entry block and follow terminator operation successors
or be returned to the containing op. Graph regions do not define a
notion of control flow.
see discussion here:
https://llvm.discourse.group/t/rfc-allowing-dialects-to-relax-the-ssa-dominance-condition/833/53
Differential Revision: https://reviews.llvm.org/D80358
2020-05-15 10:33:13 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test SSACFGRegionOp
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
RegionKind SSACFGRegionOp::getRegionKind(unsigned index) {
|
|
|
|
return RegionKind::SSACFG;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test GraphRegionOp
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
static ParseResult parseGraphRegionOp(OpAsmParser &parser,
|
|
|
|
OperationState &result) {
|
|
|
|
// Parse the body region, and reuse the operand info as the argument info.
|
|
|
|
Region *body = result.addRegion();
|
|
|
|
return parser.parseRegion(*body, /*arguments=*/{}, /*argTypes=*/{});
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print(OpAsmPrinter &p, GraphRegionOp op) {
|
|
|
|
p << "test.graph_region ";
|
2021-10-20 07:08:36 -07:00
|
|
|
p.printRegion(op.getRegion(), /*printEntryBlockArgs=*/false);
|
[MLIR] Add RegionKindInterface
Some dialects have semantics which is not well represented by common
SSA structures with dominance constraints. This patch allows
operations to declare the 'kind' of their contained regions.
Currently, two kinds are allowed: "SSACFG" and "Graph". The only
difference between them at the moment is that SSACFG regions are
required to have dominance, while Graph regions are not required to
have dominance. The intention is that this Interface would be
generated by ODS for existing operations, although this has not yet
been implemented. Presumably, if someone were interested in code
generation, we might also have a "CFG" dialect, which defines control
flow, but does not require SSA.
The new behavior is mostly identical to the previous behavior, since
registered operations without a RegionKindInterface are assumed to
contain SSACFG regions. However, the behavior has changed for
unregistered operations. Previously, these were checked for
dominance, however the new behavior allows dominance violations, in
order to allow the processing of unregistered dialects with Graph
regions. One implication of this is that regions in unregistered
operations with more than one op are no longer CSE'd (since it
requires dominance info).
I've also reorganized the LangRef documentation to remove assertions
about "sequential execution", "SSA Values", and "Dominance". Instead,
the core IR is simply "ordered" (i.e. totally ordered) and consists of
"Values". I've also clarified some things about how control flow
passes between blocks in an SSACFG region. Control Flow must enter a
region at the entry block and follow terminator operation successors
or be returned to the containing op. Graph regions do not define a
notion of control flow.
see discussion here:
https://llvm.discourse.group/t/rfc-allowing-dialects-to-relax-the-ssa-dominance-condition/833/53
Differential Revision: https://reviews.llvm.org/D80358
2020-05-15 10:33:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
RegionKind GraphRegionOp::getRegionKind(unsigned index) {
|
|
|
|
return RegionKind::Graph;
|
|
|
|
}
|
|
|
|
|
2020-04-29 05:38:23 +05:30
|
|
|
//===----------------------------------------------------------------------===//
|
2020-05-06 12:32:06 +05:30
|
|
|
// Test AffineScopeOp
|
2020-04-29 05:38:23 +05:30
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2020-05-06 12:32:06 +05:30
|
|
|
static ParseResult parseAffineScopeOp(OpAsmParser &parser,
|
|
|
|
OperationState &result) {
|
2020-04-29 05:38:23 +05:30
|
|
|
// Parse the body region, and reuse the operand info as the argument info.
|
|
|
|
Region *body = result.addRegion();
|
|
|
|
return parser.parseRegion(*body, /*arguments=*/{}, /*argTypes=*/{});
|
|
|
|
}
|
|
|
|
|
2020-05-06 12:32:06 +05:30
|
|
|
static void print(OpAsmPrinter &p, AffineScopeOp op) {
|
|
|
|
p << "test.affine_scope ";
|
2021-10-20 07:08:36 -07:00
|
|
|
p.printRegion(op.getRegion(), /*printEntryBlockArgs=*/false);
|
2020-04-29 05:38:23 +05:30
|
|
|
}
|
|
|
|
|
2019-09-08 23:39:34 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
2019-09-17 17:54:54 -07:00
|
|
|
// Test parser.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2020-12-14 11:53:43 -08:00
|
|
|
static ParseResult parseParseIntegerLiteralOp(OpAsmParser &parser,
|
|
|
|
OperationState &result) {
|
|
|
|
if (parser.parseOptionalColon())
|
|
|
|
return success();
|
|
|
|
uint64_t numResults;
|
|
|
|
if (parser.parseInteger(numResults))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
IndexType type = parser.getBuilder().getIndexType();
|
|
|
|
for (unsigned i = 0; i < numResults; ++i)
|
|
|
|
result.addTypes(type);
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print(OpAsmPrinter &p, ParseIntegerLiteralOp op) {
|
|
|
|
if (unsigned numResults = op->getNumResults())
|
|
|
|
p << " : " << numResults;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ParseResult parseParseWrappedKeywordOp(OpAsmParser &parser,
|
|
|
|
OperationState &result) {
|
2019-09-17 17:54:54 -07:00
|
|
|
StringRef keyword;
|
2019-09-20 11:36:49 -07:00
|
|
|
if (parser.parseKeyword(&keyword))
|
2019-09-17 17:54:54 -07:00
|
|
|
return failure();
|
2019-09-20 19:47:05 -07:00
|
|
|
result.addAttribute("keyword", parser.getBuilder().getStringAttr(keyword));
|
2019-09-17 17:54:54 -07:00
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
2020-12-14 11:53:43 -08:00
|
|
|
static void print(OpAsmPrinter &p, ParseWrappedKeywordOp op) {
|
2021-10-20 07:08:36 -07:00
|
|
|
p << " " << op.getKeyword();
|
2019-09-17 17:54:54 -07:00
|
|
|
}
|
|
|
|
|
2019-09-08 23:39:34 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
2019-09-17 17:54:54 -07:00
|
|
|
// Test WrapRegionOp - wrapping op exercising `parseGenericOperation()`.
|
2019-09-08 23:39:34 -07:00
|
|
|
|
2019-09-20 11:36:49 -07:00
|
|
|
static ParseResult parseWrappingRegionOp(OpAsmParser &parser,
|
2019-09-20 19:47:05 -07:00
|
|
|
OperationState &result) {
|
2019-09-20 11:36:49 -07:00
|
|
|
if (parser.parseKeyword("wraps"))
|
2019-09-08 23:39:34 -07:00
|
|
|
return failure();
|
|
|
|
|
|
|
|
// Parse the wrapped op in a region
|
2019-09-20 19:47:05 -07:00
|
|
|
Region &body = *result.addRegion();
|
2019-09-08 23:39:34 -07:00
|
|
|
body.push_back(new Block);
|
|
|
|
Block &block = body.back();
|
2019-09-20 11:36:49 -07:00
|
|
|
Operation *wrapped_op = parser.parseGenericOperation(&block, block.begin());
|
2019-09-08 23:39:34 -07:00
|
|
|
if (!wrapped_op)
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
// Create a return terminator in the inner region, pass as operand to the
|
|
|
|
// terminator the returned values from the wrapped operation.
|
2019-12-23 14:45:01 -08:00
|
|
|
SmallVector<Value, 8> return_operands(wrapped_op->getResults());
|
2021-09-29 17:47:08 -07:00
|
|
|
OpBuilder builder(parser.getContext());
|
2019-09-08 23:39:34 -07:00
|
|
|
builder.setInsertionPointToEnd(&block);
|
2019-10-28 15:11:00 -07:00
|
|
|
builder.create<TestReturnOp>(wrapped_op->getLoc(), return_operands);
|
2019-09-08 23:39:34 -07:00
|
|
|
|
|
|
|
// Get the results type for the wrapping op from the terminator operands.
|
|
|
|
Operation &return_op = body.back().back();
|
2019-09-20 19:47:05 -07:00
|
|
|
result.types.append(return_op.operand_type_begin(),
|
|
|
|
return_op.operand_type_end());
|
2019-10-28 15:11:00 -07:00
|
|
|
|
|
|
|
// Use the location of the wrapped op for the "test.wrapping_region" op.
|
|
|
|
result.location = wrapped_op->getLoc();
|
|
|
|
|
2019-09-08 23:39:34 -07:00
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
2019-09-20 20:43:02 -07:00
|
|
|
static void print(OpAsmPrinter &p, WrappingRegionOp op) {
|
2021-08-28 03:03:15 +00:00
|
|
|
p << " wraps ";
|
2021-10-20 07:08:36 -07:00
|
|
|
p.printGenericOp(&op.getRegion().front().front());
|
2019-09-08 23:39:34 -07:00
|
|
|
}
|
|
|
|
|
2021-11-23 06:05:41 +00:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test PrettyPrintedRegionOp - exercising the following parser APIs
|
|
|
|
// parseGenericOperationAfterOpName
|
|
|
|
// parseCustomOperationName
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
static ParseResult parsePrettyPrintedRegionOp(OpAsmParser &parser,
|
|
|
|
OperationState &result) {
|
|
|
|
|
|
|
|
llvm::SMLoc loc = parser.getCurrentLocation();
|
|
|
|
Location currLocation = parser.getEncodedSourceLoc(loc);
|
|
|
|
|
|
|
|
// Parse the operands.
|
|
|
|
SmallVector<OpAsmParser::OperandType, 2> operands;
|
|
|
|
if (parser.parseOperandList(operands))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
// Check if we are parsing the pretty-printed version
|
|
|
|
// test.pretty_printed_region start <inner-op> end : <functional-type>
|
|
|
|
// Else fallback to parsing the "non pretty-printed" version.
|
|
|
|
if (!succeeded(parser.parseOptionalKeyword("start")))
|
|
|
|
return parser.parseGenericOperationAfterOpName(
|
|
|
|
result, llvm::makeArrayRef(operands));
|
|
|
|
|
|
|
|
FailureOr<OperationName> parseOpNameInfo = parser.parseCustomOperationName();
|
|
|
|
if (failed(parseOpNameInfo))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
StringRef innerOpName = parseOpNameInfo->getStringRef();
|
|
|
|
|
|
|
|
FunctionType opFntype;
|
|
|
|
Optional<Location> explicitLoc;
|
|
|
|
if (parser.parseKeyword("end") || parser.parseColon() ||
|
|
|
|
parser.parseType(opFntype) ||
|
|
|
|
parser.parseOptionalLocationSpecifier(explicitLoc))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
// If location of the op is explicitly provided, then use it; Else use
|
|
|
|
// the parser's current location.
|
|
|
|
Location opLoc = explicitLoc.getValueOr(currLocation);
|
|
|
|
|
|
|
|
// Derive the SSA-values for op's operands.
|
|
|
|
if (parser.resolveOperands(operands, opFntype.getInputs(), loc,
|
|
|
|
result.operands))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
// Add a region for op.
|
|
|
|
Region ®ion = *result.addRegion();
|
|
|
|
|
|
|
|
// Create a basic-block inside op's region.
|
|
|
|
Block &block = region.emplaceBlock();
|
|
|
|
|
|
|
|
// Create and insert an "inner-op" operation in the block.
|
|
|
|
// Just for testing purposes, we can assume that inner op is a binary op with
|
|
|
|
// result and operand types all same as the test-op's first operand.
|
|
|
|
Type innerOpType = opFntype.getInput(0);
|
|
|
|
Value lhs = block.addArgument(innerOpType, opLoc);
|
|
|
|
Value rhs = block.addArgument(innerOpType, opLoc);
|
|
|
|
|
|
|
|
OpBuilder builder(parser.getBuilder().getContext());
|
|
|
|
builder.setInsertionPointToStart(&block);
|
|
|
|
|
|
|
|
OperationState innerOpState(opLoc, innerOpName);
|
|
|
|
innerOpState.operands.push_back(lhs);
|
|
|
|
innerOpState.operands.push_back(rhs);
|
|
|
|
innerOpState.addTypes(innerOpType);
|
|
|
|
|
|
|
|
Operation *innerOp = builder.createOperation(innerOpState);
|
|
|
|
|
|
|
|
// Insert a return statement in the block returning the inner-op's result.
|
|
|
|
builder.create<TestReturnOp>(innerOp->getLoc(), innerOp->getResults());
|
|
|
|
|
|
|
|
// Populate the op operation-state with result-type and location.
|
|
|
|
result.addTypes(opFntype.getResults());
|
|
|
|
result.location = innerOp->getLoc();
|
|
|
|
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print(OpAsmPrinter &p, PrettyPrintedRegionOp op) {
|
|
|
|
p << ' ';
|
|
|
|
p.printOperands(op.getOperands());
|
|
|
|
|
|
|
|
Operation &innerOp = op.getRegion().front().front();
|
|
|
|
// Assuming that region has a single non-terminator inner-op, if the inner-op
|
|
|
|
// meets some criteria (which in this case is a simple one based on the name
|
|
|
|
// of inner-op), then we can print the entire region in a succinct way.
|
|
|
|
// Here we assume that the prototype of "special.op" can be trivially derived
|
|
|
|
// while parsing it back.
|
|
|
|
if (innerOp.getName().getStringRef().equals("special.op")) {
|
|
|
|
p << " start special.op end";
|
|
|
|
} else {
|
|
|
|
p << " (";
|
|
|
|
p.printRegion(op.getRegion());
|
|
|
|
p << ")";
|
|
|
|
}
|
|
|
|
|
|
|
|
p << " : ";
|
|
|
|
p.printFunctionalType(op);
|
|
|
|
}
|
|
|
|
|
2019-07-22 17:41:38 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test PolyForOp - parse list of region arguments.
|
|
|
|
//===----------------------------------------------------------------------===//
|
2019-11-20 10:19:01 -08:00
|
|
|
|
2019-09-20 19:47:05 -07:00
|
|
|
static ParseResult parsePolyForOp(OpAsmParser &parser, OperationState &result) {
|
2019-07-22 17:41:38 -07:00
|
|
|
SmallVector<OpAsmParser::OperandType, 4> ivsInfo;
|
|
|
|
// Parse list of region arguments without a delimiter.
|
2019-09-20 11:36:49 -07:00
|
|
|
if (parser.parseRegionArgumentList(ivsInfo))
|
2019-07-22 17:41:38 -07:00
|
|
|
return failure();
|
|
|
|
|
|
|
|
// Parse the body region.
|
2019-09-20 19:47:05 -07:00
|
|
|
Region *body = result.addRegion();
|
2019-09-20 11:36:49 -07:00
|
|
|
auto &builder = parser.getBuilder();
|
2019-07-22 17:41:38 -07:00
|
|
|
SmallVector<Type, 4> argTypes(ivsInfo.size(), builder.getIndexType());
|
2019-09-20 11:36:49 -07:00
|
|
|
return parser.parseRegion(*body, ivsInfo, argTypes);
|
2019-07-22 17:41:38 -07:00
|
|
|
}
|
|
|
|
|
2019-08-06 11:08:22 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test removing op with inner ops.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
2019-08-26 09:44:09 -07:00
|
|
|
struct TestRemoveOpWithInnerOps
|
|
|
|
: public OpRewritePattern<TestOpWithRegionPattern> {
|
|
|
|
using OpRewritePattern<TestOpWithRegionPattern>::OpRewritePattern;
|
2019-08-06 11:08:22 -07:00
|
|
|
|
2021-06-02 11:43:01 -07:00
|
|
|
void initialize() { setDebugName("TestRemoveOpWithInnerOps"); }
|
|
|
|
|
2020-03-17 20:07:55 -07:00
|
|
|
LogicalResult matchAndRewrite(TestOpWithRegionPattern op,
|
|
|
|
PatternRewriter &rewriter) const override {
|
2019-10-16 09:50:28 -07:00
|
|
|
rewriter.eraseOp(op);
|
2020-03-17 20:07:55 -07:00
|
|
|
return success();
|
2019-08-06 11:08:22 -07:00
|
|
|
}
|
|
|
|
};
|
2021-12-07 18:27:58 +00:00
|
|
|
} // namespace
|
2019-08-06 11:08:22 -07:00
|
|
|
|
2019-08-26 09:44:09 -07:00
|
|
|
void TestOpWithRegionPattern::getCanonicalizationPatterns(
|
2021-03-22 16:58:34 -07:00
|
|
|
RewritePatternSet &results, MLIRContext *context) {
|
|
|
|
results.add<TestRemoveOpWithInnerOps>(context);
|
2019-08-06 11:08:22 -07:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:44:09 -07:00
|
|
|
OpFoldResult TestOpWithRegionFold::fold(ArrayRef<Attribute> operands) {
|
2021-10-20 07:08:36 -07:00
|
|
|
return getOperand();
|
2019-08-26 09:44:09 -07:00
|
|
|
}
|
|
|
|
|
2020-10-09 13:32:01 -07:00
|
|
|
OpFoldResult TestOpConstant::fold(ArrayRef<Attribute> operands) {
|
|
|
|
return getValue();
|
|
|
|
}
|
|
|
|
|
2019-10-09 20:42:32 -07:00
|
|
|
LogicalResult TestOpWithVariadicResultsAndFolder::fold(
|
|
|
|
ArrayRef<Attribute> operands, SmallVectorImpl<OpFoldResult> &results) {
|
2021-10-18 10:00:37 -07:00
|
|
|
for (Value input : this->getOperands()) {
|
2019-10-09 20:42:32 -07:00
|
|
|
results.push_back(input);
|
|
|
|
}
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
2020-05-06 17:39:23 +02:00
|
|
|
OpFoldResult TestOpInPlaceFold::fold(ArrayRef<Attribute> operands) {
|
|
|
|
assert(operands.size() == 1);
|
|
|
|
if (operands.front()) {
|
2020-12-12 10:50:41 +01:00
|
|
|
(*this)->setAttr("attr", operands.front());
|
2020-05-06 17:39:23 +02:00
|
|
|
return getResult();
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2021-02-04 01:53:59 +00:00
|
|
|
OpFoldResult TestPassthroughFold::fold(ArrayRef<Attribute> operands) {
|
|
|
|
return getOperand();
|
|
|
|
}
|
|
|
|
|
[MLIR] Add RegionKindInterface
Some dialects have semantics which is not well represented by common
SSA structures with dominance constraints. This patch allows
operations to declare the 'kind' of their contained regions.
Currently, two kinds are allowed: "SSACFG" and "Graph". The only
difference between them at the moment is that SSACFG regions are
required to have dominance, while Graph regions are not required to
have dominance. The intention is that this Interface would be
generated by ODS for existing operations, although this has not yet
been implemented. Presumably, if someone were interested in code
generation, we might also have a "CFG" dialect, which defines control
flow, but does not require SSA.
The new behavior is mostly identical to the previous behavior, since
registered operations without a RegionKindInterface are assumed to
contain SSACFG regions. However, the behavior has changed for
unregistered operations. Previously, these were checked for
dominance, however the new behavior allows dominance violations, in
order to allow the processing of unregistered dialects with Graph
regions. One implication of this is that regions in unregistered
operations with more than one op are no longer CSE'd (since it
requires dominance info).
I've also reorganized the LangRef documentation to remove assertions
about "sequential execution", "SSA Values", and "Dominance". Instead,
the core IR is simply "ordered" (i.e. totally ordered) and consists of
"Values". I've also clarified some things about how control flow
passes between blocks in an SSACFG region. Control Flow must enter a
region at the entry block and follow terminator operation successors
or be returned to the containing op. Graph regions do not define a
notion of control flow.
see discussion here:
https://llvm.discourse.group/t/rfc-allowing-dialects-to-relax-the-ssa-dominance-condition/833/53
Differential Revision: https://reviews.llvm.org/D80358
2020-05-15 10:33:13 -07:00
|
|
|
LogicalResult OpWithInferTypeInterfaceOp::inferReturnTypes(
|
2020-01-08 18:48:38 -08:00
|
|
|
MLIRContext *, Optional<Location> location, ValueRange operands,
|
2020-05-06 13:48:36 -07:00
|
|
|
DictionaryAttr attributes, RegionRange regions,
|
2020-02-28 10:59:34 -08:00
|
|
|
SmallVectorImpl<Type> &inferredReturnTypes) {
|
2020-01-11 08:54:04 -08:00
|
|
|
if (operands[0].getType() != operands[1].getType()) {
|
2019-12-06 14:42:16 -08:00
|
|
|
return emitOptionalError(location, "operand type mismatch ",
|
2020-01-11 08:54:04 -08:00
|
|
|
operands[0].getType(), " vs ",
|
|
|
|
operands[1].getType());
|
2019-11-07 07:51:12 -08:00
|
|
|
}
|
2020-02-28 10:59:34 -08:00
|
|
|
inferredReturnTypes.assign({operands[0].getType()});
|
2019-12-06 14:42:16 -08:00
|
|
|
return success();
|
2019-09-29 17:28:29 -07:00
|
|
|
}
|
|
|
|
|
2020-01-08 18:48:38 -08:00
|
|
|
LogicalResult OpWithShapedTypeInferTypeInterfaceOp::inferReturnTypeComponents(
|
2021-07-22 12:24:48 -07:00
|
|
|
MLIRContext *context, Optional<Location> location, ValueShapeRange operands,
|
2020-05-06 13:48:36 -07:00
|
|
|
DictionaryAttr attributes, RegionRange regions,
|
2020-02-28 10:59:34 -08:00
|
|
|
SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
|
2020-02-28 08:37:09 -08:00
|
|
|
// Create return type consisting of the last element of the first operand.
|
2021-07-26 17:08:31 -07:00
|
|
|
auto operandType = operands.front().getType();
|
2020-02-28 08:37:09 -08:00
|
|
|
auto sval = operandType.dyn_cast<ShapedType>();
|
|
|
|
if (!sval) {
|
|
|
|
return emitOptionalError(location, "only shaped type operands allowed");
|
2020-01-08 18:48:38 -08:00
|
|
|
}
|
2020-02-28 08:37:09 -08:00
|
|
|
int64_t dim =
|
|
|
|
sval.hasRank() ? sval.getShape().front() : ShapedType::kDynamicSize;
|
2020-12-17 12:24:45 -08:00
|
|
|
auto type = IntegerType::get(context, 17);
|
2020-02-28 10:59:34 -08:00
|
|
|
inferredReturnShapes.push_back(ShapedTypeComponents({dim}, type));
|
2020-02-28 08:37:09 -08:00
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
|
|
|
LogicalResult OpWithShapedTypeInferTypeInterfaceOp::reifyReturnTypeShapes(
|
2021-05-19 02:11:33 +00:00
|
|
|
OpBuilder &builder, ValueRange operands,
|
|
|
|
llvm::SmallVectorImpl<Value> &shapes) {
|
2020-02-28 08:37:09 -08:00
|
|
|
shapes = SmallVector<Value, 1>{
|
2021-07-01 09:58:48 +09:00
|
|
|
builder.createOrFold<tensor::DimOp>(getLoc(), operands.front(), 0)};
|
2020-01-08 18:48:38 -08:00
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
2021-06-16 22:12:16 -07:00
|
|
|
LogicalResult OpWithResultShapeInterfaceOp::reifyReturnTypeShapes(
|
|
|
|
OpBuilder &builder, ValueRange operands,
|
|
|
|
llvm::SmallVectorImpl<Value> &shapes) {
|
|
|
|
Location loc = getLoc();
|
|
|
|
shapes.reserve(operands.size());
|
|
|
|
for (Value operand : llvm::reverse(operands)) {
|
2021-12-16 14:42:27 +01:00
|
|
|
auto rank = operand.getType().cast<RankedTensorType>().getRank();
|
|
|
|
auto currShape = llvm::to_vector<4>(
|
|
|
|
llvm::map_range(llvm::seq<int64_t>(0, rank), [&](int64_t dim) -> Value {
|
2021-07-01 09:58:48 +09:00
|
|
|
return builder.createOrFold<tensor::DimOp>(loc, operand, dim);
|
2021-06-16 22:12:16 -07:00
|
|
|
}));
|
|
|
|
shapes.push_back(builder.create<tensor::FromElementsOp>(
|
2021-12-16 14:42:27 +01:00
|
|
|
getLoc(), RankedTensorType::get({rank}, builder.getIndexType()),
|
|
|
|
currShape));
|
2021-06-16 22:12:16 -07:00
|
|
|
}
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
2021-07-19 14:35:20 -07:00
|
|
|
LogicalResult OpWithResultShapePerDimInterfaceOp::reifyResultShapes(
|
|
|
|
OpBuilder &builder, ReifiedRankedShapedTypeDims &shapes) {
|
2021-06-16 22:12:16 -07:00
|
|
|
Location loc = getLoc();
|
|
|
|
shapes.reserve(getNumOperands());
|
|
|
|
for (Value operand : llvm::reverse(getOperands())) {
|
|
|
|
auto currShape = llvm::to_vector<4>(llvm::map_range(
|
|
|
|
llvm::seq<int64_t>(
|
|
|
|
0, operand.getType().cast<RankedTensorType>().getRank()),
|
|
|
|
[&](int64_t dim) -> Value {
|
2021-07-01 09:58:48 +09:00
|
|
|
return builder.createOrFold<tensor::DimOp>(loc, operand, dim);
|
2021-06-16 22:12:16 -07:00
|
|
|
}));
|
|
|
|
shapes.emplace_back(std::move(currShape));
|
2021-03-29 10:57:23 -07:00
|
|
|
}
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
[mlir][SideEffects] Define a set of interfaces and traits for defining side effects
This revision introduces the infrastructure for defining side-effects and attaching them to operations. This infrastructure allows for defining different types of side effects, that don't interact with each other, but use the same internal mechanisms. At the base of this is an interface that allows operations to specify the different effect instances that are exhibited by a specific operation instance. An effect instance is comprised of the following:
* Effect: The specific effect being applied.
For memory related effects this may be reading from memory, storing to memory, etc.
* Value: A specific value, either operand/result/region argument, the effect pertains to.
* Resource: This is a global entity that represents the domain within which the effect is being applied.
MLIR serves many different abstractions, which cover many different domains. Simple effects are may have very different context, for example writing to an in-memory buffer vs a database. This revision defines uses this infrastructure to define a set of initial MemoryEffects. The are effects that generally correspond to memory of some kind; Allocate, Free, Read, Write.
This set of memory effects will be used in follow revisions to generalize various parts of the compiler, and make others more powerful(e.g. DCE).
This infrastructure was originally proposed here:
https://groups.google.com/a/tensorflow.org/g/mlir/c/v2mNl4vFCUM
Differential Revision: https://reviews.llvm.org/D74439
2020-03-06 13:53:16 -08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Test SideEffect interfaces
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
/// A test resource for side effects.
|
|
|
|
struct TestResource : public SideEffects::Resource::Base<TestResource> {
|
|
|
|
StringRef getName() final { return "<Test>"; }
|
|
|
|
};
|
2021-12-07 18:27:58 +00:00
|
|
|
} // namespace
|
[mlir][SideEffects] Define a set of interfaces and traits for defining side effects
This revision introduces the infrastructure for defining side-effects and attaching them to operations. This infrastructure allows for defining different types of side effects, that don't interact with each other, but use the same internal mechanisms. At the base of this is an interface that allows operations to specify the different effect instances that are exhibited by a specific operation instance. An effect instance is comprised of the following:
* Effect: The specific effect being applied.
For memory related effects this may be reading from memory, storing to memory, etc.
* Value: A specific value, either operand/result/region argument, the effect pertains to.
* Resource: This is a global entity that represents the domain within which the effect is being applied.
MLIR serves many different abstractions, which cover many different domains. Simple effects are may have very different context, for example writing to an in-memory buffer vs a database. This revision defines uses this infrastructure to define a set of initial MemoryEffects. The are effects that generally correspond to memory of some kind; Allocate, Free, Read, Write.
This set of memory effects will be used in follow revisions to generalize various parts of the compiler, and make others more powerful(e.g. DCE).
This infrastructure was originally proposed here:
https://groups.google.com/a/tensorflow.org/g/mlir/c/v2mNl4vFCUM
Differential Revision: https://reviews.llvm.org/D74439
2020-03-06 13:53:16 -08:00
|
|
|
|
2021-03-24 08:25:25 +00:00
|
|
|
static void testSideEffectOpGetEffect(
|
|
|
|
Operation *op,
|
|
|
|
SmallVectorImpl<SideEffects::EffectInstance<TestEffects::Effect>>
|
|
|
|
&effects) {
|
|
|
|
auto effectsAttr = op->getAttrOfType<AffineMapAttr>("effect_parameter");
|
|
|
|
if (!effectsAttr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
effects.emplace_back(TestEffects::Concrete::get(), effectsAttr);
|
|
|
|
}
|
|
|
|
|
[mlir][SideEffects] Define a set of interfaces and traits for defining side effects
This revision introduces the infrastructure for defining side-effects and attaching them to operations. This infrastructure allows for defining different types of side effects, that don't interact with each other, but use the same internal mechanisms. At the base of this is an interface that allows operations to specify the different effect instances that are exhibited by a specific operation instance. An effect instance is comprised of the following:
* Effect: The specific effect being applied.
For memory related effects this may be reading from memory, storing to memory, etc.
* Value: A specific value, either operand/result/region argument, the effect pertains to.
* Resource: This is a global entity that represents the domain within which the effect is being applied.
MLIR serves many different abstractions, which cover many different domains. Simple effects are may have very different context, for example writing to an in-memory buffer vs a database. This revision defines uses this infrastructure to define a set of initial MemoryEffects. The are effects that generally correspond to memory of some kind; Allocate, Free, Read, Write.
This set of memory effects will be used in follow revisions to generalize various parts of the compiler, and make others more powerful(e.g. DCE).
This infrastructure was originally proposed here:
https://groups.google.com/a/tensorflow.org/g/mlir/c/v2mNl4vFCUM
Differential Revision: https://reviews.llvm.org/D74439
2020-03-06 13:53:16 -08:00
|
|
|
void SideEffectOp::getEffects(
|
|
|
|
SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
|
|
|
|
// Check for an effects attribute on the op instance.
|
2020-12-09 11:50:18 +01:00
|
|
|
ArrayAttr effectsAttr = (*this)->getAttrOfType<ArrayAttr>("effects");
|
[mlir][SideEffects] Define a set of interfaces and traits for defining side effects
This revision introduces the infrastructure for defining side-effects and attaching them to operations. This infrastructure allows for defining different types of side effects, that don't interact with each other, but use the same internal mechanisms. At the base of this is an interface that allows operations to specify the different effect instances that are exhibited by a specific operation instance. An effect instance is comprised of the following:
* Effect: The specific effect being applied.
For memory related effects this may be reading from memory, storing to memory, etc.
* Value: A specific value, either operand/result/region argument, the effect pertains to.
* Resource: This is a global entity that represents the domain within which the effect is being applied.
MLIR serves many different abstractions, which cover many different domains. Simple effects are may have very different context, for example writing to an in-memory buffer vs a database. This revision defines uses this infrastructure to define a set of initial MemoryEffects. The are effects that generally correspond to memory of some kind; Allocate, Free, Read, Write.
This set of memory effects will be used in follow revisions to generalize various parts of the compiler, and make others more powerful(e.g. DCE).
This infrastructure was originally proposed here:
https://groups.google.com/a/tensorflow.org/g/mlir/c/v2mNl4vFCUM
Differential Revision: https://reviews.llvm.org/D74439
2020-03-06 13:53:16 -08:00
|
|
|
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<DictionaryAttr>();
|
|
|
|
|
|
|
|
// Get the specific memory effect.
|
|
|
|
MemoryEffects::Effect *effect =
|
2020-10-07 16:17:35 +02:00
|
|
|
StringSwitch<MemoryEffects::Effect *>(
|
[mlir][SideEffects] Define a set of interfaces and traits for defining side effects
This revision introduces the infrastructure for defining side-effects and attaching them to operations. This infrastructure allows for defining different types of side effects, that don't interact with each other, but use the same internal mechanisms. At the base of this is an interface that allows operations to specify the different effect instances that are exhibited by a specific operation instance. An effect instance is comprised of the following:
* Effect: The specific effect being applied.
For memory related effects this may be reading from memory, storing to memory, etc.
* Value: A specific value, either operand/result/region argument, the effect pertains to.
* Resource: This is a global entity that represents the domain within which the effect is being applied.
MLIR serves many different abstractions, which cover many different domains. Simple effects are may have very different context, for example writing to an in-memory buffer vs a database. This revision defines uses this infrastructure to define a set of initial MemoryEffects. The are effects that generally correspond to memory of some kind; Allocate, Free, Read, Write.
This set of memory effects will be used in follow revisions to generalize various parts of the compiler, and make others more powerful(e.g. DCE).
This infrastructure was originally proposed here:
https://groups.google.com/a/tensorflow.org/g/mlir/c/v2mNl4vFCUM
Differential Revision: https://reviews.llvm.org/D74439
2020-03-06 13:53:16 -08:00
|
|
|
effectElement.get("effect").cast<StringAttr>().getValue())
|
|
|
|
.Case("allocate", MemoryEffects::Allocate::get())
|
|
|
|
.Case("free", MemoryEffects::Free::get())
|
|
|
|
.Case("read", MemoryEffects::Read::get())
|
|
|
|
.Case("write", MemoryEffects::Write::get());
|
|
|
|
|
|
|
|
// Check for a non-default resource to use.
|
|
|
|
SideEffects::Resource *resource = SideEffects::DefaultResource::get();
|
|
|
|
if (effectElement.get("test_resource"))
|
|
|
|
resource = TestResource::get();
|
|
|
|
|
2020-11-18 18:31:40 -08:00
|
|
|
// Check for a result to affect.
|
|
|
|
if (effectElement.get("on_result"))
|
|
|
|
effects.emplace_back(effect, getResult(), resource);
|
|
|
|
else if (Attribute ref = effectElement.get("on_reference"))
|
|
|
|
effects.emplace_back(effect, ref.cast<SymbolRefAttr>(), resource);
|
|
|
|
else
|
|
|
|
effects.emplace_back(effect, resource);
|
[mlir][SideEffects] Define a set of interfaces and traits for defining side effects
This revision introduces the infrastructure for defining side-effects and attaching them to operations. This infrastructure allows for defining different types of side effects, that don't interact with each other, but use the same internal mechanisms. At the base of this is an interface that allows operations to specify the different effect instances that are exhibited by a specific operation instance. An effect instance is comprised of the following:
* Effect: The specific effect being applied.
For memory related effects this may be reading from memory, storing to memory, etc.
* Value: A specific value, either operand/result/region argument, the effect pertains to.
* Resource: This is a global entity that represents the domain within which the effect is being applied.
MLIR serves many different abstractions, which cover many different domains. Simple effects are may have very different context, for example writing to an in-memory buffer vs a database. This revision defines uses this infrastructure to define a set of initial MemoryEffects. The are effects that generally correspond to memory of some kind; Allocate, Free, Read, Write.
This set of memory effects will be used in follow revisions to generalize various parts of the compiler, and make others more powerful(e.g. DCE).
This infrastructure was originally proposed here:
https://groups.google.com/a/tensorflow.org/g/mlir/c/v2mNl4vFCUM
Differential Revision: https://reviews.llvm.org/D74439
2020-03-06 13:53:16 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 15:25:01 +01:00
|
|
|
void SideEffectOp::getEffects(
|
|
|
|
SmallVectorImpl<TestEffects::EffectInstance> &effects) {
|
2021-03-24 08:25:25 +00:00
|
|
|
testSideEffectOpGetEffect(getOperation(), effects);
|
2020-11-15 15:25:01 +01:00
|
|
|
}
|
|
|
|
|
Add support for custom op parser/printer hooks to know about result names.
Summary:
This allows the custom parser/printer hooks to do interesting things with
the SSA names. This patch:
- Adds a new 'getResultName' method to OpAsmParser that allows a parser
implementation to get information about its result names, along with
a getNumResults() method that allows op parser impls to know how many
results are expected.
- Adds a OpAsmPrinter::printOperand overload that takes an explicit stream.
- Adds a test.string_attr_pretty_name operation that uses these hooks to
do fancy things with the result name.
Reviewers: rriddle!
Subscribers: mehdi_amini, rriddle, jpienaar, burmako, shauheen, antiagainst, nicolasvasilache, arpith-jacob, mgester, lucyrfox, liufengdb, Joonsoo, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D76205
2020-03-15 17:13:59 -07:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// StringAttrPrettyNameOp
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
// This op has fancy handling of its SSA result name.
|
|
|
|
static ParseResult parseStringAttrPrettyNameOp(OpAsmParser &parser,
|
|
|
|
OperationState &result) {
|
|
|
|
// Add the result types.
|
|
|
|
for (size_t i = 0, e = parser.getNumResults(); i != e; ++i)
|
|
|
|
result.addTypes(parser.getBuilder().getIntegerType(32));
|
|
|
|
|
|
|
|
if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
// If the attribute dictionary contains no 'names' attribute, infer it from
|
|
|
|
// the SSA name (if specified).
|
|
|
|
bool hadNames = llvm::any_of(result.attributes, [](NamedAttribute attr) {
|
2021-11-18 05:23:32 +00:00
|
|
|
return attr.getName() == "names";
|
Add support for custom op parser/printer hooks to know about result names.
Summary:
This allows the custom parser/printer hooks to do interesting things with
the SSA names. This patch:
- Adds a new 'getResultName' method to OpAsmParser that allows a parser
implementation to get information about its result names, along with
a getNumResults() method that allows op parser impls to know how many
results are expected.
- Adds a OpAsmPrinter::printOperand overload that takes an explicit stream.
- Adds a test.string_attr_pretty_name operation that uses these hooks to
do fancy things with the result name.
Reviewers: rriddle!
Subscribers: mehdi_amini, rriddle, jpienaar, burmako, shauheen, antiagainst, nicolasvasilache, arpith-jacob, mgester, lucyrfox, liufengdb, Joonsoo, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D76205
2020-03-15 17:13:59 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
// If there was no name specified, check to see if there was a useful name
|
|
|
|
// specified in the asm file.
|
|
|
|
if (hadNames || parser.getNumResults() == 0)
|
|
|
|
return success();
|
|
|
|
|
|
|
|
SmallVector<StringRef, 4> names;
|
|
|
|
auto *context = result.getContext();
|
|
|
|
|
|
|
|
for (size_t i = 0, e = parser.getNumResults(); i != e; ++i) {
|
|
|
|
auto resultName = parser.getResultName(i);
|
|
|
|
StringRef nameStr;
|
|
|
|
if (!resultName.first.empty() && !isdigit(resultName.first[0]))
|
|
|
|
nameStr = resultName.first;
|
|
|
|
|
|
|
|
names.push_back(nameStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto namesAttr = parser.getBuilder().getStrArrayAttr(names);
|
2021-11-16 17:21:15 +00:00
|
|
|
result.attributes.push_back({StringAttr::get(context, "names"), namesAttr});
|
Add support for custom op parser/printer hooks to know about result names.
Summary:
This allows the custom parser/printer hooks to do interesting things with
the SSA names. This patch:
- Adds a new 'getResultName' method to OpAsmParser that allows a parser
implementation to get information about its result names, along with
a getNumResults() method that allows op parser impls to know how many
results are expected.
- Adds a OpAsmPrinter::printOperand overload that takes an explicit stream.
- Adds a test.string_attr_pretty_name operation that uses these hooks to
do fancy things with the result name.
Reviewers: rriddle!
Subscribers: mehdi_amini, rriddle, jpienaar, burmako, shauheen, antiagainst, nicolasvasilache, arpith-jacob, mgester, lucyrfox, liufengdb, Joonsoo, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D76205
2020-03-15 17:13:59 -07:00
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print(OpAsmPrinter &p, StringAttrPrettyNameOp op) {
|
|
|
|
// Note that we only need to print the "name" attribute if the asmprinter
|
|
|
|
// result name disagrees with it. This can happen in strange cases, e.g.
|
|
|
|
// when there are conflicts.
|
2021-10-20 07:08:36 -07:00
|
|
|
bool namesDisagree = op.getNames().size() != op.getNumResults();
|
Add support for custom op parser/printer hooks to know about result names.
Summary:
This allows the custom parser/printer hooks to do interesting things with
the SSA names. This patch:
- Adds a new 'getResultName' method to OpAsmParser that allows a parser
implementation to get information about its result names, along with
a getNumResults() method that allows op parser impls to know how many
results are expected.
- Adds a OpAsmPrinter::printOperand overload that takes an explicit stream.
- Adds a test.string_attr_pretty_name operation that uses these hooks to
do fancy things with the result name.
Reviewers: rriddle!
Subscribers: mehdi_amini, rriddle, jpienaar, burmako, shauheen, antiagainst, nicolasvasilache, arpith-jacob, mgester, lucyrfox, liufengdb, Joonsoo, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D76205
2020-03-15 17:13:59 -07:00
|
|
|
|
|
|
|
SmallString<32> resultNameStr;
|
|
|
|
for (size_t i = 0, e = op.getNumResults(); i != e && !namesDisagree; ++i) {
|
|
|
|
resultNameStr.clear();
|
|
|
|
llvm::raw_svector_ostream tmpStream(resultNameStr);
|
|
|
|
p.printOperand(op.getResult(i), tmpStream);
|
|
|
|
|
2021-10-20 07:08:36 -07:00
|
|
|
auto expectedName = op.getNames()[i].dyn_cast<StringAttr>();
|
Add support for custom op parser/printer hooks to know about result names.
Summary:
This allows the custom parser/printer hooks to do interesting things with
the SSA names. This patch:
- Adds a new 'getResultName' method to OpAsmParser that allows a parser
implementation to get information about its result names, along with
a getNumResults() method that allows op parser impls to know how many
results are expected.
- Adds a OpAsmPrinter::printOperand overload that takes an explicit stream.
- Adds a test.string_attr_pretty_name operation that uses these hooks to
do fancy things with the result name.
Reviewers: rriddle!
Subscribers: mehdi_amini, rriddle, jpienaar, burmako, shauheen, antiagainst, nicolasvasilache, arpith-jacob, mgester, lucyrfox, liufengdb, Joonsoo, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D76205
2020-03-15 17:13:59 -07:00
|
|
|
if (!expectedName ||
|
|
|
|
tmpStream.str().drop_front() != expectedName.getValue()) {
|
|
|
|
namesDisagree = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (namesDisagree)
|
2021-02-26 13:28:32 +00:00
|
|
|
p.printOptionalAttrDictWithKeyword(op->getAttrs());
|
Add support for custom op parser/printer hooks to know about result names.
Summary:
This allows the custom parser/printer hooks to do interesting things with
the SSA names. This patch:
- Adds a new 'getResultName' method to OpAsmParser that allows a parser
implementation to get information about its result names, along with
a getNumResults() method that allows op parser impls to know how many
results are expected.
- Adds a OpAsmPrinter::printOperand overload that takes an explicit stream.
- Adds a test.string_attr_pretty_name operation that uses these hooks to
do fancy things with the result name.
Reviewers: rriddle!
Subscribers: mehdi_amini, rriddle, jpienaar, burmako, shauheen, antiagainst, nicolasvasilache, arpith-jacob, mgester, lucyrfox, liufengdb, Joonsoo, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D76205
2020-03-15 17:13:59 -07:00
|
|
|
else
|
2021-02-26 13:28:32 +00:00
|
|
|
p.printOptionalAttrDictWithKeyword(op->getAttrs(), {"names"});
|
Add support for custom op parser/printer hooks to know about result names.
Summary:
This allows the custom parser/printer hooks to do interesting things with
the SSA names. This patch:
- Adds a new 'getResultName' method to OpAsmParser that allows a parser
implementation to get information about its result names, along with
a getNumResults() method that allows op parser impls to know how many
results are expected.
- Adds a OpAsmPrinter::printOperand overload that takes an explicit stream.
- Adds a test.string_attr_pretty_name operation that uses these hooks to
do fancy things with the result name.
Reviewers: rriddle!
Subscribers: mehdi_amini, rriddle, jpienaar, burmako, shauheen, antiagainst, nicolasvasilache, arpith-jacob, mgester, lucyrfox, liufengdb, Joonsoo, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D76205
2020-03-15 17:13:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// We set the SSA name in the asm syntax to the contents of the name
|
|
|
|
// attribute.
|
|
|
|
void StringAttrPrettyNameOp::getAsmResultNames(
|
|
|
|
function_ref<void(Value, StringRef)> setNameFn) {
|
|
|
|
|
2021-10-20 07:08:36 -07:00
|
|
|
auto value = getNames();
|
Add support for custom op parser/printer hooks to know about result names.
Summary:
This allows the custom parser/printer hooks to do interesting things with
the SSA names. This patch:
- Adds a new 'getResultName' method to OpAsmParser that allows a parser
implementation to get information about its result names, along with
a getNumResults() method that allows op parser impls to know how many
results are expected.
- Adds a OpAsmPrinter::printOperand overload that takes an explicit stream.
- Adds a test.string_attr_pretty_name operation that uses these hooks to
do fancy things with the result name.
Reviewers: rriddle!
Subscribers: mehdi_amini, rriddle, jpienaar, burmako, shauheen, antiagainst, nicolasvasilache, arpith-jacob, mgester, lucyrfox, liufengdb, Joonsoo, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D76205
2020-03-15 17:13:59 -07:00
|
|
|
for (size_t i = 0, e = value.size(); i != e; ++i)
|
|
|
|
if (auto str = value[i].dyn_cast<StringAttr>())
|
|
|
|
if (!str.getValue().empty())
|
|
|
|
setNameFn(getResult(i), str.getValue());
|
|
|
|
}
|
|
|
|
|
2020-06-30 11:58:45 +02:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// RegionIfOp
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
static void print(OpAsmPrinter &p, RegionIfOp op) {
|
2021-08-28 03:03:15 +00:00
|
|
|
p << " ";
|
2020-06-30 11:58:45 +02:00
|
|
|
p.printOperands(op.getOperands());
|
|
|
|
p << ": " << op.getOperandTypes();
|
|
|
|
p.printArrowTypeList(op.getResultTypes());
|
|
|
|
p << " then";
|
2021-10-20 07:08:36 -07:00
|
|
|
p.printRegion(op.getThenRegion(),
|
2020-06-30 11:58:45 +02:00
|
|
|
/*printEntryBlockArgs=*/true,
|
|
|
|
/*printBlockTerminators=*/true);
|
|
|
|
p << " else";
|
2021-10-20 07:08:36 -07:00
|
|
|
p.printRegion(op.getElseRegion(),
|
2020-06-30 11:58:45 +02:00
|
|
|
/*printEntryBlockArgs=*/true,
|
|
|
|
/*printBlockTerminators=*/true);
|
|
|
|
p << " join";
|
2021-10-20 07:08:36 -07:00
|
|
|
p.printRegion(op.getJoinRegion(),
|
2020-06-30 11:58:45 +02:00
|
|
|
/*printEntryBlockArgs=*/true,
|
|
|
|
/*printBlockTerminators=*/true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ParseResult parseRegionIfOp(OpAsmParser &parser,
|
|
|
|
OperationState &result) {
|
|
|
|
SmallVector<OpAsmParser::OperandType, 2> operandInfos;
|
|
|
|
SmallVector<Type, 2> operandTypes;
|
|
|
|
|
|
|
|
result.regions.reserve(3);
|
|
|
|
Region *thenRegion = result.addRegion();
|
|
|
|
Region *elseRegion = result.addRegion();
|
|
|
|
Region *joinRegion = result.addRegion();
|
|
|
|
|
|
|
|
// Parse operand, type and arrow type lists.
|
|
|
|
if (parser.parseOperandList(operandInfos) ||
|
|
|
|
parser.parseColonTypeList(operandTypes) ||
|
|
|
|
parser.parseArrowTypeList(result.types))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
// Parse all attached regions.
|
|
|
|
if (parser.parseKeyword("then") || parser.parseRegion(*thenRegion, {}, {}) ||
|
|
|
|
parser.parseKeyword("else") || parser.parseRegion(*elseRegion, {}, {}) ||
|
|
|
|
parser.parseKeyword("join") || parser.parseRegion(*joinRegion, {}, {}))
|
|
|
|
return failure();
|
|
|
|
|
|
|
|
return parser.resolveOperands(operandInfos, operandTypes,
|
|
|
|
parser.getCurrentLocation(), result.operands);
|
|
|
|
}
|
|
|
|
|
|
|
|
OperandRange RegionIfOp::getSuccessorEntryOperands(unsigned index) {
|
|
|
|
assert(index < 2 && "invalid region index");
|
|
|
|
return getOperands();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RegionIfOp::getSuccessorRegions(
|
|
|
|
Optional<unsigned> index, ArrayRef<Attribute> operands,
|
|
|
|
SmallVectorImpl<RegionSuccessor> ®ions) {
|
|
|
|
// We always branch to the join region.
|
|
|
|
if (index.hasValue()) {
|
|
|
|
if (index.getValue() < 2)
|
2021-10-20 07:08:36 -07:00
|
|
|
regions.push_back(RegionSuccessor(&getJoinRegion(), getJoinArgs()));
|
2020-06-30 11:58:45 +02:00
|
|
|
else
|
|
|
|
regions.push_back(RegionSuccessor(getResults()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The then and else regions are the entry regions of this op.
|
2021-10-20 07:08:36 -07:00
|
|
|
regions.push_back(RegionSuccessor(&getThenRegion(), getThenArgs()));
|
|
|
|
regions.push_back(RegionSuccessor(&getElseRegion(), getElseArgs()));
|
2020-06-30 11:58:45 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 07:21:41 -04:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// SingleNoTerminatorCustomAsmOp
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
static ParseResult parseSingleNoTerminatorCustomAsmOp(OpAsmParser &parser,
|
|
|
|
OperationState &state) {
|
|
|
|
Region *body = state.addRegion();
|
|
|
|
if (parser.parseRegion(*body, /*arguments=*/{}, /*argTypes=*/{}))
|
|
|
|
return failure();
|
|
|
|
return success();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print(SingleNoTerminatorCustomAsmOp op, OpAsmPrinter &printer) {
|
|
|
|
printer.printRegion(
|
|
|
|
op.getRegion(), /*printEntryBlockArgs=*/false,
|
|
|
|
// This op has a single block without terminators. But explicitly mark
|
|
|
|
// as not printing block terminators for testing.
|
|
|
|
/*printBlockTerminators=*/false);
|
|
|
|
}
|
|
|
|
|
2019-11-25 11:29:21 -08:00
|
|
|
#include "TestOpEnums.cpp.inc"
|
2020-11-15 15:25:01 +01:00
|
|
|
#include "TestOpInterfaces.cpp.inc"
|
2020-04-27 17:52:59 -07:00
|
|
|
#include "TestOpStructs.cpp.inc"
|
2020-06-30 15:42:52 -07:00
|
|
|
#include "TestTypeInterfaces.cpp.inc"
|
2019-11-25 11:29:21 -08:00
|
|
|
|
2019-05-23 15:11:19 -07:00
|
|
|
#define GET_OP_CLASSES
|
|
|
|
#include "TestOps.cpp.inc"
|