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

Joseph Halfmoon

前回までの整数乗算命令「いろいろ」あって全4回を要しました。今回は整数除算命令ですが、この1回で完了です。乗算のような組み合わせの多さもエイリアスもなくシンプル。シンプル・イズ・ベスト?シンプル・ライフ?(古い)でも除算はいいけれど剰余算はどうよ?というと「すでにやっていた」事実が発覚。なんてこったい。

※「ぐだぐだ低レベルプログラミング」投稿順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つの組合わせしかありません

    • 符合無のudiv、符合付のsdiv
    • 32ビットオペランドか、64ビットオペランドか

ソース1をソース2で割った結果をデスティネーションレジスタに格納すると。ソースもデスティネーションも等ビット幅です。

割り算命令というと、多くのCISCでは商と剰余を一度に求める命令が多いです。するとデスティネーションが2つ必要なので転送パターンが特殊になり(実装にちょいと)苦労するもんです。しかしA64の場合、商を求めることしかしないのでシンプル。でも、余りが必要なこともあるじゃん、と。RISC-Vでは、商を求める除算命令以外に、余りを求める剰余算命令がありました。しかし、A64では、剰余算に使う命令は既にやってるじゃん、と言われます。乗算で出てきたMSUB命令です。

MSUB: DST = SRC3 – (SRC1 * SRC2)

剰余 = 被除数 – (商 * 除数)

ほらね。被除数と除数で商を求めた後で、MSUBすればよろしい、と。

他にも割り切りっている部分あり。割り算というと「0で割ったらどうなるの?」というのが永遠について回る質問なのですが、A64の整数除算の場合、結果はゼロです。そしてゼロ割り算の例外処置も無しだと。まあ多くのプロセッサがゼロ割り算の例外処置のために苦労していることを考えると止めてしまうのもありかと思います。割り算する前に除数が0じゃないよね、と確認するだけで良いのだし。

同様に割り算後のオーバフローもほったらかしです(2の補数だと負で表せる数の範囲は正の範囲より1大きいので、そういうことを簡単に起こせますがチェックも簡単。)

こと整数除算にかんしては、Armにしては珍しい割り切りような感じがします。

実験用のアセンブリ言語ソース

いつもの手抜き(関数プロローグもエピローグもない)な、ほぼ1命令1関数スタイルです。組み合わせが少ないのでお楽。

.globl	udivW, udivX, sdivW, sdivX
.text
.balign	4

udivW:
    udiv w0, w1, w2
    ret

udivX:
    udiv x0, x1, x2
    ret

sdivW:
    sdiv w0, w1, w2
    ret

sdivX:
    sdiv x0, x1, x2
    ret
実験に使用したC言語ソース

上記のアセンブリ言語関数を呼び出すテスト用のCソースが以下に。最初に符合なしを触って、次に符合付を計算してます。符合付の途中に同じオペランドの符合無を挿入して、符合付と符合無で「結果が違うよね」というところを「ことさらに」確認してます。

#include <stdio.h>
#include <stdint.h>

extern uint32_t udivW(uint32_t, uint32_t, uint32_t);
extern uint64_t udivX(uint64_t, uint64_t, uint64_t);
extern int32_t  sdivW(int32_t, int32_t, int32_t);
extern int64_t  sdivX(int64_t, int64_t, int64_t);

int main(void)
{
    uint32_t uresult;
    uint64_t uresultX;
    int32_t result;
    int64_t resultX;

    uresult= udivW(0, 0x10E, 0xE);
    printf ("udivW(0, 0x10E, 0xE): 0x%08x\n", uresult);
    uresultX=udivX(0, 0x1FFFFFFFF, 0xFFFFFFFF);
    printf ("udivX(0, 0x1FFFFFFFF, 0xFFFFFFFF): 0x%016lx\n", uresultX);

    result = sdivW(0, 1, -1);
    printf ("sdivW(0, 1, -1): %d\n", result);
    uresult= udivW(0, 1, 0xFFFFFFFF);
    printf ("udivW(0, 1, 0xFFFFFFFF: 0x%08x\n", uresult);
    resultX= sdivX(0, 1, -1);
    printf ("sdivX(0, 1, -1): %ld\n", resultX);

    return 0;
}
ビルドと実行

以下はビルドして実行の画面キャプチャです。

BuildAndResults

さらっと、1回なでるだけなら簡単。組み合わせが少ないのは楽、絶対。

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

ぐだぐだ低レベルプログラミング(90) ARM64(AArarh64)、ロードストア命令その1 へ進む