gcc のフロントエンド作る日記 (12) Programming Language BL
■ コンパイラの中身を知らない子ども達
あまりも当たり前過ぎて21世紀に入ってから言葉にだしたことはあまりないのですが,当然のことながら,プログラムというのは,ソースコードと抽象構文木の対応を理解して初めて「書ける」と言うのです。
プログラムが書ける,という状態は「構文木が作れる」という状態の延長線上にあるべきで,構文木を理解していないということはソースコードという文字列が表現している構造を理解していない,つまりプログラムを理解していないのとほぼ同じだと思います.
最近はLLと呼ばれる,いわゆる軽量スクリプト言語がメインになってきていますし,僕も普段はPHPでプログラムを書くことが増えてきましたが,それでも依然として,言語処理系というのは,インタプリタにせよコンパイラにせよ構文木の操作で動くもので,プログラムというものは全て構文木の延長上にあると思っています.
最近は素晴らしい時代になったもので,コンパイラのごく初歩的な入門書は本屋さんに行けばたくさん並んでいます.しかし本格的に構文木で遊びたくなったら,世界一移植性が高くて世界一多くのプラットフォームをサポートしていてそれなりに最適化してくれるGCC を使うのがお勧めです.
今の GCC の中身は複雑になりすぎていて,初心者が全ての機能を知ろうとすると膨大な時間と労力がかかります.
しかし,最終的にはそれは全て知らなければならないことですし,知っておくべきことです.
どれだけデバッガやコンパイラが進化しても,その仕組みを理解していることは絶対に必要です。
抽象構文木は全て tree の組み合わせでできています.現在の主流言語であるCやC++,Ada,Java,Pascal,Objective-C など,全てのコンパイル言語の元祖ともいえるのが tree です.全ての言語のソースコードは,全て GCC の GENERIC tree に変換されます.つまり tree は,全ての言語のソースコード文字列に共通の表示的意味をあたえることができます.GCC GENERIC を構成するデータ構造は全て tree なので,tree を理解しないと GCC の動作原理を理解していないことになります.
最低でも,GENERIC だけでネストをサポートした関数を作ってアセンブリを出せる程度の理解はしておいて欲しいと思います.tree,GCC,C言語の3つは,現在でもあらゆるプログラムの基礎になっているので,最低限おさえておきたいところです.
最後に参考文献をまとめておきます.
ただ読むだけでもとても面白い文章ばかりです.
GNU Compiler Collection (GCC) Internals
とかいう話はどうでもいいとして.
# shi3zの日記 2007-09-11 マシン語を知らない子ども達
ようやく tree を操れるようになってきたので,GCC の俺言語フロントエンドを作り始めました.とりあえず言語名はいいのが思いつかなかったので,とりあえず Buggy Language (ポンコツ言語) とか付けてみる.BL です.
当初は,なんか一文字の名前 (e あたりが最適だったのですが… うほっ,e 言語) を付けようと思ったのですが,A 〜 Z まで,だいたいめぼしいのは使われてる感じ.
自分の命名センスの無さを呪うしかない.オープンソースの成功の 9 割は名前で決まると言われています (本当か ?) 今思えば,Shiki (式) を Kokuban (黒板) という名前にしておけば,今頃 2ch 等の Scheme スレで標準エディタとしての地位を不動のものにしていたに間違いありません (嘘)
# 個人的に,今まで見た中で最高の言語名は, wo さんが昔作っていた NL (neta language) です.
ダウンロード : bl.tar.gz
ビルドは,通常のフロントエンドと同様に,(時間はかかりますが) 簡単にできます.
$ tar jxvf gcc-core-4.2.1.tar.bz2
$ tar zxvf bl.tar.gz
$ mv bl gcc-4.2.1/gcc/
$ cd gcc-4.2.1/
$ ./configure --prefix=/home/aloha/BL --enable-languages=bl --enable-checking=tree --enable-debug
$ make
$ make install
$ ../bin/gbc --version
GCC buggy language (GCC 4.2.1)
GCC buggy language comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of GCC buggy language
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING
$ cd ..
$ mkdir test
$ cd test
$ touch test.bl
$ ../bin/gcc -S test.bl
$ cat test.s
.file "test.bl"
.globl hoge
.data
.align 4
.type hoge, @object
.size hoge, 4
hoge:
.long 123
.ident "GCC: (GNU) 4.2.1"
.section .note.GNU-stack,"",@progbits
ソースファイル (拡張子は bl) は,いまのところダミーです.何を入力しても同じ結果になります.
次回から,ちゃんと中身を作っていく予定.
ちなみに,gcc (コンパイラドライバ) の代わりに,gbc (コンパイラ本体) を直接指定しても,もちろん OK です.
--disable-bootstrap とか --target=i686-elf とかしたら 3 回ビルド (システムの gcc で gcc をビルド,できた gcc でもう一回ビルド,もう一回ビルドして,2 回目と同じバイナリが生成されていれば成功) してテストしたり,全てのターゲットに向けてビルドとかしなくなるので速くなるかな〜 ? と思ったら,最初の時はなぜか host-i686-pc-linux-gnu/gcc/as の exec の前が空 (本来は /usr/bin/as とかがくる) だったり,i686-elf-ar が作られてないよ ! とか,途中でビルドがコケてしまいます (手動でやってもなんとかなると思いますが,いろいろ面倒なので,最初は地道に全部 boostrap するのが吉かも)
■[LLVM] まずは
とりあえず今のところLLVMの一番の利点は、一応libLLVM*.aはありますがそんなもの使わなくても、テキストファイルとして.llを出力してしまえば、内部なんか何も知らなくてもコンパイラが作れてしまうことですね。gccのフロントエンドはMake-lang.inを書くところからして気が遠くなるからなあ……。確かgccは思惑があってわざわざ中間コードを扱えなくしてるんでしたっけ。ビルドにかかる時間を思えば、トライアンドエラーなんてできないし。その点テキストなら、極端な話、正規表現の羅列だけでコンパイラが書けるわけで。
……あろはさんに怒られそうです。gccのフロントエンド作る日記、楽しみに読ませていただいてますよっ。
とりあえず,一つにファイル内に全部書く (今回は bl1.c) という手法で,Make-lang.in は触らなくてすみます… とか (笑) この程度の規模ならば,常に 1 ファイルだけを修正するようにしていれば,そんなにコンパイル時間はかからないです (生成された Makefile を弄ってもいいし).make よりも make install の方がはるかに長いくらい.
# 追記 : 以前 30 秒かかるとか言っていたのは,最終的な実行ファイルの位置をわかってなかった (stage1 とか,いろいろディレクトリがあって,どれがどれだかわからなかった) から,律義に make installl までしていたから (info とかのインストールが馬鹿みたいに長い) make して, できた実行ファイルは, ./host-i686-pc-linux-gnu/gcc/gbc とかを使えば良いみたいです.ファイルを変更して make するたびに 2 回ぐらいコンパイルが繰り返されますが,make だけならばせいぜい 10 秒です.
# 追追記 : ありゃ,やっぱり make install までしないと駄目みたい.ドキュメントとかの無駄なインストールを止めるのってどうすりゃいいんだ ?
まぁ,確かに gcc のフロントエンドをトライアンドエラーで毎回コンパイルはしんどいので,インタプリタからバックエンドを直接操作できるような言語を作った方がいいんですが,それは既に wo さんが 3 年ぐらい前に通った道という.
# ようやく 3 年遅れで,wo さんの技術力にちょっとは追い付けたのかと思うと,いろいろ感慨深い.
あるいは,Ruby とか Gauche から gcc の tree を作る API を呼び出せるようにして,ruby とか scheme のインタプリタを LANGHOOKS の parse_file に噛ませて,ruby とか scheme で書かれた ilogscript に相当するような tree を操作するコードを飲ませると,アセンブラが出力されるようにするとか.
まぁ,もうそういうのも作ろうと思えば作れると思うんですが,作業量的にいろいろ大変.
とりあえず,この日記にノウハウはそれなりに書いてるので (どこまで伝わってるか,何人が興味あるのか謎だけど) それなりの技術力がある人ならば,もう GCC のバックエンドを操れるんじゃないかと.
LANGHOOKS に登録する parse_file() 関数の中で,何らかの手段でテキトーにソースコードから tree 作って,ミドルエンドに渡すだけで,広範なターゲットをサポートする最適化コンパイラができちゃうわけです.いわば,(最初のうちは) 他のところ全部無視して,parse_file() を main() だと思って普通に文字列を処理するプログラムを書けば良いだけ.
インタプリタが評価関数呼び出してプログラムを実行するかわりに,GCC の API を呼んで木を作るように評価器を作ると,それがコンパイラになるという.インタプリタとコンパイラってのは,本質的にはほとんど変わりません (二村射影)
そんなこんなで,俺言語インタプリタを作るのはもう古い.これからは静的コンパイル言語を自作するのが流行る ! (わけがない).
# というか,C/C++ みたいな型宣言が強制される言語ならばともかく,それなりに書きやすいスクリプト言語を作ろうと思ったら,型推論とかが大変.ランタイムライブラリも作らないといけないし.
