前回の続き。
今回はFizzBuzzを作る。
そのために、ローカル変数、制御構文(if、while)、数値の文字列化が必要になる。
※LLVMのバージョンは9.0.1。
目次
ローカル変数
ローカル変数はalloca
命令で定義する。
alloca
命令の説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#alloca-instruction。
構文のうち、今回使用する最小限の部分は以下。
<result> = alloca <type>
alloca
はスタック領域のポインタを返す。ポインタ経由で値を代入する場合はstore
命令、取得する場合はload
命令を使う。
store
命令の説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#store-instruction。
構文のうち、今回使用する最小限の部分は以下。
store <ty> <value>, <ty>* <pointer>
<ty> <value>
に代入する値、<ty>* <pointer>
に代入先を指定する。
<ty>
は代入する値の型。
load
命令の説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#load-instruction。
構文のうち、今回使用する最小限の部分は以下。
<result> = load <ty>, <ty>* <pointer>
<ty>
は取得する値の型。
<pointer>
は取得先のポインタ。
変数の定義、代入、取得を行うサンプルは以下。
define i32 @main() { %1 = alloca i32 store i32 111, i32* %1 %2 = load i32, i32* %1 ret i32 %2 }
制御構文
条件分岐
br
命令で、指定したラベルにジャンプする。
br
は分岐(branch)を意味する。
br
命令の説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#br-instruction。
構文のうち、今回使用する最小限の部分は以下。
br i1 <cond>, label <iftrue>, label <iffalse>
<cond>
にはi1
型の値、つまり真偽値を指定する。
真の場合のジャンプ先を<iftrue>
に、偽の場合のジャンプ先を<iffalse>
に指定する。
ラベルの構文は、サンプルコードを見る限りでは以下と思われる。(説明を公式ドキュメントから見つけられなかった。)
<label_name>:
使うときは%<label_name>
。
%
で始まるということはローカルスコープ、つまり定義した関数内からのみ参照可能ということ。
C言語のラベルと同様と考えてよさそう。
条件分岐のサンプルは以下。
define i32 @main() { br i1 1, label %label1, label %label2 label1: ret i32 0 label2: ret i32 1 }
条件式
定数で条件分岐しても意味がないので、変数と何らかの値を比較したい。
i1
型の値を返す命令ならなんでもいいが、今回は整数の比較を行うのでicmp
命令を使う。
icmp
命令の説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#icmp-instruction。
構文は以下。
<result> = icmp <cond> <ty> <op1>, <op2>
<cond>
には以下のいずれかのキーワードを指定できる。
キーワード | 説明 |
---|---|
eq | == |
ne | != |
ugt | > (符号なし) |
uge | >= (符号なし) |
ult | < (符号なし) |
ule | <= (符号なし) |
sgt | > (符号あり) |
sge | >= (符号あり) |
slt | < (符号あり) |
sle | <= (符号あり) |
<ty>
には比較する値の型を指定する。
整数型、ポインタ型かベクトル型を指定できる。
LLVM IRでは、符号ありか符号なしかの情報は型に含まれていない。
icmp
などで演算を行うときに、符号ありとして演算するか符号なしとして演算するかを指定するようになっている。
条件分岐のサンプルは以下。
define i32 @main() { %cond = icmp eq i32 111, 111 ; %condは真になる ; %cond = icmp eq i32 111, 222 ; こうすると偽になる br i1 %cond, label %label1, label %label2 label1: ret i32 0 label2: ret i32 1 }
無条件分岐
br
命令で無条件に分岐することもできる。
その場合の構文は以下。
br label <dest>
C言語のgoto
のように使える。また、if else
を実現するために使う。
サンプルコードは以下。
define i32 @main() { %ptr_return_value = alloca i32 %cond = icmp eq i32 111, 111 br i1 %cond, label %then, label %else then: store i32 1111, i32* %ptr_return_value br label %end else: store i32 2222, i32* %ptr_return_value br label %end end: %return_value = load i32, i32* %ptr_return_value ret i32 %return_value }
繰り返し
C言語のfor (int i = 0; i < 10; i++){}
に相当するサンプルコード。
add
命令でi
をインクリメントする。add
の説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#add-instruction。
define i32 @main() { entry: %ptr_i = alloca i32 ; `int i ` store i32 0, i32* %ptr_i ; ` = 0` br label %for_cond for_cond: %i = load i32, i32* %ptr_i %cond = icmp ult i32 %i, 10 ; `i < 10` br i1 %cond, label %for_body, label %end for_body: %i_next = add i32 %i, 1 ; ` i + 1` store i32 %i_next, i32* %ptr_i ; `i = ` br label %for_cond end: ret i32 %i }
数値の文字列化
C言語ならfprintf
関数を使うところだが、LLVM IRで使うためには構造体、可変長引数などが必要になる。
Windowsの場合、fprintf
関数にかかわる構造体は_iobuf
、__crt_locale_pointers
、__crt_locale_data
、__crt_multibyte_data
。
_iobuf
はFILE
構造体のことで、標準出力を表すstdout
変数の型。
stdout
を取得するには__acrt_iob_func
関数を使う。
fprintf
はロケールを考慮するので(小数点がピリオドかカンマか等)、ロケール情報を取り扱うために_iobuf
以外にもいろいろな構造体を使っているようだ。
__crt_multibyte_data
が何なのかは調べていない。
fprintf
を理解するためにはまだ覚えなければならないことが多いので、今回はfprintf
を使うのはやめておく。
(ちなみに、Linux環境なら結構簡単そうだった。)
今まで覚えたこととputchar
関数を駆使して、自分で数値を文字列化することにする。
putchar
putchar
はC言語の同名の関数と同じものと思われる。
今回は数字を出力したいので、0
から9
、つまり48から57までのいずれかの数値を渡す。
サンプルコード
declare i32 @putchar(i32) define i32 @main() { call i32 @putchar(i32 48) call i32 @putchar(i32 49) call i32 @putchar(i32 50) call i32 @putchar(i32 51) call i32 @putchar(i32 52) call i32 @putchar(i32 53) call i32 @putchar(i32 54) call i32 @putchar(i32 55) call i32 @putchar(i32 56) call i32 @putchar(i32 57) ret i32 0 }
実行結果 0123456789
一桁の数値であれば、48を足してからputchar
に渡すことで文字列化できる。
複数桁の数値の文字列化
今まで学んだ内容と、除算、剰余演算を利用して関数を自作する。
符号なし整数の除算はudiv
。説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#udiv-instruction。
符号なし剰余演算はurem
。説明はhttps://releases.llvm.org/10.0.0/docs/LangRef.html#urem-instruction。
declare i32 @putchar(i32) ; 符号なし整数を標準出力に出力 define void @print_unsigned_integer(i32 %c) { %tmp = alloca i32 %buff = alloca [10 x i32] ; 文字列バッファ %index = alloca i32 ; buffのインデックス store i32 %c, i32* %tmp store i32 0, i32* %index br label %stringify ; 1の位から順番にASCIIコード化し、buff配列に格納 stringify: %1 = load i32, i32* %tmp %2 = urem i32 %1, 10 %3 = add i32 %2, 48 %4 = load i32, i32* %index %5 = getelementptr [10 x i32], [10 x i32]* %buff, i32 0, i32 %4 store i32 %3, i32* %5 %6 = udiv i32 %1, 10 store i32 %6, i32* %tmp %7 = add i32 %4, 1 store i32 %7, i32* %index %8 = icmp eq i32 %6, 0 br i1 %8, label %print, label %stringify ; buff配列を逆順に出力 print: %9 = load i32, i32* %index %10 = sub i32 %9, 1 %11 = getelementptr [10 x i32], [10 x i32]* %buff, i32 0, i32 %10 %12 = load i32, i32* %11 call i32 @putchar(i32 %12) store i32 %10, i32* %index %14 = icmp eq i32 %10, 0 br i1 %14, label %end, label %print end: ret void }
FizzBuzz
あとは今までのものを組み合わせればFizzBuzzが作れる。
declare i32 @putchar(i32) declare i32 @puts(i8*) @fizz = constant [5 x i8] c"fizz\00" @buzz = constant [5 x i8] c"buzz\00" @fizzbuzz = constant [9 x i8] c"fizzbuzz\00" define i32 @main() { %ptr_fizz = getelementptr [5 x i8], [5 x i8]* @fizz, i32 0, i32 0 %ptr_buzz = getelementptr [5 x i8], [5 x i8]* @buzz, i32 0, i32 0 %ptr_fizzbuzz = getelementptr [9 x i8], [9 x i8]* @fizzbuzz, i32 0, i32 0 ; int i = 1 %ptr_i = alloca i32 store i32 1, i32* %ptr_i br label %for_cond for_cond: ; if (1 <= 100) continue; else break; %i = load i32, i32* %ptr_i %cond_for = icmp ule i32 %i, 100 br i1 %cond_for, label %for_body, label %end for_body: ; if (i % 15 == 0) %rem15 = urem i32 %i, 15 %cond_fizzbuzz = icmp eq i32 %rem15, 0 br i1 %cond_fizzbuzz, label %print_fizzbuzz, label %check_buzz check_buzz: ; else if (i % 5 == 0) %rem5 = urem i32 %i, 5 %cond_buzz = icmp eq i32 %rem5, 0 br i1 %cond_buzz, label %print_buzz, label %check_fizz check_fizz: ; else if (i % 3 == 0) %rem3 = urem i32 %i, 3 %cond_fizz = icmp eq i32 %rem3, 0 br i1 %cond_fizz, label %print_fizz, label %print_number print_fizzbuzz: call i32 @puts(i8* %ptr_fizzbuzz) br label %next print_buzz: call i32 @puts(i8* %ptr_buzz) br label %next print_fizz: call i32 @puts(i8* %ptr_fizz) br label %next print_number: call void @print_unsigned_integer(i32 %i) call i32 @putchar(i32 13) ; '\r' call i32 @putchar(i32 10) ; '\n' br label %next next: %i_next = add i32 %i, 1 ; ` i + 1` store i32 %i_next, i32* %ptr_i ; `i = ` br label %for_cond end: ret i32 0 } ; 符号なし整数を標準出力に出力 define void @print_unsigned_integer(i32 %c) { %tmp = alloca i32 %buff = alloca [10 x i32] ; 文字列バッファ %index = alloca i32 ; buffのインデックス store i32 %c, i32* %tmp store i32 0, i32* %index br label %stringify ; 1の位から順番にASCIIコード化し、buff配列に格納 stringify: %1 = load i32, i32* %tmp %2 = urem i32 %1, 10 %3 = add i32 %2, 48 %4 = load i32, i32* %index %5 = getelementptr [10 x i32], [10 x i32]* %buff, i32 0, i32 %4 store i32 %3, i32* %5 %6 = udiv i32 %1, 10 store i32 %6, i32* %tmp %7 = add i32 %4, 1 store i32 %7, i32* %index %8 = icmp eq i32 %6, 0 br i1 %8, label %print, label %stringify ; buff配列を逆順に出力 print: %9 = load i32, i32* %index %10 = sub i32 %9, 1 %11 = getelementptr [10 x i32], [10 x i32]* %buff, i32 0, i32 %10 %12 = load i32, i32* %11 call i32 @putchar(i32 %12) store i32 %10, i32* %index %14 = icmp eq i32 %10, 0 br i1 %14, label %end, label %print end: ret void }