今回は、シフト命令を使って行きたいと思います。ハッキリ言ってシフト系の命令冷遇されています。16ビットの圧縮命令にエンコードしてくれるオペランドは限られているし、ローテイト命令など基本命令セットであるRV32Iには含まれとりません。最低限必要なものは用意したので、後はコンパイラでよしなに、という感じですか。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
約半世紀近く前の8ビットマイコンのベストセラーZ80(実際には8080命令セットの継承ですが)を思い出します。シフト、ローテイト系の命令が沢山ありました。シフトには、左右と算術/論理の違いがあり(左論理シフトと左算術シフトは同じなので省かれてましたが)、さらにローテイトが左右だけでなく、キャリーフラグを入れたのや入れないのやら。ニブル単位の操作のための命令などもあり、初心者には覚えきれない気がしてました。
しかしRISC-Vの基本命令セットであるRV32Iを何度か勉強してきて、既にその傾向はなんとなくつかめた気がしています。
RISC-Vはオペコード空間を無駄にしない(ケチだ!)
です。シフト系の命令でも割り切りはキッパリしています。
-
- 操作は左シフト、右論理シフト、右算術シフトの3種類のみ
- 基本は3オペランド。ソースレジスタをシフト量指定オペランド分シフトして、デスティネーションに書き込む
- シフト量の指定のために即値を使える命令も定義されている
- ソースとデスティネーションが一致する即値シフト量指定の場合のみRV32Cの16ビット化可能
- ローテイトは「また別な拡張」に含まれるかもしれないが、ごく一般的な実装である「RV32IMAC」相当のGD32VF103には含まれていない
3種類、確かにこれだけあれば十分かも。だいたいC言語書いていてローテイト使ったことないし、シフトも即値で済むものが多いし。早速サンプルを書いてみますが、その前に、毎度の参照資料へのリンクを再掲載いたします。
今回実験するシフトのサンプル・プログラム
ことさらにシフトしまくるだけのサンプル・プログラムです。前半で、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)しかないことが分かります。前回、前々回などと比べると「冷遇」されている感じがヒシヒシと伝わってきます。
念のため、上記のアセンブラ関数を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の値がそのままコピーされていることをご確認くだされ。
最初の左論理シフト(sll)の結果が以下に。a0レジスタの内容をa2で指定された4ビット分左にシフトしたものをt0レジスタに代入しています。
次は、a0レジスタの内容を即値で8ビット左シフト。結果はt1レジスタに格納です。
今度も似たようなa0の即値左シフトですが、a0に書き戻しています。実にこのケースのみが16ビットのコンパクトなコードに変換されてます。
次は右論理シフト、シフト量はレジスタ指定の場合。書き込み先はt2。
次も論理右シフトですが、シフト量は即値で指定。書き込み先はt3です。
さてようやく出てきたのが算術右シフト、sra命令です。最初は、レジスタでシフト量指定。
以下は同じく算術右シフトですが、シフト量を即値で指定した場合。どういうわけか srai 命令については、VS Code上で、オペコードの色になっとりません(アセンブル自体には問題なしです。単にエディタとして色づけに失敗しているだけ。どのVS Code拡張がこのソースに色を付けてくれているのだろう?sraiが抜けてる?)
ここから以下の3命令で8ビットの左ローテイト相当の操作です。結果が戻り値レジスタa0に書き込まれます。
slli t0, a1, 8 srli t1, a1, 24 or a0, t0, t1
最初の左シフトの結果が以下に。
左も右も論理シフトの「跡地」はゼロ詰めされているので、ORすれば8ビット左ローテイトと同様な結果になります。以下のa0レジスタに注目。
一応、シフトも「ローテイト」も自在にできる?ようになりました。次回は、比較か?比較するとジャンプしない分けにはいかないが。