#First
みなさんこんにちは。あきっちです。
いきなりですがLLVM、知っていますか?もちろんみなさんならご存じですよね。
そうです。コンパイラ基盤です。
最近のプログラミング言語ではよく採用されているようで、とっても優秀らしいです。
私たちも流れに乗ってやってみましょう!!
#前提条件など
C++が多少かける
CMakeのやり方がわかる
includeディレクトリが適切に設定できる
リンクエラーしてもめげない心
#筆者の環境
Windows 10 Home
i7 7700HQ
RAM 16GB
Visual Studio 2019 Community
#環境構築など
この方が書いてくださっている LLVMに実行可能コードの生成をまかせたいがとってもわかりやすいです。
#LLVM IR
LLVMには中間表現(IR)があり、任意のフロントエンドがIRにすることであとはLLVMに実行ファイルの生成と最適化を任せることが出来ます。例としてCでHello World!を出力するコードと、それに対応するLLVM IRを載せます。
###C言語
#include <stdio.h>
int main() {
printf("Hello world");
return 0;
}
###LLVM IR
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
%1 = alloca i32, align 4
store i32 0, i32* %1, align 4
%2 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @"??_C@_0M@KIBDPGDE@Hello?5world?$AA@", i32 0, i32 0))
ret i32 0
}
...うーん。よくわかりませんね。
allocaとかstoreはCとかアセンブリ触ってれば分かると思いますがgetelementptrがよくわかりません。
この記事ではその解説はしませんので各自調べてください。
#LLVM IR Builder入門!
さて、ついてにやってきました!LLVM IR BuilderというIRをBuildするやつを使って書いてみましょう!
Hello world!は標準出力の関係で少しややこしくなるので今回は簡単な足し算をしてみます。
まずは加算をやってみましょう!
#include "llvm/IR/IRBuilder.h"
using namespace llvm;
LLVMContext context;
IRBuilder<> builder(context);
Module llvm_module("Example", context);
int main() {
// 戻り値を設定する。Int32(i32)を返す。
FunctionType* return_type = FunctionType::get(builder.getInt32Ty(), false);
// "main"という名で関数を生成する。llvm_moduleを忘れないように。
Function* fn = Function::Create(return_type, Function::ExternalLinkage, "main", llvm_module);
// ブロックを生成して挿入ポイントをそこに変える。
BasicBlock* bb = BasicBlock::Create(context, "entry", fn);
builder.SetInsertPoint(bb);
//i32で1, 2をそれぞれ生成する。
Value* x = builder.getInt32(1);
Value* y = builder.getInt32(2);
//x, yをaddしてみる。
Value* result = builder.CreateAdd(x, y, "result");
//addの結果を返すreturnを生成する。
builder.CreateRet(result);
//IRをダンプする
llvm_module.dump();
return 0;
}
おっと。FunctionType
、Function
やBasicBlock
が登場してきました。これらはなんでしょうか?
一つずつみていきましょう。
Functionの宣言をのぞいてみると、
Function *Function::Create(FunctionType *Ty, LinkageTypes Linkage,
const Twine &N, Module &M);
となっています。
うーん...。*Ty
はなんだ?
ということでFunctionType
をのぞいてみます。
FunctionType *FunctionType::get(Type *Result, bool isVarArg);
おぉ...なるほどType
型の*Result
があります。つまりこれは戻り値ということですね。
main関数はi32
を返したいのでbuilder.getInt32Ty()
とし、isVarArg
はfalse
とします。(isVarArg
は可変長引数です)
FunctionType* return_type = FunctionType::get(builder.getInt32Ty(), false);
とします。
FunctionType
ができたので次にFunction
をしてみます。
Function* fn = Function::Create(return_type, Function::ExternalLinkage, "main", llvm_module);
これは簡単ですね。
次に、BasicBlock
が出てきました。BasicBlock
はIRの挿入位置を作るもので、とても便利なものです。CとかC++でいうラベルです。
BasicBlock* bb = BasicBlock::Create(context, "entry", fn);
builder.SetInsertPoint(bb);
**"entry"**という名のBasicBlock
を作成して、挿入ポイントを作ったBasicBlock
に変えています。
さて、準備がすべて整いました!ということでさっそく加算をやってみます。
IRで数値を扱うにはどうしたらいいでしょうか?IRにはValue
というそれっぽいものがあります。これを使って数値を作ります。
//i32で1, 2をそれぞれ生成する。
Value* x = builder.getInt32(1);
Value* y = builder.getInt32(2);
とりあえず32ビット整数で数値1と2を作成してみました。
これを加算してみます。
Value* result = builder.CreateAdd(x, y, "result");
これでできましたね!加算の結果はresult
に格納します。
そして加算の結果をreturn
します。
builder.CreateRet(result);
簡単です。戻り値の型と違う型を返そうとするとエラーになりますのでご注意くださいね。
さいごに生成されたIRを眺めるためにダンプします。
llvm_module.dump();
cppでreturn 0;
も忘れずに。
ビルドして実行してみます。
; ModuleID = 'Example'
source_filename = "Example"
define i32 @main() {
entry:
ret i32 3
}
これで動きました。ちゃんと定数畳み込みもしてくれてます。うれしいですね。
#Last
好評だったら続編も作ってみようかと思います。
私自身もLLVM初心者ですので間違っていたら随時修正します。
LLVMはとっても素晴らしいコンパイラインフラストラクチャです。ぜひやってみてください。