GCC Compile Server

http://per.bothner.com/papers/GccSummit03/gcc-server.pdf

を読んでみました。7年前の話で、かつ今実現してる気配がないので失敗したんでしょうけど、まぁコンパイラの state を再利用するには何をしなければならなくて、何が難しいか、みたいなことがざっとわかる感じでした。

まずは pre-compiled header なんかも似たような目的だねーとかそんなこと。

1章はとりあえず gcc --server とかいうオプションで起動すると既に動いてるサーバを使いますよ、と。

2章はサーバとクライアントのやりとりの仕方について。ちょっと面白いと思ったのはファイルが更新された時には、何者かがファイルが更新されたと伝えなければならない、ということ。 Xcode に組み込むつもりだったってことですかねえと思います。

3章は共通の初期化とかは一回でいいね、というような。

4章からが本番で、何を cache するか、って話を書いてます。 header file の中身を覚えておく、 preprocessor token を覚えておく、 preprocess した後の token を覚えておく、 parse した後の tree を覚えておく、というアプローチが比較されています。当然最後のが一番 performance 的にはおいしくて、この人らは最後のアプローチで頑張ってみた、とのこと。

5章はファイルの更新があった時にどこを読み直すか、っていう話。ファイル全部読み直す、 preprocess directive の間を読み直す、 #if とか #include の間だけを読み直す、各宣言の更新があったとこだけを読み直す、の4つのオプションが提示されています。現状では2番目のアプローチを採っていて、それは現状の GCC のコードを使いまわせるからで、ベストは3番目でないかと思ってる、とのこと。4番目は更新チェックのためのオーバヘッドがでかすぎるでしょう、とのこと。

6章は5章の実装の細かい話。

7章が色々問題がありました、って話。こんなコードがあったとして、

#if M1
typedef int word;
#else
typedef long word;
#endif;
extern word flags;

これが M1 が define されてる時とされてない時で2回読まれた場合、 extern word flags の tree は2回目も parse しなおさなければなりませんよ、という話。オーバヘッドとか考えてそれぞれの fragment (preprocessor directive の間) ごとに依存してる名前とかを記録しておいて、それが変わった場合は reparse する、って実装になってるとのこと。

これでも問題になるのが、

// in do_something.h
extern int do_somothing(struct x*);
// in another code
struct x;
// in do_something.h again
extern int do_somothing(struct x*);

みたいな感じで不完全型が後で宣言されてまた現れた場合だ、とか書いてあるんだけど、これはなんか実害無さそうに思えた。が、まぁ実際コンパイラのコードに詳しいと問題あるんじゃないかと思います。ああ少なくとも適切な warning とか error が吐けなくなるって問題はあるのかな。

8章。7章の話は型の名前とかだけが問題だったけど、マクロ考えると全ての識別子が問題になってきますね、という話。例えば、

// in something.h
int something;
// in another code
#define int long;
// in something.h again
int something;

とかいうのが出てくると、もう大変と。簡単な解は7章と同じく依存してる識別子を全部覚えておくことだけど、識別子はなにぶん多いので、オーバヘッドがデカかろうと。実装してないけど良さげな解決としては、 parse された全ての識別子に2bitのフラグを足して、マクロから展開されたものですよーとかそいう情報を残しておけば良かろう、と。

こうすればたぶんマクロで識別子の意味が変わりまくるようなケースは遅いけど、一般的なシーンでは早い感じになるんじゃないかなと言っています。

9章は言語ごとの frontend に入れないといけない変更について。

10章はコンパイラってやつは結構完成した tree をいじくることがあるから、そいう時に通知する一般的なフレームワークがあった方がいいだろう、って話。

例えば header に forward declaration があって、後で実際の定義があった場合に、その定義を読んだ時に元の tree を変更してしまうので、次に同じ header を読んだ時に実際の定義をまだ覚えてしまっている、と。解決するには undo みたいなことができるようになってないといけない。

11章。他にもある問題について。

enum {
  SI_ASYNCNL = -6,
#define SI_ASYNCNL SI_ASYNCNL
  SI_SIGIO,
#define SI_SIGIO SI_SIGIO
...

とかなってる時に、 #define SI_ASYNCNL と #define SI_SIGIO の間に変更があった場合に、その間は不完全な tree なので parser が混乱しますよ、という話。まあたぶんひとつ上の nest level まで読み直す必要があるでしょうね、と。

しかし

#ifdef __cplusplus
extern "{"
#endif

とか namespace とかででかくくくられてる時は結構困るね、と。

あとは #include => #include という順で include した場合と、逆順で include した場合でマクロの定義順が変わって再利用が全然できなくて遅くなるケースがあるとかなんとか。これはヘッダの方で解決しろとか投げ槍な感じで良い。

12章はまとめ。とりあえず3倍速くなったけど、未実装な機能も多いし最終的にどうしたらいいかわかってない問題もあるし、まぁうまいこといってもそこまでは速くならんかもわからんね、とずいぶん正直なことが書いてある好感の持てるまとめでした。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h