ぐだぐだ低レベルプログラミング(78)ARM64(AArch64)、シフト、ローテイトの片割

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
シフト、ローテイト命令

アセンブラを書くという観点から言えば、シフト、ローテイト命令を記述するときに「元の命令がなんなの」などと気にする必要はありません。

    • ASR、右算術シフト
    • LSL、左論理シフト
    • LSR、右論理シフト
    • ROR、右ローテイト

どの命令も第1オペランドはデスティネーション・レジスタ、第2がソース・レジスタ、第3がビット数指定のオペランドです。第3が即値指定の場合は以下の表の青色部分(前回までにやった実体命令)が使われ、第3がレジスタ指定の場合は赤色部分が使われます。Shift

前回までの青色部分の場合、エイリアスであるシフト、ローテイト系命令と実体命令のオペランド表記では値の「変換」が必要でした。しかし、今回の赤色部分は違います。

ASR=ASRV

と考えて良さそうです。単に「エイリアス」として使われているASRなどのニーモニックに「実体命令」も同時に割り当てるのが座りが悪いために、レジスタでビット数を指定する(可変の)実体命令にはVをつけたのかと想像されます。知らんけど。

なお、算術左シフトは意味無し、左ローテイトは右ローテイトのビット数を操作すれば代用可能なのでありません。念のため。

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

いつもであると、エイリアスのある命令では、ことさらに同一の機械語コードに落ちる元の命令とエイリアス表記を並べてアセンブルしていますが、今回はエイリアスと元の命令の差異はvをつけるか否かだけなので無しです。どっちゃでもよいのだと。

.globl	asrvW, asrvX, lslvW, lslvX, lsrvW, lsrvX, rorvW, rorvX
.text
.balign	4

asrvW:
    asrv w0, w1, w2
    ret

asrvX:
    asrv x0, x1, x2
    ret

lslvW:
    lslv w0, w1, w2
    ret

lslvX:
    lslv x0, x1, x2
    ret

lsrvW:
    lsrv w0, w1, w2
    ret

lsrvX:
    lsrv x0, x1, x2
    ret

rorvW:
    rorv w0, w1, w2
    ret

rorvX:
    rorv x0, x1, x2
    ret

実際、一部ディスアセンブルをするとこんな感じです。
ASRalias

asrと書いても、asrvと書いても同じ意味なら、短いエイリアスの方がええじゃないか。

アセンブル関数を呼び出してテストするCのコード

例によって「とりあえず各命令1例だけ」触ってみるコードが以下に。

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

extern uint32_t asrvW(uint32_t, uint32_t, uint32_t);
extern uint64_t asrvX(uint64_t, uint64_t, uint64_t);
extern uint32_t lslvW(uint32_t, uint32_t, uint32_t);
extern uint64_t lslvX(uint64_t, uint64_t, uint64_t);
extern uint32_t lsrvW(uint32_t, uint32_t, uint32_t);
extern uint64_t lsrvX(uint64_t, uint64_t, uint64_t);
extern uint32_t rorvW(uint32_t, uint32_t, uint32_t);
extern uint64_t rorvX(uint64_t, uint64_t, uint64_t);

int main(void)
{
    uint32_t result;
    uint64_t resultX;

    result = asrvW(0, 0x87654321, 8);
    printf ("asrvW: %08x\n", result);
    resultX = asrvX(0, 0xFEDCBA9876543210, 8);
    printf ("asrvX %lx\n", resultX);

    result = lslvW(0, 0x87654321, 8);
    printf ("lslvW: %08x\n", result);
    resultX = lslvX(0, 0xFEDCBA9876543210, 8);
    printf ("lslvX %lx\n", resultX);

    result = lsrvW(0, 0x87654321, 8);
    printf ("lsrvW: %08x\n", result);
    resultX = lsrvX(0, 0xFEDCBA9876543210, 8);
    printf ("lsrvX %lx\n", resultX);

    result = rorvW(0, 0x87654321, 8);
    printf ("rorvW: %08x\n", result);
    resultX = rorvX(0, 0xFEDCBA9876543210, 8);
    printf ("rorvX %lx\n", resultX);

    return 0;
}
ビルドして実行

コマンドラインが以下に

gcc -g -O0 -o shift shift.c shift.s

実行結果が以下に。

result

シフト、ローテイトされておりますな。

ぐだぐだ低レベルプログラミング(77)ARM64(AArch64)、EXTR、RORでもある へ戻る

ぐだぐだ低レベルプログラミング(78)ARM64(AArch64)、先行ビットカウント命令 へ進む