29.13.9Zcmp: cm.popretz

Part I Linux boot: optional Vol. I (Unprivileged) pp. 203–207 · ~1 min read

cm.popretz {reg_list}, stack_adj is the complete return 0; epilogue in one 16-bit instruction: reload the register list, write a0 ← 0, release the frame, ret.

101151311100128rlist74spimm321010cm.popretz
Click a field for its role.

Software view of cm.popretz {ra, s0-s3}, 32:

# body — may reorder, repeat, partially update before a trap
lw   s3, 28(sp)
lw   s2, 24(sp)
lw   s1, 20(sp)
lw   s0, 16(sp)
lw   ra, 12(sp)

# atomic tail — all-or-nothing, only after every load is fault-free
li   a0, 0
addi sp, sp, 32
ret

The three tail operations are indivisible: a0’s zero is never observable without the frame release and the return, and once the sp adjustment commits, the ret must execute.

Hardware Designer Notes

One microsequencer nuance: the reloaded ra is both a body result (a load destination) and the tail’s jump target — the sequencer must source the ret target from the committed reload, including on re-execution after a mid-body trap.

Minimal Linux-boot hart MUST

  • Keep li a0,0 inside the atomic tail — zeroing a0 early corrupts the return value on trap re-execution paths where a0 held a live argument
  • Predict the ret through the RAS like any jalr x0, x1 (the reloaded ra is the target)

MAY simplify / trap-and-emulate

  • Fuse the tail into one commit slot — nothing between the three ops is observable

Check yourself — cm.popretz

1.Why does cm.popretz exist as a separate instruction from cm.popret?

2.In cm.popretz, when may software observe a0=0 without the return having happened?

2 questions