※LLVMのバージョンは10.0.0
目次
リテラルの作成
llvm::LLVMContext context; // 整数型リテラル llvm::Value* value1 = llvm::ConstantInt::get( llvm::Type::getInt32Ty(context), // i32型 123, false // 符号なし ); // 浮動小数点数(Floating Point)型リテラル llvm::Value* value2 = llvm::ConstantFP::get( llvm::Type::getDoubleTy(context), // double型(f64型) 3.14159265359 );
算術演算
※ lhs
はleft hand side
の省略形で左辺値のこと。同様にrhs
はright hend side
で右辺値。コンパイラ界隈(?)では頻出の略語。
llvm::IRBuilder<> builder(context); // 整数の四則演算、剰余算、単項演算子- llvm::Value* result1 = builder.CreateAdd(lhs, rhs); llvm::Value* result2 = builder.CreateSub(lhs, rhs); llvm::Value* result3 = builder.CreateMul(lhs, rhs); llvm::Value* result4 = builder.CreateSDiv(lhs, rhs); // signed llvm::Value* result5 = builder.CreateUDiv(lhs, rhs); // unsigned llvm::Value* result6 = builder.CreateSRem(lhs, rhs); // signed llvm::Value* result7 = builder.CreateURem(lhs, rhs); // unsigned llvm::Value* result6 = builder.CreateNeg(value); // 浮動小数点数の四則演算、剰余算、単項演算子- llvm::Value* result1 = builder.CreateFAdd(lhs, rhs); llvm::Value* result2 = builder.CreateFSub(lhs, rhs); llvm::Value* result3 = builder.CreateFMul(lhs, rhs); llvm::Value* result4 = builder.CreateFDiv(lhs, rhs); llvm::Value* result5 = builder.CreateFRem(lhs, rhs); llvm::Value* result6 = builder.CreateFNeg(value);
関数定義
関数宣言、関数呼び出し、引数なしの関数定義は前回書いたので省略。
引数ありの関数定義は以下のように行う。
// i32型とi8*型をとる引数リストを作成 llvm::ArrayRef<llvm::Type*> args = { llvm::Type::getInt32Ty(context), llvm::Type::getInt8PtrTy(context) }; // 関数の型(いわゆるシグネチャ)を作成 llvm::FunctionType* ft = llvm::FunctionType::get( llvm::Type::getInt32Ty(context), // 関数の戻り値の型に i32 型を指定 args, // 先に作成した引数リストを指定 false // 可変長引数はなし ); // 関数を作成 llvm::Function* f = llvm::Function::Create( ft, // 関数の型 llvm::Function::ExternalLinkage, // リンケージ name, // 関数の名前 module // 関数の追加先モジュール );
次に関数の中にBasicBlock
を追加し、さらにその中に命令を追加していく。
命令を追加するために、SetInsertPoint()
で命令の追加位置を指定した後、llvm::IRBuilder
のCreate
系のメンバ関数を実行する。
llvm::BasicBlock* bb = llvm::BasicBlock::Create(context, "entry", f); builder.SetInsertPoint(bb); builder.CreateRet(value); // `ret`を追加
getOrInsertFunction()
とllvm::Function::Create()
getOrInsertFunction()
は、関数が未定義な場合に、関数の宣言(C言語のプロトタイプ宣言的なもの)を行う命令を追加する。
同じ名前の関数をgetOrInsertFunction()
したあとllvm::Function::Create()
すると、後者は別名で(関数名末尾に.1
等がつけられて)関数が作成される。
それに気づかずに元の名前を指定して関数を呼ぼうとすると(CreateCall()
すると)、関数定義がないのでエラーになってしまう。
getOrInsertFunction()
は、あくまで別モジュールで定義される関数を呼び出す場合にのみ使ったほうがよさそう。
同一モジュール内で、ソースコードの後方で定義する関数を呼び出す場合は、何らかの工夫をして先にllvm::Function::Create()
すべきなようだ。
ローカル変数の追加
mem2reg
パスで最適化するために、ローカル変数の確保(alloca
)は、関数の先頭ブロックで行うほうが良い。
以下のように、先頭ブロックにローカル変数を追加する。
// 今SetInsertPoint()されているブロックが所属する関数を取得 llvm::Function* f = builder.GetInsertBlock()->getParent(); // 関数の先頭ブロックを取得 llvm::BasicBlock& entryBlock = f->getEntryBlock(); // 取得したブロック用のビルダーを作成 llvm::IRBuilder<> tempBuilder(&entryBlock, entryBlock.begin()); // alloca を追加 llvm::AllocaInst* ptr = tempBuilder.CreateAlloca(builder.getInt32Ty());
llvm::IRBuilder
のコンストラクタに&entryBlock, entryBLock.begin()
を渡して、先頭ブロック用に一時的なビルダーを作成する。
このコンストラクタの引数の意味はdoxygenを見てもさっぱりわからなかったが、コードを見る限りでは、第一引数で渡したBasic Blockからllvm::LLVMContext
を取り出し、命令の挿入位置を第二引数で渡したBasic Blockのイテレータにしているようだ。
CreateAlloca()
は確保したスタック領域のアドレスをあらわすオブジェクトを返却する。
llvm::AllocInst
はllvm::Value
のサブクラスなので、llvm::Value*
を受け取る関数に渡すことができる。
llvm::AllocInst*
は、スタック領域へのポインタを表すので、値の読み書きはLLVM IRでいうとstore
命令とload
命令で行う必要がある。
例
// スタック領域に格納するための値を作成 llvm::Value* value1 = llvm::ConstantInt::get( llvm::Type::getInt32Ty(context), 123, false ); // store builder.CreateStore(value1, ptr); // load llvm::Value* value2 = builder.CreateLoad(ptr);