MLIRについて勉強している。
独自言語を作成し、その中間表現をMLIRを使って表現してみることに挑戦する。
ここで想定する流れは以下の通りだ。
- 独自言語(MYSV : System Verilogオリジナル改造バージョン) を読み込んで、独自形式でASTを作成する
- MYSVのASTをMLIRのツリーに変換する
- MLIRのツリーをダンプする
という流れでMLIRを体験したいと思う。対象とするサンプルプログラムは、とりあえず以下の通り。
assign A = 0; assign Hoge = 2;
Toyのサンプルプログラムを見ながら構築する。まずはこれからASTを作る。
int dumpAST() { auto moduleAST = parseInputFile(inputFilename); if (!moduleAST) return 1; dump(*moduleAST); return 0; }
parseInputFile()
は、入力ファイルを読み込んで、それからASTを作成する。最終的にASTはmoduleAST
として返される。
この時点ではまだMLIRは登場していなくて、単純に独自形式でASTが作られただけである。
mysv.cpp
/// Returns a MYSV AST resulting from parsing the file or a nullptr on error. std::unique_ptr<mysv::ModuleAST> parseInputFile(llvm::StringRef filename) { llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> fileOrErr = llvm::MemoryBuffer::getFileOrSTDIN(filename); if (std::error_code ec = fileOrErr.getError()) { llvm::errs() << "Could not open input file: " << ec.message() << "\n"; return nullptr; } auto buffer = fileOrErr.get()->getBuffer(); LexerBuffer lexer(buffer.begin(), buffer.end(), std::string(filename)); Parser parser(lexer); return parser.parseModule(); }
ASTのフォーマットは以下のようになる。これは独自に作成した。
AST.h
/// Expression class for assignment. class AssignExprAST : public ExprAST { std::string name; std::unique_ptr<NumberExprAST> initVal; public: AssignExprAST(Location loc, llvm::StringRef name, std::unique_ptr<NumberExprAST> initVal) : ExprAST(Expr_Assign, std::move(loc)), name(name), initVal(std::move(initVal)) {} llvm::StringRef getName() { return name; } NumberExprAST *getInitVal() { return initVal.get(); } /// LLVM style RTTI static bool classof(const ExprAST *c) { return c->getKind() == Expr_Assign; } }; /// This class represents a list of functions to be processed together class ModuleAST { std::vector<AssignExprAST> assigns; public: ModuleAST(std::vector<AssignExprAST> assigns) : assigns(std::move(assigns)) {} auto begin() { return assigns.begin(); } auto end() { return assigns.end(); } };
ASTを作成するに当たり、LexerとParserが必要なのだが、Toyのサンプルプログラムを見る限りは独自に作成している。
Lexer.h
/// The Lexer is an abstract base class providing all the facilities that the /// Parser expects. It goes through the stream one token at a time and keeps /// track of the location in the file for debugging purposes. /// It relies on a subclass to provide a `readNextLine()` method. The subclass /// can proceed by reading the next line from the standard input or from a /// memory mapped file. class Lexer { public: /// Create a lexer for the given filename. The filename is kept only for /// debugging purposes (attaching a location to a Token). Lexer(std::string filename) : lastLocation( {std::make_shared<std::string>(std::move(filename)), 0, 0}) {} virtual ~Lexer() = default; /// Look at the current token in the stream. Token getCurToken() { return curTok; } /// Move to the next token in the stream and return it. Token getNextToken() { return curTok = getTok(); } /// Move to the next token in the stream, asserting on the current token /// matching the expectation. void consume(Token tok) { assert(tok == curTok && "consume Token mismatch expectation"); getNextToken(); } /// Return the current identifier (prereq: getCurToken() == tok_identifier) llvm::StringRef getId() { assert(curTok == tok_identifier);
Parser.h
/// This is a simple recursive parser for the MYSV language. It produces a well /// formed AST from a stream of Token supplied by the Lexer. No semantic checks /// or symbol resolution is performed. For example, variables are referenced by /// string and the code could reference an undeclared variable and the parsing /// succeeds. class Parser { public: /// Create a Parser for the supplied lexer. Parser(Lexer &lexer) : lexer(lexer) {} /// Parse a full Module. A module is a list of function definitions. std::unique_ptr<ModuleAST> parseModule() { lexer.getNextToken(); // prime the lexer // Parse functions one at a time and accumulate in this vector. std::vector<AssignExprAST> functions; while (auto f = parseAssign()) { functions.push_back(std::move(*f)); if (lexer.getCurToken() == tok_eof) break; } // If we didn't reach EOF, there was an error during parsing if (lexer.getCurToken() != tok_eof) return parseError<ModuleAST>("nothing", "at end of module"); return std::make_unique<ModuleAST>(std::move(functions)); } private: Lexer &lexer; /// Parse a literal number. /// numberexpr ::= number std::unique_ptr<NumberExprAST> parseNumberExpr() { auto loc = lexer.getLastLocation(); auto result = std::make_unique<NumberExprAST>(std::move(loc), lexer.getValue()); lexer.consume(tok_number); return result; } /// Parse a variable declaration, it starts with a `var` keyword followed by /// and identifier and an optional type (shape specification) before the /// initializer. /// decl ::= assign identifier = expr std::unique_ptr<AssignExprAST> parseAssign() { if (lexer.getCurToken() != tok_assign) { return parseError<AssignExprAST>("assign", "to begin assign"); }
とりあえずココまでで、独自形式のASTは生成できるようになった。
$ ./bin/mysv --emit=ast ../mlir/examples/mysv/test/assign.sv Module: assign A @../mlir/examples/mysv/test/assign.sv:1:1 0 @../mlir/examples/mysv/test/assign.sv:1:12 assign Hoge @../mlir/examples/mysv/test/assign.sv:2:1 2 @../mlir/examples/mysv/test/assign.sv:2:15
次はMLIRに変換してダンプできるようになろう。