LLVMで「コンソールに"Hello World"を出力するコード」を生成するコード書いた
久しぶりに日記書きます。
LLVMで遊んでみます。(まだよく分かっていないことが多いので間違ってたらコメントもらえると助かります)
LLVMとは、コンパイラ基盤とかいうやつ。
簡単にオレオレ言語のコンパイラが作れたり、はたまた、LLVM IR という中間表現を利用して、C/C++のソースコードをJavascriptのソースコードに変換したりとか、いろいろ面白そうなことに使えるみたいです。
さっそく Hello world してみます。
以下の手順を実行するには、LLVM Core がインストールされている必要があります。
私の環境は Mac OS X ですので、 Homebrew を使って brew install llvm
でインストールできました。
確認時点のバージョンは
$ llc -version Low Level Virtual Machine (http://llvm.org/): llvm version 2.9 Optimized build. Built Nov 29 2011 (23:32:23). Host: x86_64-apple-darwin11 Host CPU: corei7 Registered Targets: x86 - 32-bit X86: Pentium-Pro and above x86-64 - 64-bit X86: EM64T and AMD64
でした。
コンソールに "Hello world" と出力する LLVM アセンブリコード を生成する C++ コード
ちょっとややこしいですが、「コンソールに"Hello world"と出力するコード」を LLVMが提供するクラス群/コマンド群を使って生成してみます。
上記のコードを hello.cpp というファイル名で保存したという前提で手順を続けます。
上記のコードをコンパイルしてみる
上記のコードをコンパイルするには、
$ g++ -o hello `llvm-config --libs core --cxxflags --ldflags` hello.cpp
と打ち込みます。
すると、hello
という実行ファイルができます。
helloを実行して、LLVM アセンブリコードを生成する
では、コマンドプロンプトに ./hello
と打ち込んでみます。すると、以下のようなC言語に似たソースコードが出力されます。
; ModuleID = 'Hello world module' @.str = private constant [18 x i8] c"Hello llvm world.\00" define i32 @main() { entry: %0 = call i32 @puts(i8* getelementptr inbounds ([18 x i8]* @.str, i32 0, i32 0)) ret i32 0 } declare i32 @puts(i8*)
LLVM アセンブリコードからビットコードを生成してJITで実行する
アセンブリ言語からマシンコードを生成する、as
コマンドに相当するのが、llvm-as
です。
ただし、llvm-as
はネイティブなマシンコードの代わりに、ビットコード(Bitcode)という LLVMの中間表現(LLVM IR)をバイナリ表現に置き換えたものを出力します。
hello
が出力した、LLVM アセンブリコードを llvm-as
に渡してビットコードを生成してみます。
$ ./hello > hello.ll # helloが生成する LLVM アセンブリコードを hello.ll に出力 $ llvm-as hello.ll # hello.ll をアセンブルして hello.bc を出力
これで、カレントディレクトリにhello.bc
というファイルが出来ているはずです。
llvm-as
は標準入力を読み込ませることができるので、以下のようにやっても同じ結果となります。
$ ./hello | llvm-as -o hello.bc
この場合、-o hello.bc
のように明示的に出力ファイル名を指定してあげる必要があるので注意。
ビットコードをJIT上で実行してみる
ビットコードを lli
コマンドに渡すと、JIT(Just-In-Time コンパイラ)上で実行することができます。
$ lli hello.bc Hello llvm world.
無事、"Hello llvm world." とコンソールに表示されたら成功です。
ビットコードから LLVM アセンブリコードを取り出す
ちなみに、llvm-dis
コマンドを使うと ビットコードから LLVM アセンブリコード を取り出すことができます。
以下のように使います。
$ llvm-dis hello.bc
すると、hello.ll
というファイルに LLVM アセンブリコードが出力されます。
./hello
を実行したのと同じ LLVM アセンブリコードが出力されているものと思います。
llvm-di hello.bc -o -
とすると、標準出力にコードを出力できます。
ビットコード又は LLVM アセンブリコードからネイティブなアセンブリコード*1に変換する
llc
コマンドにビットコードまたは LLVM アセンブリコード を渡すとターゲットアーキテクチャ向けのアセンブリコードが生成できます。
$ llc hello.bc
または、
$ llc hello.ll
を実行すると、hello.s
がカレントディレクトリに作成されます。
hello.s
の中身は、以下のようなアセンブリ言語のソースコードになっています。(生成されるアセンブリ言語ソースコードはOSやCPUアーキテクチャによって異なる場合があるので、まったく一緒とはならないかもしれません...)
.section __TEXT,__text,regular,pure_instructions .globl _main .align 4, 0x90 _main: ## @main ## BB#0: ## %entry pushq %rax leaq L_.str(%rip), %rdi callq _puts xorl %eax, %eax popq %rdx ret .section __TEXT,__const L_.str: ## @.str .asciz "Hello world" .subsections_via_symbols
アセンブリ言語ソースをコンパイルして実行ファイルを得る
上記で生成したアセンブリ言語のソースコードは、gcc
コマンドでコンパイルできます。
$ gcc hello.s
すると、a.out
がカレントディレクトリに出力されているので、これを実行すると
$ ./a.out Hello llvm world.
のように出力されたら成功です。
これで無事、
LLVMアセンブリコード生成>(ビットコード生成)>ネイティブなアセンブリコード生成>実行コード生成>コード実行
の手順で、"Hello world"が出力できました。
次回は、オレオレ言語のコンパイラを作る手順を紹介してみたいと思います。
参考
The LLVM Compiler Infrastructure Project
LLVMプロジェクトのオフィシャルページ
Getting Started with LLVM System
「LLVMをはじめよう」的なドキュメント
Try out LLVM and Clang in your browser!
C/C++のソースコードを、ターゲットCPU向けアセンブリコード、あるいは LLVMアセンブリコード や そのコードを生成する LLVMのAPIを使ったC++コード を生成することができるCGI
Writing Your Own Toy Compiler Using Flex, Bison and LLVM (gnuu.org)
Flex, Bison を使ってオレオレ言語のコンパイラを作る手順を紹介した記事(記事に掲載されているサンプルコードは古いので、最新のコードは http://github.com/lsegal/my_toy_compiler を参照したほうがいい)
LLVM で hello, world - 言語ゲーム
この記事を書くのにとても参考になりました。