【问题标题】:Integer describing number of floating point arguments in xmm registers not passed to rax描述 xmm 寄存器中未传递给 rax 的浮点参数数量的整数
【发布时间】:2019-01-13 01:19:16
【问题描述】:

我有一个函数声明如下:

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 位的

【问题讨论】:

  • 请注意,此号码存储在al,而不是rax。事先尝试movzx eax, al 清除高位。
  • 值在al中
  • @MateuszStompór :这是正确的。 RAX 中唯一重要的部分是 AL。其他位可以是任何东西。正如 fuz 建议的那样,使用movzx eax, al 使所有 RAX 等于 AL 中的值。它恰好在 Ubuntu 上,RAX 的高位已经是 0。在你的 Mac 上,它们不是。试试foo:movzx eax, alcvtsi2sd xmm0, raxret。需要注意的是,当指令的目标是 32 位寄存器时,CPU 零会自动扩展到整个 64 位寄存器。
  • 是的,这正是问题所在

标签: linux macos assembly nasm x86-64


【解决方案1】:

x86-64 System V ABI 说 FP 寄存器 arg 计数在 AL 中传递,并且 RAX 的高字节允许包含垃圾。 (与任何窄整数或 FP arg 相同。但另请参阅 this Q&A 关于 clang 假设窄整数 args 零或符号扩展为 32 位。这仅适用于函数 args,不适用于 al。)

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

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

使用 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&lt;&lt; 会产生大量代码)。

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

使用 gcc 和 clang -O3 在 the Godbolt compiler explorer 上编译到这个 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 避免了对不将 AL 与其他 RAX 分开重命名的 CPU 对 RAX 旧值的错误依赖。 (只有 Intel P6 系列和 Sandybridge 能做到这一点,IvyBridge 及更高版本没有。任何 AMD CPU、Pentium 4 或 Silvermont 都没有。)

有关 IvB 及更高版本与 Core2 / Nehalem 有何不同的更多信息,请参阅How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-02
    • 2016-02-15
    • 1970-01-01
    相关资源
    最近更新 更多