双重否定的奇怪 SSE 汇编指令

Weird SSE assembler instructions for double negation

GCC 和 Clang 编译器似乎使用了一些黑魔法。 C 代码只是否定双精度值,但汇编指令涉及按位 XOR 和指令指针。有人可以解释发生了什么以及为什么它是最佳解决方案。谢谢。

test.c的内容:

void function(double *a, double *b) {
    *a = -(*b); // This line.
}

生成的汇编程序指令:

(gcc)
0000000000000000 <function>:
 0: f2 0f 10 06             movsd  xmm0,QWORD PTR [rsi]
 4: 66 0f 57 05 00 00 00    xorpd  xmm0,XMMWORD PTR [rip+0x0]        # c <function+0xc>
 b: 00 
 c: f2 0f 11 07             movsd  QWORD PTR [rdi],xmm0
10: c3                      ret 

(clang)
0000000000000000 <function>:
 0: f2 0f 10 06             movsd  xmm0,QWORD PTR [rsi]
 4: 0f 57 05 00 00 00 00    xorps  xmm0,XMMWORD PTR [rip+0x0]        # b <function+0xb>
 b: 0f 13 07                movlps QWORD PTR [rdi],xmm0
 e: c3                      ret    

地址0x4处的汇编指令表示"This line",但我不明白它是如何工作的。 xorpd/xorps 指令应该是按位 XOR 并且 PTR [rip] 是指令指针。

我怀疑在执行时 rip 指向 0f 57 05 00 00 00 0f 字节带附近的某处,但我不太清楚,这是如何工作的以及为什么两个编译器选择这种方法。

P.S。我应该指出,这是使用 -O3

编译的

xorps xmm0,XMMWORD PTR [rip+0x0]

[]包围的指令的任何部分都是对内存的间接引用。 在这种情况下,对地址 RIP+0
处的内存的引用 (我怀疑它实际上是 RIP+0,您可能已经编辑了实际偏移量)

X64指令集增加instruction pointer relative addressing。这意味着您可以在程序中拥有(通常是只读的)数据,即使程序在内存中四处移动,您也可以轻松访问这些数据。

A XOR xmm0,Y 反转在 Y.
中设置的 xmm0 中的所有位 否定涉及反转符号位,因此这就是使用 xor 的原因。特别是 xorpd/s 因为我们正在处理双重响应。单花车。

对我来说,使用相同代码的 -S -O3 选项的 gcc 的输出是:

    .file   "test.c"
    .text
    .p2align 4,,15
    .globl  function
    .type   function, @function
function:
.LFB0:
    .cfi_startproc
    movsd   (%rsi), %xmm0
    xorpd   .LC0(%rip), %xmm0
    movsd   %xmm0, (%rdi)
    ret
    .cfi_endproc
.LFE0:
    .size   function, .-function
    .section    .rodata.cst16,"aM",@progbits,16
    .align 16
.LC0:
    .long   0
    .long   -2147483648
    .long   0
    .long   0
    .ident  "GCC: (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406"
    .section    .note.GNU-stack,"",@progbits

这里的xorpd指令使用指令指针相对寻址,偏移量指向.LC0标签,64位值0x8000000000000000(第63位设置为1)。

.LC0:
    .long   0
    .long   -2147483648

如果你的编译器是 big endian 这些行交换了。

将双精度值与 0x8000000000000000 进行异或运算,将符号位(第 63 位)设置为 1 表示负值。

clang 使用 xorps 指令以相同的方式异或 double 值的前 32 位。

如果您 运行 带有 -r 选项的对象转储,它将向您显示在 运行 启动程序之前应该在程序上完成的重定位。

objdump -d test.o -r

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <function>:
   0:   f2 0f 10 06             movsd  (%rsi),%xmm0
   4:   66 0f 57 05 00 00 00    xorpd  0x0(%rip),%xmm0        # c <function+0xc>
   b:   00 
            8: R_X86_64_PC32    .LC0-0x4
   c:   f2 0f 11 07             movsd  %xmm0,(%rdi)
  10:   c3                      retq   

Disassembly of section .text.startup:

0000000000000000 <main>:
   0:   31 c0                   xor    %eax,%eax
   2:   c3                      retq   

这里 <function + 0xb> 我们有一个 R_X86_64_PC32 类型的重定位。

PS: 我正在使用 gcc 6.3.0