Help us understand the problem. What is going on with this article?

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

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はとっても素晴らしいコンパイラインフラストラクチャです。ぜひやってみてください。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away