はじめに
x86はやはり疲れるので、一旦RiscVで進めることに方針転換した。
あと、せっかく先進的なRiscVを使うなら、他のツールも新しくしたいと思い、clangでコンパイルした。
もはやgccではないが、シリーズ化して作ってしまっているのでこのまま進める。
本題
下記のCコードをRiscV向けにコンパイルのみして、オブジェクトファイルを作成した。
riscv64-unknown-elf-objdumpで、このファイルをdisassembleした。
int main() {
return 0;
}
コマンド
riscv64-unknown-elf-objdump --disassemble $FILE.o
眺めると、sw以外は2byteの命令になっており、圧縮命令(RVC)が多用されていることが分かる。
結果
Disassembly of section .text:
0000000000000000 <main>:
0: 1101 addi sp,sp,-32
2: ec06 sd ra,24(sp)
4: e822 sd s0,16(sp)
6: 1000 addi s0,sp,32
8: 4501 li a0,0
a: fea42623 sw a0,-20(s0)
e: 60e2 ld ra,24(sp)
10: 6442 ld s0,16(sp)
12: 6105 addi sp,sp,32
14: 8082 ret
読みづらいので、手で書き換えた。
sp -= 32
[24 + sp] = ra
[16 + sp] = s0
s0 = sp + 32
a0 = 0
[-20 + sp] = a0[31:0]
ra = [24 + sp]
s0 = [16 + sp]
sp += 32
まずスタックポインタを32引く(下に進むということと思われる)。 レジスタサイズは64bit(8byte)なので、ローカル変数用に4つの値を格納できるようにメモリを確保したと言える。
sp -= 32
[sp + 24]に、今のraを保存しておく(別の関数を実行したときにraが失われないようにしておく。)
retする直前に、raレジスタに戻す(つまり、メモリに一時退避するということ)。
[24 + sp] = ra
[sp + 16]に、s0を代入する。
つまり、スタックに今のs0(フレームポインタ)を保存する。関数実行の開始を表す。
retする直前に、s0レジスタに戻す。
[16 + sp] = s0
s0レジスタに、sp + 32を代入する。 新たな関数の実行が開始したことを、s0で表現する。 具体的には、今後のスタックに関する操作はs0を基準に行う。
s0 = sp + 32
a0レジスタに0を代入する。 a0は関数の返り値を表すので、このままretしたら0が返る。
a0 = 0
[s0 - 20]に、a0を代入する。
[-20 + s0] = a0[31:0]
raレジスタに、元のraの値を復元(このコードに限れば、raは変えていなかったので不要だった)
ra = [24 + sp]
同様にs0レジスタに、元のs0の値を復元(このコードに限れば、s0は変えていなかったので不要だった)
s0 = [16 + sp]
関数の実行が終わったので、spを元に戻す。
sp += 32
関数のshreturnを行い、pcレジスタへraレジスタの値をセットする。
ret
RiscVレジスタメモ
全レジスタ
pc, x0-x31(1+32で33個)
ただし、xから始まる名前のレジスタ(つまり、pc以外)にはそれぞれ別名がついている。
スタック
アドレス空間を下へ進む。関数の中で関数が実行されるごとにスタックフレームが追加され、関数から戻るとスタックフレームが取り除かれる。
sp
レジスタ番号: x2
スタックポインタ。小さい方へ伸びる。現在のスタックフレームの開始地点を表す。
関数を実行するごとに、先に下げて使うことで安全に扱える(s0と併用することで簡単に安全になる)。
s0
レジスタ番号: x8
フレームポインタ。スタックフレーム内の基準とする点。
spと一見かなり似た挙動をするが、1つののスタック内のローカル変数などスタックフレーム内の値に対する操作はs0を基準に行う。
ra
レジスタ番号: x1
return address。スタックフレームが終わるとき(つまり、おそらく大体は関数が返るとき)に、呼び出し元に戻る必要がある。
このとき、呼び出し元の処理が途中なので、適切なところに戻る必要があるが、その命令列が格納されたメモリの戻るべきアドレス位置を入れる。
別の言い方をすれば、ret実行時にpcレジスタにraレジスタの値がコピーされるので、そのときに適切な挙動になるようにraレジスタを設定する。
pc
プログラムカウンタ。現在実行している命令列のアドレスをもつ。実行のたびに32ビット、64ビットのCPU問わず、4byte増加する。
(なお、x86_64においては、命令は可変長で1-15byteらしい。RiscVは基本的に2byteか4byte。)
圧縮命令(Reduced Vector Compression, RVC)の場合は、2byteしか増えないこともあるらしい。