WASMで負の数を得る命令ってどうやるんだろうか? と思って試しに絶対値を返す関数をC言語からコンパイルしてみたら、条件分岐のないイカしたビット操作のコードを出力してきた。
C言語で絶対値を得るコード:
|
を書いて、EmscriptenでWASMにコンパイルして出力結果を見てみると:
$ emcc -s WASM=1 -s NO_EXIT_RUNTIME=1 -O1 neg.c && wasm2wat a.out.wasm |
条件分岐がないコードを出力してくる。 一見何しているのかわからなかったので、符号付き4ビット整数で考えてみる:
- 引数が負の場合(例: 引数=変数0=
-5=1011b、ブラケット内は現在のスタック):shr_s [-5, -5, 31] (=> 1111b = -1): 算術右シフトで、符号ビットで埋めた値-1が得られるtee 1 [-5, -1]ローカル変数に格納で、スタックトップを変数1に格納(値は取り除かない)add [-5, -1] (=> 1010b = -6): 加算、引数-1xor [-6, -1] (=> 0101b = 5): 排他的論理和によって、全ビット反転- 要するに2の補数を取っている
- 正か0の場合(例: 引数=変数0=
5):shr_s [5, 5, 31] (=> 0):0tee 1 [-5, 0]: 変化なしadd [5, 0] (=> 5): 変化なしxor [5, 0] (=> 5): 変化なし- 引数の値がそのまま得られる
算術右シフトで負の場合は-1を、正または0の場合は0を得ることで、条件分岐をせずに済むようにしている。 出力されるコードが多少長くなってもそのほうがパフォーマンスがいいということなんですかね。
x86の場合
試しにMacOS/x86のgcc(Clang)でコンパイルしてみたらまたちょっと違って、negl で符号反転した値を作って cmovl という条件付きムーブ命令で選択するコードが出力された:
$ gcc -S -O1 -fno-asynchronous-unwind-tables neg.c && cat neg.s |
gcc/UbuntuではWASM版と同じような方法だった(xor を先に行って、add の代わりに sub)。