今回でようやく整数乗算命令の「舐め終わり」です。前回積み残していた「符合付」の命令群です。前々回のようにデスティネーションがソースと等ビット幅であれば出る幕がないですが、前回のようにデスティネーションがソースの倍幅あると必須な奴ら。文字で書いていても何だかよくわからなくなりますが、これで計算はできるのだと。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※実習には以下を使用しております。
-
- Raspberry Pi 4 model B、Cortex-A72コア ARMv8-A
- Raspberry Pi OS (64bit) bullseye
- gcc (Debian 10.2.1-6) 10.2.1 20210110
ARMv8もいろいろレベルがあり、Arm Cortex-A72はARMv8の中でもベーシックな(命令数の少ない)ARMv8p0です。
符合付整数乗算命令
今回試してみるのは以下の表の赤枠で囲った命令です。勝手に名付けた「パターン2」と「パターン3」のタイプです。
上の表をご覧いただけるとわかるように、前回の符合なしのUから始まる命令群と『対』になっています。そしてU付とS付の差が現れるのは上位32ビットの部分であります。
実験用のアセンブリ言語ソース
手抜き(関数プロローグもエピローグもない)な、ほぼ1命令1関数スタイル。エイリアス命令については、元の命令表記と、エイリアス表記を2つ並べてあります。後でディスアセンブルして生成されたコードを観察します。
.globl smaddlX, smullX, smsublX, smneglX, smulhX .text .balign 4 smaddlX: smaddl x0, w1, w2, x0 ret smsublX: smsubl x0, w1, w2, x0 ret smullX: smaddl x0, w1, w2, xzr smull x0, w1, w2 ret smneglX: smsubl x0, w1, w2, xzr smnegl x0, w1, w2 ret smulhX: smulh x0, x1, x2 ret
上記の smullX 関数と smneglX 関数は、オリジナルの命令表記とエイリアス表記で等価な命令を並べてあります。以下は逆アセンブルした結果です。まったく同じ命令コードになっている(表記はエイリアス)ことがわかると思います。
実験に使用したC言語ソース
上記のアセンブリ言語関数を呼び出すテスト用のCソースが以下に。今回は符合付と符合無の差をみるために、前回使用した符合無のアセンブリ言語ソースともリンクして一部関数を比較用に使ってます。
#include <stdio.h> #include <stdint.h> extern int64_t smaddlX(int64_t, int32_t, int32_t); extern int64_t smsublX(int64_t, int32_t, int32_t); extern int64_t smullX(int64_t, int32_t, int32_t); extern int64_t smneglX(int64_t, int32_t, int32_t); extern uint64_t smulhX(int64_t, int64_t, int64_t); extern uint64_t umaddlX(uint64_t, uint32_t, uint32_t); extern uint64_t umsublX(uint64_t, uint32_t, uint32_t); extern uint64_t umullX(uint64_t, uint32_t, uint32_t); extern uint64_t umneglX(uint64_t, uint32_t, uint32_t); extern uint64_t umulhX(uint64_t, uint64_t, uint64_t); int main(void) { int64_t resultX; uint64_t uresultX; resultX = smaddlX(-1, -1, -1); printf ("smaddlX(-1, -1, -1): %ld\n", resultX); resultX = smsublX(-1, -1, -1); printf ("smsublX(-1, -1, -1): %ld\n", resultX); uresultX = umaddlX(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); printf ("umaddlX(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF): %16lx\n", uresultX); uresultX = umsublX(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); printf ("umsublX(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF): %16lx\n", uresultX); resultX = smullX(0, -1, 1); printf ("smullX(0, -1, 1): %ld\n", resultX); resultX = smneglX(0, -1, 1); printf ("smnegX(0, -1, 1): %ld\n", resultX); uresultX = smulhX(0, 4294967295, -2); printf ("smulhX(0, 4294967295, -2): %16lx\n", uresultX); uresultX = smullX(0, 4294967295, -2); printf ("smullX(0, 4294967295, -2): %16lx\n", uresultX); return 0; }
ビルドと実行
ビルドのコマンドラインが以下に。符合無関数を呼び出すために umul.sも一緒にしてます。
$ gcc -g -O0 smul.c smul.s umul.s
結局、符合付と符合無の差は上位のビットの部分ぞなもし。