OpenFastLadderをインストールしようとしたらCFLAGSを書き直してJSON gemにpull requestを送ることになった

OpenFastLadderをダウンロードしてbundle installしてたらjson 1.7.7のインストールでエラーが出たので原因を調べた。

エラーメッセージは

Building native extensions.  This could take a while...
ERROR:  Error installing json:
    ERROR: Failed to build gem native extension.

        /usr/bin/ruby19 extconf.rb
creating Makefile

make
compiling generator.c
cc1: エラー: ‘-O’ への引数は非負整数であるべきです
make: *** [generator.o] エラー 1


Gem files will remain installed in /usr/local/lib/ruby/gems/1.9.1/gems/json-1.7.7 for inspection.
Results logged to /usr/local/lib/ruby/gems/1.9.1/gems/json-1.7.7/ext/json/ext/generator/gem_make.out

というもの。エラーメッセージでそのままググってみたけど似た事例が出てこない。

/usr/local/lib/ruby/gems/1.9.1/gems/json-1.7.7/ext/json/ext/generator/Makefileを見たらCFLAGSに-O3fastというオプションがあった。これがエラーの原因らしい。Makefileは同じディレクトリのextconf.rbというファイルで生成されているようだ。見てみると

unless $CFLAGS.gsub!(/ -O[\dsz]?/, ' -O3')
  $CFLAGS << ' -O3'
end

という処理があった。ここで$CFLAGSの-Ofast-O3fastに置換されている。

改めてgccのマニュアルを読んでみると、-Ofast-O3を有効にしたうえでさらにいくつかの最適化を有効にするというものだった。Gentooのドキュメント(CFLAGS - Gentoo Linux Wiki, Gentoo Linux ドキュメント -- コンパイル最適化ガイド)を読むと、-O3はgcc 4.xでは非推奨だし、-Ofastで有効になる-ffast-mathも、実装がIEEEやISOの数学関数の仕様を順守していることを前提としたコードでは不正な結果を出力する可能性があるので-Oオプションでは無効にされているとのこと。深く考えずに、なんか早くなりそうだからCFLAGSに入れてしまったが、こちらも軽い気持ちで手を出してはいけない雰囲気のオプションだった。

とりあえず/etc/make.conf-Ofastを外して-Osに書き換えたが、またしても同じエラーが出る。extconf.rb内の$CFLAGSが書き換わっていない。$CFLAGSを提供しているmkmfのソースを確認すると、$CFLAGSはrbconfigというライブラリから取得していて、これはRuby インタプリタ作成時に設定された情報を格納したライブラリなのだそうだ。rubyを入れ直したら$CFLAGSに変更が反映され、JSON gemのインストールに成功した。

せっかくなのでJSONのextconf.rbを-Ofastに対応させてpull requestを送ることにした。-Oオプションは最後に指定されたものが有効になるので、gsub!を使って置換したり条件分岐したりするのはやめて、何も考えずに-O3を付け足すという処理にした。$CFLAGS-Oオプションで始まってると正常に置換できないという問題もついでに解決できるし、今後gccの-Oオプションが追加されてもたぶん問題ない。

もうひとつ気になったのがgenerator/extconf.rb

  unless $DEBUG && !$CFLAGS.gsub!(/ -O[\dsz]?/, ' -O0 -ggdb')
    $CFLAGS << ' -O0 -ggdb'
  end

というコード。$DEBUGがfalseのときにデバッグ用っぽいオプションが追加されている。parser/extconf.rbのほうはif $DEBUG && !$CFLAGS.gsub!(/ -O[\dsz]?/, ' -O0 -ggdb')になっている。generatorのほうが条件式を間違えてるようだ。ついでにこれも修正してpull requestを送ってみた

それにしても、gentooのドキュメントではプラス面よりもマイナス面が勝っていますとまで書かれている-O3をJSON gemが有効にしてるのは不思議に思える。日頃からソフトのインストールはほとんどパッケージマネージャーに頼りきりで、自分ではMakefileなどを触ったりしないので事情がよく分からない。rubyのソースを取ってきてautoconf;./configureして生成されたMakefileを見てみると

CFLAGS = ${cflags} $(ARCH_FLAG)
cflags =  ${optflags} ${debugflags} ${warnflags}
optflags = -O3 -fno-fast-math

こうなっていた。configureのほうには

if test "$GCC" = yes; then
    linker_flag=-Wl,
    : ${optflags=-O3}

こんな記述がある。optflagsのデフォルト値を-O3にしているらしい。本体が-O3を使っている以上、ライブラリが使ってはいけない道理はないが、実際のところどれほど効き目があるのだろうか。