RISC-Vのとき基本整数命令セットを一当たりなめるのは大した回数でなかった記憶。しかしArmの64ビット命令セットA64は命令多いです。まだ整数乗算命令です。今回は前回やってない「パターン2」と「パターン3」ですが符合無のみ。符合付も一度にやるとちょっと疲れそうだったから。そういうところで回数多くなっているな、オイ。
※「ぐだぐだ低レベルプログラミング」投稿順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です。
今回実習の符合無整数乗算命令
前回は、ソースもデスティネーションもすべて同じビット幅の「パターン1」(当方で勝手に名付けてます)でした。ソースが32ビットなら32ビットの結果、ソースが64ビットなら64ビットの結果でした。今回は「パターン2」と「パターン3」を試してみます。前々回の説明で使った図を再掲します。
パターン2
パターン3
パターン2もパターン3も符合付、符合無あるのですが、符合付は「疲れる」ので今回は符合無のみです。ニーモニックでいうと以下の5つ。
-
- UMADDL
- UMULL
- UMSUBL
- UMNEGL
- UMULH
例によって赤字の2つはエイリアスなんでありますが。
実験用のアセンブリ言語ソース
例によって手抜き(関数プロローグもエピローグもない)な、ほぼ1命令1関数スタイルです。エイリアス命令については、元の命令表記と、エイリアス表記を2つ並べてあります。後でディスアセンブルして生成されたコードをみるため。
なお、umaddlとumsublは4オペランド命令ですが、簡単のため、デスティネーションと第3ソースは同じX0レジスタにしてあります。この方が「積和」な感じがするから?
.globl umaddlX, umullX, umsublX, umneglX, umulhX .text .balign 4 umaddlX: umaddl x0, w1, w2, x0 ret umsublX: umsubl x0, w1, w2, x0 ret umullX: umaddl x0, w1, w2, xzr umull x0, w1, w2 ret umneglX: umsubl x0, w1, w2, xzr umnegl x0, w1, w2 ret umulhX: umulh x0, x1, x2 ret
エイリアス命令使っている umullXとumneglX関数の逆アセンブル結果が以下です。上記のソースでは、xzrレジスタを第4オペランドに持つ命令が、エイリアスと解釈されて3オペランドに見えてるのがわかります。
実験に使用したC言語ソース
上記のアセンブリ言語関数を呼び出すテスト用のCソースが以下に。
#include <stdio.h> #include <stdint.h> 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) { uint32_t uresult; uint64_t uresultX; uresultX = umaddlX(0x100000000, 0x80000000, 16); printf ("umaddlX(0x100000000, 0x80000000, 16): %16lx\n", uresultX); uresultX = umsublX(0x800000000, 0x80000000, 16); printf ("umsublX(0x800000000, 0x80000000, 16): %16lx\n", uresultX); uresultX = umullX(0, 0x80000000, 16); printf ("umullX(0, 0x80000000, 16): %16lx\n", uresultX); uresultX = umneglX(0, 0x80000000, 16); printf ("umnegX(0, 0x80000000, 16): %16lx\n", uresultX); uresultX = umulhX(0, 0x00000000FFFFFFFE, 0x00000000FFFFFFFE); printf ("umulhX(0, 0x00000000FFFFFFFE, 0x00000000FFFFFFFE): %16lx\n", uresultX); uresultX = umullX(0, 0xFFFFFFFE, 0xFFFFFFFE); printf ("umullX(0, 0xFFFFFFFE, 0xFFFFFFFE): %16lx\n", uresultX); return 0; }
ビルドと実行
ビルドのコマンドラインが以下に。
gcc -g -O0 umul.c umul.s
実行したところが以下に。
符合無の16進表記だと、桁が多くて目が回りそうですが、あっているハズ。ホントか?