前回は条件フラグを「見て」飛ぶ一般的な条件分岐命令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ニーモニックしかありません。
-
- CBNZ
- CBZ
- TBNZ
- TBZ
第1のCBNZ命令は、指定されたレジスタの内容がNZ(ノン・ゼロ)であったならば分岐するという命令です。第2の命令CBZは条件がひっくり返っていて、ゼロなら分岐です。
第3のTBNZ命令は、レジスタ以外にビット位置を即値指定でき、該当のレジスタの該当のビットがNZ(ノン・ゼロ)ならば分岐、第4のTBZは同様な場所がゼロなら分岐です。
どちらも64ビット幅のXレジスタ、32ビット幅のWレジスタをレジスタとして指定できますが、TBNZとTBZの場合、Wレジスタにビット位置32以上は指定できませぬ(当たり前か。)
実験に使ったアセンブリ言語ソース
いつものように手抜き(関数プロローグもエピローグもない)なものですが、分岐したのかしないのか返したいので以下のようにしています。
-
- 関数の引数が x0 に与えられているハズ
- x0の内容を見て分岐する(か分岐しない)
- 分岐したら1を返す
- 分岐しなかったら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; }
ビルドして実行
ビルドして実行したところが以下に。
予定通り飛んでるな。当たり前か。