描述 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 更容易(new
和 std::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 有何不同的更多信息,请参阅 。
我有一个函数声明如下:
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 相同。但另见 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 更容易(new
和 std::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 有何不同的更多信息,请参阅