附录 D. 寄存器 ABI 速查表
RISC-V 定义 32 个通用整数寄存器(x0-x31)和 32 个浮点寄存器(f0-f31)。应用二进制接口(ABI)规定了寄存器在函数调用中的角色和保存责任。Caller-saved(调用者保存):被调函数可随意修改,调用者需在调用前自行保存。Callee-saved(被调者保存):被调函数如需使用,必须在函数入口保存并在返回前恢复原值。
整数寄存器(x0-x31)
| 寄存器 | ABI 名称 | 用途 | 保存约定 | 备注 |
|---|---|---|---|---|
| x0 | zero | 硬连线 0 | — | 写入丢弃,读取恒为 0 |
| x1 | ra | 返回地址 | Caller | jal / jalr 自动写入 |
| x2 | sp | 栈指针 | Callee | 指向栈底(向低地址增长) |
| x3 | gp | 全局指针 | — | 位置无关代码中指向 GOT |
| x4 | tp | 线程指针 | — | 线程局部存储(TLS)基址 |
| x5 | t0 | 临时 0 | Caller | 函数调用中不保留 |
| x6 | t1 | 临时 1 | Caller | |
| x7 | t2 | 临时 2 | Caller | |
| x8 | s0 / fp | 保存 0 / 帧指针 | Callee | 通常用于指向当前栈帧 |
| x9 | s1 | 保存 1 | Callee | |
| x10 | a0 | 参数 0 / 返回值 0 | Caller | 第一参数,第一返回值 |
| x11 | a1 | 参数 1 / 返回值 1 | Caller | 第二参数,第二返回值 |
| x12 | a2 | 参数 2 | Caller | |
| x13 | a3 | 参数 3 | Caller | |
| x14 | a4 | 参数 4 | Caller | |
| x15 | a5 | 参数 5 | Caller | |
| x16 | a6 | 参数 6 | Caller | |
| x17 | a7 | 参数 7 | Caller | 也用于系统调用号 |
| x18 | s2 | 保存 2 | Callee | |
| x19 | s3 | 保存 3 | Callee | |
| x20 | s4 | 保存 4 | Callee | |
| x21 | s5 | 保存 5 | Callee | |
| x22 | s6 | 保存 6 | Callee | |
| x23 | s7 | 保存 7 | Callee | |
| x24 | s8 | 保存 8 | Callee | |
| x25 | s9 | 保存 9 | Callee | |
| x26 | s10 | 保存 10 | Callee | |
| x27 | s11 | 保存 11 | Callee | |
| x28 | t3 | 临时 3 | Caller | |
| x29 | t4 | 临时 4 | Caller | |
| x30 | t5 | 临时 5 | Caller | |
| x31 | t6 | 临时 6 | Caller |
寄存器分类汇总
| 类别 | 寄存器 | 说明 |
|---|---|---|
| 硬连线 | x0 (zero) | 恒为 0 |
| 返回地址 | x1 (ra) | 函数返回地址 |
| 栈/全局/线程 | x2(sp), x3(gp), x4(tp) | 特殊用途指针 |
| 临时 (Caller) | x5-x7(t0-t2), x10-x17(a0-a7), x28-x31(t3-t6) | 共 15 个,调用者负责保存 |
| 保存 (Callee) | x8-x9(s0-s1/fp), x18-x27(s2-s11) | 共 12 个,被调者负责保存 |
| 参数/返回值 | x10-x11(a0-a1) | 前 2 个参数,前 2 个返回值 |
| 参数 | x12-x17(a2-a7) | 第 3-8 个参数 |
浮点寄存器(f0-f31)
| 寄存器 | ABI 名称 | 用途 | 保存约定 | 备注 |
|---|---|---|---|---|
| f0 | ft0 | 临时 0 | Caller | |
| f1 | ft1 | 临时 1 | Caller | |
| f2 | ft2 | 临时 2 | Caller | |
| f3 | ft3 | 临时 3 | Caller | |
| f4 | ft4 | 临时 4 | Caller | |
| f5 | ft5 | 临时 5 | Caller | |
| f6 | ft6 | 临时 6 | Caller | |
| f7 | ft7 | 临时 7 | Caller | |
| f8 | fs0 | 保存 0 | Callee | |
| f9 | fs1 | 保存 1 | Callee | |
| f10 | fa0 | 参数 0 / 返回值 0 | Caller | 第一个浮点参数和返回值 |
| f11 | fa1 | 参数 1 / 返回值 1 | Caller | 第二个浮点参数和返回值 |
| f12 | fa2 | 参数 2 | Caller | |
| f13 | fa3 | 参数 3 | Caller | |
| f14 | fa4 | 参数 4 | Caller | |
| f15 | fa5 | 参数 5 | Caller | |
| f16 | fa6 | 参数 6 | Caller | |
| f17 | fa7 | 参数 7 | Caller | |
| f18 | fs2 | 保存 2 | Callee | |
| f19 | fs3 | 保存 3 | Callee | |
| f20 | fs4 | 保存 4 | Callee | |
| f21 | fs5 | 保存 5 | Callee | |
| f22 | fs6 | 保存 6 | Callee | |
| f23 | fs7 | 保存 7 | Callee | |
| f24 | fs8 | 保存 8 | Callee | |
| f25 | fs9 | 保存 9 | Callee | |
| f26 | fs10 | 保存 10 | Callee | |
| f27 | fs11 | 保存 11 | Callee | |
| f28 | ft8 | 临时 8 | Caller | |
| f29 | ft9 | 临时 9 | Caller | |
| f30 | ft10 | 临时 10 | Caller | |
| f31 | ft11 | 临时 11 | Caller |
浮点寄存器分类汇总
| 类别 | 寄存器 | 数量 |
|---|---|---|
| 临时 (Caller) | f0-f7(ft0-ft7), f10-f17(fa0-fa7), f28-f31(ft8-ft11) | 20 |
| 保存 (Callee) | f8-f9(fs0-fs1), f18-f27(fs2-fs11) | 12 |
| 参数/返回值 | f10-f11(fa0-fa1) | 2 |
函数调用约定
参数传递规则
当函数参数同时包含整数和浮点类型时,按照 RISC-V LP64D ABI:
| 参数位置 | 整数寄存器 | 浮点寄存器 | 说明 |
|---|---|---|---|
| 参数 1 | a0 (x10) | fa0 (f10) | 整数在 a0,浮点在 fa0 |
| 参数 2 | a1 (x11) | fa1 (f11) | |
| 参数 3 | a2 (x12) | fa2 (f12) | |
| 参数 4 | a3 (x13) | fa3 (f13) | |
| 参数 5 | a4 (x14) | fa4 (f14) | |
| 参数 6 | a5 (x15) | fa5 (f15) | |
| 参数 7 | a6 (x16) | fa6 (f16) | |
| 参数 8 | a7 (x17) | fa7 (f17) | |
| 参数 9+ | 栈传递 | 栈传递 | sp+0, sp+8, ... |
混合参数:如
func(int a, double b, int c),a 用 a0,b 用 fa0,c 用 a1。整型和浮点各自独立编号。
返回值规则
| 返回值大小 | 寄存器 | 说明 |
|---|---|---|
| 8/16/32/64 位整数 | a0 (x10) | 符号/零扩展至 64 位 |
| 128 位整数 | a0+a1 (x10+x11) | 低于 64 位在 a0 |
| 浮点数 (float) | fa0 (f10) | |
| 浮点数 (double) | fa0 (f10) | |
| 结构体(<=2*XLEN) | a0+a1 | 按整型寄存器分解传递 |
| 大型结构体 | 调用者分配内存,a0 传入指针 |
栈帧布局
高地址
+-------------------+
| 调用者栈帧 |
| ... |
| varargs(如有) | <- 被调函数的可选可变参数
+-------------------+
| ra 保存区 | <- 返回地址(如有必要)
| fp 保存区 | <- 帧指针(如有必要)
| s0-s11 保存区 | <- Callee-saved 寄存器
| 局部变量 |
| 溢出参数区 | <- 超过 8 个的参数
+-------------------+ <- sp(栈指针,16 字节对齐)
低地址栈对齐与红区
| 约定 | 规则 |
|---|---|
| 栈对齐 | 16 字节边界(sp 必须满足 sp % 16 == 0) |
| 红区(Red Zone) | sp 以下 2048 字节内,信号处理程序不会修改,可被函数用作临时空间 |
| 调用入口 sp | 16 字节对齐 |
| 调用返回 sp | 必须恢复到调用前的值 |
Linux RISC-V 系统调用约定(LP64)
| 项目 | 规则 |
|---|---|
| 系统调用号 | 存入 a7 (x17) |
| 参数 1 | a0 (x10) |
| 参数 2 | a1 (x11) |
| 参数 3 | a2 (x12) |
| 参数 4 | a3 (x13) |
| 参数 5 | a4 (x14) |
| 参数 6 | a5 (x15) |
| 返回值 | a0 (x10);错误时返回 -errno(负值) |
| 指令 | ecall |
| 临时破坏 | a1、t0-t6内容可能被破坏 |
常用 Linux RISC-V 系统调用号(64 位)
| 系统调用 | 调用号 (a7) | 说明 |
|---|---|---|
sys_read | 63 | 读取文件 |
sys_write | 64 | 写入文件 |
sys_openat | 56 | 打开文件 |
sys_close | 57 | 关闭文件 |
sys_exit | 93 | 退出进程 |
sys_brk | 214 | 调整堆顶 |
sys_mmap | 222 | 内存映射 |
sys_munmap | 215 | 取消内存映射 |
sys_clone | 220 | 创建线程 |
sys_nanosleep | 101 | 休眠 |
完整系统调用号参考:
/usr/include/asm-generic/unistd.h或 Linux 内核include/uapi/asm-generic/unistd.h
寄存器保存约定速记
被调函数入口/出口模板
asm
# 函数入口(Prologue)
addi sp, sp, -N # 分配栈空间
sd ra, 0(sp) # 保存返回地址(如需调用子函数)
sd s0, 8(sp) # 保存 callee-saved 寄存器
# ... 保存其他 s2-s11
# 函数体 ...
# 函数出口(Epilogue)
ld ra, 0(sp) # 恢复返回地址
ld s0, 8(sp) # 恢复 callee-saved 寄存器
# ... 恢复其他 s2-s11
addi sp, sp, N # 释放栈空间
ret # jalr x0, ra, 0寄存器保存决策树
函数会修改 s0-s11 中的任何一个?
└─ 是 → 必须在入口保存原值,出口恢复
└─ 否 → 不需要
函数会调用其他函数(非叶函数)?
└─ 是 → ra 必须在入口保存(sd ra, offset(sp))
└─ 否 → 叶函数无需保存 ra
函数会使用 x5-x7, x10-x17, x28-x31 中的寄存器?
└─ 是 → 调用者需在调用前保存(被调函数不需要处理)
└─ 否 → 无需处理