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)
: 加算、引数-1
xor [-6, -1] (=> 0101b = 5)
: 排他的論理和によって、全ビット反転- 要するに2の補数を取っている
- 正か0の場合(例: 引数=変数0=
5
):shr_s [5, 5, 31] (=> 0)
:0
tee 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
)。