ぐだぐだ低レベルプログラミング(80)ARM64(AArch64)、Reverse命令一族

Joseph Halfmoon

前回は先行ビットカウント命令でした。今回はビット、バイトのリバース命令群です。どちらも「場合によっては」必要な操作で、こういう専用命令なしに処理しようとすると結構メンドイ処理になる操作であります。有って悪いことはないですが、こういう複雑な命令群がいろいろ実装されているArmは複雑(なRISC)?

※「ぐだぐだ低レベルプログラミング」投稿順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
REV64命令について

ビット、バイトのReverse命令群のうち、REV64というアセンブリ言語命令は Armv8.2でないとアセンブルできない、ということになっています。そのため、ARMv8で動作確認している今回はパスしました。しかし、命令の実体はARMv8でもつかえるREV命令に64ビットレジスタを指定した場合と等価です。実体はなく、表記の問題だけなのだけれどもv8.2でないと使えない、ということみたいです。なんだかな~。

Reverse命令群

Reverse命令群はソースオペランドのレジスタ内の値を「順序反転」してデスティネーションレジスタへ書き込む命令群です。Reverse命令群としてまとめましたが、その操作は大きく二つにわかれます。冒頭のアイキャッチ画像にその動作の概念を図にしましたのでご覧ください。

    • RBIT命令、ビット順序の反転
    • REV、 REV16、 REV32命令、バイト順序の反転

ビット順序の反転でぱっと思い浮かぶのがFFTなどで使う「バタフライ演算」です。しかしMSBとLSBの順序をひっくり返したいという「需要」は(通信がらみなどで)時々あり、こういう命令がないと1ビットずつシフト処理することになるので、有って嬉しいんじゃないかと思います。W(32ビット)レジスタ指定であれば32ビット幅、X(64ビット)レジスタ指定であれば64ビット幅で順序反転操作を行います。

バイト順序の反転命令は、指定するオペランドのレジスタ全幅に対して操作するREVと、各16ビット(ハーフワード)単位で順序交換するREV16、各32ビット(ワード)単位で順序交換するREV32があります。前述のとおり、Armv8.2ではREV64というニーモニックがありますが、これはREVのオペランドにXレジスタを指定した場合と等価です。

1命令1関数スタイルの、実験用アセンブリ言語ソースが以下に

.globl	rbitW, rbitX, revW, revX, rev16W, rev16X, rev32W, rev32X
.text
.balign	4

rbitW:
    rbit w0, w1
    ret

rbitX:
    rbit x0, x1
    ret

revW:
    rev w0, w1
    ret

revX:
    rev x0, x1
    ret

rev16W:
    rev16 w0, w1
    ret

rev16X:
    rev16 x0, x1
    ret

rev32X:
    rev32 x0, x1
    ret
アセンブル関数を呼び出してテストするCのコード

例によって「とりあえず各命令1例だけ」触ってみるコードが以下です。ダラダラと16進数を入力し、その順番が入れ替わることを観察するだけのコードです。

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

extern uint32_t rbitW(uint32_t, uint32_t);
extern uint64_t rbitX(uint64_t, uint64_t);
extern uint32_t revW(uint32_t, uint32_t);
extern uint64_t revX(uint64_t, uint64_t);
extern uint32_t rev16W(uint32_t, uint32_t);
extern uint64_t rev16X(uint64_t, uint64_t);
extern uint64_t rev32X(uint64_t, uint64_t);

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

    result = rbitW(0, 0xA0F0F0F5);
    printf ("rbitW: %08x\n", result);
    resultX = rbitX(0, 0xA0F0F0F0F0F0F0F5);
    printf ("rbitX %lx\n", resultX);

    result = revW(0, 0x12345678);
    printf ("revW: %08x\n", result);
    resultX = revX(0, 0x123456789ABCDEF0);
    printf ("revX %lx\n", resultX);

    result = rev16W(0, 0x12345678);
    printf ("rev16W: %08x\n", result);
    resultX = rev16X(0, 0x123456789ABCDEF0);
    printf ("rev16X %lx\n", resultX);

    resultX = rev32X(0, 0x123456789ABCDEF0);
    printf ("rev32X: %lx\n", resultX);

    return 0;
}
ビルドして実行

コマンドラインが以下に。

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

実行結果が以下に。

result

ひっくり返っておるようです。

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

ぐだぐだ低レベルプログラミング(81) ARM64(AArch64)、条件フラグ、NZCV へ進む