Ric*_*mas 0 assembly x86-64 cpu-registers compiler-optimization
我有以下 C 函数:
void proc(long a1, long *a1p,
int a2, int *a2p,
short a3, short *a3p,
char a4, char *a4p)
{
*a1p += a1;
*a2p += a2;
*a3p += a3;
*a4p += a4;
}
Run Code Online (Sandbox Code Playgroud)
使用Godbolt,我已将其转换为 x86_64 程序集(为简单起见,我使用该-Og标志来最小化优化)。它产生以下组件:
proc:
movq 16(%rsp), %rax
addq %rdi, (%rsi)
addl %edx, (%rcx)
addw %r8w, (%r9)
movl 8(%rsp), %edx
addb %dl, (%rax)
ret
Run Code Online (Sandbox Code Playgroud)
我对汇编的第一行感到困惑:movq 16(%rsp), %rax. 我知道%rax寄存器用于存储返回值。但是该proc过程没有返回值。所以我很好奇为什么在此处使用该寄存器,而不是 %r9其他一些不用于返回值的寄存器。
相对于其他指令,我也对这条指令的位置感到困惑。它首先出现,远在%rax任何需要其目标寄存器之前(实际上,直到最后一步才需要该寄存器)。它也出现在 之前addq %rdi, (%rsi),也就是过程(*a1p += a1;)中第一行代码的翻译。
我错过了什么?
它只是使用临时寄存器来加载堆栈参数。 RAX 是划痕版的首选。这个函数没有返回值,所以 RAX 并不特殊。
提前调度负载通常是隐藏负载使用延迟的好主意,因此无序执行程序不必努力隐藏它。请记住,这是优化代码,因此每个 C 语句的指令不是单独的单个块。对于这么简单的事情,这很好(未优化会将所有内容存储到堆栈中,然后重新加载它。另见this)
R9 将是一个更糟糕的选择,因为它已经在函数入口(与另一个 arg)占用,限制了指令调度。更重要的是因为addb %dl, (%r9)需要一个 REX 前缀而不需要addb %dl, (%rax)。所以它会浪费代码大小。
已经在使用的缺点不适用于 R10 或 R11(像 RAX 它们是纯粹的调用破坏但不用于 arg 传递),但代码大小的缺点仍然适用。
R9B 甚至没有意义;堆栈 arg 是一个指针。char a4加载到 EDX 后,唯一使用的字节寄存器是 DL ( )。
(双字加载避免写入部分寄存器,并且不需要 movzx / movzbl 因为调用者通常会写入整个 qword,或者至少是 dword,即使对于窄参数也是如此)。
编译器也可以更早地移动这个负载,但选择不这样做。但是add %dl, (%rax)是 RMW on (%rax),因此在dl加载(%rax)准备好数据之前不需要数据。提前准备好RAX地址比 DL 数据更有价值,因为该地址正在用于另一个加载,而不是 ALU -> 存储。
| 归档时间: |
|
| 查看次数: |
384 次 |
| 最近记录: |