当然,在增加了x64扩展这个特性之后,FPU在x86兼容处理器中还是存在的。但是同事,SIMD扩展(SSE, SSE2等)已经有了,他们也可以处理浮点数。数字格式依然相同(使用IEEE754标准)。

    所以,x86-64编译器通常都使用SIMD指令。可以说这是一个好消息,因为这让我们可以更容易的使用他们。 24.1 简单的例子

    清单24.1: MSFC 2012 x64 /Ox

    1. __real@40091eb851eb851f DQ 040091eb851eb851fr ; 3.14
    2. a$ = 8
    3. b$ = 16
    4. f PROC
    5. divsd xmm0, QWORD PTR __real@40091eb851eb851f
    6. mulsd xmm1, QWORD PTR __real@4010666666666666
    7. addsd xmm0, xmm1
    8. ret 0
    9. f ENDP

    输入的浮点数被传入了XMM0-XMM3寄存器,其他的通过栈来传递。 a被传入了XMM0,b则是通过XMM1。 XMM寄存器是128位的(可以参考SIMD22一节),但是我们的类型是double型的,也就意味着只有一半的寄存器会被使用。

    这是无优化的MSVC编译器的结果:

    清单24.2: MSVC 2012 x64

    有一些繁杂,输入参数保存在“shadow space”(影子空间,7.2.1节),但是只有低一半的寄存器,也即只有64位存了这个double的值。

    GCC编译器生成了几乎一样的代码。

    1. #include <math.h>
    2. {
    3. printf ("32.01 ^ 1.54 = %lf\n", pow (32.01,1.54));
    4. return 0;
    5. }

    他们通过XMM0-XMM3的低一半寄存器传递。

    清单24.3: MSVC 2012 x64 /Ox

    在Intel和AMD的手册中(见14章和1章)并没有MOVSDX这个指令,而只有MOVSD一个。所以在x86中有两个指令共享了同一个名字(另一个见B.6.2)。显然,微软的开发者想要避免弄得一团糟,所以他们把它重命名为MOVSDX,它只是会多把一个值载入XMM寄存器的低一半中。 pow()函数从XMM0和XMM1中加载参数,然后返回结果到XMM0中。 然后把值移动到RDX中,因为接下来printf()需要调用这个函数。为什么?老实说我也不知道,也许是因为printf()是一个参数不定的函数?

    清单24.4:GCC 4.4.6 x64 -O3

    1. .LC2:
    2. .string "32.01 ^ 1.54 = %lf\n"
    3. main:
    4. sub rsp, 8
    5. movsd xmm1, QWORD PTR .LC0[rip]
    6. movsd xmm0, QWORD PTR .LC1[rip]
    7. mov edi, OFFSET FLAT:.LC2
    8. mov eax, 1 ; number of vector registers passed
    9. call printf
    10. xor eax, eax
    11. add rsp, 8
    12. ret
    13. .LC0:
    14. .long 171798692
    15. .long 1073259479
    16. .LC1: