跳到主要内容

RISC-V 函数调用规范描述了在函数调用过程中,如何通过寄存器传递参数和返回值,如何管理堆栈以及调用方和被调用方的职责。以下是整型和浮点型数据的调用约定:

1. RISC-V 寄存器分类

RISC-V 的寄存器划分如下表所示:

寄存器类型名称编号用途
临时寄存器t0-t6x5-x7, x28-x31临时数据,调用方保存
保存寄存器s0-s11x8-x9, x18-x27保存数据,被调用方保存
参数寄存器a0-a7x10-x17用于传递整型或指针参数,以及返回值
返回地址寄存器rax1返回地址
堆栈指针spx2指向当前堆栈帧的顶部
浮点临时寄存器ft0-ft11f0-f11临时浮点数据,调用方保存
浮点保存寄存器fs0-fs11f12-f31保存浮点数据,被调用方保存
浮点参数寄存器fa0-fa7f10-f17用于传递浮点参数和浮点返回值

2. 整型调用规范

2.1 参数传递:

前 8 个整型或指针参数通过 a0 到 a7 寄存器传递

如果参数超过 8 个,剩余的参数通过堆栈传递,按 8 字节对齐

2.2 返回值:

单个整型返回值存储在 a0

两个整型返回值分别存储在 a0 和 a1

超过两个的返回值通过调用方提供的内存缓冲区返回,指针通过 a0 传递

2.3 调用方职责:

调用方需保存 t0-t6 中的重要数据

在传递参数时,需确保参数按照约定存储到相应寄存器或堆栈

2.4 被调用方职责:

被调用方需保存并恢复 s0-s11

必要时创建新的堆栈帧

3. 浮点调用规范

3.1 参数传递:

前 8 个浮点参数通过 fa0 到 fa7 寄存器传递

如果浮点参数超过 8 个,剩余参数通过堆栈传递,按 8 字节对齐

混合参数中,整型使用 a0-a7,浮点使用 fa0-fa7

3.2 返回值:

单精度浮点返回值存储在 fa0

双精度浮点返回值存储在 fa0

两个浮点返回值分别存储在 fa0 和 fa1

3.3 调用方职责:

保存 ft0-ft11 中的数据避免丢失

3.4 被调用方职责:

保存并恢复 fs0-fs11

4. 堆栈管理

4.1 堆栈对齐:

堆栈指针 sp 必须保持 16 字节对齐

堆栈用于传递参数或保存局部变量时,需保持对齐约定

4.2 堆栈帧结构:

保存返回地址 ra

保存的 s0-s11

局部变量

超出寄存器的参数存放区域

5. 调用与返回

5.1 调用函数:

使用 jal 指令跳转,返回地址存储在 ra

示例:jal ra, func

5.2 函数返回:

使用 ret 指令,等价于 jalr x0, ra, 0

6. 职责对比

6.1 调用方:

保存 t0-t6 和 ft0-ft11

参数压栈并按 16 字节对齐

6.2 被调用方:

保存 s0-s11 和 fs0-fs11

必要时创建堆栈帧


7. 示例

整型函数调用:

# 函数原型:int add(int x, int y);
add:
add a0, a0, a1 # a0 = x + y
ret # 返回结果存储在 a0

浮点函数调用:

# 函数原型:float multiply(float x, float y);
multiply:
fmul.s fa0, fa0, fa1 # fa0 = x * y
ret # 返回结果存储在 fa0

7.1 进入示例

下载测试代码

下载解压并进入示例目录

7.2 编译

make

7.3 运行

make run

运行后LOG如下

qemu-system-riscv64 -nographic -machine virt -m 128M -bios m_mode.bin -device loader,file=s_mode.bin,addr=0x80200000 -kernel s_mode.elf
_____ ____ _____
/ ____| _ \_ _|
| (___ | |_) || |
\ \___ \| _ < | |
____) | |_) || |_
|_|_____/|____/_____|

add: 5
nmultiply: 8.32

详见请参考文档:实验环境搭建。