为什么 %rax 寄存器在这个程序的程序集中使用 8 个参数?

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;)中第一行代码的翻译。

我错过了什么?

Pet*_*des 7

它只是使用临时寄存器来加载堆栈参数。 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 -> 存储。