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 アセンブリコードです。

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アセンブリコード や そのコードを生成する LLVMAPIを使った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 - 言語ゲーム
この記事を書くのにとても参考になりました。

*1:「ネイティブなアセンブリコード」と書いているが、この表現が正しいのかは良く分かっていない。llvmのドキュメントを直訳すると、「特定のアーキテクチャ向けのアセンブリ言語」(assembly language for specified architecture)という表現