考虑下面的C代码:
void foo(void);
long bar(long x) {
foo();
return x;
}
当我在GCC 9.3上使用-O3
或编译它时-Os
,得到以下信息:
bar:
push r12
mov r12, rdi
call foo
mov rax, r12
pop r12
ret
clang的输出是相同的,除了选择rbx
而不是r12
作为被调用者保存的寄存器。
但是,我希望/希望看到看起来像这样的程序集:
bar:
push rdi
call foo
pop rax
ret
用英语,这就是我所看到的:
- 将已保存被调用者的寄存器的旧值推入堆栈
- 移动
x
到该被调用方保存寄存器 - 呼叫
foo
- 移动
x
从被调用者被保存的寄存器到返回值寄存器 - 弹出堆栈以恢复被调用方保存的寄存器的旧值
为什么要弄乱所有保存在被调用方中的寄存器?为什么不这样做呢?它看起来更短,更简单,并且可能更快:
- 推
x
入栈 - 呼叫
foo
x
从堆栈弹出到返回值寄存器
我的大会错了吗?它比以多余的寄存器搞乱效率低吗?如果对这两个问题的回答都是“否”,那么为什么GCC或clang都不这样做呢?
编辑:这是一个比较简单的示例,以显示即使变量被有意义地使用也会发生:
long foo(long);
long bar(long x) {
return foo(x * x) - x;
}
我得到这个:
bar:
push rbx
mov rbx, rdi
imul rdi, rdi
call foo
sub rax, rbx
pop rbx
ret
我宁愿这样:
bar:
push rdi
imul rdi, rdi
call foo
pop rdi
sub rax, rdi
ret
这次,只有两条指令,只有一条指令,但是核心概念是相同的。