[mlir][Toy] Update the tutorial to use tablegen for dialect declarations

This was missed when the feature was originally added.

Differential Revision: https://reviews.llvm.org/D87060
This commit is contained in:
River Riddle 2021-03-17 17:36:31 -07:00
parent f4bb076a44
commit ee74860597
22 changed files with 114 additions and 188 deletions

View File

@ -168,19 +168,49 @@ provide an easy avenue for high-level analysis and transformation.
/// constructor). It can also override virtual methods to change some general
/// behavior, which will be demonstrated in later chapters of the tutorial.
class ToyDialect : public mlir::Dialect {
public:
public:
explicit ToyDialect(mlir::MLIRContext *ctx);
/// Provide a utility accessor to the dialect namespace. This is used by
/// several utilities.
static llvm::StringRef getDialectNamespace() { return "toy"; }
/// An initializer called from the constructor of ToyDialect that is used to
/// register operations, types, and more within the Toy dialect.
void initialize();
};
```
The dialect can now be registered in the global registry:
This is the C++ definition of a dialect, but MLIR also supports defining
dialects declaratively via tablegen. Using the declarative specification is much
cleaner as it removes the need for a large portion of the boilerplate when
defining a new dialect. In the declarative format, the toy dialect would be
specified as:
```tablegen
// Provide a definition of the 'toy' dialect in the ODS framework so that we
// can define our operations.
def Toy_Dialect : Dialect {
// The namespace of our dialect, this corresponds 1-1 with the string we
// provided in `ToyDialect::getDialectNamespace`.
let name = "toy";
// The C++ namespace that the dialect class definition resides in.
let cppNamespace = "toy";
}
```
To see what this generates, we can run the `mlir-tblgen` command with the
`gen-dialect-decls` action like so:
```shell
${build_root}/bin/mlir-tblgen -gen-dialect-decls ${mlir_src_root}/examples/toy/Ch2/include/toy/Ops.td -I ${mlir_src_root}/include/
```
The dialect can now be loaded into an MLIRContext:
```c++
mlir::registerDialect<ToyDialect>();
context.loadDialect<ToyDialect>();
```
Any new `MLIRContext` created from now on will contain an instance of the Toy
@ -249,11 +279,10 @@ class ConstantOp : public mlir::Op<ConstantOp,
};
```
and we register this operation in the `ToyDialect` constructor:
and we register this operation in the `ToyDialect` initializer:
```c++
ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
: mlir::Dialect(getDialectNamespace(), ctx) {
void ToyDialect::initialize() {
addOperations<ConstantOp>();
}
```
@ -311,27 +340,9 @@ C++ API changes.
Lets see how to define the ODS equivalent of our ConstantOp:
The first thing to do is to define a link to the Toy dialect that we defined in
C++. This is used to link all of the operations that we will define to our
dialect:
```tablegen
// Provide a definition of the 'toy' dialect in the ODS framework so that we
// can define our operations.
def Toy_Dialect : Dialect {
// The namespace of our dialect, this corresponds 1-1 with the string we
// provided in `ToyDialect::getDialectNamespace`.
let name = "toy";
// The C++ namespace that the dialect class definition resides in.
let cppNamespace = "toy";
}
```
Now that we have defined a link to the Toy dialect, we can start defining
operations. Operations in ODS are defined by inheriting from the `Op` class. To
simplify our operation definitions, we will define a base class for operations
in the Toy dialect.
Operations in ODS are defined by inheriting from the `Op` class. To simplify our
operation definitions, we will define a base class for operations in the Toy
dialect.
```tablegen
// Base class for toy dialect operations. This operation inherits from the base

View File

@ -99,7 +99,7 @@ We then register our dialect interface directly on the Toy dialect, similarly to
how we did for operations.
```c++
ToyDialect::ToyDialect(mlir::MLIRContext *ctx) : mlir::Dialect("toy", ctx) {
void ToyDialect::initialize() {
addInterfaces<ToyInlinerInterface>();
}
```

View File

@ -177,12 +177,11 @@ public:
};
```
We register this type in the `ToyDialect` constructor in a similar way to how we
We register this type in the `ToyDialect` initializer in a similar way to how we
did with operations:
```c++
ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
: mlir::Dialect(getDialectNamespace(), ctx) {
void ToyDialect::initialize() {
addTypes<StructType>();
}
```
@ -193,12 +192,32 @@ storage class must be visible.)
With this we can now use our `StructType` when generating MLIR from Toy. See
examples/toy/Ch7/mlir/MLIRGen.cpp for more details.
### Exposing to ODS
After defining a new type, we should make the ODS framework aware of our Type so
that we can use it in the operation definitions and auto-generate utilities
within the Dialect. A simple example is shown below:
```tablegen
// Provide a definition for the Toy StructType for use in ODS. This allows for
// using StructType in a similar way to Tensor or MemRef. We use `DialectType`
// to demarcate the StructType as belonging to the Toy dialect.
def Toy_StructType :
DialectType<Toy_Dialect, CPred<"$_self.isa<StructType>()">,
"Toy struct type">;
// Provide a definition of the types that are used within the Toy dialect.
def Toy_Type : AnyTypeOf<[F64Tensor, Toy_StructType]>;
```
### Parsing and Printing
At this point we can use our `StructType` during MLIR generation and
transformation, but we can't output or parse `.mlir`. For this we need to add
support for parsing and printing instances of the `StructType`. This can be done
by overriding the `parseType` and `printType` methods on the `ToyDialect`.
Declarations for these methods are automatically provided when the type is
exposed to ODS as detailed in the previous section.
```c++
class ToyDialect : public mlir::Dialect {
@ -321,22 +340,8 @@ the IR. The next step is to add support for using it within our operations.
#### Updating Existing Operations
A few of our existing operations will need to be updated to handle `StructType`.
The first step is to make the ODS framework aware of our Type so that we can use
it in the operation definitions. A simple example is shown below:
```tablegen
// Provide a definition for the Toy StructType for use in ODS. This allows for
// using StructType in a similar way to Tensor or MemRef.
def Toy_StructType :
Type<CPred<"$_self.isa<StructType>()">, "Toy struct type">;
// Provide a definition of the types that are used within the Toy dialect.
def Toy_Type : AnyTypeOf<[F64Tensor, Toy_StructType]>;
```
We can then update our operations, e.g. `ReturnOp`, to also accept the
`Toy_StructType`:
A few of our existing operations, e.g. `ReturnOp`, will need to be updated to
handle `Toy_StructType`.
```tablegen
def ReturnOp : Toy_Op<"return", [Terminator, HasParent<"FuncOp">]> {

View File

@ -1,4 +1,5 @@
set(LLVM_TARGET_DEFINITIONS Ops.td)
mlir_tablegen(Ops.h.inc -gen-op-decls)
mlir_tablegen(Ops.cpp.inc -gen-op-defs)
mlir_tablegen(Dialect.h.inc -gen-dialect-decls)
add_public_tablegen_target(ToyCh2OpsIncGen)

View File

@ -18,24 +18,9 @@
#include "mlir/IR/Dialect.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
namespace mlir {
namespace toy {
/// This is the definition of the Toy dialect. A dialect inherits from
/// mlir::Dialect and registers custom attributes, operations, and types (in its
/// constructor). It can also override some general behavior exposed via virtual
/// methods.
class ToyDialect : public mlir::Dialect {
public:
explicit ToyDialect(mlir::MLIRContext *ctx);
/// Provide a utility accessor to the dialect namespace. This is used by
/// several utilities for casting between dialects.
static llvm::StringRef getDialectNamespace() { return "toy"; }
};
} // end namespace toy
} // end namespace mlir
/// Include the auto-generated header file containing the declaration of the toy
/// dialect.
#include "toy/Dialect.h.inc"
/// Include the auto-generated header file containing the declarations of the
/// toy operations.

View File

@ -24,10 +24,9 @@ using namespace mlir::toy;
// ToyDialect
//===----------------------------------------------------------------------===//
/// Dialect creation, the instance will be owned by the context. This is the
/// point of registration of custom types and operations for the dialect.
ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
: mlir::Dialect(getDialectNamespace(), ctx, TypeID::get<ToyDialect>()) {
/// Dialect initialization, the instance will be owned by the context. This is
/// the point of registration of types and operations for the dialect.
void ToyDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "toy/Ops.cpp.inc"

View File

@ -1,4 +1,5 @@
set(LLVM_TARGET_DEFINITIONS Ops.td)
mlir_tablegen(Ops.h.inc -gen-op-decls)
mlir_tablegen(Ops.cpp.inc -gen-op-defs)
mlir_tablegen(Dialect.h.inc -gen-dialect-decls)
add_public_tablegen_target(ToyCh3OpsIncGen)

View File

@ -18,24 +18,9 @@
#include "mlir/IR/Dialect.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
namespace mlir {
namespace toy {
/// This is the definition of the Toy dialect. A dialect inherits from
/// mlir::Dialect and registers custom attributes, operations, and types (in its
/// constructor). It can also override some general behavior exposed via virtual
/// methods.
class ToyDialect : public mlir::Dialect {
public:
explicit ToyDialect(mlir::MLIRContext *ctx);
/// Provide a utility accessor to the dialect namespace. This is used by
/// several utilities for casting between dialects.
static llvm::StringRef getDialectNamespace() { return "toy"; }
};
} // end namespace toy
} // end namespace mlir
/// Include the auto-generated header file containing the declaration of the toy
/// dialect.
#include "toy/Dialect.h.inc"
/// Include the auto-generated header file containing the declarations of the
/// toy operations.

View File

@ -24,10 +24,9 @@ using namespace mlir::toy;
// ToyDialect
//===----------------------------------------------------------------------===//
/// Dialect creation, the instance will be owned by the context. This is the
/// point of registration of custom types and operations for the dialect.
ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
: mlir::Dialect(getDialectNamespace(), ctx, TypeID::get<ToyDialect>()) {
/// Dialect initialization, the instance will be owned by the context. This is
/// the point of registration of types and operations for the dialect.
void ToyDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "toy/Ops.cpp.inc"

View File

@ -2,6 +2,7 @@
set(LLVM_TARGET_DEFINITIONS Ops.td)
mlir_tablegen(Ops.h.inc -gen-op-decls)
mlir_tablegen(Ops.cpp.inc -gen-op-defs)
mlir_tablegen(Dialect.h.inc -gen-dialect-decls)
add_public_tablegen_target(ToyCh4OpsIncGen)
# Most dialects should use add_mlir_interfaces().

View File

@ -21,24 +21,9 @@
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "toy/ShapeInferenceInterface.h"
namespace mlir {
namespace toy {
/// This is the definition of the Toy dialect. A dialect inherits from
/// mlir::Dialect and registers custom attributes, operations, and types (in its
/// constructor). It can also override some general behavior exposed via virtual
/// methods.
class ToyDialect : public mlir::Dialect {
public:
explicit ToyDialect(mlir::MLIRContext *ctx);
/// Provide a utility accessor to the dialect namespace. This is used by
/// several utilities for casting between dialects.
static llvm::StringRef getDialectNamespace() { return "toy"; }
};
} // end namespace toy
} // end namespace mlir
/// Include the auto-generated header file containing the declaration of the toy
/// dialect.
#include "toy/Dialect.h.inc"
/// Include the auto-generated header file containing the declarations of the
/// toy operations.

View File

@ -79,10 +79,9 @@ struct ToyInlinerInterface : public DialectInlinerInterface {
// ToyDialect
//===----------------------------------------------------------------------===//
/// Dialect creation, the instance will be owned by the context. This is the
/// point of registration of custom types and operations for the dialect.
ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
: mlir::Dialect(getDialectNamespace(), ctx, TypeID::get<ToyDialect>()) {
/// Dialect initialization, the instance will be owned by the context. This is
/// the point of registration of types and operations for the dialect.
void ToyDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "toy/Ops.cpp.inc"

View File

@ -2,6 +2,7 @@
set(LLVM_TARGET_DEFINITIONS Ops.td)
mlir_tablegen(Ops.h.inc -gen-op-decls)
mlir_tablegen(Ops.cpp.inc -gen-op-defs)
mlir_tablegen(Dialect.h.inc -gen-dialect-decls)
add_public_tablegen_target(ToyCh5OpsIncGen)
# Most dialects should use add_mlir_interfaces().

View File

@ -21,24 +21,9 @@
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "toy/ShapeInferenceInterface.h"
namespace mlir {
namespace toy {
/// This is the definition of the Toy dialect. A dialect inherits from
/// mlir::Dialect and registers custom attributes, operations, and types (in its
/// constructor). It can also override some general behavior exposed via virtual
/// methods.
class ToyDialect : public mlir::Dialect {
public:
explicit ToyDialect(mlir::MLIRContext *ctx);
/// Provide a utility accessor to the dialect namespace. This is used by
/// several utilities for casting between dialects.
static llvm::StringRef getDialectNamespace() { return "toy"; }
};
} // end namespace toy
} // end namespace mlir
/// Include the auto-generated header file containing the declaration of the toy
/// dialect.
#include "toy/Dialect.h.inc"
/// Include the auto-generated header file containing the declarations of the
/// toy operations.

View File

@ -79,10 +79,9 @@ struct ToyInlinerInterface : public DialectInlinerInterface {
// ToyDialect
//===----------------------------------------------------------------------===//
/// Dialect creation, the instance will be owned by the context. This is the
/// point of registration of custom types and operations for the dialect.
ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
: mlir::Dialect(getDialectNamespace(), ctx, TypeID::get<ToyDialect>()) {
/// Dialect initialization, the instance will be owned by the context. This is
/// the point of registration of types and operations for the dialect.
void ToyDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "toy/Ops.cpp.inc"

View File

@ -2,6 +2,7 @@
set(LLVM_TARGET_DEFINITIONS Ops.td)
mlir_tablegen(Ops.h.inc -gen-op-decls)
mlir_tablegen(Ops.cpp.inc -gen-op-defs)
mlir_tablegen(Dialect.h.inc -gen-dialect-decls)
add_public_tablegen_target(ToyCh6OpsIncGen)
# Most dialects should use add_mlir_interfaces().

View File

@ -21,24 +21,9 @@
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "toy/ShapeInferenceInterface.h"
namespace mlir {
namespace toy {
/// This is the definition of the Toy dialect. A dialect inherits from
/// mlir::Dialect and registers custom attributes, operations, and types (in its
/// constructor). It can also override some general behavior exposed via virtual
/// methods.
class ToyDialect : public mlir::Dialect {
public:
explicit ToyDialect(mlir::MLIRContext *ctx);
/// Provide a utility accessor to the dialect namespace. This is used by
/// several utilities for casting between dialects.
static llvm::StringRef getDialectNamespace() { return "toy"; }
};
} // end namespace toy
} // end namespace mlir
/// Include the auto-generated header file containing the declaration of the toy
/// dialect.
#include "toy/Dialect.h.inc"
/// Include the auto-generated header file containing the declarations of the
/// toy operations.

View File

@ -79,10 +79,9 @@ struct ToyInlinerInterface : public DialectInlinerInterface {
// ToyDialect
//===----------------------------------------------------------------------===//
/// Dialect creation, the instance will be owned by the context. This is the
/// point of registration of custom types and operations for the dialect.
ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
: mlir::Dialect(getDialectNamespace(), ctx, TypeID::get<ToyDialect>()) {
/// Dialect initialization, the instance will be owned by the context. This is
/// the point of registration of types and operations for the dialect.
void ToyDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "toy/Ops.cpp.inc"

View File

@ -2,6 +2,7 @@
set(LLVM_TARGET_DEFINITIONS Ops.td)
mlir_tablegen(Ops.h.inc -gen-op-decls)
mlir_tablegen(Ops.cpp.inc -gen-op-defs)
mlir_tablegen(Dialect.h.inc -gen-dialect-decls)
add_public_tablegen_target(ToyCh7OpsIncGen)
# Most dialects should use add_mlir_interfaces().

View File

@ -26,34 +26,13 @@ namespace toy {
namespace detail {
struct StructTypeStorage;
} // end namespace detail
/// This is the definition of the Toy dialect. A dialect inherits from
/// mlir::Dialect and registers custom attributes, operations, and types (in its
/// constructor). It can also override some general behavior exposed via virtual
/// methods.
class ToyDialect : public mlir::Dialect {
public:
explicit ToyDialect(mlir::MLIRContext *ctx);
/// A hook used to materialize constant values with the given type.
Operation *materializeConstant(OpBuilder &builder, Attribute value, Type type,
Location loc) override;
/// Parse an instance of a type registered to the toy dialect.
mlir::Type parseType(mlir::DialectAsmParser &parser) const override;
/// Print an instance of a type registered to the toy dialect.
void printType(mlir::Type type,
mlir::DialectAsmPrinter &printer) const override;
/// Provide a utility accessor to the dialect namespace. This is used by
/// several utilities for casting between dialects.
static llvm::StringRef getDialectNamespace() { return "toy"; }
};
} // end namespace toy
} // end namespace mlir
/// Include the auto-generated header file containing the declaration of the toy
/// dialect.
#include "toy/Dialect.h.inc"
//===----------------------------------------------------------------------===//
// Toy Operations
//===----------------------------------------------------------------------===//

View File

@ -23,6 +23,10 @@ include "toy/ShapeInferenceInterface.td"
def Toy_Dialect : Dialect {
let name = "toy";
let cppNamespace = "::mlir::toy";
// We set this bit to generate a declaration of the `materializeConstant`
// method so that we can materialize constants for our toy operations.
let hasConstantMaterializer = 1;
}
// Base class for toy dialect operations. This operation inherits from the base
@ -34,9 +38,11 @@ class Toy_Op<string mnemonic, list<OpTrait> traits = []> :
Op<Toy_Dialect, mnemonic, traits>;
// Provide a definition for the Toy StructType for use in ODS. This allows for
// using StructType in a similar way to Tensor or MemRef.
// using StructType in a similar way to Tensor or MemRef. We use `DialectType`
// to demarcate the StructType as belonging to the Toy dialect.
def Toy_StructType :
Type<CPred<"$_self.isa<StructType>()">, "Toy struct type">;
DialectType<Toy_Dialect, CPred<"$_self.isa<StructType>()">,
"Toy struct type">;
// Provide a definition of the types that are used within the Toy dialect.
def Toy_Type : AnyTypeOf<[F64Tensor, Toy_StructType]>;

View File

@ -544,10 +544,9 @@ void ToyDialect::printType(mlir::Type type,
// ToyDialect
//===----------------------------------------------------------------------===//
/// Dialect creation, the instance will be owned by the context. This is the
/// point of registration of custom types and operations for the dialect.
ToyDialect::ToyDialect(mlir::MLIRContext *ctx)
: mlir::Dialect(getDialectNamespace(), ctx, TypeID::get<ToyDialect>()) {
/// Dialect initialization, the instance will be owned by the context. This is
/// the point of registration of types and operations for the dialect.
void ToyDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "toy/Ops.cpp.inc"