2013-03-24

GCC 4.8でぶっ壊れるSPECのお粗末なコード

本の虫: GCC 4.8のリリースノートとC++関連の変更で、GCC 4.8は464.h264ref: SPEC CPU2006 Benchmarkを壊してしまう。これはSPECベンチマークの規格違反によるものであると書いた。では、具体的に何なのか。それを解説している記事を発見したのでかいつまんで紹介。

Embedded in Academia : GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks
Regehr: GCC 4.8 Breaks Broken SPEC 2006 Benchmarks [LWN.net]

どうやら、SPECベンチマークは以下のようなコードを含むそうだ。

int d[16];
 
int SATD (void)
{
  int satd = 0, dd, k;
  for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
  }
  return satd;
}

問題は、for文のd[++k]だ。このfor文では、k==15のときにdd=d[16]が実行されてしまう。dの型はint [16]であり、添字として有効なのは0から15までの間である。d[16]は未定義動作である。

さて、GCCはどう考えるかというと、まずd[++k]をみて、kの取りうる範囲は-1から14までであると判断する。なぜならば、dに対する0未満16以上の添字は違法だから、d[++k]として使われているkの範囲は当然、合法な範囲まで狭めることができる。違法なコードは存在しないという立場にたてば、この判断は正しい。とすると、ループの終了条件であるk<16は、常に真である。なぜならば、kの取りうる範囲は、解析結果から、決して16以上にはならない。ループの終了条件が真ということは、これは無限ループだと判断できる。どうせ常に真になる式で無限ループなら、真におきかえてしまえばいいのだ。わざわざk<16というコードを馬鹿正直に実行して、比較と条件分岐をするには及ばない。いざ無限ループとしてコードを生成しよう。

と、こうした理由で、for文が無限ループになり、結果として規格違反のコードが壊れる。これはお粗末なSPECベンチマークのコードのせいである。

ちなみに、静的解析と最適化オプションは結びついているらしく、GCCは-O2ではこのコードに対してコンパイル時警告を出さないが、-O3ではコンパイル時警告を出すそうだ。最適化オプションが高くないとコンパイル時警告を出さないというのは、コーディング中やデバッグ中には最適化オプションを高くしない(あるいはできない)という慣習から、気が付きにくいのではないか。

思うに、for文のオペランドにやたらに複雑な式を書きたがるC言語の慣習が悪い。

FAQ CPU2006

SPEC側は愚かにも、これはC99違反であるがC89には違反していない、故にコードは修正しないと宣言。自分たちは問題の本質を理解できないと堂々と宣言している。

このコードの挙動はC89だろうと未定義である。ということは、SPECベンチマークとは、未定義動作の実装ごとの比較を目的としたソフトウェアなのだろう。

4 comments:

Anonymous said...

SPECが「C99では未定義かもしれないがC89では合法だ」とか大嘘こいてるのに笑った。C89でも未定義に決まってるだろ(すでにGCC Bugizillaでも突っ込まれているが)。
「C99では未定義だ」は「C89では未定義でない」ということを意味しないなんて初歩的なことすらわからないほどSPECの開発者って馬鹿なのか

江添亮 said...

その話も付け加えようと思ったのですが、SPEC側の声明が発見できなかっため書いていませんでした。
言われてBugzillaをみたら、SPECによる公式な一次ソースを発見できたので追加。

Anonymous said...

低い最適化レベルでも警告出すには、その為だけに構文解析を高度なレベルでしないといけない訳で、コンパイラ屋さん的にはやりにくいのかも。
特にC++はビルドが割と遅いし。
バイナリの最適化と別にオプションが有ればいいのかも。

エヌユル said...

あまり関係ないけど,abs(x)使ったり,for文にごちゃごちゃ入れないだけで相当可読性高くなるのに…
まあ平然と未定義動作書くようなプログラマに可読性求めても無駄か…