Chapter18

Calling Convention

调用约定


C Datatypes and Alignment

C数据类型与对齐

下表总结了RISC-V程序原生支持的数据类型。long和指针的宽度与整数寄存器相同,RV32采用ILP32整数类型,RV64采用LP64,float为32位IEEE 754-2008浮点数,double为64位,long double为128位。charunsigned char等存入整数寄存器时进行零扩展,signed charshort进行符号扩展。在存储时会保持上述数据类型自然对齐。

RVG Calling Convention

RVG调用约定

RISC-V调用约定尽可能通过寄存器传递参数,最多使用八个整数寄存器a0-a7和八个浮点寄存器fa0-fa7来实现。

  • 如果第i<8个参数是浮点类型,则通过浮点寄存器fai传递,否则通过整数寄存器ai传递;

    示例:void func(int a, float b, double c)

    • a -> a0

    • b -> fa0

    • c -> fa1

  • 浮点参数是联合体(union)或结构体数组的字段时,通过整数寄存器传递;

    示例:

    union Data { int i; float f; };
    void foo(union Data d)    // d.f 通过 a0 而非 fa0传递
  • 变参函数(如printf)中的浮点参数(显式声明在参数列表中的除外)通过整数寄存器传递。

    示例:

    printf("%f", 3.14);    // 3.14通过 a0 而非 fa0 传递

对小于指针字大小的参数,通过参数寄存器的最低有效位传递(如char参数占用a0的最低8位)。相应的,在栈上传递的小于指针字大小的参数会出现在指针字的低地址处,这是因为RISC-V采用小端内存系统。

而当原始参数大小是指针字长的两倍时。如果通过栈传递,它们会自然对齐;当通过整数寄存器传递,这些参数会存放在一个对齐的偶数-奇数寄存器对中,其中偶数寄存器存放最低有效位,例如在RV32中,函数void foo(int, long long)的第一个参数通过a0传递,第二个参数通过a2(低32位)和a3(高32位)传递,而未使用a1

对于大小超过指针字长两倍的参数,通过内存引用传递,调用者分配内存并传递指针,例如:

struct Huge { int data[10]; };
void foo(struct Huge h);    // 隐含传递指向 h 的指针

概念中未通过寄存器传递的参数会通过栈传递,栈指针sp指向第一个未通过寄存器传递的参数。

函数返回值通过整数寄存器a0a1和浮点寄存器fa0fa1传递。大部分能容纳在两个指针字长内的返回值都会通过a0a1返回;只有当浮点值是基本浮点类型或者是仅包含一个或两个浮点字段的结构体成员时才会使用浮点寄存器返回;更大的返回值则完全通过内存返回,调用者会分配内存并隐式传递其指针。

在标志的RISC-V调用约定中,栈向下增长,且栈指针始终保持16字节对齐。

除了参数和返回值寄存器外,还有一些其他寄存器。7个整数寄存器t0-t6和12个浮点寄存器fs0-fs11是临时寄存器。它们在调用过程中易失;还有12个整数寄存器s0-s11和12个浮点寄存器fs0-fs11在调用期间保留。

Soft-Float Calling Convention

软浮点调用约定

该约定用于不支持浮点硬件的RV32和RV64实现,它完全避免使用浮点指令和浮点寄存器。

Last updated