ぐだぐだ低レベルプログラミング(32) RISC-V、RV32Iシフトあれどもローテイト無

Joseph Halfmoon

今回は、シフト命令を使って行きたいと思います。ハッキリ言ってシフト系の命令冷遇されています。16ビットの圧縮命令にエンコードしてくれるオペランドは限られているし、ローテイト命令など基本命令セットであるRV32Iには含まれとりません。最低限必要なものは用意したので、後はコンパイラでよしなに、という感じですか。

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

約半世紀近く前の8ビットマイコンのベストセラーZ80(実際には8080命令セットの継承ですが)を思い出します。シフト、ローテイト系の命令が沢山ありました。シフトには、左右と算術/論理の違いがあり(左論理シフトと左算術シフトは同じなので省かれてましたが)、さらにローテイトが左右だけでなく、キャリーフラグを入れたのや入れないのやら。ニブル単位の操作のための命令などもあり、初心者には覚えきれない気がしてました。

しかしRISC-Vの基本命令セットであるRV32Iを何度か勉強してきて、既にその傾向はなんとなくつかめた気がしています。

RISC-Vはオペコード空間を無駄にしない(ケチだ!)

です。シフト系の命令でも割り切りはキッパリしています。

    1. 操作は左シフト、右論理シフト、右算術シフトの3種類のみ
    2. 基本は3オペランド。ソースレジスタをシフト量指定オペランド分シフトして、デスティネーションに書き込む
    3. シフト量の指定のために即値を使える命令も定義されている
    4. ソースとデスティネーションが一致する即値シフト量指定の場合のみRV32Cの16ビット化可能
    5. ローテイトは「また別な拡張」に含まれるかもしれないが、ごく一般的な実装である「RV32IMAC」相当のGD32VF103には含まれていない

3種類、確かにこれだけあれば十分かも。だいたいC言語書いていてローテイト使ったことないし、シフトも即値で済むものが多いし。早速サンプルを書いてみますが、その前に、毎度の参照資料へのリンクを再掲載いたします。

    • RISC-Vの公式ドキュメンテーションはこちら
    • 実験に使用しているGD32VF103VBT6開発ボードはこちら
今回実験するシフトのサンプル・プログラム

ことさらにシフトしまくるだけのサンプル・プログラムです。前半で、3種類のシフト(sll, srl, sra)全てを使ってみてその動作を確認します。後半で、シフトと論理演算を組み合わせてローテイト相当の操作をやり、関数全体の返り値としています。

アセンブラの差分はこちら(全文は第28回に掲載)

shift1:
    addi    sp,sp,-16
    sw      ra, 12(sp)
// --- Under test ---
    sll     t0, a0, a2
    slli    t1, a0, 8
    slli    a0, a0, 12
    srl     t2, a1, a2
    srli    t3, a1, 8
    sra     t4, a1, a2
    srai    t5, a1, 8
    slli    t0, a1, 8
    srli    t1, a1, 24
    or      a0, t0, t1
// --- End of test ---
    lw      ra, 12(sp)
    addi    sp,sp,16
    ret

上記のアセンブラをコンパイルした結果のオブジェクトコードが以下です。9個もシフト命令を連続しているのですが、16ビットのRV32C「圧縮」命令にエンコードされたのが、たった一命令(slli a0, a0, 0xC)しかないことが分かります。前回、前々回などと比べると「冷遇」されている感じがヒシヒシと伝わってきます。

shift_disasm念のため、上記のアセンブラ関数をC言語から呼び出すための関数プロトタイプを書き添えておきます。関数全体の動作としては仮引数 arg1 に与えた32ビット符号なし整数を8ビット左ローテイトした結果を返します。なお、上記のアセンブラには書いてないですが、アセンブラ・ソース内でシンボル shift1 がリンカに見えるように宣言しておかねばなりません。

unsigned int shift1(uint32_t arg0, uint32_t arg1, uint32_t arg2);

C言語からの関数呼び出し部分が以下に、引数に「観察しやすい」値を与えておりますよ。

a0 = 0x76543210;
a1 = 0xFEDCBA98;
a2 = 4;
result = shift1(a0, a1, a2);
printf("shift11   : 0x%08x\n",result);
実動作の観察

さて、上記のアセンブラ関数に飛び込んできたところでブレークしているところが以下に。レジスタ a0, a1, a2にCのコード上の変数 a0, a1, a2の値がそのままコピーされていることをご確認くだされ。

shift001最初の左論理シフト(sll)の結果が以下に。a0レジスタの内容をa2で指定された4ビット分左にシフトしたものをt0レジスタに代入しています。

shift002次は、a0レジスタの内容を即値で8ビット左シフト。結果はt1レジスタに格納です。

shift003

今度も似たようなa0の即値左シフトですが、a0に書き戻しています。実にこのケースのみが16ビットのコンパクトなコードに変換されてます。

shift004次は右論理シフト、シフト量はレジスタ指定の場合。書き込み先はt2。

shift005次も論理右シフトですが、シフト量は即値で指定。書き込み先はt3です。

shift006さてようやく出てきたのが算術右シフト、sra命令です。最初は、レジスタでシフト量指定。

shift007以下は同じく算術右シフトですが、シフト量を即値で指定した場合。どういうわけか srai 命令については、VS Code上で、オペコードの色になっとりません(アセンブル自体には問題なしです。単にエディタとして色づけに失敗しているだけ。どのVS Code拡張がこのソースに色を付けてくれているのだろう?sraiが抜けてる?)

shift008ここから以下の3命令で8ビットの左ローテイト相当の操作です。結果が戻り値レジスタa0に書き込まれます。

slli t0, a1, 8
srli t1, a1, 24
or a0, t0, t1

最初の左シフトの結果が以下に。

shift009次に右論理シフトが以下に。

shift010左も右も論理シフトの「跡地」はゼロ詰めされているので、ORすれば8ビット左ローテイトと同様な結果になります。以下のa0レジスタに注目。

shift011一応、シフトも「ローテイト」も自在にできる?ようになりました。次回は、比較か?比較するとジャンプしない分けにはいかないが。

ぐだぐだ低レベルプログラミング(31) RISC-V、ADDとSUBも凸凹じゃけんの へ戻る

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