以前の記事「数日前からCコンパイラを書き始めた。」「自作Cコンパイラの途中経過」に続く記事。

これは日記であって、知見が書かれた文章ではない。

経過報告

気づけば2020年も半分が終わり、梅雨に入って雨が続き、今日もジメジメと蒸し暑い。 大学の研究室は相変わらずリモートで、新型コロナウィルスの東京都の感染者数も100人を超える日が続き、収束の気配がない。

世間は春から夏へ季節が変わろうとしているし、世界的にウィルスとの戦いを繰り広げている昨今だが、私のやっていることは相変わらずCコンパイラ「willani」の制作。もう6週目になるだろうか。

昨日あたりからセルフホストに向けて、やっとコンパイラ本体のコードを第一世代コンパイラに流し始めた。 まだ本体のコード全ての機能は実装していないため、そのあたりは置き換えるシェルスクリプトを書いて、一旦スクリプトを挟んでからコンパイルする。 (#include, 可変長引数定義, 構造体の初期化などが未実装)

プリプロセスは#defineだけ実装した。プリプロセッサの大枠はできており、他のプリプロセス文も頑張れば実装できそう感はある。 他にも、ちょっと頑張れば追加できそうな機能がいくつかあるが、一旦機能追加よりもセルフホストに軸をおくことにした。

20以上のファイルに分割しているので、ファイル単位で置き換えていく。 一つのオブジェクトファイルをを第一世代コンパイラで出力したものに置き換える。 問題なければ次のファイルを第一世代コンパイラで出力したものに置き換える。 問題なければ、、、と繰り返し作業を進める。

よくわからなかったこと

親コンパイラと第一世代コンパイラでコンパイルしたものを混ぜてgccに投げると、うまく静的リンクできない。 PIEがどうこう、.textセクションやdataセクションは動的リンクでは使えないぞ、などと怒られる。

$gcc -static -o willani *.c *.s # *.sはwillaniでコンパイルしたアセンブリ

解決策として、コンパイルと、アセンブル&リンクを分けて行った。 親コンパイラの出力も一旦、個々のアセンブリファイルとして出力する。 $gcc hoge.c -S -o hoge.sのように。

これらを第一世代コンパイラで出力したアセンブリと混ぜてリンクしてもらう。 リンクはlibcが必要である。 gccコマンド($gcc *.s -static -o willani)で行った。 ($ldを使ってlibcを含んで静的リンクするのってどうやるの?未調査。)

アセンブルとリンクは分けなくても問題なかった。 ($as hoge.s -o hoge.oとアセンブルを別途行ってもいいが、gccにアセンブリを投げたらいい感じにリンクまで終わらせてくれる。)

第二世代コンパイラのデバッグがつらそう

構造体のメモリ配置がABIに従ってなさそうで、今第1.5世代コンパイラ1がバグっている。 第一世代コンパイラはテストに通るのに第二世代コンパイラ(1.5世代含む)はバグっている状況は、どこから手をつけていいかわからずに混乱した。 今後も前途多難だな。。

技術メモ:gccでデバッグ情報のないアセンブリを出力する

今さっき知ったtips

$gcc sample.c -S -o sample-sのようにコンパイルすると、デバッグ情報が含まれる。(-gオプションとは違い、最低限。) 例えば.cfi_startprocなどの行が出力に含まれて、人間が読むときには邪魔。

そこで、***-fno-asynchronous-unwind-tables***オプションが有効。

$gcc sample.c -fno-asynchronous-unwind-tables -S -static -o sample.sのようにオプションを追加すると無駄な行が出力されないので読みやすくなる。

Cの仕様を調べるとき、gccの出力と見比べたりするのが結構役に立つので、見やすいアセンブリを出せて助かる。(車輪の再発明だからできることだが。)

コンパイラをつくっていて良いこと

車輪の再発明あるあるではあるが、ブラックボックスを一つ紐解けた。2

作る前はコンパイラはよくわからないがよしなにやってくれるもので、アセンブリはとっつきづらいもので、オブジェクトファイルってあるよな程度の理解だった。 概念としてコンパイル、アセンブル、リンクは知っていたが、実装レベルで知れるのは良い。 コンパイラは要するに文字列変換プログラムだし、アセンブリはただのテキストデータだし、(リンクはそこそこ大変そうで学ぶべきことがたくさんありそうだけど)、何やってるかは以前より想像がつくようになった。

これからやること

  • 構造体のメモリ配置を System V ABI に準拠させる
  • 構造体の初期化文
  • セルフホスト
  • #include
  • #ifndef #ifdef #if #end

Footnotes

  1. 第1.5世代コンパイラ ... 親コンパイラ(gcc)の出力と第一世代コンパイラの出力を混ぜてアセンブル、リンクして作ったコンパイラを指す。

  2. ブラックボックスを紐解く ... 過去にPDFファイルを自分で作ったり、正規表現エンジン(作りかけ)を作ったり、認証認可の仕組みを作ったときも同じ気持ちになった。今後、OS(途中で止まってる)やHTTPサーバ、インタプリタ(やJITコンパイラ)なんかも生きてるうちに取り組みたい。