With the semantics fixed (previous page), this page
pins down the bits: how register lists and stack adjustments pack into
16 bits, and exactly what a cm.push expands to.
Worked expansions (RV32)
cm.push {ra, s0-s2}, -64 — encoding rlist=7, spimm=3:
sw s2, -4(sp) # s-registers descend from just below old sp
sw s1, -8(sp)
sw s0, -12(sp)
sw ra, -16(sp) # ra lowest
addi sp, sp, -64 # 16 bytes of registers + 3×16 extra
cm.pop {ra}, 16 — rlist=4, spimm=0:
lw ra, 12(sp) # loads mirror the layout, top of frame
addi sp, sp, 16
The frame layout is canonical — s11 sits nearest the old sp, ra lowest — because debuggers and unwinders must parse these frames; only the access order/grouping is implementation-loose.
The CMPP encoding
| reg_list | base (RV32I) | base (RV64) | |
|---|---|---|---|
| rlist 4 | {ra} | 16 | 16 |
| rlist 5 | {ra, s0} | 16 | 16 |
| rlist 6 | {ra, s0-s1} | 16 | 32 |
| rlist 7 | {ra, s0-s2} | 16 | 32 |
| rlist 8 | {ra, s0-s3} | 32 | 48 |
| rlist 9 | {ra, s0-s4} | 32 | 48 |
| rlist 10 | {ra, s0-s5} | 32 | 64 |
| rlist 11 | {ra, s0-s6} | 32 | 64 |
| rlist 12 | {ra, s0-s7} | 48 | 80 |
| rlist 13 | {ra, s0-s8} | 48 | 80 |
| rlist 14 | {ra, s0-s9} | 48 | 96 |
| rlist 15 | {ra, s0-s11} | 64 | 112 |
stack_adj = stack_adj_base + spimm × 16 — the base is the register
storage rounded up to a 16-byte multiple (the UABI keeps sp 16-byte
aligned), spimm buys up to 48 bytes of locals, and anything bigger chains
a c.addi16sp. On RV32E only rlist 4–6 exist and the base is always 16.
Hardware Designer Notes
Implementation sketch: rlist → a 13-bit register bitmap (via a 12-entry ROM), then a µop sequencer walks the bitmap descending, addr starting at sp − bytes. The stack_adj adder needs only the base ROM (per XLEN) plus spimm≪4. The following pages cover cm.pop/popret[z] (same skeleton, loads + tail group) and the mv-pair helpers.
Minimal Linux-boot hart MUST
- Generate the canonical frame layout (s11 highest, ra lowest) — unwinders depend on it
- Treat rlist 0-3 as reserved (future EABI), and respect the missing {ra,s0-s10} list
- Compute stack_adj_base per-XLEN (the RV64 table differs: 8-byte slots)
MAY simplify / trap-and-emulate
- Emit the stores in any order/grouping — only the layout and the atomic sp tail are contractual
Check yourself — cm.push encoding
1.cm.push {ra, s0-s2}, -64 on RV32 encodes rlist=7, spimm=3. Reconstruct the math.
2.Why do rlist values 0-3 not encode {}, {ra} variants… what are they?
3.In what order does cm.push store, per the pseudocode?