128-битные значения-от регистров XMM до общего назначения

у меня есть несколько вопросов, связанных с перемещением значений 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-бит) в четыре 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 ответов


вы не можете переместить верхние биты регистра 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 register

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 register

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

64-разрядный вариант сдвига может работать в 2 циклах. The pextrq версия принимает минимум 4. Для 32-разрядных чисел это 4 и 10 соответственно.


на Intel SnB-family (включая Skylake), shuffle+movq или movd имеет ту же производительность, что и pextrq/d. Он декодирует до перетасовки uop и A movd uop, так что это не удивительно.

на AMD Ryzen,pextrq по-видимому, имеет 1 цикл более низкую задержку, чем shuffle + movq. pextrd/q является задержкой 3c, а также movd/q, по данным таблицы Агнера Фога. Это аккуратный трюк (если он точен), так как pextrd/q декодирует до 2 uops (против 1 для movq).

поскольку перетасовки имеют ненулевую задержку, перетасовка+movq всегда строго хуже, чем pextrq на Ryzen (за исключением возможных интерфейсных эффектов декодирования / uop-cache).

основным недостатком чистой стратегии ALU для извлечения всех элементов является пропускная способность: требуется много uops ALU, и большинство процессоров имеют только один блок / порт выполнения, который может перемещать данные из XMM в integer. Store/reload имеет более высокую задержку для первого элемента, но лучшую пропускную способность (потому что современные процессоры могут выполнять 2 нагрузки за цикл). Если окружающий код ограничен пропускной способностью ALU, стратегия хранения/перезагрузки может быть хорошей. Возможно, сделайте низкий элемент с movd или movq таким образом, выполнение вне заказа может начать работу с тем, что использует его, в то время как остальные векторные данные проходят через переадресацию магазина.


еще один вариант стоит рассмотреть (помимо того, что упомянул Йохан) для извлечения 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 может работать на p0 или p6 в Intel Haswell/Skylake. p6 не имеет векторных ALUs, поэтому эта последовательность довольно хороша, если вы хотите низкую задержку, но и низкое давление на векторные ALUs.


или если вы хотите, чтобы держать их вокруг:

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]

следующие дескрипторы 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     ,     %%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;
}