Как переместить 128-битные немедленные в регистры XMM
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, обычно лучше группировать константы вместе отдельно от инструкций.
Если обе половинки вашей константы совпадают, вы можете передать ее с помощью SSE3movddup (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
Существует несколько способов встраивания констант в поток команд:
- используя непосредственные операнды
- путем загрузки с адресов, относящихся к ПК
Так что пока нет возможности сделать немедленную загрузку в 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, самая быстрая, какую только можно получить.