2015年07月15日
64bit Ubuntu上でshターゲットのWindows用gccをカナディアンクロスビルドすると発生する不具合
不具合原因の詳細や、根本的な対策まではわかっていませんが、とりあえず現象と回避策を整理がてら記しておきます。
- 現象
Ubuntu 14.04/15.04 64bit (x86_64)上で Windows で動作する sh-*-elf ターゲットのクロス gcc を、Ubuntu の MinGW ツールチェーンでカナディアンクロスビルドすると、その gcc で特定パターンをコンパイルした時に、コンパイラ内部エラー(ICE)が発生する不具合がある。
gcc 4.8.4/4.8.5/4.9.2/4.9.3 で確認。32bit (x86) exe でも 64bit (x64) exe でも同じ結果。4.9.x が特に起こりやすく、libgcc の fp-bit.c コンパイル時に ICE が発生する。(後述するが、再現コードも作成できた。)4.8.x でも locale-inst.cc に -O2 以上の最適化で発生する。これらは Linux 上でネイティブ動作する sh のクロス gcc では発生しない。ソースや依存ライブラリ、configure 時のオプション等は、host の設定以外は全て Windows 向けと同じ。(また、Ubuntu 14.04 と 15.04 ではネイティブ、MinGW 共に gcc のバージョンが異なるが、あまり関係無いようだ。)
- 現状の回避策
32bit の Ubuntu (i386)上でカナディアンクロスビルドする。
- 原因の予想
おそらく gcc のカナディアンクロスビルドシステムにバグがある。特に、64bit Linux のネイティブ gcc は long が 64bit なのだが、MinGW gcc は 32bit でも 64bit でも long が 32bit (LLP64 モデル)なので、(gcc が LP64 モデルの Linux 上で使われてきた歴史を考えても)そのようなマイナーなパターンの対策は不十分な可能性が高い。また、sh のバックエンドの sh.md ファイルの記述のしかたにも問題があると思われる。具体的には、Tbit 操作のために 0x80000000 という定数が必要なようなのだが、md ファイル内では 10 進符号付整数しか記述できないようで、(const_int -2147483648) という、32bit の INT_MAX(2147483647)を超える即値が記述されている ※1。さらにややこしいことに、md ファイルは、genrecog という gcc の内部ツールにより、insn-recog.c に変換されてビルドされるのだが、この genrecog はビルド途中でネイティブ gcc でビルドされて作られ、一方 insn-recog.c は MinGW gcc でビルドされるのだが、その際の long の長さの違いが考慮されていない可能性が高い。
※1 C コンパイラは、通常はトークン単位で処理を行うため、仮に - が付いていても、- と 2147483648 は独立して処理されるので、問題が起こることがある。そのため INT_MIN は、通常は (-INT_MAX-1) のように定義されることが多く、MinGW gcc の limits.h もそうなっている。
詳細は以下になります。
- 現象
Ubuntu 14.04/15.04 64bit (x86_64)上で Windows で動作する sh-*-elf ターゲットのクロス gcc を、Ubuntu の MinGW ツールチェーンでカナディアンクロスビルドすると、その gcc で特定パターンをコンパイルした時に、コンパイラ内部エラー(ICE)が発生する不具合がある。
gcc 4.8.4/4.8.5/4.9.2/4.9.3 で確認。32bit (x86) exe でも 64bit (x64) exe でも同じ結果。4.9.x が特に起こりやすく、libgcc の fp-bit.c コンパイル時に ICE が発生する。(後述するが、再現コードも作成できた。)4.8.x でも locale-inst.cc に -O2 以上の最適化で発生する。これらは Linux 上でネイティブ動作する sh のクロス gcc では発生しない。ソースや依存ライブラリ、configure 時のオプション等は、host の設定以外は全て Windows 向けと同じ。(また、Ubuntu 14.04 と 15.04 ではネイティブ、MinGW 共に gcc のバージョンが異なるが、あまり関係無いようだ。)
- 現状の回避策
32bit の Ubuntu (i386)上でカナディアンクロスビルドする。
- 原因の予想
おそらく gcc のカナディアンクロスビルドシステムにバグがある。特に、64bit Linux のネイティブ gcc は long が 64bit なのだが、MinGW gcc は 32bit でも 64bit でも long が 32bit (LLP64 モデル)なので、(gcc が LP64 モデルの Linux 上で使われてきた歴史を考えても)そのようなマイナーなパターンの対策は不十分な可能性が高い。また、sh のバックエンドの sh.md ファイルの記述のしかたにも問題があると思われる。具体的には、Tbit 操作のために 0x80000000 という定数が必要なようなのだが、md ファイル内では 10 進符号付整数しか記述できないようで、(const_int -2147483648) という、32bit の INT_MAX(2147483647)を超える即値が記述されている ※1。さらにややこしいことに、md ファイルは、genrecog という gcc の内部ツールにより、insn-recog.c に変換されてビルドされるのだが、この genrecog はビルド途中でネイティブ gcc でビルドされて作られ、一方 insn-recog.c は MinGW gcc でビルドされるのだが、その際の long の長さの違いが考慮されていない可能性が高い。
※1 C コンパイラは、通常はトークン単位で処理を行うため、仮に - が付いていても、- と 2147483648 は独立して処理されるので、問題が起こることがある。そのため INT_MIN は、通常は (-INT_MAX-1) のように定義されることが多く、MinGW gcc の limits.h もそうなっている。
詳細は以下になります。
具体的な不具合としては、sh-*-elf gcc 4.9.3 で、最適化 -O2 をかけると、以下のような ICE が出ます。(この gcc には KMC の exeGCC 向けのパッチが当たっているので、裏で -m1 -mb などのオプションを GCCSW 環境変数経由で渡していて、sh1 向けのオブジェクトファイルを生成していますが、おそらく他のターゲットでも同様と思われます。ただし、なぜか sh2a では問題は起きないようです。)
ただし、(const_int XXX) の型は、HOST_WIDE_INT という型になり、これは 64bit 整数になるので、32bit の INT_MAX や INT_MIN の保持自体には何の問題もありません。
この sh.md ファイルは、genrecog というツールで、insn-recog.c という C ファイルに変換された後、gcc の一部としてビルドされます。
おそらくここに問題があります。gcc の configure 時に、build machine と host が異なっている場合、host 向けに auto-host.h というヘッダ、build machine 向けに auto-build.h というヘッダがそれぞれ生成されるのですが。Ubuntu 64bit では long は 64bit なので、HOST_WIDE_INT は long になります。一方 MinGW gcc では long は 32bit (これは 64bit でも同じ) なので、HOST_WIDE_INT は long long になります。
おそらく、この、genrecog をビルドする gcc と、生成された C ファイルをビルドする gcc の HOST_WIDE_INT が異なる事が、問題になっているのだと思われます。
具体的には、genrecog は HOST_WIDE_INT を long だと思っているので、(const_int -2147483648) を、そのまま long の定数として処理し、-2147483648L という C コードを生成します(L が 1 つ)。しかし、この C コードをコンパイルする gcc では、long は 32bit なので、32bit の INT_MAX を超える整数が記述されていると問題になります。本来、genrecog は、コンパイルする gcc に合わせて、-2147483648LL と、HOST_WIDE_INT を long long 扱いして C コードを生成しなければならないはずです(L が 2 つ)。実際に生成された insn-recog.c の diff を取ってみると、この L と LL の違い以外は完全一致しました。
非常にややこしいですが、これが現状、私が理解している範囲での問題の解説となります。
根本的な解決策は、引き続き調査中ですが、おそらく非常に複雑な gcc のビルドシステムを修正することとなり、簡単では無さそうです。このパターンのカナディアンクロス自体があまり使われているとは思えませんし、おそらく sh ターゲット固有の問題なので、(既にディスコンとなっている CPU ということもあり)そのうち誰かが直してくれる、ということもあまり期待できません…。
とりあえず、HOST_WIDE_INT が long long として扱われる環境、Ubuntu 32bit 上(と、未確認ですが、おそらく Window 上でのネイティブビルド)でならば、MinGW 32bit も 64bit も問題無くビルドできるような感じなので、少なくとも sh の Windows 向け gcc をカナディアンクロスビルドする場合は、注意した方が良いと思われます。
> set GCCSW GCCSW= -m1 -mb -DENDIAN_BIG -D__MSL_NO_IO_SUPPORT__ -fleading-underscore -DFAST -mieee --no-sysroot-suffix -D__sh1__ > cat test.c int f(int x) { return x ? (-((int) ((~(unsigned int)0) >> 1)))-1 : ((int) ((~(unsigned int)0) >> 1)); } > gcc -c -O2 test.c test.c: In function 'f': test.c:4:1: error: unrecognizable insn: } ^ (insn 32 29 33 2 (set (reg:SI 167) (const_int 2147483648 [0x80000000])) test.c:3 -1 (expr_list:REG_EQUIV (const_int 2147483648 [0x80000000]) (nil))) test.c:4:1: internal compiler error: in extract_insn, at recog.c:2202 Please submit a full bug report, with preprocessed source if appropriate.-fdump-rtl オプションを使って、途中経過を全部ダンプして diff を取ってみると、Linux ホストの sh gcc と MinGW ホストの sh gcc では、以下のような差分が出ました。(上が MinGW 32bit、下が Ubuntu 64bit)
(note 29 28 30 2 NOTE_INSN_DELETED) (insn 30 29 17 2 (parallel [ (set (reg:SI 160 [ D.1371 ]) - (minus:SI (const_int 2147483648 [0x80000000]) + (minus:SI (const_int -2147483648 [0xffffffff80000000]) (reg:SI 147 t))) (clobber (reg:SI 147 t)) ]) test.c:3 400 {*mov_t_msb_neg}INT_MAX 境界の定数が出てきているので、いかにも怪しい感じです。gcc-4.9.3/gcc/config/sh/sh.md を見てみると、Tbit を操作するための 0x80000000 という定数を作るために (const_int -2147483648) という記述が何か所か使用されていて(md ファイルは 10 進符号付整数しか書けない模様)、非常にクサい。
ただし、(const_int XXX) の型は、HOST_WIDE_INT という型になり、これは 64bit 整数になるので、32bit の INT_MAX や INT_MIN の保持自体には何の問題もありません。
この sh.md ファイルは、genrecog というツールで、insn-recog.c という C ファイルに変換された後、gcc の一部としてビルドされます。
おそらくここに問題があります。gcc の configure 時に、build machine と host が異なっている場合、host 向けに auto-host.h というヘッダ、build machine 向けに auto-build.h というヘッダがそれぞれ生成されるのですが。Ubuntu 64bit では long は 64bit なので、HOST_WIDE_INT は long になります。一方 MinGW gcc では long は 32bit (これは 64bit でも同じ) なので、HOST_WIDE_INT は long long になります。
おそらく、この、genrecog をビルドする gcc と、生成された C ファイルをビルドする gcc の HOST_WIDE_INT が異なる事が、問題になっているのだと思われます。
具体的には、genrecog は HOST_WIDE_INT を long だと思っているので、(const_int -2147483648) を、そのまま long の定数として処理し、-2147483648L という C コードを生成します(L が 1 つ)。しかし、この C コードをコンパイルする gcc では、long は 32bit なので、32bit の INT_MAX を超える整数が記述されていると問題になります。本来、genrecog は、コンパイルする gcc に合わせて、-2147483648LL と、HOST_WIDE_INT を long long 扱いして C コードを生成しなければならないはずです(L が 2 つ)。実際に生成された insn-recog.c の diff を取ってみると、この L と LL の違い以外は完全一致しました。
非常にややこしいですが、これが現状、私が理解している範囲での問題の解説となります。
根本的な解決策は、引き続き調査中ですが、おそらく非常に複雑な gcc のビルドシステムを修正することとなり、簡単では無さそうです。このパターンのカナディアンクロス自体があまり使われているとは思えませんし、おそらく sh ターゲット固有の問題なので、(既にディスコンとなっている CPU ということもあり)そのうち誰かが直してくれる、ということもあまり期待できません…。
とりあえず、HOST_WIDE_INT が long long として扱われる環境、Ubuntu 32bit 上(と、未確認ですが、おそらく Window 上でのネイティブビルド)でならば、MinGW 32bit も 64bit も問題無くビルドできるような感じなので、少なくとも sh の Windows 向け gcc をカナディアンクロスビルドする場合は、注意した方が良いと思われます。