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

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

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

5 ответов

Решение

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

Вы можете сделать это так, только с одним 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,

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

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

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

  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 (% ЭСП),% XMM0

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

0000000000000000:
   0: 48 b8 f0 de bc 9a 78 56 34 12 movabs $ 0x123456789abcdef0,% rax
   A: 50% толщины
   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, самая быстрая, какую только можно получить.

Другие вопросы по тегам