glibc-2.10.1 をビルドしようとしたら、 「__sync_bool_compare_and_swap_4 が定義されていない」 というエラーが出た:
senri:/usr/local/src/glibc-2.10.1.i386 % ../glibc-2.10.1/configure ... senri:/usr/local/src/glibc-2.10.1.i386 % make ... /usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `__libc_fork': /usr/local/src/glibc-2.10.1/posix/../nptl/sysdeps/unix/sysv/linux/i386/../fork.c:79: undefined reference to `__sync_bool_compare_and_swap_4' /usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `__nscd_drop_map_ref': /usr/local/src/glibc-2.10.1/nscd/nscd-client.h:320: undefined reference to `__sync_fetch_and_add_4' ... /usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `*__GI___libc_freeres': /usr/local/src/glibc-2.10.1/malloc/set-freeres.c:39: undefined reference to `__sync_bool_compare_and_swap_4' collect2: ld returned 1 exit status make[1]: *** [/usr/local/src/glibc-2.10.1.i386/libc.so] Error 1 make[1]: Leaving directory `/usr/local/src/glibc-2.10.1' make: *** [all] Error 2
__sync_bool_compare_and_swap_4 は gcc の組み込み関数なので、 関数が未定義であることを示す 「undefined reference to ...」 というエラーメッセージは、 誤解を招く不親切なメッセージだと思う。
__sync_bool_compare_and_swap_4(mem, oldval, newval) は、 mem が指し示すメモリの値 (4バイト分) が oldval であれば newval に変更する、 という操作をアトミックに行なう組み込み関数。 アトミック (不可分) 操作とは、 操作の途中が存在してはいけない操作のことで、 この例なら比較 (メモリの値が oldval か?) と代入 (newval に変更) が必ず 「いっぺん」 に行なわれ、 「比較だけ行なったけどまだ代入は行なわれていない」 という状態が存在しないことを意味する。
アトミックに行なうためには、 当然ながら CPU でその操作をサポートしている必要がある (複数個の命令の列で実現しようとすると、 命令列の半ばを実行中の状態が必ず存在してしまう) わけだが、 残念ながら Intel 386 プロセッサでは、 この compare_and_swap (CMPXCHG 命令) をサポートしておらず、 サポートするのは Intel 486 以降の CPU である。 テストプログラムを書いて試してみる:
#include <stdio.h> int main() { int mem[1], oldval, newval; oldval=0; newval=1; mem[0] = 0; __sync_bool_compare_and_swap(mem, oldval, newval); printf("mem[0]=%d\n", mem[0]); return 0; }
見ての通り、 mem[0] の値を oldval の値 (0) と比較し、 一致していたら newval の値 (1) を代入し、 mem[0] の値を表示するだけのプログラムである。
関数名が 「__sync_bool_compare_and_swap」 であって、 後ろに 「_4」 がついていないことに注意。 gcc が引数の型 (この例では int) を見て、 その型のビット長を後ろにつけてくれる (この例では int 型は 4 バイトなので 「_4」 をつけてくれる)。
gcc では 「-march=タイプ」 オプションを指定することによって CPU タイプを指定できる。 -march オプションを指定しなかったり (この場合は全 CPU でサポートされる組み込み関数のみ利用できる)、 あるいは -march=i386 を指定したりすると、 コンパイル時にエラーになる:
% gcc -Wall test.c /tmp/cc4eNX6L.o: In function `main': test.c:(.text+0x3b): undefined reference to `__sync_bool_compare_and_swap_4' collect2: ld returned 1 exit status % gcc -Wall -march=i386 test.c /tmp/cc6chtFj.o: In function `main': test.c:(.text+0x36): undefined reference to `__sync_bool_compare_and_swap_4' collect2: ld returned 1 exit status % gcc -Wall -march=i486 test.c % ./a.out mem[0]=1
いまさら i486 というのもアレなので、 今なら i686 を指定するのがよさげ。 私の手元にはいまだ PentiumIII マシンがあるものの、 PentiumIII より古いマシンはない (昨年 ML115 と SC440 を買ったとき PentiumII マシンを引退させた) ので、 pentium3 を指定すれば SSE (Streaming SIMD Extensions) が利用できるようになるが、 glibc をビルドするときに必要かというと、 たぶん必要ない。
というわけでエラーの原因は分かったが、 では glibc をビルドするときは、 どうすればいいだろうか?
とりあえず google で検索してみたら、 gcc の configure オプションに 「--with-arch=i686」 を指定して gcc をビルドする必要がある、 と書いてあるページが見つかった。
--with-arch オプションは、 -march のデフォルトを設定するための configure オプションである。 つまり 「--with-arch=i686」 を指定して gcc を再インストールすると、 gcc に -march オプションをつけなくてもデフォルトが i686 になる。 なるほど確かにそうすれば、 glibc 側で何も変更せずに __sync_bool_compare_and_swap_4 関数が使えるようになりそうである。
いまどき i686 以前の CPU 用のコードが必要になりそうなケースは滅多にないだろうから、 -march オプションのデフォルトを i686 にするのも悪い選択ではないように思えた。 gcc をビルドし直すのは面倒だなーと思いつつも、 ついでに gcc のバージョンを上げておこうと gcc-4.3.4 をダウンロードしてきて 「--with-arch=i686」 付でビルドしてみた。
ところが!
More...