しょぼいマシンで自作プログラミング言語を作ってみる (14)
fmain宣言の実装をすることで実行可能なLLVM IRができた!
fmain宣言のAST化
fmain宣言のAST化ができました。
fmain {
}
fmain main {
}
ASTを表示させると以下の通りになります。
-- ../example/bootstrap/fmain2.trot to AST --
ast::Root(File)
=> Module(File)
|-> filename: ../example/bootstrap/fmain2.trot
|-> package name: main
|-> module name: fmain2
=> fmain
|-> entryPointId: link
fmain宣言をLLVM IRに出力
さて、上記ASTのentryPointId、これがポイントかな? って思います。
こいつをコマンドライン引数--entry-point-idのあとに指定してやれば、指定のfmainブロックがLLVM IRではmain関数として出力されるようにしました。
コマンドラインオプションでの--entry-point-idの指定とコンパイルされるfmainブロックの関係。その仕様は以下の通りです。
コマンドラインで指定されたentryPointIdが、fmain宣言に記述されたものに存在した場合、該当のfmainブロックをコンパイル。
コマンドラインで指定されたentryPointIdが、fmain宣言に記述されたものに存在しなかった場合、entryPointIdが設定されていないfmainブロックをコンパイル。
コマンドラインでentryPointIdの指定がなかった場合、かつentryPointId指定されたfmainブロックが存在しなかった場合、コンパイル対象のソースで最初に見つかったfmainブロックをコンパイル。
ただ、今後、パッケージ名とモジュール名を結合したものを指定させることを考えるかもしれません。
おそらく十中八九はそうなる可能性が高いです。
さて、fmainブロック宣言時におけるLLVM IRの生成は抜粋すると以下の感じになりました。
void AstNodeDeclareFmain::generateLlvmIr(IrGenContext *const context_) {
context_->setExecutableBinary();
llvm::LLVMContext *lContext = context_->getContext();
llvm::Module *lModule = ((AstNodeModule *)(getModule()))->getLlvmModule();
llvm::IRBuilder<> *lBuilder = context_->getBuilder();
llvm::FunctionType *mainFuncType =
llvm::FunctionType::get(llvm::Type::getInt32Ty(*lContext), false);
llvm::Function *mainFunc = llvm::Function::Create(
mainFuncType, llvm::Function::ExternalLinkage, "main", *lModule);
llvm::BasicBlock *entry =
llvm::BasicBlock::Create(*lContext, "entrypoint", mainFunc);
lBuilder->SetInsertPoint(entry);
if (!hasChild()) {
lBuilder->CreateRet(lBuilder->getInt32(0));
}
};
llvm::FunctionTypeが関数の戻り値の型を示すものですね。32ビット整数を戻り値としています。
llvm::Functionが実際の関数定義になります。
llvm::BasicBlockがLLVM IR上でのmain関数のブロックを定義するものですね。
関数ブロック内のエントリポイントを設定しておきます。
あと、暫定的に子ノードがない場合は戻り値を0に設定しています。
前回ではLLVM IRを表示してみましたが、コマンドラインオプションで--emit-llvm-irを指定した場合に、LLVM IRを実際にファイル出力する処理も追加しました。
std::error_code ec;
llvm::raw_fd_stream outLlvmIrFd(llvmIrFilename, ec);
module->print(outLlvmIrFd, nullptr);
上記の行を実行すれば、LLVM IRはファイルに出力されます。
あ、そうそう。コマンドラインオプション--source-root-dir、--dest-root-dirを指定することでLLVM IRの出力先を書き換えることを可能としました。
相対パスを絶対パスに書き換えてから、source-root-dirをdest-root-dirに書き換えることで出力先が変わります。
これをすると何が嬉しいかというと?
例えば、今はexample/bootstrap/ディレクトリにgallop言語のソースを置いているわけですが、git管理しています。
ただ、.gitignoreではプロジェクトのどこにでもあるbuild/ディレクトリ配下はソース管理対象外とするようにしています。
なので、dest-root-dirにexample/buildを指定してやれば、LLVM IRファイルが出力されるのでリポジトリは汚れないという算段です。
色々と話は脱線しましたが、実際にLLVM IRを表示すると以下の感じになります。
-- ../example/bootstrap/fmain2.trot to LLVM IR --
; ModuleID = 'main.fmain2'
source_filename = "../example/bootstrap/fmain2.trot"
define i32 @main() {
entrypoint:
ret i32 0
}
出力されるLLVM IRファイルはfmain2.llファイルになります。
これで実際にllcコマンドなりでアセンブリコードに変換したり、clangコマンドでアセンブリコードを実際に実行可能オブジェクトファイルに変換できたりするというわけです。
次はfmain宣言に続くコードブロック、それと何らかの処理を実装することになります。
このコードブロックや何らかの処理が実装できたりすると、gallop言語もちょっとずつサマになってくるなぁとは思いますね。
では、また次回。