ぐだぐだ低レベルプログラミング(88)ARM64(AArach64)、整数乗算命令その4

Joseph Halfmoon

今回でようやく整数乗算命令の「舐め終わり」です。前回積み残していた「符合付」の命令群です。前々回のようにデスティネーションがソースと等ビット幅であれば出る幕がないですが、前回のようにデスティネーションがソースの倍幅あると必須な奴ら。文字で書いていても何だかよくわからなくなりますが、これで計算はできるのだと。

※「ぐだぐだ低レベルプログラミング」投稿順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」のタイプです。mul_INSN_AliasS

上の表をご覧いただけるとわかるように、前回の符合なしの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 関数は、オリジナルの命令表記とエイリアス表記で等価な命令を並べてあります。以下は逆アセンブルした結果です。まったく同じ命令コードになっている(表記はエイリアス)ことがわかると思います。smulAlias

 

実験に使用した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

実行したところが以下に。smulResults

結局、符合付と符合無の差は上位のビットの部分ぞなもし。

ぐだぐだ低レベルプログラミング(87)ARM64(AArach64)、整数乗算命令その3 へ戻る

ぐだぐだ低レベルプログラミング(89)ARM64(AArach64)、除算命令 へ進む