FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

MLIRの勉強 (2. チュートリアルをベースとした自作言語作成)

MLIRについて勉強している。

独自言語を作成し、その中間表現をMLIRを使って表現してみることに挑戦する。

ここで想定する流れは以下の通りだ。

  1. 独自言語(MYSV : System Verilogオリジナル改造バージョン) を読み込んで、独自形式でASTを作成する
  2. MYSVのASTをMLIRのツリーに変換する
  3. 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に変換してダンプできるようになろう。