ITエンジニアのブログ

IT企業でエンジニアやってる人間の日常について

プログラミング言語を作る。第7回:LLVM入門

LLVM を用いて FizzBuzz を書きました。色々と躓いた点があったので、それを記述しておきます。

まずはプログラム本体から

@s1 = constant [10 x i8] c"FizzBuzz\0A\00"
@s2 = constant [6 x i8] c"Buzz\0A\00"
@s3 = constant [6 x i8] c"Fizz\0A\00"
@s4 = constant [4 x i8] c"%d\0A\00"

declare i32 @printf(i8*, ...)

define i32 @main(){
    %i = alloca i32, align 4
    store i32 1, i32* %i
    br label %L1
L1:
    %v1 = load i32* %i, align 4
    %v2 = icmp sle i32 %v1, 100
    br i1 %v2, label %L2, label %L3
L2:
    %v3 = srem i32 %v1, 15
    %v4 = icmp eq i32 %v3, 0
    br i1 %v4, label %L4, label %L5
L4:
    %v5 = call i32 (i8*, ...)* @printf(i8* getelementptr ([10 x i8]* @s1, i32 0, i32 0))
    br label %L6
L5:
    %v7 = srem i32 %v1, 5
    %v8 = icmp eq i32 %v7, 0
    br i1 %v8, label %L7, label %L8
L7:
    %v9 = call i32 (i8*, ...)* @printf(i8* getelementptr ([6 x i8]* @s2, i32 0, i32 0))
    br label %L6
L8:
    %v10 = srem i32 %v1, 3
    %v11 = icmp eq i32 %v10, 0
    br i1 %v11, label %L9, label %L10
L9:
    %v12 = call i32 (i8*, ...)* @printf(i8* getelementptr ([6 x i8]* @s3, i32 0, i32 0))
    br label %L6
L10:
    %v13 = call i32 (i8*, ...)* @printf(i8* getelementptr ([4 x i8]* @s4, i32 0, i32 0), i32 %v1)
    br label %L6
L6:
    %v6 = add i32 %v1, 1
    store i32 %v6, i32* %i
    br label %L1
L3:
    ret i32 0
}

特に二種類の要点を挙げます。
ラベルはそれ以前で使われている必要がある。
例えば、次のような命令列は、ラベルの部分で存在しない operator と言われます。

L2:
    %i = alloca i32, align 4
    store i32 1, i32* %i
    %b = icmp eq i32 %i, 0
    br i1 %b, label %L1, label %L2
L1:
    ret i32 0

L2 というラベルが br で初めて使われる前に来ているからです。上のプログラムの for ループを書く部分で、 br label %L1 という記述があり、直後にジャンプする不自然な挙動をしていますが、そういう理由で付けざるを得ませんでした。

単なる数値の変数は、後ろにインクリメントされたラベルが付与される。
上のプログラムでは %v1 のように v と数字で変数を表現していますが、最初は %1 のように書いていました。しかし、例えば

    %1 = alloca i32
    ret i32 0

のようなプログラムは、

    %1 = alloca i32
2:
    ret i32 0

というように勝手にラベルの付与がなされるようです。%数字で変数を表現すると特殊な処理になっているのですね。これで前回の疑問(コメントアウトしたラベルがなぜか有効)も理由がわかりました。

LLVM をちょっとさわってみて、なかなか制約の多いように感じました。これでは、独自の中間コードを記述してシミュレータを書くのもありかもしれません。