Cコンパイラの学習

この記事

「低レイヤを知りたい人のためのCコンパイラ作成入門」を読んでCコンパイラ書いてみた感想などです。

実際書いたコードはここに置いています。

github.com

テキスト

www.sigbus.info

このテキストは結構前に公開されたもののようでいろんな人がブログに書いたりしていて評判もよく実際学びやすいと思います。

難しいところはあまりなく読んでいて自分でもコンパイラ書けそうと思えてきます。

実習的な内容で読んで自分でコードを書きながら進めるところがよかったです。

まず最小構成でコード生成まで作って、小さいステップでテストを書きながら機能を追加していくというやり方も取り組みやすいと思いました。

何かコンパイラ的なものを作ろうとしてパーサーだけ作ってやめてしまったり、 途中の実装を無駄に凝ってすすめられなくなった経験が少なからずあるのでコンパイラ以外を作る場合にも参考にしたいなあと思いました。

後半は少し難しくなっていく気がしますが そこまで読み進めて理解できていれば解決できるということで意図的に細かいことは書かれていないのかもしれません。

言語の選択

セルフホストするところまでやるのが切りがよさそうなのでやっぱりC言語でやるのがいいのかなと思います。

セルフホストを気にしない場合は好きな言語で書いていいと思います。

私はC言語をあまり書きたくなかったのと、サンプルと同じように書いてしまうとコピペになってしまうかなという気がして Go で書いてました。

Go 使うことにしたのは前に少し触って書き方を忘れそうなので Go の学習のためというのもありました。

ただよく知っていて調べずに書ける言語で書いていく方がコンパイラを学ぶことに集中できると思います。

Go で書いてよかったところや書きにくかったところを挙げてみます。(私が Go の書き方を知らないせいもあるかもしれません。)

よかったところ

  • 実装にあまり迷わなかった
  • C言語に近い構文は大体ある
  • スライスやマップが標準で使える
  • type が別名ではなく別の型
  • 関数から関数を返せる
  • 無名関数を使える

書きにくく感じたところなど

  • enumを簡単に文字列表示したい
  • 整数型のmin, maxが欲しい
  • スライスの比較が欲しい
  • mapにキーがあるかどうか調べる関数が欲しい

式と文

最初は四則演算などの式だけをコンパイルして、スタックには一つ値が残った状態になるのでこれを pop するという実装になっていました。

if などを導入したときにこの部分でバグって少しはまっていました。 文の場合は式と違ってスタックに値が残らない場合があるので常に pop するとスタックがずれて壊れます。

結局 IsExpr 関数を作って式だったら pop するような感じで修正しました。

最適化は今回は全然手をつけられていないですが 簡単な実装だと無駄なレジスタ、スタックの移動が発生してしまうので最適化でこの辺の無駄をなくしたりするのかなと思いました。

Cの型のパース

Cの型のパースは難しく感じました。

ただこのテキストで扱う型は限られているので最初からそんなに完全な実装で無くてもよくてここで長く悩む必要はないです。

配列、関数、ポインタなど型の修飾が適用される順番が読む順番と逆にになっているところが難しいような気がします。

ここはパーサーの戻り値に型を受け取って型を返す関数を使うと実装しやすかったと思います。

色々な整数型の扱い

int だけでなく char が出てきて、レジスタも64ビットの他に32ビット、16ビット、8ビット版を扱うあたりが難しかったです。

変数からレジスタに読み込むところと、変数に書き込む部分だけ変数型とレジスタのタイプを気にしておけば 途中計算は int にして大体よさそうな気がするんですがいまいちこのあたり理解できてません。

ステップ28

ステップ28 でテストをC言語で書き直すというところがあります。

書く前はそこまでできるのかちょっと疑問に思いましたがやってみると意外にできて感動がありました。

ただここでテストに使う式や構文をどうやってテストに入れて実行したり表示したりするのか最初わかりませんでした。

ここは書かれてないけど多分プリプロセッサでマクロを使って書くということになると思います。

プリプロセッサは実装していないので gccプリプロセスのみを処理してからコンパイルしました。