128位值 - 从XMM寄存器到通用

时间:2017-05-17 07:44:53

标签: assembly x86 sse

我有几个与将XMM值移动到通用寄存器有关的问题。在SO上找到的所有问题都集中在相反的方面,即将gp寄存器中的值传递给XMM。

  1. 如何将XMM寄存器值(128位)移动到两个64位通用寄存器?

    movq RAX XMM1 ; 0th bit to 63th bit
    mov? RCX XMM1 ; 64th bit to 127th bit
    
  2. 同样,如何将XMM寄存器值(128位)移动到4个32位通用寄存器?

    movd EAX XMM1 ; 0th bit to 31th bit
    mov? ECX XMM1 ; 32th bit to 63th bit
    
    mov? EDX XMM1 ; 64th bit to 95th bit
    mov? ESI XMM1 ; 96th bit to 127 bit
    

3 个答案:

答案 0 :(得分:11)

您不能直接将XMM寄存器的高位移动到通用寄存器中 您必须遵循两个步骤,这可能涉及也可能不涉及到记忆的往返或破坏登记。

寄存器

movq rax,xmm0       ;lower 64 bits
movhlps xmm0,xmm0   ;move high 64 bits to low 64 bits.
movq rbx,xmm0       ;high 64 bits.

通过内存

movdqu [mem],xmm0
mov rax,[mem]
mov rbx,[mem+8]

慢,但不会破坏xmm寄存器

mov rax,xmm0
pextrq rbx,xmm0,1        ;3 cycle latency on Ryzen!

对于32位,代码类似:

寄存器

movd eax,xmm0
psrldq xmm0,xmm0,4    ;shift 4 bytes to the right
movd ebx,xmm0
psrldq xmm0,xmm0,4
movd ecx,xmm0
psrlq xmm0,xmm0,4
movd edx,xmm0

通过内存

movdqu [mem],xmm0
mov eax,[mem]
mov ebx,[mem+4]
mov ecx,[mem+8]
mov edx,[mem+12]

慢,但不会破坏xmm寄存器

mov eax,xmm0
pextrd ebx,xmm0,1        ;3 cycle latency on Skylake!
pextrd ecx,xmm0,2       
pextrd edx,xmm0,3       

64位移位变体可以在2个周期内运行。 pextrq版本最少需要4个版本。对于32位,数字分别为4和10。

答案 1 :(得分:1)

在Intel SnB系列(包括Skylake)上,随机播放+ movqmovdpextrq / d具有相同的效果。它解码为一个shuffle uop和一个movd uop,所以这并不奇怪。

在AMD Ryzen上,pextrq显然比shuffle + movq的延迟低1个周期。根据{{​​3}},pextrd/q是3c延迟,movd/q也是{1}}。这是一个巧妙的技巧(如果它是准确的),因为pextrd/q确实解码为2 uops(而movq为1)。

由于shuffle具有非零延迟,因此Shuffle + movq总是严格地比Ryzen上的pextrq严重(除了可能的前端解码/ uop-cache效果)。

用于提取所有元素的纯ALU策略的主要缺点是吞吐量:它需要大量ALU uop,并且大多数CPU只有一个执行单元/端口可以将数据从XMM移动到整数。存储/重新加载对于第一个元素具有更高的延迟,但是更好的吞吐量(因为现代CPU可以在每个周期执行2次加载)。如果周围的代码受到ALU吞吐量的瓶颈,则存储/重新加载策略可能会很好。也许使用movdmovq来执行低元素,这样无序执行可以在任何使用它时启动,而其余的矢量数据通过存储转发。

另一个值得考虑的选择(除了Johan提到的)将32位元素提取到整数寄存器是用整数移位做一些“改组”:

mov  rax,xmm0
# use eax now, before destroying it
shr  rax,32    

pextrq rcx,xmm0,1
# use ecx now, before destroying it
shr  rcx, 32

shr可以在Intel Haswell / Skylake的p0或p6上运行。 p6没有矢量ALU,所以如果你想要低延迟但是对矢量ALU压力很低,这个序列非常好。

或者如果你想保留它们:

mov  rax,xmm0
rorx rbx, rax, 32    # BMI2
# shld rbx, rax, 32  # alternative that has a false dep on rbx
# eax=xmm0[0], ebx=xmm0[1]

pextrq rdx,xmm0,1
mov  ecx, edx     # the "normal" way, if you don't want rorx or shld
shr  rdx, 32
# ecx=xmm0[2], edx=xmm0[3]

答案 2 :(得分:-1)

以下处理get和set并且似乎有效(我认为它的AT& T语法):

#include <iostream>

int main() {
    uint64_t lo1(111111111111L);
    uint64_t hi1(222222222222L);
    uint64_t lo2, hi2;

    asm volatile (
            "movq       %3,     %%xmm0      ; " // set high 64 bits
            "pslldq     $8,     %%xmm0      ; " // shift left 64 bits
            "movsd      %2,     %%xmm0      ; " // set low 64 bits
                                                // operate on 128 bit register
            "movq       %%xmm0, %0          ; " // get low 64 bits
            "movhlps    %%xmm0, %%xmm0      ; " // move high to low
            "movq       %%xmm0, %1          ; " // get high 64 bits
            : "=x"(lo2), "=x"(hi2)
            : "x"(lo1), "x"(hi1)
            : "%xmm0"
    );

    std::cout << "lo1: [" << lo1 << "]" << std::endl;
    std::cout << "hi1: [" << hi1 << "]" << std::endl;
    std::cout << "lo2: [" << lo2 << "]" << std::endl;
    std::cout << "hi2: [" << hi2 << "]" << std::endl;

    return 0;
}