LLVM の C-API を使ったコンパイラ
対象読者
解決すること
内容
低レイヤを知りたい人のためのCコンパイラ作成入門 を読んで一応のコンパイラを作成した後、LLVM でコードジェネレータ部分を作りたいなぁなんて思ってました。が、かなり限定されたコンパイラにしたくて。小さくて自己コンパイル出来るもの。
作成したのはこちら。
主に工夫したのは2点。LLVM の C-API を使うことと、外部コードを読み込まないこと。
LLVMの記事を検索すると、LLVM IR を書く話とか、C++ でのコンパイラ作成の話は出てくるのですが、C-API での自作コンパイラ作成の話はあまり見つからなくて。Clang 入れると C の API も入っているので、これを調べながら使いました。これなら C++ のコードを自己コンパイル対象にしないですみます。私の環境だと、/usr/include/llvm-c にヘッダファイルがインストールされていて、ここの、Core.h にある関数でほぼ書けます。
もう一つの、外部コードを読み込まないというのは、つまり、ヘッダファイルを読み込まない、プリプロセッサを通さないということ。プリプロセッサを作るとなるとコンパイラ本体に集中出来なくなりますし、ヘッダファイルと言えども外のコードを読まなければならないとなると、予期せぬ構文のサポートが増えます。
ヘッダファイルを読み込まずにどうするのかと言うと、ヘッダファイルで定義された定数や変数は使わない縛りにします。一方関数はプロトタイプを直書きしてしまえば使用可能です。LLVM の関数はポインタで引数や返り値をやりとりしていますので、void * 汎用ポインタを使って関数プロトタイプをファイル内で直書き出来ます。外部定数や変数は直書きしたもの以外使えませんが、この縛りだけを守れば、後は自身のコードだけをコンパイル出来ればいいことになるので、サポートする構文パターンはグッと少なくなります。
浮動小数はもちろん扱いませんし、union 型もないし、配列の初期化構文もなく、なにより for 文、やりませんでした。Cの構文解析は、型の部分が難しいと私は感じてるのですが、関数へのポインタなど入れ子になった型は除外しましたし、構造体も、入れ子構造になった定義や定義と一緒に変数を宣言するパターンなどを外しました。
コードジェネレータの方も、アセンブラを吐こうとすると x86-64での関数呼び出しの 16byte アライメントや、引数の受け渡しでのレジスタへの詰め込みなどありますが、これらが LLVM にお任せになっているので、楽になっていると思います。
その代わりと言ってはなんですが、1ファイルに詰め込んでます...。(1700行くらい)
Cの規約に従ったり、定義した BNF があるわけではないので、自身のコードがコンパイル出来るというだけですが、自作コンパイラの参考になれば幸いです。