Как перемещать 128-битные операторы в регистры XMM

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

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

Ответы

Ответ 2

Вы можете сделать это так, используя только одну инструкцию 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

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

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

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

Ответ 3

Как один из 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

Ответ 4

Лучшее решение (особенно если вы хотите придерживаться 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

Ответ 5

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

  • с помощью непосредственных операндов
  • путем загрузки с адресов, относящихся к ПК.

Таким образом, хотя нет возможности выполнить немедленную загрузку в регистр XMM, можно выполнить перенос PC-относительной нагрузки (на 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, является самой быстрой, которую вы можете получить.