This tutorial runs through the implementation of a basic toy language on top of MLIR. The difference with the tutorial done in the official MLIR page is that this dialect is setup to be an out-of-tree dialect.
NOTE: The tutorial is up-to-date until Ch2 of the Toy Tutorial.
$LLVM_SRC_DIR stores the directory of the llvm-project repository:
$ echo $LLVM_SRC_DIR
/Users/daniels/git/llvm-projectClone the LLVM git project (I chose to put it in ~/git):
$ cd ~/git && git clone https://github.com/llvm/llvm-project.git && cd llvm-projectCreate a directory to store the MLIR build:
$ mkdir build-mlir && cd build-mlir
Configure the build using cmake (see https://mlir.llvm.org/getting_started/):
$ cmake -G Ninja ../llvm \
-DLLVM_ENABLE_LLD=ON -DMLIR_INCLUDE_INTEGRATION_TESTS=ON \
-DLLVM_ENABLE_PROJECTS=mlir \
-DLLVM_BUILD_EXAMPLES=ON \
-DLLVM_TARGETS_TO_BUILD=host \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_ASSERTIONS=ON
$ cmake --build . --target check-mlir$MLIR_BUILD_DIR stores the directory where MLIR was build:
$ echo $MLIR_BUILD_DIR
/Users/daniels/git/llvm-project/build-mlirNow that MLIR is built we proceed to build this project:
$ rm -rf build
$ cmake -S . -B build -DMLIR_DIR=$MLIR_BUILD_DIR/lib/cmake/mlir
$ cmake --build build
[ 11%] Building Ops.h.inc...
[ 22%] Building Ops.cpp.inc...
[ 33%] Building Dialect.h.inc...
[ 44%] Building Dialect.cpp.inc...
[ 44%] Built target ToyOpsIncGen
[ 55%] Building CXX object toyc/CMakeFiles/toyc.dir/toyc.cpp.o
[ 66%] Building CXX object toyc/CMakeFiles/toyc.dir/parser/AST.cpp.o
[ 77%] Building CXX object toyc/CMakeFiles/toyc.dir/mlir/Dialect.cpp.o
[ 88%] Building CXX object toyc/CMakeFiles/toyc.dir/mlir/MLIRGen.cpp.o
[100%] Linking CXX executable ../bin/toyc
[100%] Built target toycThe executables built are stored in:
$ ls ./build/bin
toycLet's convert the following toy code:
$ cat test/Ch2/codegen.toy
# User defined generic function that operates on unknown shaped arguments.
def multiply_transpose(a, b) {
return transpose(a) * transpose(b);
}
def main() {
var a<2, 3> = [[1, 2, 3], [4, 5, 6]];
var b<2, 3> = [1, 2, 3, 4, 5, 6];
var c = multiply_transpose(a, b);
var d = multiply_transpose(b, a);
print(d);
To its AST representation:
$ ./build/bin/toyc -emit=ast test/Ch2/codegen.toy
Module:
Function
Proto 'multiply_transpose' @test/Ch2/codegen.toy:2:1
Params: [a, b]
Block {
Return
BinOp: * @test/Ch2/codegen.toy:3:25
Call 'transpose' [ @test/Ch2/codegen.toy:3:10
var: a @test/Ch2/codegen.toy:3:20
]
Call 'transpose' [ @test/Ch2/codegen.toy:3:25
var: b @test/Ch2/codegen.toy:3:35
]
} // Block
Function
Proto 'main' @test/Ch2/codegen.toy:6:1
Params: []
Block {
VarDecl a<2, 3> @test/Ch2/codegen.toy:7:3
Literal: <2, 3>[ <3>[ 1.000000e+00, 2.000000e+00, 3.000000e+00], <3>[ 4.000000e+00, 5.000000e+00, 6.000000e+00]] @test/Ch2/codegen.toy:7:17
VarDecl b<2, 3> @test/Ch2/codegen.toy:8:3
Literal: <6>[ 1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00, 5.000000e+00, 6.000000e+00] @test/Ch2/codegen.toy:8:17
VarDecl c<> @test/Ch2/codegen.toy:9:3
Call 'multiply_transpose' [ @test/Ch2/codegen.toy:9:11
var: a @test/Ch2/codegen.toy:9:30
var: b @test/Ch2/codegen.toy:9:33
]
VarDecl d<> @test/Ch2/codegen.toy:10:3
Call 'multiply_transpose' [ @test/Ch2/codegen.toy:10:11
var: b @test/Ch2/codegen.toy:10:30
var: a @test/Ch2/codegen.toy:10:33
]
Print [ @test/Ch2/codegen.toy:11:3
var: d @test/Ch2/codegen.toy:11:9
]
} // BlockLet's convert the following toy code:
$ cat test/Ch2/codegen.toy
# User defined generic function that operates on unknown shaped arguments.
def multiply_transpose(a, b) {
return transpose(a) * transpose(b);
}
def main() {
var a<2, 3> = [[1, 2, 3], [4, 5, 6]];
var b<2, 3> = [1, 2, 3, 4, 5, 6];
var c = multiply_transpose(a, b);
var d = multiply_transpose(b, a);
print(d);
To MLIR using the toyc executable and the -emit=mlir flag:
$ ./build/bin/toyc -emit=mlir test/Ch2/codegen.toy -emit=mlir test/Ch2/codegen.toy
module {
toy.func @multiply_transpose(%arg0: tensor<*xf64>, %arg1: tensor<*xf64>) -> tensor<*xf64> {
%0 = toy.transpose(%arg0 : tensor<*xf64>) to tensor<*xf64>
%1 = toy.transpose(%arg1 : tensor<*xf64>) to tensor<*xf64>
%2 = toy.mul %0, %1 : tensor<*xf64>
toy.return %2 : tensor<*xf64>
}
toy.func @main() {
%0 = toy.constant dense<[[1.000000e+00, 2.000000e+00, 3.000000e+00], [4.000000e+00, 5.000000e+00, 6.000000e+00]]> : tensor<2x3xf64>
%1 = toy.reshape(%0 : tensor<2x3xf64>) to tensor<2x3xf64>
%2 = toy.constant dense<[1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00, 5.000000e+00, 6.000000e+00]> : tensor<6xf64>
%3 = toy.reshape(%2 : tensor<6xf64>) to tensor<2x3xf64>
%4 = toy.generic_call @multiply_transpose(%1, %3) : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64>
%5 = toy.generic_call @multiply_transpose(%3, %1) : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64>
toy.print %5 : tensor<*xf64>
toy.return
}
}Print additional debugging information:
$ ./build/bin/toyc -emit=mlir -mlir-print-debuginfo test/Ch2/codegen.toy
module {
toy.func @multiply_transpose(%arg0: tensor<*xf64> loc("test/Ch2/codegen.toy":2:1), %arg1: tensor<*xf64> loc("test/Ch2/codegen.toy":2:1)) -> tensor<*xf64> {
%0 = toy.transpose(%arg0 : tensor<*xf64>) to tensor<*xf64> loc("test/Ch2/codegen.toy":3:10)
%1 = toy.transpose(%arg1 : tensor<*xf64>) to tensor<*xf64> loc("test/Ch2/codegen.toy":3:25)
%2 = toy.mul %0, %1 : tensor<*xf64> loc("test/Ch2/codegen.toy":3:25)
toy.return %2 : tensor<*xf64> loc("test/Ch2/codegen.toy":3:3)
} loc("test/Ch2/codegen.toy":2:1)
toy.func @main() {
%0 = toy.constant dense<[[1.000000e+00, 2.000000e+00, 3.000000e+00], [4.000000e+00, 5.000000e+00, 6.000000e+00]]> : tensor<2x3xf64> loc("test/Ch2/codegen.toy":7:17)
%1 = toy.reshape(%0 : tensor<2x3xf64>) to tensor<2x3xf64> loc("test/Ch2/codegen.toy":7:3)
%2 = toy.constant dense<[1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00, 5.000000e+00, 6.000000e+00]> : tensor<6xf64> loc("test/Ch2/codegen.toy":8:17)
%3 = toy.reshape(%2 : tensor<6xf64>) to tensor<2x3xf64> loc("test/Ch2/codegen.toy":8:3)
%4 = toy.generic_call @multiply_transpose(%1, %3) : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64> loc("test/Ch2/codegen.toy":9:11)
%5 = toy.generic_call @multiply_transpose(%3, %1) : (tensor<2x3xf64>, tensor<2x3xf64>) -> tensor<*xf64> loc("test/Ch2/codegen.toy":10:11)
toy.print %5 : tensor<*xf64> loc("test/Ch2/codegen.toy":11:3)
toy.return loc("test/Ch2/codegen.toy":6:1)
} loc("test/Ch2/codegen.toy":6:1)
} loc(unknown)Call mlir-tblgen on Ops.td to generate dialect declarations:
$ ${MLIR_BUILD_DIR}/bin/mlir-tblgen -gen-dialect-decls include/toy/Ops.td -I ${LLVM_SRC_DIR}/mlir/include/
/*===- TableGen'erated file -------------------------------------*- C++ -*-===*\
|* *|
|* Dialect Declarations *|
|* *|
|* Automatically generated file, do not edit! *|
|* From: Ops.td *|
|* *|
\*===----------------------------------------------------------------------===*/
namespace toy {
/// A high-level dialect for analyzing and optimizing the Toy language.
/// The Toy language is a tensor-based language that allows you to define
/// functions, perform some math computation, and print results. This dialect
/// provides a representation of the language that is amenable to analysis and
/// optimization.
class ToyDialect : public ::mlir::Dialect {
explicit ToyDialect(::mlir::MLIRContext *context);
void initialize();
friend class ::mlir::MLIRContext;
public:
~ToyDialect() override;
static constexpr ::llvm::StringLiteral getDialectNamespace() {
return ::llvm::StringLiteral("toy");
}
};
} // namespace toy
MLIR_DECLARE_EXPLICIT_TYPE_ID(toy::ToyDialect)Call mlir-tblgen on Ops.td to generate operation declarations:
$ ${MLIR_BUILD_DIR}/bin/mlir-tblgen -gen-op-decls include/toy/Ops.td -I ${LLVM_SRC_DIR}/mlir/include/
/*===- TableGen'erated file -------------------------------------*- C++ -*-===*\
|* *|
|* Op Declarations *|
|* *|
|* Automatically generated file, do not edit! *|
|* From: Ops.td *|
|* *|
\*===----------------------------------------------------------------------===*/
namespace toy {
/// constant
/// Constant operation turns a literal into an SSA value. The data is attached
/// to the operation as an attribute. For example:
///
/// ```mlir
/// %0 = toy.constant dense<[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]>
/// : tensor<2x3xf64>
/// ```
class ConstantOp;
} // namespace toy
...Alternatively, use -gen-op-defs to generate the class C++ implementation.