昨日はFreedom Studioでデバッグする方法について書きましたが今日はRICV-Vに慣れるためにレジスタやアセンブラについて少し調べてみました。
mcommit.hatenadiary.com
概要
HiFive1 Rev.Bで使用されているCPUはFE310-G002。命令セットはRV32IMACというISAらしい。
名前を知ったところで現時点では何も分からない。
よく出てくる命令
とりあえずCのコードの逆アセを眺めて出てきた命令を列挙してみました。
命令 | 意味(英語) | 処理 |
---|---|---|
jal | Jump and Link | 関数呼び出し |
add | Add | 加算(レジスタ) |
addi | Add Immediate | 加算(直値) |
sw | store word | メモリへのストア(word) |
sd | store double | メモリへのストア(double) |
li | load Immediate | レジスタへのロード |
add, addi
レジスタを使った加算はadd。直値を使う場合はaddiになる。
レジスタか直値で命令が違うのはあまりみたことがなかった。
デコードするときに扱える値の範囲が変わるというメリットはあるかもしれない。
sw,sd
オフセット(レジスタ名)
例). 8($sp)
のような表記になります。一度覚えてしまうとわかりやすい表現方法のような気がします。
gcc の冗長さ
最適化をかけないと関数の入り口と出口で、使おうが使うまいが愚直にプロローグコードとエピローグコード(ローカル変数用領域の確保と解放)を出力しています。
また reutrn 0 や return 1 のような直値を返す関数であっても一度a5レジスタを介してa0に書き込みをしています...
MIPS流のお作法なのかもしれないがさすがに冗長ですよね...
なお、この状況なコードは -O1 の最適化をつけると一瞬にして素直なコードになりました。
RISC-Vのgcc、最適化かけなかったら信じられないくらい愚直なコード吐くので思わず「いや、真面目か!」って突っ込みを入れた。
— simotin13 (@simotin13) 2019年5月27日
直値のエンコードの謎
Cで0を返すコード(return 0;)をコンパイルしてみました。
0を返す機械語が4501になっていたので不思議に思い0~5までの直値を返す関数のアセンブラを見てみました。
int return_zero(void) { return 0; } int return_one(void) { return 1; } int return_two(void) { return 2; } int return_three(void) { return 3; } int return_four(void) { return 4; } int return_five(void) { return 5; }
結果、
0000000000000002 <return_zero>: 2: 4501 li a0,0 4: 8082 ret 0000000000000006 <return_one>: 6: 4505 li a0,1 8: 8082 ret 000000000000000a <return_two>: a: 4509 li a0,2 c: 8082 ret 000000000000000e <return_three>: e: 450d li a0,3 10: 8082 ret 0000000000000012 <return_four>: 12: 4511 li a0,4 14: 8082 ret 0000000000000016 <return_five>: 16: 4515 li a0,5 18: 8082 ret
0→4501を開始として、1:4505 2:4509 3:450d ... と直値は4ずつ増えてエンコードされているようです。
どこまでつづくのかは分かりませんが、ぱっと見た限りでは、4 * n + 1 がエンコードされた値になります。
ちなみに、関数の戻り値は a0 レジスタで受け渡しするようです。
圧縮命令ってなんだ?
li a0, 1
を tmp.sというファイルに保存して、riscv64-unknown-elf-asでアセンブルしてみました。objdumpで見てみると、上記のC言語からコンパイルしたコードとは違うコードが生成されました。
上記のC言語からコンパイルしたアセンブラでは16bitでエンコードされています。
ところがアセンブラでエンコードされたコードは同じニーモニックにも関わらず 4byteでエンコードされています。
調べてみるとRISC-Vには圧縮命令という命令セットがあり、短い命令長でエンコードされるようです。雰囲気的にはARMのThumb命令と似たような感じでしょうか...
ただし、ARMの場合は命令のアラインメント位置でThumb命令かどうかの判別ができますがRISC-Vの場合ぱっと見では32bit命令なのか圧縮命令なのか分かりませんね。
つまり、上記の
0000000000000002 <return_zero>: 2: 4501 li a0,0 4: 8082 ret
のような2byteでエンコードされた命令は圧縮命令になります。
見比べてみた限りでは、li命令は 32bit命令では 0513 が下位ワードに来るようですので1ワードの特定のビットでスムーズにデコードできるようになっているんだと思います。
圧縮命令は4501や4505なので14bit目のようにも見えます。この辺は真面目に命令セットの仕様を見てみないとよくわかりません。
直値が4ずつ増えるのも圧縮命令と関係がありそうな気もします。
RISC-Vの圧縮命令で直値のエンコードが4ずつ(An=4n+1)増えていくんですけどこれはなぜ?いや、なぜというより誰得なんでしょうか。
— simotin13 (@simotin13) 2019年5月27日
最下位ビットを立てた上で何かしないといけなかったとか!?
とりあえず手元のモナリザ本を開いてみると7章にRV32C:圧縮命令の章がありました。
- 作者: デイビッド・パターソン,アンドリュー・ウォーターマン,成田光彰
- 出版社/メーカー: 日経BP社
- 発売日: 2018/10/18
- メディア: 単行本
- この商品を含むブログを見る
今日は疲れたので、明日はこの章と第2章のRV32I:RISC-V基本整数ISAの章を読んでみたいと思います。