首页/指令库/CM-POPRET
CM.POPRET

RISC-V CM.POPRET 指令详解

指令手册C2 (Zcmp CMPP)

恢复寄存器、回收栈帧并执行ret

指令语法

cm.popret {reg_list}, stack_adj
操作数说明
RV32/RV64 Zcmp,依赖 Zca,与 Zcd 不兼容;16 位 CMPP 格式,不使用 x8–x15 紧凑寄存器字段。
rlist 选择 ra/x1 及 s0/x8、s1/x9、s2–s11/x18–x27 的连续列表;0–3 保留,RV32E 仅允许 4–6。
rlist=15 同时包含 s10/s11,不能只到 s10。stack_adj 为按列表和 XLEN 向上取整到 16 字节的 base 加 spimm×16(spimm=0–3)。
按伪代码从旧 sp+stack_adj-XLEN/8 向低地址恢复选中寄存器;成功后 sp 加 stack_adj。
最终段调整 sp 并执行 ret;ret 使用恢复后的 ra 清零 bit0,不改 a0 或写新链接。
访问允许乱序、合并、重试,故障前可能已有部分访存效果;最终段仅在整条指令可提交时提交。
Zcmp栈与函数压缩指令

指令行为说明

CM.POPRET (Zcmp)弹出{reg_list}中的寄存器,调整sp,然后执行ret。它不写a0;复用c.fsdsp编码,因此与Zcd不兼容。加载完成后,sp调整和ret在最终原子段提交。属于Zcmp,依赖Zca。

CM.POPRET 指令解析与执行动画

仅展示 Zcmp 架构可见序列和条件;访问次序、合并或重试不代表实现细节。

cm.popret {reg_list}, stack_adj
执行环境

Zcmp 依赖 Zca 且与 Zcd 不兼容。正确执行要求 sp 指向幂等内存;RV32E 仅允许 rlist=4-6。输入值为保存前寄存器值或成功加载的内存值;complete 假设 EEI 允许全部访问,不代表任意地址都会成功。

sp=0x00001000, rlist=7 -> s2/x18 <- mem32[0x0000103C]=0x00000030; s1/x9 <- mem32[0x00001038]=0x00000020; s0/x8 <- mem32[0x00001034]=0x00000010; ra/x1 <- mem32[0x00001030]=0x00002001; sp <- 0x00001040; PC <- restored ra & ~1 = 0x00002000
16 位编码字段
15..13
funct3
101
12..8
fixed
11110
7..4
rlist
0111
3..2
spimm
11
1..0
op
10
rlist=7 还原为 {ra/x1, s0/x8, s1/x9, s2/x18};stack_adj_base=16;stack_adj=16+3×16=64 字节。
伪代码地址映射:s2/x18 <- mem32[0x0000103C]=0x00000030; s1/x9 <- mem32[0x00001038]=0x00000020; s0/x8 <- mem32[0x00001034]=0x00000010; ra/x1 <- mem32[0x00001030]=0x00002001。实际 加载可按任意顺序、合并或重复;sp' = 0x00001000 + 64 = 0x00001040;target = 0x00002001 & ~1 = 0x00002000。
s2/x18 <- mem32[0x0000103C]=0x00000030; s1/x9 <- mem32[0x00001038]=0x00000020; s0/x8 <- mem32[0x00001034]=0x00000010; ra/x1 <- mem32[0x00001030]=0x00002001; sp <- 0x00001040; PC <- restored ra & ~1 = 0x00002000
尚未提交架构状态。

快速理解与检索要点

从栈加载列表寄存器,再令 sp 加上 stack_adj。随后用恢复后的 ra 返回;不改 a0。

RV32/RV64 Zcmp,依赖 Zca,与 Zcd 不兼容。16 位 CMPP:bits[15:13]=101、[12:8]=11110、rlist=[7:4]、spimm=[3:2]、[1:0]=10。
rlist=4 为 {ra};5–14 依次加上 s0 至 s9;15 为 {ra,s0-s11},没有 {ra,s0-s10}。0–3 保留;RV32E 仅允许 4–6。
stack_adj_base=ceil(寄存器数×XLEN/8÷16)×16;stack_adj=base+spimm×16,spimm=0–3。RV32E 的 base=16。
从旧 sp+stack_adj-XLEN/8 开始,按选中寄存器号降序,每次减 XLEN/8 恢复。没有直接等价的单条 32 位编码。
访问可乱序、合并或重复;正确执行要求幂等内存。精确故障前可能已有部分寄存器加载;最终 sp 和 ret 仅在整条指令能提交时原子提交。

常见使用场景

函数尾声

RV32I Zcmp、访问成功:{ra,s0-s2} 共 16 字节,stack_adj=64 可编码为 rlist=7、spimm=3。PUSH 将旧 sp 下的值保存后 sp-=64;POP 类从旧 sp+48 至旧 sp+60 恢复后 sp+=64;返回形式使用恢复后的 ra,POPRETZ 另将 a0 置零。

使用前检查清单

语法检查
  • RV32/RV64 Zcmp,依赖 Zca,与 Zcd 不兼容;16 位 CMPP 格式,不使用 x8–x15 紧凑寄存器字段。
  • rlist 选择 ra/x1 及 s0/x8、s1/x9、s2–s11/x18–x27 的连续列表;0–3 保留,RV32E 仅允许 4–6。
语义检查
  • rlist=15 同时包含 s10/s11,不能只到 s10。stack_adj 为按列表和 XLEN 向上取整到 16 字节的 base 加 spimm×16(spimm=0–3)。
  • 按伪代码从旧 sp+stack_adj-XLEN/8 向低地址恢复选中寄存器;成功后 sp 加 stack_adj。
  • 最终段调整 sp 并执行 ret;ret 使用恢复后的 ra 清零 bit0,不改 a0 或写新链接。
  • 访问允许乱序、合并、重试,故障前可能已有部分访存效果;最终段仅在整条指令可提交时提交。

容易混淆 / 常见误区

与Zcd不兼容
保留a0值(与POPRETZ区别)
sp调整和ret必须原子提交

常见问题

栈调整可以是任意 16 的倍数吗?

不可以。先按寄存器列表与 XLEN 计算 base,只允许 base、base+16、base+32、base+48。

故障前的访存一定没有效果吗?

不是。部分目的寄存器可能已被加载;返回陷阱后会重新执行序列,不能把加载全部当作原子操作。