仕様書を読みながら、RISC-VのZc拡張について勉強する。 もうちょっと詳細に読み解いていくことにする。
PUSH/POP レジスタ命令
PUSH/POP 命令は、レジスタの保存および復元を一括で行うための命令群である。
cm.push 命令は指定されたレジスタ群をスタックに保存し、スタックポインタを減算する。
cm.pop 命令は保存したレジスタ群をスタックから復元し、スタックポインタを加算する。
- PUSH 命令
- スタックフレームを確保するためにスタックポインタを調整する。
- レジスタリストで指定されたレジスタをスタックフレームへプッシュ(格納)する。
- POP 命令
- スタックフレームからレジスタリストで指定されたレジスタをポップ(ロード)する。
- スタックフレームを破棄するためにスタックポインタを調整する。
- POPRET 命令
- スタックフレームからレジスタリストで指定されたレジスタをポップ(ロード)する。
cm.popretzはさらに、戻り値としてa0にゼロを格納する (これはreturn 0に最適化したもの)- スタックフレームを破棄するためにスタックポインタを調整する。
- ret 命令を実行して関数から復帰する。
要するに、Push/Pop命令は以下のようなレジスタ退避・復帰のコードを1命令に置き換える:
0001098a <processMarkers>: 1098a: 711d addi sp,sp,-96 ;#cm.push(1) 1098c: c8ca sw s2,80(sp) ;#cm.push(2) 1098e: c6ce sw s3,76(sp) ;#cm.push(3) 10990: c4d2 sw s4,72(sp) ;#cm.push(4) 10992: ce86 sw ra,92(sp) ;#cm.push(5) 10994: cca2 sw s0,88(sp) ;#cm.push(6) 10996: caa6 sw s1,84(sp) ;#cm.push(7) 10998: c2d6 sw s5,68(sp) ;#cm.push(8) 1099a: c0da sw s6,64(sp) ;#cm.push(9) 1099c: de5e sw s7,60(sp) ;#cm.push(10) 1099e: dc62 sw s8,56(sp) ;#cm.push(11) 109a0: da66 sw s9,52(sp) ;#cm.push(12) 109a2: d86a sw s10,48(sp);#cm.push(13) 109a4: d66e sw s11,44(sp);#cm.push(14) ... 109f4: 4501 li a0,0 ;#cm.popretz(1) 109f6: 40f6 lw ra,92(sp) ;#cm.popretz(2) 109f8: 4466 lw s0,88(sp) ;#cm.popretz(3) 109fa: 44d6 lw s1,84(sp) ;#cm.popretz(4) 109fc: 4946 lw s2,80(sp) ;#cm.popretz(5) 109fe: 49b6 lw s3,76(sp) ;#cm.popretz(6) 10a00: 4a26 lw s4,72(sp) ;#cm.popretz(7) 10a02: 4a96 lw s5,68(sp) ;#cm.popretz(8) 10a04: 4b06 lw s6,64(sp) ;#cm.popretz(9) 10a06: 5bf2 lw s7,60(sp) ;#cm.popretz(10) 10a08: 5c62 lw s8,56(sp) ;#cm.popretz(11) 10a0a: 5cd2 lw s9,52(sp) ;#cm.popretz(12) 10a0c: 5d42 lw s10,48(sp);#cm.popretz(13) 10a0e: 5db2 lw s11,44(sp);#cm.popretz(14) 10a10: 6125 addi sp,sp,96 ;#cm.popretz(15) 10a12: 8082 ret ;#cm.popretz(16)
0001080e <processMarkers>:
1080e: b8fa cm.push {ra,s0-s11},-96
...
10866: bcfa cm.popretz {ra,s0-s11}, 96
- POP は通常、スタックフレームを破棄した後に
raに戻るretを使用しないテールコールシーケンスで使用される。 - スタックポインタ調整の取り扱い: これらの命令はすべて、保存または復元されるレジスタに必要なメモリをカバーするだけの分だけ、自動的にスタックポインタを調整する。
- レジスタリストの取り扱い: {ra, s0-s11} のレジスタリストを使用しなければならない。
- これは、要するに以下のようにオペランド
rlistによって取り扱うレジスタを変えられるのだが、s10を含めるときはs11も必ず含まれる、ということらしい。
- これは、要するに以下のようにオペランド
switch (rlist){ case 4: {reg_list="ra"; xreg_list="x1";} case 5: {reg_list="ra, s0"; xreg_list="x1, x8";} case 6: {reg_list="ra, s0-s1"; xreg_list="x1, x8-x9";} case 7: {reg_list="ra, s0-s2"; xreg_list="x1, x8-x9, x18";} case 8: {reg_list="ra, s0-s3"; xreg_list="x1, x8-x9, x18-x19";} case 9: {reg_list="ra, s0-s4"; xreg_list="x1, x8-x9, x18-x20";} case 10: {reg_list="ra, s0-s5"; xreg_list="x1, x8-x9, x18-x21";} case 11: {reg_list="ra, s0-s6"; xreg_list="x1, x8-x9, x18-x22";} case 12: {reg_list="ra, s0-s7"; xreg_list="x1, x8-x9, x18-x23";} case 13: {reg_list="ra, s0-s8"; xreg_list="x1, x8-x9, x18-x24";} case 14: {reg_list="ra, s0-s9"; xreg_list="x1, x8-x9, x18-x25";} //note - to include s10, s11 must also be included case 15: {reg_list="ra, s0-s11"; xreg_list="x1, x8-x9, x18-x27";} default: reserved(); } stack_adj = stack_adj_base + spimm * 16;
ソフトウェアから見ると、cm.push命令とcm.popretz 命令は以下のように見える:
cm.push {ra, s0-s5}, -64 # sp-1 から sp-28 までの任意のバイトは、命令が完了する前に複数回書き込まれる可能性があるため、 # これらの更新はスタックポインタの下で割り込み/例外ハンドラからも見える可能性がある sw s5, -4(sp) sw s4, -8(sp) sw s3,-12(sp) sw s2,-16(sp) sw s1,-20(sp) sw s0,-24(sp) sw ra,-28(sp) # 以下は一度だけ実行される必要があり、すべてのストアが正確なフォールトなしに完了した後にのみ実行される。 # したがって、この更新は cm.push が完了した場合にのみ、割り込み/例外ハンドラから見える。 addi sp, sp, -64
cm.popretz {ra, s0-s3}, 32; # これらのロード命令はいずれも複数回実行される可能性がある。 # したがって、これらの更新は割り込み/例外ハンドラからも見える可能性がある。 lw s3, 28(sp) lw s2, 24(sp) lw s1, 20(sp) lw s0, 16(sp) lw ra, 12(sp) # 以下は一度だけ実行される必要があり、すべてのロードが正常に完了した後にのみ実行される。 # すべての命令はアトミックに実行されなければならないため、これらの更新は割り込み/例外ハンドラからは見えない。 li a0, 0 addi sp, sp, 32 ret