動かないのは g++ -O3 最適化のバグのせいだったというオチ
昨日 bison と re2c で書いた数値パーサが OSX では動くが Ubuntu では動かないという現象に遭遇。(コード壊れた)
ずっと調べていたところ bison が生成したコードの中の
/* Discard the shifted token unless it is eof. */ if (yychar != YYEOF) yychar = YYEMPTY; yystate = yyn; // ★ここ *++yyvsp = yylval; goto yynewstate;
の変数代入が g++ -O3 の最適化で消えているのが原因なことが分かった。(bisonの吐くコードは goto が多いので、解析を誤って不要な代入としてしまったのだろうな。)
bison のパーサ用の状態保持スタックの中身が壊れていることからたどり着いたがこれは中々気づきにくいバグだなあ。
自分のバグ取り大原則は「バグが出てもコンパイラやOSを疑うな。今までの経験上ほぼ100%、自分の書いたコードが原因なのだから」というものだったが、考えを改めよう。
g++ (GCC) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2) bison (GNU Bison) 2.3
以下調査ログ。
OSX では動いていた数値の read が Ubuntu では動かない件。
- bison のバージョンは同じだろうか。
- バージョンをあわせても結果は同じ。
- 最小限の再現方法は #b1 とだけ書いたファイルを ./mosh で実行する事。
- OSX と Ubuntu の違いを調べても埒が開かないので Ubuntu でなぜ動かないかを調べる。
- エラーとなっている部分を具体的に
- number_yyparse() が 1 を返している
- エラーの内容は syntax エラー。
- 場所は #b1 の 1 を読み込んだ直後あたり。
- number_yyparse はエラーまでにどういう動きをしているか?
- exactness が /* empty * / にマッチするところまでは動いている
- 後続の radix_2 にマッチする部分が動いていない
- OSX で同じコードでログを吐かせるとやはり動く。
- ふむ。せっかく動く環境とそうでない環境があるから、ログをとって差分を見ていくか。
- scanner は token を正しく返しているのだろうか?
- TOKEN:DIGIT_2 は OK 。その後の TOKEN:END_OF_FILE には到達していない
- fill 時にバッファに正しいものが格納されているだろうか?
- OSXと同じ動き。
- NumberReader.tab.cpp のどこでエラーになっているのか?
yydebug = 1
#define YYDEBUG 1
した。
after cursor=<> TOKEN:DIGIT_2=1 exactness empty -> $$ = nterm exactness () Stack now 0 -16393 Entering state 8 Next token is token DIGIT_2 () syntax errorError: popping nterm exactness () Stack now 0 -16393 Error: popping token MY_NAN ()
stack がおかしいよ。なぜ?
うーん。経験的にはこれは GC の予感。
ちょっと見てみたが怪しいところがない。しょうがないので NumberReader.tab.cpp あれこれいじっていたら。
printf("yyss=%x yyssp=%x %s %s:%d\n", yyss, yyssp, __func__, __FILE__, __LINE__);fflush(stdout);
を特定の場所に書くとエラーが起きなくなる。どうみてもメモリ破壊バグ。うーむ。
yyssp が怪しいっぽいので見ていくか。
なるほどparse用のスタックがネイティブスタックに確保されていると。
ここがおかしくなるってことは yyparse から呼び出した関数でスタック破壊をしているとかそういう話になるのか。
調べたら違った。むしろスタックの中身が正しい値に設定されないのが問題。
状態遷移を追っていくと、どうも goto で別のところに jump する直前におかしいっぽい。まさかとは思うが -S でアセンブリを見てみる。
ビンゴ。どう見ても state の代入文がありません。試しに yystate の宣言に volatile つけたら動く。-O2 でも動く。