ぐだぐだ低レベルプログラミング(34) RISC-V、充実の?条件分岐命令、無駄は無いのだ

Joseph Halfmoon

前回は比較命令でした。比較した結果を格納するフラグが無いと知ってちょっとギョッとしましたが、キャリーやオーバーフローに対応する結果がレジスタに入れば問題ない、と。今回は条件分岐です。フラグが無いので分岐命令自身がレジスタの内容を判定して飛びます。慣れたらかえって便利かも。必要十分な命令がそろっているし。

※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら

前回の比較命令で、RISC-Vの「手口」が大分理解できた感じですが、比較命令は「冷遇」されている感じがありました。比較結果を保存して使う、という行為は、論理演算でBooleanを扱うとき、レジスタ幅を超える算術演算でのキャリー、オーバフローの処理のときなど、使用シーンがそれほど多くないからだと思います。

それに対して「比較した結果でジャンプ」する条件分岐命令は、if文に限らずプログラムの中には多用されています。こいつは冷遇しているわけにはいかないな。

念のため、参照資料へのリンクを再掲載いたします。

    • RISC-Vの公式ドキュメンテーションはこちら
    • 実験に使用しているGD32VF103VBT6開発ボードはこちら
RISC-Vの分岐命令

RISC-Vの「短い」分岐命令は全て条件分岐です。「条件の無い」分岐命令も勿論あるのですが、それはそれでまた、クセが強い、のでまた後で。

短い、といっても飛べる範囲はオフセットで符号付き13ビット(実質、命令は必ず偶数番地に置かれるのでLSBは常に0)もあるので、PC位置を基準として前後4Kバイト分の範囲をカバーできることになります。疑似命令レベルでは、2つのレジスタを比較して、「一致」、「不一致」、「大なり」、「小なり」、「大なりイコール」、「小なりイコール」、全ての関係をチェックしてジャンプすることができます。しかし、「大なり」のレジスタをひっくり返せば、「小なりイコール」になるような関係があるので、レジスタをひっくり返して実現できる奴らは1個にまとめられています。

しかし、「一致(イコール)」の逆は「不一致(ノットイコール)」だからといっても、逆をとったジャンプで代用すると2命令必要になります。それは性能低下を招くからか、一致、不一致、両方の命令が用意されています。それどころか、0との一致、不一致に関しては、以下の条件に当てはまるときのみですが RV32C の16ビット短縮命令が用意されています。よく使うからでしょう。

    • 比較するレジスタはx8~x15(引数レジスタのほとんどをカバー)
    • とび先指定のオフセットは9ビットと短い

また、比較は何も言わなければ符号付きですが、符号無の比較を使う命令も用意されています。必要なところにはちゃんとオペコード空間を割り当てている感じです。

結局、RV32Iレベルで実装される命令は以下となります。その他の条件分岐はアセンブラレベルの「疑似命令」ということになります。

    • BEQ、等しかったら分岐
    • BNE、等しくなかったら分岐
    • BLT、符号付き比較で小さかったら分岐
    • BGE、符号付き比較で大きいか、等しかったら分岐
    • BLTU、符号無比較で小さかったら分岐
    • BGEU、符号無比較で大きいか、等しかったら分岐
実験用のアセンブラコード

さていつものように 第28回 のコードに付け加える形でアセンブラのテスト関数を書いてみました。前回に引き続き、スタックフレームも作らない「手抜き」の関数スタイルです。

tsteq1:
    beq     a1, a2, tsteqLBL0
    bne     a1, a3, tsteqLBL1
    ret
tsteqLBL0:
    addi    a0, zero, 1
    ret
tsteqLBL1:
    addi    a0, zero, 2
    ret

tstlt1:
    blt     a0, a1, tstlt1LBL0
    addi    a0, zero, 1
    ret
tstlt1LBL0:
    addi    a0, zero, 2
    ret

tstge1:
    bge     a0, a1, tstge1LBL0
    addi    a0, zero, 1
    ret
tstge1LBL0:
    addi    a0, zero, 2
    ret

tstltu1:
    bltu    a0, a1, tstltu1LBL0
    addi    a0, zero, 1
    ret
tstltu1LBL0:
    addi    a0, zero, 2
    ret

tstgeu1:
    bgeu     a0, a1, tstgeu1LBL0
    addi    a0, zero, 1
    ret
tstgeu1LBL0:
    addi    a0, zero, 2
    ret

どの関数も、引数の値を比較してジャンプし、比較結果を戻り値として戻します。

C言語から呼び出すときに使う、関数プロトタイプは以下です。

