〜LLVMコンパイラを作ってみたいと思っていても、アセンブリ言語はよくわからない。パーサーみたいなコードは書いたことがあるけれど、コード生成の処理はさっぱりだ。実行ファイルをバイナリエディターで見るとかなにそれ怖い。そんな私なのですが、LLVMに興味を持ち始めています。SwiftやRust、あるいはEmscriptenなど、近年注目されている言語やコンパイラ技術の中枢にはLLVMがあります。アセンブリはよく分からなくてもLLVMを使いこなせるようになれば、マルチプラットフォームで実行ファイルを生成できる言語処理系を作るのではないか。コンパイラ作ってみたいな、LLVMを使ってみようかなと思っている今日このごろです。ところが、いざLLVMを勉強しようと思ってもどこから始めればいいかよく分かりませんでした。マニュアルは巨大で読む気が起きないし、リファレンスを見てもさっぱりです。雰囲気はわかるんです、インストラクションっていうのはプリミティブな命令のことでしょう、そのリファレンスは便利なんでしょう。素人にはどこから手を付けてよいのかわからない。IRにたどり着かずに飽きてしまう。bindingのソースコードとLLVM本体のコードをあっちこっち飛んで回って疲弊してしまう。他の人が作った処理系のコードを読んでみようと思ってcloneしてみても、いつの間にか読む暇もなく忘れてしまう。こんなダメダメな私はいったいどうすればいいのかと思って調べていたのですが、ある日次のようなブログ記事を見つけました。こんな簡単なことを忘れていた。(本記事はLLVMがインストールされており、LLVMツールのパスが通っていることを前提とします。私の手元では/usr/local/opt/llvm/bin/でした)。高ぶる気持ちを抑えて実行ファイル生成まで試してみます。$?42実行ファイルを生成し、実行することができました。-emit-llvmが生成したファイル、test.llをおそるおそる覗いてみます。(clang-800.0.42.1)"}うーん、これはさっぱりです。コメントなどいらなさそうな物を削ってみて、どれだけ消しても動くか試してみます。#0と書いてあるのがよく分からないですが消してみます。コードを眺めてみます。intなんだろなと、容易に想像が付きます。こんな小さいコードなら、手で書くこともできそうです。i32といったキーワードを理解し始めているのです。ここまでやったことはとても簡単で、しかもすぐに試せることです。IRファイルを生成し、lliやllcといったコマンドで実行できることを確かめながら、ファイルを削って本当に必要な部分を探しただけです。LLVMのリファレンスは全く見なくても、Cのどういうコードがどういう命令列になるかはclangが教えてくれるのです。IRを学んでいけるのです。42となることはわかりました。IRがどうなるかを調べてみましょう。変数の宣言を調べてみます。次のようなコードを書いてみます。-O0で最適化されないように気をつけます。test.llコメントやattributesなどを削除したら次のようになります。0}実際このファイルは実行できます。longがi64に対応していることがわかります。alignはアドレスのアラインメントを表しており、変数のアドレスがこの数字の倍数であることが保証されます。先程のコードに代入を追加して調べてみます。IRは次のようになります。0}store命令となりました。引数が代入したい値で、第二引数が代入先のアドレスとなるようです。コードを変えてみます。IRは次のようになります。0}%1に32を保存し、%1の値をload命令で取り出して%3とし、それを%2にstoreしています。足し算や引き算するとどうなるでしょうか。int同士の足し引きを見てみます。引き算はsub命令となりました。nswは理解を後回しして良いでしょう。後は標準出力が気になります。...)declare命令でprintfを使うことを宣言し、call命令で呼び出しを行っています。printfではなくてputcharを使ってみます。@putchar(i32)よく見ると、loadしてputcharするだけではなくて、sext命令が入っています。いえばputcharの引数はcharではなくてintでしたね。extendの略で、型の変換を行う命令です。IRでどのようになるかを観察してきました。これだけでは実用的なプログラミング言語のコンパイラを作るには知識が足りないのですが、プリミティブな言語の処理系なら作れる気がしてきました。プリミティブな言語...といえばBrainf**kですよね。私はこの言語が大好きです。worldと言ってもいいのではないでしょうか。先ほど紹介した記事でも、Brainf**kのコンパイラを書いています。目的が同じですが、そっくり真似にならないようにちら見程度にして、自分の手で書いてみようと思います。Brainf**kのデータ領域の用意をします。IRを見てみましょう。@free(i8*)i8*型の変数dataとptrを作り、callocの結果を代入しています。1getelementptrという新しい命令が出てきましたね。意味は後で調べるとして、今はこういうものだと思っておきましょう。dereferenceして%8に入れて、1足して%9に入れて、それを%7に代入する、と一行ずつ意味を読み取りやすいですね。あとBrainf**kの.命令に対応するputchar(*ptr);のコードを見ておきます。命令でi32に変換する必要がありましたね。IRコードが分かりました。-のコードも容易に想像がつきます。IRコンパイラを書いてみます。0;}動かしてみます。これでは+と.しか試してないので他の命令も使ってみます。