Warm tip: This article is reproduced from serverfault.com, please click

x86 64-为什么在程序集中将%rax寄存器用于带有8个参数的此过程?

(x86 64 - Why is the %rax register used in the assembly for this procedure with 8 args?)

发布于 2020-12-09 03:42:31

我有以下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;
}

使用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

我对汇编的第一行感到困惑movq 16(%rsp), %rax我知道该%rax寄存器用于存储返回值。但是该proc过程没有返回值。因此,我很好奇为什么在这里 %r9使用该寄存器,而不是使用某些不用于返回值的寄存器。

我对这条指令相对于其他指令的位置也感到困惑。它首先出现,很早就%rax需要它的目标寄存器了(实际上,直到最后一步才需要此寄存器)。它也出现在之前addq %rdi, (%rsi),它是过程(*a1p += a1;)中第一行代码的翻译

我想念什么?

Questioner
Richie Thomas
Viewed
0
Peter Cordes 2020-12-09 21:52:51

它只是使用临时寄存器加载堆栈arg。 RAX是暂存注册表的首选。该函数没有返回值,因此RAX并不特殊。

通常,提前计划负载是隐藏负载使用延迟的好主意,因此,无序的exec不必费劲地隐藏它。请记住,这是优化的代码,因此每个C语句的指令都不是单独的单个块。对于一些这个简单的,这是很好的(未优化将一切存储到堆栈中,然后重新装入。另请参见

R9将是一个较差的选择,因为R9已被函数入口占用(带有另一个arg),从而限制了指令调度。更重要的是,因为addb %dl, (%r9)不需要REX前缀addb %dl, (%rax)因此,这将浪费代码大小。

已经在使用的缺点不适用于R10或R11(像RAX一样,它们只是调用对象,但不用于arg传递),但是代码大小的缺点仍然适用。

R9B甚至没有任何意义。堆栈arg是一个指针。char a4加载到EDX之后,唯一使用的字节寄存器是DL()。

(双字加载避免编写部分寄存器,并且不需要movzx / movzbl,因为调用者通常会写整个qword或至少是dword,即使对于窄args也是如此)。

编译器也可以早些移动此负载,但选择不移动。但是add %dl, (%rax)RMW处于上(%rax),因此在dl加载之前(%rax)已准备好数据之前,不需要数据。尽早准备好RAX地址比DL数据更有价值,因为该地址正用于其他加载而不是ALU->存储。