x86 - 寄存器分配 --- 如何利用和溢出调用者保存的寄存器
问题描述
我了解到,如果caller saved registers (rax rdx rcx rsi rdi r8 r9 r10 r11)
被调用者使用了任何一个,那么它必须在调用者指令之前保存并call
在调用者指令之后恢复。
通过下面的例子,
int read();
void print(int i);
int main()
{
int a = read();
int b = read();
int c = read();
int d = read();
int e = read();
int f = read();
int g = read();
print(a);
print(b);
print(c);
print(d);
print(e);
print(f);
print(g);
}
笔记
变量
a - g
应使用所有callee saved registers (rbp rsp rbx r12 r13 r14 r15)
. 而且我们不能同时使用rbp
或rsp
,因为必须使用其中任何一个来寻址堆栈内存。和
read
来自print
一些外部编译单元。main
因此,当我们编译当前编译单元时,特别是在为函数分配寄存器期间,我们并不真正了解它们的调用者保存寄存器的使用情况。
在godbolt中,-O3
它编译为以下
main:
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbp
pushq %rbx
subq $24, %rsp # spill here
call read()
movl %eax, 12(%rsp) # spill here
call read()
movl %eax, %ebx
call read()
movl %eax, %r15d
call read()
movl %eax, %r14d
call read()
movl %eax, %r13d
call read()
movl %eax, %r12d
call read()
movl 12(%rsp), %edi
movl %eax, %ebp
call print(int)
movl %ebx, %edi
call print(int)
movl %r15d, %edi
call print(int)
movl %r14d, %edi
call print(int)
movl %r13d, %edi
call print(int)
movl %r12d, %edi
call print(int)
movl %ebp, %edi
call print(int)
addq $24, %rsp
xorl %eax, %eax
popq %rbx
popq %rbp
popq %r12
popq %r13
popq %r14
popq %r15
ret
笔记
变量
a
溢出到12(%rsp)
.我们不需要溢出任何东西,
caller saved registers
因为它们根本没有被使用,结果证明在这里更有效。
我的问题
caller saved registers
如果我们不使用它们,看起来我们真的不需要处理溢出的问题。因此,我们什么时候应该使用caller saved registers
?对于像这样的被调用者,
read
并且print
由于我们不知道他们的寄存器使用情况,我们应该如何为caller saved registers
?
谢谢
解决方案
看起来令人困惑且不直观的“调用者保存/被调用者保存”术语误导您认为每个寄存器都应该始终由某个地方的某个人保存。请参阅什么是被调用者和调用者保存的寄存器?- “呼叫保留”与“呼叫破坏”在易于记忆和作为心理模型方面更有用。让值被破坏是正常的,就像函数 arg 一样。
看起来我们真的不需要处理溢出调用者保存的寄存器,如果我们不使用它们。
请注意,您的函数确实使用了几个 call-clobbered(“调用者保存”)寄存器:它使用 RDI 将 arg 传递给print(int)
,并将 RAX 归零作为 main 的返回值。
如果它在调用破坏寄存器中有一个值需要在函数调用中存活,GCC 选择mov
将该值保存到调用保留寄存器。例如,当read()
返回时,它的返回值在 EAX 中,它会被下一次调用销毁。因此,它使用mov %eax, %ebp
或其他任何方式将其保存到保留调用的寄存器中,或者将一个溢出到12(%rsp)
.
(请注意,GCC 使用 push/pop 来保存/恢复它使用的调用保留寄存器的调用者值。)
GCC 的默认代码生成策略是保存/恢复调用保留的寄存器以在调用之间保存值,而不是溢出到该函数内部的内存中。对于不那么琐碎的情况,这通常是一件好事,尤其是对于循环内的调用。请参阅为什么编译器在这里坚持使用被调用者保存的寄存器?有关更多信息。
而且我们不能同时使用 rbp 或 rsp,因为必须使用其中任何一个来寻址堆栈内存。
也是错误的:使用-fomit-frame-pointer
(在大多数优化级别上),RBP 只是另一个保留调用的寄存器。您的函数使用它来保存read
返回值。(EBP 是 RBP 的低 32 位)。
推荐阅读
- html - 即使页面半空,如何将页脚设置为底部?
- fish - 打开 vim 模式,为什么 gU 将当前单词变为大写而不是等待移动?
- python - 如何修复 python rest api 上的错误 403?
- csl - 如何更改 CSL 中的折叠引用范围分隔符?
- javascript - 使用 Cypress 测试 wordpress 网站
- firebase - 为 Firebase 应用程序授权应用引擎版本
- javascript - 使用jquery每个函数循环项目并将项目包装到div容器中
- flutter - 有没有办法从飞镖/颤振中的音频中分割左右声道?
- c# - 自定义 IdentityDb 上下文以使用 ApplicationRole
- r - 如何在 ggplot 瀑布图上添加误差线?