__sync_bool_compare_and_swap #とは
Cで計算結果をAtomicに代入したいとき、gccには__sync_bool_compare_and_swap
という組み込み命令が用意されている。この関数の存在意義については説明しない(ググって)。
__sync_bool_compare_and_swap(ptr, cmp, swp)
は第一引数で与えたポインタ(ptr
)の先がcmp
と等価なら、ptr
の先にswp
を代入する、という一連の動作をAtomicに行ってくれる。
実際はptr
の大きさに応じて、__sync_bool_compare_and_swap_2
, __sync_bool_compare_and_swap_4
, __sync_bool_compare_and_swap_8
などの命令の中からコンパイラがどれを使うかを判断している。
問題
ただ、8bytesの型にはlong longなどがあるので、8bytesまではサポートされているのだけれども、16bytesの型というのはあまりメジャーではない。ところが、どうしても16bytesのデータをAtomicに処理したい場合というのがあったりする(complex doubleとか)。
Mac付属のLLVM Clangだとデフォルトで__sync_bool_compare_and_swap_16
が用意されているのだが、Ubuntuのgccでこれをコンパイルしようとすると、Undefined Referenceが出てしまう。
対処
前置きが長くなったが、これをどうにかするには、gccに次のオプションを指定するだけで良い。
gcc -mcx16
一応、gnuのドキュメントも引用しておこう。
-mcx16
This option enables GCC to generate CMPXCHG16B instructions. CMPXCHG16B allows for atomic operations on 128-bit double quadword (or oword) data types. This is useful for high-resolution counters that can be updated by multiple processors (or cores). This instruction is generated as part of atomic built-in functions: see __sync Builtins or __atomic Builtins for details.
使用例
例えば、次のように使うことができる。
complex double tgt[1];
complex double tmp = hoge;
complex double new = fizz;
complex double diff = buzz;
while(!__sync_bool_compare_and_swap_16((__int128_t*)tgt, *((__int128_t*)&tmp), *((__int128_t*)&new))){tmp=*tgt; new=tmp+diff;}