ぐだぐだ低レベルプログラミング(98)ARM64(AArach64)、条件分岐CBNZ族

Joseph Halfmoon

前回は条件フラグを「見て」飛ぶ一般的な条件分岐命令B.condでした。しかし思い起こせばRISC-Vでは条件フラグが無く、「その場」でレジスタの内容を判断して飛ぶ命令ばかりでした。A64でもそういう命令が無いわけではないです。今回はフラグを使わずレジスタの内容で分岐するCBNZ命令一族について実習してみます。

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

※A64の最新のマニュアルは以下でダウンロード可能です。

Arm Architecture Reference Manual for A-profile architecture

レジスタの内容で分岐する命令一族

勝手に「CBNZ命令」一族などと呼んでしまいましたが、A64ではレジスタ内容を直接判断して分岐するものは、たかだか以下の4ニーモニックしかありません。

    1. CBNZ
    2. CBZ
    3. TBNZ
    4. TBZ

第1のCBNZ命令は、指定されたレジスタの内容がNZ(ノン・ゼロ)であったならば分岐するという命令です。第2の命令CBZは条件がひっくり返っていて、ゼロなら分岐です。

第3のTBNZ命令は、レジスタ以外にビット位置を即値指定でき、該当のレジスタの該当のビットがNZ(ノン・ゼロ)ならば分岐、第4のTBZは同様な場所がゼロなら分岐です。

どちらも64ビット幅のXレジスタ、32ビット幅のWレジスタをレジスタとして指定できますが、TBNZとTBZの場合、Wレジスタにビット位置32以上は指定できませぬ(当たり前か。)

実験に使ったアセンブリ言語ソース

いつものように手抜き(関数プロローグもエピローグもない)なものですが、分岐したのかしないのか返したいので以下のようにしています。

    1. 関数の引数が x0 に与えられているハズ
    2. x0の内容を見て分岐する(か分岐しない)
    3. 分岐したら1を返す
    4. 分岐しなかったら0を返す

本当は、wレジスタ引数でも実行可能なのですがメンドイので x0 のみ。

.globl	tst_cbnz, tst_cbz, tst_tbnz, tst_tbz
.text
.balign	4

tst_cbnz:
    cbnz x0, lblCBNZ
    mov x0, 0
    ret
lblCBNZ:
    mov x0, 1
    ret

tst_cbz:
    cbz x0, lblCBZ
    mov x0, 0
    ret
lblCBZ:
    mov x0, 1
    ret

tst_tbnz:
    tbnz x0, #1, lblTBNZ
    mov x0, 0
    ret
lblTBNZ:
    mov x0, 1
    ret

tst_tbz:
    tbz x0, #1, lblTBZ
    mov x0, 0
    ret
lblTBZ:
    mov x0, 1
    ret
実験に使用したC言語ソース

上記のアセンブリ言語関数を呼び出すテスト用のCソースが以下に。一応、分岐するケースと分岐しないケースの両方を通したので、4命令x2通りで8ケースやってます。

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

extern uint64_t tst_cbnz(uint64_t);
extern uint64_t tst_cbz(uint64_t);
extern uint64_t tst_tbnz(uint64_t);
extern uint64_t tst_tbz(uint64_t);

int main(void)
{
    uint64_t resultX;

    resultX = tst_cbnz(0x100000000);
    printf ("tst_cbnz(0x100000000): %ld\n", resultX);
    resultX = tst_cbnz(0);
    printf ("tst_cbnz(0): %ld\n", resultX);

    resultX = tst_cbz(0x100000000);
    printf ("tst_cbz(0x100000000): %ld\n", resultX);
    resultX = tst_cbz(0);
    printf ("tst_cbz(0): %ld\n", resultX);

    resultX = tst_tbnz(3);
    printf ("tst_tbnz(3): %ld\n", resultX);
    resultX = tst_tbnz(1);
    printf ("tst_tbnz(1): %ld\n", resultX);

    resultX = tst_tbz(3);
    printf ("tst_tbz(3): %ld\n", resultX);
    resultX = tst_tbz(1);
    printf ("tst_tbz(1): %ld\n", resultX);

    return 0;
}
ビルドして実行

ビルドして実行したところが以下に。

cbzResults

予定通り飛んでるな。当たり前か。

ぐだぐだ低レベルプログラミング(97)ARM64(AArach64)、条件分岐B.cond へ戻る

ぐだぐだ低レベルプログラミング(99)ARM64(AArach64)、所謂call、ret へ進む