2010年03月29日
LLVM の JIT を使ってみる (2)
JIT の API を見ていきます。
ざっと考えてみると、最低限プログラムを実行するためには、以下のような項目が必要だと考えられます。
- 初期化、終了処理
- コンパイル単位 (モジュール)
- 関数定義
- 文
- 式(変数、定数、関数呼び出しなど)
- 文
- 関数定義
- プログラムの実行、結果の取得
まずは HowToUseJIJT.cpp で使われている API から広げていきます。
索引: http://llvm.org/docs/doxygen/html/namespacemembers.html
- 初期化、終了処理
bool llvm::InitializeNativeTarget()
(llvm/Target/TargetSelect.h)
JIT なので、当然ネイティブ環境で実行されます。
void llvm_shutdown()
(llvm/Support/ManagedStatic.h)
終了処理です。ManagedStatic 変数というのが何なのかまだよくわかってないのですが、全ての ManagedStatic 変数の後始末を行ってくれるそうです。
- コンパイル単位 (モジュール)
LLVMContext Context;
(llvm/LLVMContext.h)
LLVM が使用するグローバルデータが入っているクラスです。1 スレッド 1 LLVMContext を保証する必要があります。
Module* m = new Module("モジュール名", Context);
(llvm/Module.h)
これが中間表現のメインコンテナです。
モジュールのメンバ関数を呼び出して、関数などを追加していきます。
- 関数定義
Constant *getOrInsertFunction(StringRef Name,
const Type *RetTy, ...) END_WITH_NULL
(llvm/Module.h)
この API で関数(を表現するデータ構造)を作ります。
可変長引数になっていて、「関数名、関数(の戻り値)の型、引数1 の型、引数 2 の型、…、NULL」となります。
モジュール内のシンボルテーブルを引き、関数が存在しなければ新たに作成し、モジュールに追加した後、返します。
型はいろいろあります。(llvm/Type.h)
00399 // These are the builtin types that are always available... 00400 // 00401 static const Type *getVoidTy(LLVMContext &C); 00402 static const Type *getLabelTy(LLVMContext &C); 00403 static const Type *getFloatTy(LLVMContext &C); 00404 static const Type *getDoubleTy(LLVMContext &C); 00405 static const Type *getMetadataTy(LLVMContext &C); 00406 static const Type *getX86_FP80Ty(LLVMContext &C); 00407 static const Type *getFP128Ty(LLVMContext &C); 00408 static const Type *getPPC_FP128Ty(LLVMContext &C); 00409 static const IntegerType *getInt1Ty(LLVMContext &C); 00410 static const IntegerType *getInt8Ty(LLVMContext &C); 00411 static const IntegerType *getInt16Ty(LLVMContext &C); 00412 static const IntegerType *getInt32Ty(LLVMContext &C); 00413 static const IntegerType *getInt64Ty(LLVMContext &C);
特定のアーキテクチャ固有らしき型も primitive に入ってますね。
- 文
BasicBlock *BB = BasicBlock::Create(Context, "ブロック名", 関数);
(BasicBlock.h)
BasicBlock (BB) は、ブランチ(ジャンプ)命令 (TerminatorInst) を中間に含まない(最後には含んでも良い)命令列を表現するデータ構造です。ブランチ命令や switch case のテーブルは必ず BB を参照することになります。(BB の型は Type::LabelTy です。)
- 式(変数、定数、関数呼び出しなど)
Value *One = ConstantInt::get(Type::getInt32Ty(Context), 1);
定数の 1 (を表すデータ構造) は、こんな感じで作られるようです。Value は全ての値(を表すデータ構造)の基底なので、何でも指せます。
(llvm/Value.h)
同じ要領で、関数の引数を取得したり、加算式や関数呼び出し式、return 文(命令) などを生成しています。
ちょっと面白いと思った API が、
setTailCall(true)
これはおそらく、末尾呼び出しの最適化のための API でしょう。
関数の最後で関数が呼び出された場合、わざわざ呼び出し元の関数に戻ってくる必要が無いので、x86 で言うところの ret ではなく、jmp に最適化することができます。
参考: なんでも継続
- プログラムの実行、結果の取得
ExecutionEngine* EE = EngineBuilder(M).create();
(llvm/ExecutionEngine/ExecutionEngine.h)
GenericValue gv = EE->runFunction(エントリー関数, 引数);
(llvm/ExecutionEngine/GenericValue.h)
EE->freeMachineCodeForFunction(関数); delete EE;
後半駆け足になってしまいました。
後は実際にサンプルを参考にしながら API リファレンスを引き引き作っていくのが良さそうです。