LoginSignup
4
3

More than 3 years have passed since last update.

[LLVM超入門] [C++] LLVMを利用してLLVM IRを生成してみる

Last updated at Posted at 2019-09-10

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言語

Hello.c
#include <stdio.h>
int main() {
 printf("Hello world");
 return 0;
}

LLVM IR

Hello.ll
; 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!は標準出力の関係で少しややこしくなるので今回は簡単な足し算をしてみます。

まずは加算をやってみましょう!

Add.cpp
#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;
}

おっと。FunctionTypeFunctionBasicBlockが登場してきました。これらはなんでしょうか?
一つずつみていきましょう。
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()とし、isVarArgfalseとします。(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はとっても素晴らしいコンパイラインフラストラクチャです。ぜひやってみてください。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3