単子葉類プログラマーのメモ

プログラミング関連の自分用メモだけど他の人の役に立つかもしれないので公開しておく感じのブログ

LLVM C++ API 学習メモ(4) - リテラル、算術演算、関数定義、ローカル変数

前回までで書いたもの以外の主要なAPIについてのメモ。

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
);

算術演算

lhsleft hand sideの省略形で左辺値のこと。同様にrhsright 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::IRBuilderCreate系のメンバ関数を実行する。

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::AllocInstllvm::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);