cm.pop {reg_list}, stack_adj destroys a stack frame: reload ra and the
listed s-registers from their canonical slots, then release the frame by
adding stack_adj to sp. No return — cm.pop is the epilogue for
tail-calling functions, where a jump (not ret) follows.
The expansion mirrors the push exactly — same slots, positive offsets, sp adjustment last:
# cm.pop {ra, s0-s3}, 48 (rlist=8, spimm=1, RV32)
lw s3, 44(sp) # top of frame, just below where old sp was
lw s2, 40(sp)
lw s1, 36(sp)
lw s0, 32(sp)
lw ra, 28(sp)
addi sp, sp, 48 # the atomic tail: commits only when all loads are safe
All the sequence semantics apply: loads may reorder, repeat, and partially update destinations before a trap (xEPC = the cm.pop PC; full re-execution after); the sp adjustment is all-or-nothing; idempotent memory required.
Hardware Designer Notes
cm.pop is the same microsequence skeleton as cm.push with loads instead of stores and the frame math shifted by +stack_adj. If your sequencer parameterizes direction, base offset, and tail ops, all four PUSH/POP instructions are one state machine.
Minimal Linux-boot hart MUST
- Read the canonical slot layout (identical to cm.push) — unwinders and longjmp implementations depend on the frame shape
- Commit the sp adjustment only after every load has cleared its fault checks
MAY simplify / trap-and-emulate
- Reuse the cm.push µop sequencer with direction=load and offset base sp+stack_adj−bytes
Check yourself — cm.pop
1.How does cm.pop's frame layout relate to cm.push's?
2.Where does cm.pop differ from cm.popret in the tail commit group?