しょぼいマシンで自作プログラミング言語を作ってみる (13)

最小限のLLVM IRが生成できた!

コメントアウトのAST化ができた

 まず、コメントアウトのAST化ができました。

 この記事で触れていた仕様通りにコメントアウトをAST化できました。

 コマンドラインオプションで以下の指定があった場合のみ、ASTのノード
に追加されます。

  • "--with-commentout-all"が指定された場合はすべてのコメントアウトがASTのノードに追加。

  • "--with-commentout-for-doc"が指定された場合はドキュメント生成用のコメントアウトがASTのノードに追加。

 ドキュメント生成用のコメントから、ドキュメントHTMLを生成するのはセルフホスティングができてから着手します。

パッケージ名、モジュール名のAST情報への設定

 この記事で触れていた通り、パッケージ名、モジュール名を指定できるようにしました。

  
pkgdef pkg1.pkg2;


moddef module1;


 上記の通り、pkgdef文で指定された場合にパッケージ名、moddef文で指定された場合にモジュール名が設定できるようになりました。
 改めて仕様を整理すると、以下の通りになります。

  • インタプリタの場合
    パッケージ名、モジュール名は宣言できない。
    デフォルトのモジュール名は"moduleForInterpreter"。

  • 標準入力ストリームからの単一コンパイルの場合
    パッケージ名は宣言できない。モジュール名は宣言できる。
    デフォルトのモジュール名は"moduleFromStdin"。

  • ファイルからのコンパイルの場合
    パッケージ名、モジュール名を宣言できる。
    デフォルトのパッケージ名は"main"。
    デフォルトのモジュール名は入力ファイル名からディレクトリ名、拡張子を取り除いたもの。
    例)hoge/hoge.trotならば”hoge”がデフォルトのモジュール名に。

 宣言した位置によるコンパイルエラーの発生はC++での実装時はしません。
 セルフホスティングの際に実装します。

ASTの表示

 ASTの表示において、ツリー構造を一度、平坦化してから、表示するようにしました。
 深さ優先探索(行きがけ順)でノードを動的配列に追加していきます。その時には深さも情報として保存します。
 あとは深さ毎にインデントをしながら、ASTの情報を表示させます。

 実際に表示させるとこんな感じですかね?

  
/#<
comment out (コメントアウト ドキュメント用)\
    aa
   aa
/#>

//<
comment out (コメントアウト)
  comm\
   ent
//>

pkgdef pkg1.pkg2;


moddef module1;

 このソースをASTにすると?

-- ../example/bootstrap/pkgmoddef0.trot to AST --
ast::Root(File)
 => Module(File)
    |-> filename: ../example/bootstrap/pkgmoddef0.trot
    |-> package name: pkg1.pkg2
    |-> module name: module1
     => CommentOut For Doc
        |-> beginLocation: [2,1]
        |-> endLocation: [6,4]
        |-> body: comment out (コメントアウト ドキュメント用)aa
   aa
     => CommentOut
        |-> beginLocation: [8,1]
        |-> endLocation: [12,4]
        |-> body: comment out (コメントアウト)
  comment

LLVM IRの生成

 最低限のASTができたので、LLVM IRに変換していきます。
 LLVM IRの変換もASTの表示と同様に深さ優先探索(行きがけ順)で平坦化してから、生成する方法で行けるとみています。

 前述のASTの表示もそうですが、なんで、そんなことする? って話ですが、コールスタックをガシガシ積むのが嫌だった! って話です。

 それはそれとして、最低限のLLVM IRを作るには以下のヘッダが必要になります。

#include <llvm/IR/ConstantFolder.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>

  このヘッダを使って実装するというわけですが、以下のようにLLVM IR生成のための情報を保持するクラスを用意しました。

class IrGenContext {
public:
  IrGenContext();
  ~IrGenContext() {};
  IrGenContext(const IrGenContext &rhs);
  IrGenContext &operator=(const IrGenContext &rhs);
  void pushModule(llvm::Module *const module);
  llvm::LLVMContext *getContext();
  llvm::IRBuilder<> *getBuilder();
  size_t getModuleCnt() const;
  llvm::Module *getModule(const size_t pos);

private:
  llvm::LLVMContext *context;
  llvm::IRBuilder<> *builder;
  size_t moduleCnt;
  std::vector<llvm::Module *> modules;
};

 LLVMContextクラスがコンパイル時の大きな塊の情報を持つクラスですね。
 Moduleクラスがソースファイル単位の情報を持つクラスですね。
 IRBuilderクラスがLLVM IRを生成するためのクラスですね。

 で、コンストラクタは以下の通りになります。

IrGenContext::IrGenContext()
    : context(new llvm::LLVMContext()),
      builder(new llvm::IRBuilder<>(*context, llvm::ConstantFolder(),
                                    llvm::IRBuilderDefaultInserter())),
      moduleCnt(0), modules(std::vector<llvm::Module *>()) {};

 GithubにあるLLVMプロジェクトのサンプルソースは古いっぽいですね。
 何が古いかっていうと、IRBuilderのコンストラクタです。最新のLLVM-19では第2引数、第3引数が必要っぽいです。

 ということで、ファイル読み込みによるコンパイル時においてはそれ専用のASTノードに情報設定してから、llvm::Moduleへの変換を行うというわけです。

void AstNodeModuleFile::generateLlvmIr(IrGenContext *const context_) {
  llvm::LLVMContext *lContext = context_->getContext();
  llvm::Module *module = new llvm::Module(getFullModuleName(), *lContext);
  context_->pushModule(module);
  module->setSourceFileName(filename);
};

 こんな感じでLLVMコンテキストを取得してから、モジュール名とLLVMコンテキストを使ってLLVMモジュールを定義します。
 更に元となったソースファイル名も追加してしまいましょうか。

 ここまでできたら、LLVM IRを表示してみましょう。

module->print(llvm::outs(), nullptr);

 上記の行を実行すれば、LLVM IRはコンソールに表示されます。
 まあ、実際には以下の関数内でLLVM IRを表示するようにしました。

void CodeGen::printLlvmIr() {
  size_t moduleCnt = context->getModuleCnt();
  for (size_t moduleIdx = 0; moduleIdx < moduleCnt; moduleIdx++) {
    llvm::Module *module = context->getModule(moduleIdx);
    std::string moduleFilename = module->getSourceFileName();
    llvm::outs() << "\n";
    if (moduleFilename != "") {
      llvm::outs() << "-- " << moduleFilename << " to LLVM IR --\n";
    } else {
      llvm::outs() << "-- to LLVM IR --\n";
    }
    module->print(llvm::outs(), nullptr);
  }
};

 で、実際にLLVM IRを表示すると?

-- ../example/bootstrap/pkgmoddef0.trot to LLVM IR --
; ModuleID = 'pkg1.pkg2.module1'
source_filename = "../example/bootstrap/pkgmoddef0.trot"

 おお! 出てきた!

 いやあ、第一歩です! これが大いなる第一歩です!

 次はfmainブロックをASTノードに追加したり、LLVM IRへ出力するようにしましょう。

いいなと思ったら応援しよう!