描述 xmm 寄存器中未传递给 rax 的浮点参数数量的整数

Integer describing number of floating point arguments in xmm registers not passed to rax

我有一个函数声明如下:

double foo(int ** buffer, int size, ...);

该函数是程序的cpp实现的一部分。

我使用最后一个参数将多个双精度变量传递给函数。

问题是在 Mac 上我没有在 rax 寄存器中收到有效数字,另一方面在 ubuntu 上它按预期工作。

一个简单的例子:

CPP

#include <iostream>
extern "C" double foo(int ** buffer, int buffer_size, ...);

int main() {
    int* buffer [] = {new int(2), new int(3), new int(4)};
    std::cout<< foo(buffer, 2, 1.0, 2.0, 3.0) << '\n';
    std::cout<< foo(buffer, 3, 2.0, 3.0) << '\n';
    std::cout<< foo(buffer, 3) << '\n';
}

大会,NASM2

global foo

section .text

foo:
    cvtsi2sd xmm0, rax
    ret

Mac 输出:

1.40468e+14
1.40736e+14
1.40736e+14

Ubuntu 输出:

3
2
0

程序是 64 位的

x86-64 系统 V ABI 表示 FP 寄存器 arg 计数在 AL 中传递,RAX 的高位字节允许包含垃圾。 (与任何窄整数或 FP arg 相同。但另见 关于 clang 假设窄整数 args 的零或符号扩展到 32 位。这仅适用于函数 args 本身,而不适用于 al。 )

使用movzx eax, al 将 AL 零扩展为 RAX。 (与写入 8 位或 16 位寄存器不同,写入 EAX 隐式零扩展到 RAX。)

如果有另一个整数寄存器你可以破坏,使用movzx ecx,al所以英特尔 CPU 上的移动消除可以工作,使其零延迟并且不需要执行端口.当 src 和 dst 是同一寄存器的一部分时,Intel 的 mov-elimination 失败。

使用 64 位源转换为 FP 的好处也为零。 cvtsi2sd xmm0, eax 短了一个字节(没有 REX 前缀),并且在零扩展到 EAX 之后你知道 cvtsi2sd 使用的 EAX 和 RAX 的带符号 2 的补码解释是相同的。


在您的 Mac 上,clang/LLVM 选择将垃圾留在 RAX 的高位字节中。 LLVM 的优化器在避免错误依赖方面不像 gcc 那样谨慎,因此它有时会写入部分寄存器。 (有时即使它不节省代码大小,但在这种情况下它会)。

根据您的结果,我们可以得出结论,您在 Mac 上使用了 clang,在 Ubuntu 上使用了 gcc 或 ICC。

从一个简化的示例中查看编译器生成的 asm 更容易(newstd::cout::operator<< 导致大量代码)。

extern "C" double foo(int, ...);
int main() {
    foo(123, 1.0, 2.0);
}

the Godbolt compiler explorer 上用 gcc 和 clang -O3 编译成这个 asm:

### clang7.0 -O3
.section .rodata
.LCPI0_0:
    .quad   4607182418800017408     # double 1
.LCPI0_1:
    .quad   4611686018427387904     # double 2

.text
main:                                   # @main
    push    rax                  # align the stack by 16 before a call
    movsd   xmm0, qword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero
    movsd   xmm1, qword ptr [rip + .LCPI0_1] # xmm1 = mem[0],zero
    mov     edi, 123
    mov     al, 2                # leave the rest of RAX unmodified
    call    foo
    xor     eax, eax             # return 0
    pop     rcx
    ret

GCC 发出基本相同的东西,但

 ## gcc8.2 -O3
    ...
    mov     eax, 2               # AL = RAX = 2   FP args in regs
    mov     edi, 123
    call    foo
    ...

mov eax,2 而不是 mov al,2 避免了对 RAX 的旧值的错误依赖,在不将 AL 与 RAX 的其余部分分开重命名的 CPU 上 . (只有 Intel P6 系列和 Sandybridge 这样做,IvyBridge 和更高版本不这样做。而且不是任何 AMD CPU、Pentium 4 或 Silvermont。)

有关 IvB 及更高版本与 Core2 / Nehalem 有何不同的更多信息,请参阅