先日、少し大きめのプログラムを新しいコンパイラでビルドを通すという作業を行った。 GCC 4.7.x でビルドしていたものを 4.8.x でもビルドできるようにするという作業で、以下に述べるような、お約束的な問題にいろいろはまった。
新しい警告がエラーを起こす
大抵のファイルは -Werror (警告をエラーとして扱う)でビルドしているので、-Wall (できるだけ警告を出す)がカバーする警告が増えると、そのままビルドエラーにつながる。新しい警告が増えるのは基本的には大歓迎で、警告が出たコードを修正すればいいのだが、サードパーティーのコードにパッチを当てるのは面倒だったりするので、そういう場合は -Wno-unused-variable みたいに、警告を除外するオプションを足してごまかす。
新しい警告の中でちょっとおもしろかったのは -Werror=narrowing が出してきたもので、 char s[] = {128} みたいなコードは narrowing conversion of '128' from 'int' to 'const char' inside { } is ill-formed in C++11 のような警告が出るようになった。
暗黙的な #include が壊れる
コンパイラに付属するヘッダファイルが新しくなると、#include で読み込まれる中身が微妙に変わって、これまでたまたま解決していた、型や関数の定義が見つからなくなったりする。こういうのは明示的に正しい #include を足せばいいだけなので直すのは簡単だ。
浮動小数点の計算誤差が微妙に増える
テストの中で、浮動小数点の計算結果を比較するコードがあり、元々ある程度の誤差を許すようになったいたのだが、新しいコンパイラではこの許容度を超えてしまった。といっても十分に小さいので、許容度を少し増やして解決した。
コンパイラの内部エラーを踏む
コンパイラのバグを踏んで、内部エラー(Internal compiler issue)でコンパイルが止まってしまった。私が踏んだバグは、最適化のパスの中のものなので、問題のファイルを -O2 ではなく -O0 でビルドするとエラーは消えた。もう少し調べてみると、 -fno-strict-aliasing というオプションを取り除いてもエラーが消えることはわかったが、-fno-strict-aliasing を必要とするコードもあるので、これは外せない。
困り果てて同僚に相談すると、この辺に volatile をいれて最適化を邪魔してみましょう、と言って、いれてみると、本当にエラーが消えた。長い経験を積むと、コンパイラの気持ちがわかるようになるようだ。
コンパイラがバグっているコードを出す
Internal compiler error は困りものだが、こちらはさらに厄介なバグだ。テストが一つ通らなくなったので、調べてみると、特定の関数が返す値がまるっきり違うものになっていた。この辺りのコードは GCC の特殊なアトリビュートを使って非常にややこしいことをやっていたので、いかにもはまりやすそうではあったが、理由があってそういうことをやっているので、そう簡単にやめるわけにはいかない。この部分は仕方がないので -O0 で最適化を止めてごまかした。
まとめ
以上のような問題をひとつづつ解決していくと、すべてのビルドとテストが通るようになり、難航したトンネル工事が完了したような晴れ晴れとした気分になった。
が、よく考えてみると、こういうことはUnixを使い始めた頃からずっとやっているわけで、 Mule とか Lynx とかをビルドしようと試行錯誤していた日々とやっていることはあまり変わらない気がする。三つ子の魂百までとはこういうことを言うんだなと思った。