単子葉類プログラマーのメモ

プログラミング関連の自分用メモだけど他の人の役に立つかもしれないので公開しておく感じのブログ

LLVM言語 学習メモ (2) - FIzzBuzz

前回の続き。

今回は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
}

制御構文

条件分岐

LLVM IRには、C言語のようなif文はない。

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

_iobufFILE構造体のことで、標準出力を表すstdout変数の型。 stdoutを取得するには__acrt_iob_func関数を使う。

fprintfロケールを考慮するので(小数点がピリオドかカンマか等)、ロケール情報を取り扱うために_iobuf以外にもいろいろな構造体を使っているようだ。

__crt_multibyte_dataが何なのかは調べていない。

fprintfを理解するためにはまだ覚えなければならないことが多いので、今回はfprintfを使うのはやめておく。 (ちなみに、Linux環境なら結構簡単そうだった。)

今まで覚えたこととputchar関数を駆使して、自分で数値を文字列化することにする。

putchar

putcharC言語の同名の関数と同じものと思われる。

今回は数字を出力したいので、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
}