关于为什么要区分caller saved和callee saved registers

yuanheci 2023年09月19日 169次浏览

  主要是如果把所有的寄存器都压栈和出栈,一方面是性能下降,毕竟处理器访问内存的时间是很长的,一个无用的寄存器做保护,压栈和出栈相比计算本身很慢;另一方面,也占用了内存,当函数调用栈很深或者出现递归的时候,就会更加明显。

  从较深层的原因去分析,为什么会出现caller savecallee save

  在程序中,有的声明的变量,有的是临时变量,声明的变量往往会alive很久,但临时变量用完即废,举个例子:

int a, b, c, d;

a = b - c + d;

...

  上面的a b c d是声明出来的变量,假如分配的寄存器是r0~r3,计算a的值,处理器无法一条指令完成,中间结果需要使用临时的变量进行存储,比如tmp,分配了t0寄存器:

首先机选tmp = b - c

sub r1, r2, t0;

然后再计算a = tmp + d;

add t0, r3, r0;

…(tmp生命期到此为止,t0也可以释放)

  你会发现tmp这中临时的变量,生命期非常短,有专门的寄存器来保存这种临时变量,这种寄存器里面的值总是易变的,用完即废(保存完tmp,接下来又保存tmp1,tmp2…);

  所以函数调用过程中,对于这两类寄存器,有着不同的保护责任方。前面这种寄存器,如果在callee里面改了,不做保护的话,caller里面声明的变量的值都变了,破坏了功能,所以这类寄存器,callee使用了一个就要保护一个;但针对第二类的寄存器,临时变量用完即废,callee破坏了也可能没啥影响,callee就没义务保护,但是如果caller确实希望保存这个临时变量,那么必须由caller声明进行保护。

  具体的calling convention,受保护的寄存器将由callee负责保护,而不受保护的寄存器,将由caller根据情况而定声明保护。

RISC-V的情况如下图:
image-1695109805724