戻り値を返すだけのLLVM IRの作成、LLVM IR文字列の出力方法についてのメモ。
※この手順でLLVM 10.0.0をビルドした環境で、Visual Studioのプロジェクトをこの内容に設定してビルド確認している。
目次
目的のLLVM IRコード
この記事で作成した以下のようなLLVM IRを出力するプログラムを、LLVM API(C++)で作成する。
define i32 @main() { ret i32 111 }
C++ソースコード
#include "llvm/IR/LLVMContext.h" // for `llvm::LLVMContext` #include "llvm/IR/Module.h" // for `llvm::Module` #include "llvm/IR/Function.h" // for `llvm::Function` and `llvm::FunctionType` #include "llvm/IR/BasicBlock.h" // for `llvm::BasicBlock` #include "llvm/IR/IRBuilder.h" // for `llvm::IRBuilder` #include "llvm/IR/Verifier.h" // for `llvm::outs()` int main() { llvm::LLVMContext context; // Moduleを作成 llvm::Module mainModule("", context); // 関数の型を作成 llvm::FunctionType* functionType = llvm::FunctionType::get( llvm::Type::getInt32Ty(context), // 戻り値の型 false // 可変長引数の有無 ); // main関数`define i32 @main()`を作成 llvm::Function* fMain = llvm::Function::Create( functionType, // 関数の型 llvm::Function::ExternalLinkage, // リンケージ "main", // 関数名 mainModule // この関数の追加先モジュール ); // main関数内にBasicBlockを作成 llvm::BasicBlock* basicBlock = llvm::BasicBlock::Create(context, "", fMain); // BasicBlockに命令を追加 llvm::IRBuilder<> builder(context); builder.SetInsertPoint(basicBlock); // basicBlockの末尾に命令を追加するように設定 builder.CreateRet(builder.getInt32(111)); // `return i32 111`を追加 // 標準出力に作成したLLVM IRを出力 mainModule.print(llvm::outs(), nullptr); return 0; }
使用したクラスのdoxygenへのリンクと概要。
クラス | doxygen |
---|---|
LLVMContext | LLVMのグローバルデータを管理するクラス |
Module | モジュール |
Function | 関数 |
FunctionType | 関数の型を表すクラス |
BasicBlock | BasicBlock。1つ以上の命令のかたまり。ラベルで始まりbr 命令やret 命令等で終わるブロック。 |
IRBuilder | 命令を表すLLVM IRを生成し、それをBasicBlockに挿入するためのクラス。 |
解説
Moduleの作成
llvm::LLVMContext context;
llvm::Module mainModule("", context);
LLVMContext
クラスはLLVM全般にかかわる情報を内部に持つ。Moduleの作成以外にもいろいろな操作の際に使用する。
LLVM IRは、Moduleという入れ物の中にグローバル変数や関数を追加する形で作成する。
大雑把に言うと、Moduleは一つの.ll
ファイルに相当する。
Moduleのコンストラクタの第一引数は、Moduleの名前。
Moduleのコンストラクタの第二引数は、LLVMContextオブジェクト。 特に何も考えずにLLVMContextオブジェクトを指定しておけばよさそう。 ただし、LLVMContextオブジェクトを複数のスレッドで使用する場合は、排他制御を自分で行う必要がある。
今回はModuleの名前に空文字列を指定しているが、何らかの名前をつけると以下のようなLLVM IRが追加で出力される。
; ModuleID = 'moduleName' source_filename = "moduleName"
source_filenameは、プロファイルデータで使用するローカル関数のユニークでグローバルな識別子を生成するために使われる。 その際、ローカル関数名の先頭にソースファイル名が付加される。
ここでいうプロファイルデータというのは、最適化に使用するものと思われる(参考)。
関数の型の作成
llvm::FunctionType* functionType = llvm::FunctionType::get(
llvm::Type::getInt32Ty(context),
false
);
関数を作成する前に、関数の型FunctionType
を作成しておく。
関数の型は、FunctionType::get
関数で取得する。
get関数の第一引数に、関数の戻り値の型を指定する。
第二引数は、Functionの引数に可変長引数があるか否かを指定する。
関数の作成
llvm::Function* fMain = llvm::Function::Create(
functionType,
llvm::Function::ExternalLinkage,
"main",
mainModule
);
関数はFunctionクラスで作成する。
Create関数の第一引数で、関数の型を指定する。
Create関数の第二引数には、Functionのリンケージを指定する。
よくわからないうちは、とにかくllvm::Function::ExternalLinkage
を指定しておけばよさそう。
第三引数には関数名を指定する。
第四引数には、Functionの追加先のModuleを指定する。
BasicBlockの作成
llvm::BasicBlock* basicBlock = llvm::BasicBlock::Create(context, "", fMain);
BasicBlock::Create
関数で、BasicBlockを作成してFunctionに追加する。
第一引数は、LLVMContextオブジェクト。
第二引数は、BasicBlockにつけるラベル名。 正確には、Twineクラスのオブジェクトを指定するのだが、今のところ必要なさそうなのでTwineの理解は後回しにしておく。
第三引数は、BasicBlockの追加先のFunction。
命令の作成
llvm::IRBuilder<> builder(context);
builder.SetInsertPoint(basicBlock);
builder.CreateRet(builder.getInt32(111));
BasicBlockを作成したら、その中に命令を追加していく。
そのためにIRBuilder
クラスを利用する。
今回はテンプレート引数はすべてデフォルト値でいいので、llvm::IRBuilder<> builder(context);
のようにオブジェクトを作成する。
builder.SetInsertPoint(basicBlock);
で、命令を挿入する位置を、basicBlockの末尾に指定する。
builder.CreateRet(builder.getInt32(111));
で、ret i32 111
命令を作成し、指定した位置に挿入する。
IRBuilderにはCreate〇〇〇
という形式のメンバ関数が多数あり、それらはLLVM IRの各命令に対応している。
命令の一覧と詳細については、IRBuilderのdoxygenを見るよりInstruction-referenceを見たほうがたぶん良い。
LLVM IRの出力
mainModule.print(llvm::outs(), nullptr);
上記で、文字列形式のLLVM IRを標準出力に出力できる。
もし標準エラー出力に出力したければllvm::outs()
をllvm::errs()
にすればよい。
第二引数にはAssemblyAnnotationWriterを指定する。
とりあえずexampleをまねてnullptr
を指定しておく。理解は後回し。
参考
Module、Function、BasicBlock、Instructionの関係は以下の説明がわかりやすい。
愚痴
公式のdoxygenを読んでも使い方が全然わからなくて辛い。