以下の内容はhttps://msyksphinz.hatenablog.com/entry/2025/07/09/040000より取得しました。


RISC-V「Zc」拡張の概要 (2)

仕様書を読みながら、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



以上の内容はhttps://msyksphinz.hatenablog.com/entry/2025/07/09/040000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14