int tsteq1(int arg0, int arg1, int arg2, int arg3);
int tstlt1(int arg0, int arg1);
int tstge1(int arg0, int arg1);
int tstltu1(uint32_t arg0, uint32_t arg1);
int tstgeu1(uint32_t arg0, uint32_t arg1);
Cのmain()関数からのアセンブラ関数の呼び出しと実行結果

Cのmain()関数内の、被テストアセンブラ関数の呼び出し部分は以下のとおりです。printf文の中に、どういう値を判断したらどんな値が返るのかを記したので、アセンブラ関数の期待動作は把握できると思います。

a0 = 0;
a1 = 100;
a2 = 100;
a3 = 100;
printf("tsteq1=%d if (%d == %d) 1 elif (%d != %d) 2 else %d \n", tsteq1(a0, a1, a2, a3), a1, a2, a1, a3, a0);
a0 = 0;
a1 = 100;
a2 = 200;
a3 = 300;
printf("tsteq1=%d if (%d == %d) 1 elif (%d != %d) 2 else %d \n", tsteq1(a0, a1, a2, a3), a1, a2, a1, a3, a0);
a0 = 0;
a1 = 100;
a2 = 200;
a3 = 100;
printf("tsteq1=%d if (%d == %d) 1 elif (%d != %d) 2 else %d \n", tsteq1(a0, a1, a2, a3), a1, a2, a1, a3, a0);
a0 = (int)0x7FFFFFFF;
a1 = -1;
printf("tstlt1=%d if (%d < %d) 2 else 1 \n", tstlt1(a0, a1), a0, a1);
a0 = -1;
a1 = (int)0x7FFFFFFF;
printf("tstlt1=%d if (%d < %d) 2 else 1 \n", tstlt1(a0, a1), a0, a1);
a0 = (int)0x7FFFFFFF;
a1 = -1;
printf("tstge1=%d if (%d >= %d) 2 else 1 \n", tstge1(a0, a1), a0, a1);
a0 = -1;
a1 = (int)0x7FFFFFFF;
printf("tstge1=%d if (%d >= %d) 2 else 1 \n", tstge1(a0, a1), a0, a1);
a0 = 0x7FFFFFFF;
a1 = 0xFFFFFFFF;
printf("tstltu1=%d if (0x%x < 0x%x) 2 else 1 \n", tstltu1(a0, a1), a0, a1);
a0 = 0xFFFFFFFF;
a1 = 0x7FFFFFFF;
printf("tstltu1=%d if (0x%x < 0x%x) 2 else 1 \n", tstltu1(a0, a1), a0, a1);
a0 = 0x7FFFFFFF;
a1 = 0xFFFFFFFF;
printf("tstgeu1=%d if (0x%x >= 0x%x) 2 else 1 \n", tstgeu1(a0, a1), a0, a1);
a0 = 0xFFFFFFFF;
a1 = 0x7FFFFFFF;
printf("tstgeu1=%d if (0x%x >= 0x%x) 2 else 1 \n", tstgeu1(a0, a1), a0, a1);

実際に上記のコードを実行した結果が以下です。

tsteq1=1 if (100 == 100) 1 elif (100 != 100) 2 else 0 
tsteq1=2 if (100 == 200) 1 elif (100 != 300) 2 else 0 
tsteq1=0 if (100 == 200) 1 elif (100 != 100) 2 else 0
tstlt1=1 if (2147483647 < -1) 2 else 1
tstlt1=2 if (-1 < 2147483647) 2 else 1
tstge1=2 if (2147483647 >= -1) 2 else 1
tstge1=1 if (-1 >= 2147483647) 2 else 1
tstltu1=2 if (0x7fffffff < 0xffffffff) 2 else 1
tstltu1=1 if (0xffffffff < 0x7fffffff) 2 else 1
tstgeu1=1 if (0x7fffffff >= 0xffffffff) 2 else 1
tstgeu1=2 if (0xffffffff >= 0x7fffffff) 2 else 1

期待通りの動作をしているようです。

あ、いけない、RV32Cの短縮コードにアセンブルされる命令を含めるのを忘れてしまいました。最初の方に短くなる時の条件書いたのでご勘弁を。

次は「クセの強い」ジャンプ命令かとも思いましたが、その前にメモリへのロード、ストアですかね。

ぐだぐだ低レベルプログラミング(33) RISC-V、比較命令はあるけどフラグは無い へ戻る

ぐだぐだ低レベルプログラミング(35) RISC-V、32ビットのアドレスをロードする小技 へ進む