Как переместить 128-битные немедленные в регистры XMM - PullRequest
23 голосов
/ 11 июля 2011

Уже есть вопрос по этому вопросу, но он был закрыт как "неоднозначный", поэтому я открываю новый - я нашел ответ, может быть, это поможет и другим.

Вопрос заключается в следующем: как написать последовательность кода сборки для инициализации регистра XMM с помощью 128-битного непосредственного (постоянного) значения?

Ответы [ 5 ]

18 голосов
/ 11 июля 2011

Просто хотел добавить, что о создании различных констант с использованием ассемблера можно прочитать в руководстве Agner Fog Оптимизация подпрограмм на языке ассемблера , Генерация констант, раздел 13.4, с. 121.

10 голосов
/ 11 июля 2011

Вы можете сделать это так, используя всего одну movaps инструкцию:

.section .rodata    # put your constants in the read-only data section
.p2align 4          # align to 16 = 1<<4
LC0:
        .long   1082130432
        .long   1077936128
        .long   1073741824
        .long   1065353216

.text
foo:
        movaps  LC0(%rip), %xmm0

Загрузка с загрузкой данных обычно предпочтительнее встраивания в поток команд, особенно из-за того, сколько инструкцийзанимает.Это несколько дополнительных мопов для выполнения CPU, для произвольной константы, которая не может быть сгенерирована из всех с парой сдвигов.

Если это проще, вы можете поместить константы прямо перед или после функции, котораявы jit-компилируете, а не в отдельном разделе.Но поскольку процессоры разделяют кэши L1d / L1i и TLB, обычно лучше группировать константы вместе отдельно от инструкций.

Если обе половины вашей константы совпадают, вы можете широковещательно загрузить ее с помощью SSE3
movddup (m64), %xmm0.

7 голосов
/ 14 июня 2012

В качестве одного из 10000 способов сделать это используйте SSE4.1 pinsrq

mov    rax, first half
movq   xmm0, rax      ; better than pinsrq xmm0,rax,0 for performance and code-size

mov    rax, second half
pinsrq xmm0, rax, 1
6 голосов
/ 12 июля 2011

Существует несколько способов встраивания констант в поток команд:

  1. с использованием непосредственных операндов
  2. при загрузке с ПК-относительных адресов

Так что, хотя нет способа сделать немедленную загрузку в регистр XMM, можно выполнить относительную загрузку ПК (в 64-битной системе) из значения, хранящегося «прямо рядом» с код выполняется. Это создает что-то вроде:

.align 4
.val:
    .long   0x12345678
    .long   0x9abcdef0
    .long   0xfedbca98
    .long   0x76543210
func:
     movdqa .val(%rip), %xmm0

При разборке:

0000000000000000 :
   0:   78 56 34 12 f0 de bc 9a
   8:   98 ca db fe 10 32 54 76

0000000000000010 :
  10:   66 0f 6f 05 e8 ff ff    movdqa -0x18(%rip),%xmm0        # 0 

что является очень компактным , 23 байта.

Другие варианты - построить значение в стеке и снова загрузить его оттуда. В 32-битной x86, где у вас нет доступа к памяти, относящейся к %rip, это можно сделать за 24 байта (при условии, что указатель стека выровнен при входе; в противном случае требуется загрузка без выравнивания):

00000000 :
   0:   68 78 56 34 12          push   $0x12345678
   5:   68 f0 de bc 9a          push   $0x9abcdef0
   a:   68 98 ca db fe          push   $0xfedbca98
   f:   68 10 32 54 76          push   $0x76543210
  14:   66 0f 6f 04 24          movdqa (%esp),%xmm0

В 64-битном режиме (выравнивание стека указателем при входе в функцию там гарантируется ABI), что заняло бы 27 байт:

0000000000000000 :
   0:   48 b8 f0 de bc 9a 78 56 34 12   movabs $0x123456789abcdef0,%rax
   a:   50                              push   %rax
   b:   48 b8 10 32 54 76 98 ba dc fe   movabs $0xfedcba9876543210,%rax
  15:   50                              push   %rax
  16:   66 0f 6f 04 24                  movdqa (%rsp),%xmm0

Если вы сравните любой из них с версией MOVLHPS, вы заметите, что она самая длинная:

0000000000000000 :
   0:   48 b8 f0 de bc 9a 78 56 34 12   movabs $0x123456789abcdef0,%rax
   a:   66 48 0f 6e c0                  movq   %rax,%xmm0
   f:   48 b8 10 32 54 76 98 ba dc fe   movabs $0xfedcba9876543210,%rax
  19:   66 48 0f 6e c8                  movq   %rax,%xmm1
  1e:   0f 16 c1                        movlhps %xmm1,%xmm0

при 33 байтах.

Другое преимущество загрузки непосредственно из памяти команд состоит в том, что movdqa не зависит от чего-либо предыдущего. Скорее всего, первая версия, предоставленная @Paul R, самая быстрая, какую только можно получить.

6 голосов
/ 11 июля 2011

Лучшее решение (особенно если вы хотите придерживаться SSE2 - т.е. избегать использования AVX) для инициализации двух регистров (скажем, xmm0 и xmm1) с двумя 64-битными половинками вашего непосредственного значения, выполните MOVLHPS xmm0, xmm1Чтобы инициализировать 64-разрядное значение, самое простое решение - использовать регистр общего назначения (скажем, AX), а затем использовать MOVQ для передачи его значения в регистр XMM.Таким образом, последовательность будет выглядеть примерно так:

MOV RAX, <first_half>
MOVQ XMM0, RAX
MOV RAX, <second_half>
MOVQ XMM1, RAX
MOVLHPS XMM0,XMM1
...