Расширения SSE/AVX предоставляют ряд инструкций для умножения чисел:
pmullw: перемножает слова из 8 дорожек, младшее слово результата помещается в итоговую дорожку
vpmullw: перемножает слова из 8 дорожек (128-битная версия) или из 16 дорожек (256-битная версия), младшее слово результата помещается в итоговую дорожку
pmulhuw: перемножает беззнаковые слова из 8 дорожек, старшее слово результата помещается в итоговую дорожку
vpmulhuw: перемножает слова из 8 дорожек (128-битная версия) или из 16 дорожек (256-битная версия), старшее слово результата помещается в итоговую дорожку
pmulhw: перемножает слова со знаком из 8 дорожек, старшее слово результата помещается в итоговую дорожку
vpmulhw: перемножает слова из 8 дорожек (128-битная версия) или из 16 дорожек (256-битная версия), старшее слово результата помещается в итоговую дорожку
pmulld: перемножает двойные слова (.long) из 4 дорожек, младшее двойное слово результата помещается в итоговую дорожку
Схематично это выглядит так:
Temp0[63:0] := SRC1[31:0] * SRC2[31:0] Temp1[63:0] := SRC1[63:32] * SRC2[63:32] Temp2[63:0] := SRC1[95:64] * SRC2[95:64] Temp3[63:0] := SRC1[127:96] * SRC2[127:96] DEST[31:0] := Temp0[31:0] DEST[63:32] := Temp1[31:0] DEST[95:64] := Temp2[31:0] DEST[127:96] := Temp3[31:0] DEST[MAXVL-1:128] := 0
vpmulld: перемножает двойные слова из 4 дорожек (128-битная версия) или из 8 дорожек (256-битная версия), младшее двойное слово результата помещается в итоговую дорожку
Схематично это выглядит так (для 128-битной версии):
Temp0[63:0] := SRC1[31:0] * SRC2[31:0] Temp1[63:0] := SRC1[63:32] * SRC2[63:32] Temp2[63:0] := SRC1[95:64] * SRC2[95:64] Temp3[63:0] := SRC1[127:96] * SRC2[127:96] DEST[31:0] := Temp0[31:0] DEST[63:32] := Temp1[31:0] DEST[95:64] := Temp2[31:0] DEST[127:96] := Temp3[31:0] DEST[MAXVL-1:128] := 0
Для 256-разрядной версии
Temp0[63:0] := SRC1[31:0] * SRC2[31:0] Temp1[63:0] := SRC1[63:32] * SRC2[63:32] Temp2[63:0] := SRC1[95:64] * SRC2[95:64] Temp3[63:0] := SRC1[127:96] * SRC2[127:96] Temp4[63:0] := SRC1[159:128] * SRC2[159:128] Temp5[63:0] := SRC1[191:160] * SRC2[191:160] Temp6[63:0] := SRC1[223:192] * SRC2[223:192] Temp7[63:0] := SRC1[255:224] * SRC2[255:224] DEST[31:0] := Temp0[31:0] DEST[63:32] := Temp1[31:0] DEST[95:64] := Temp2[31:0] DEST[127:96] := Temp3[31:0] DEST[159:128] := Temp4[31:0] DEST[191:160] := Temp5[31:0] DEST[223:192] := Temp6[31:0] DEST[255:224] := Temp7[31:0] DEST[MAXVL-1:256] := 0
vpmullq: перемножает четверные слова из 2 дорожек (128-битная версия) или из 4 дорожек (256-битная версия), младшее четверное слово результата помещается в итоговую дорожку
Проблема умножения дорожек связана с тем, что при умножении двух n-битных чисел результат надо помещать в дорожку, которая занимает n бит, хотя умножение n × n может дать результат 2×n бит. Таким образом, операция умножения по дорожкам создает проблемы, поскольку теряется переполнение. Подобные инструкции предполагают, что результат может занять 2*n бит, однако в итоговую дорожку регистра помещается только младшие или старшие n бит результата.
Синтаксис инструкций:
pmullw xmmsrc/mem128, xmmdest vpmullw xmm/mem128, xmmsrc, xmmdest vpmullw ymm/mem256, ymmsrc, ymmdest pmulhuw xmmsrc/mem128, xmmdest vpmulhuw xmm/mem128, xmmsrc, xmmdest vpmulhuw ymm/mem256, ymmsrc, ymmdest pmulhw xmmsrc/mem128, xmmdest vpmulhw xmm/mem128, xmmsrc, xmmdest vpmulhw ymm/mem256, ymmsrc, ymmdest pmulld xmmsrc/mem128, xmmdest vpmulld xmm/mem128, xmmsrc, xmmdest vpmulld ymm/mem256, ymmsrc, ymmdest vpmullq xmm/mem128, xmmsrc, xmmdest vpmullq ymm/mem256, ymmsrc, ymmdest
Пример перемножения
.globl main .data nums0: .long 5, 6, 7, 8 nums1: .long 4, 5, 6, 7 format_str: .asciz "%d, %d, %d, %d\n" .text main: subq $8, %rsp movaps nums0, %xmm0 movaps nums1, %xmm1 vpmulld %xmm1, %xmm0, %xmm0 # XMM0 = XMM0 * XMM1 # XMM0 = 20, 30, 42, 56 # выводим данные на консоль movd %xmm0, %esi psrldq $4, %xmm0 movd %xmm0, %edx psrldq $4, %xmm0 movd %xmm0, %ecx psrldq $4, %xmm0 movd %xmm0, %r8d movq $format_str, %rdi call printf addq $8, %rsp ret
Здесь перемножаются 32-битные дорожки регистров %xmm0 и %xmm1. Консольный вывод программы:
root@Eugene:~/asm# gcc -static hello.s -o hello root@Eugene:~/asm# ./hello 20, 30, 42, 56 root@Eugene:~/asm#
Ряд инструкций позволяют обойти ограничения на разрядность результата
pmuldq: умножает двойные слова (.long) со знаком в 32-битных дорожках 0 и 2 и сохраняет 64-битные результаты в 64-битных дорожках 0 и 1 (они же 32-битные дорожки 0, 1, 2 и 3). Старшие 128 бит перекрывающего регистра YMM остаются без изменений
pmuludq: умножает беззнаковые двойные слова в 32-битных дорожках 0 и 2 и сохраняет 64-битные результаты в 64-битных дорожках 0 и 1 (они же 32-битные дорожки 0, 1, 2 и 3). Старшие 128 бит перекрывающего регистра YMM остаются без изменений
vpmuldq: 256-битные варианты умножает двойные слова со знаком в 32-битных дорожках 0, 2, 4, 6 и сохраняет 64-битные результаты
в 64-битных дорожках 0, 1, 2 и 3 (они же 32-битные дорожки 0, 1, 2, 3 и 4, 5, 6, 7). 128-битная аналогична pmuldq
за тем исключением, что старшие 128 бит перекрывающего регистра YMM заполняются нулями
vpmuludq: 256-битные варианты умножает беззнаковые двойные слова в 32-битных дорожках 0, 2, 4, 6 и сохраняет 64-битные результаты
в 64-битных дорожках 0, 1, 2 и 3 (они же 32-битные дорожки 0, 1, 2, 3 и 4, 5, 6, 7). 128-битная аналогична pmuludq
за тем исключением, что старшие 128 бит перекрывающего регистра YMM заполняются нулями
Синтаксис инструкций:
pmuldq xmmsrc/mem128, xmmdest vpmuldq xmm/mem128, xmmsrc1, xmmdest vpmuldq ymm/mem256, ymmsrc1, ymmdest pmuludq xmmsrc/mem128, xmmdest vpmuludq xmm/mem128, xmmsrc1, xmmdest vpmuludq ymm/mem256, ymmsrc1, ymmdest
И еще пара инструкций - pclmulqdq и vpclmulqdq умножают четверные слова (qword) и сохраняют 128-битныq результат.
pclmulqdq
оставляет старшие 128 бит перекрывающего регистра YMM без изменений, а vpclmulqdq
заполняет их нулями.
pclmulqdq imm8, xmmsrc/mem128, xmmdest vpclmulqdq imm8, xmmsrc2/mem128, xmmsrc1, xmmdest
Операнд imm8 указывает, какие четверные слова использовать в качестве исходных операндов. На примере pclmulqdq
:
imm8 | Результат |
0x00 | XMMdest = xmmdest[0-63] * XMM/mem128[0-63] |
0x01 | XMMdest = xmmdest[64-127] * XMM/mem128[0-63] |
0x10 | XMMdest = xmmdest[0-63] * XMM/mem128[64-127] |
0x11 | XMMdest = xmmdest[64-127] * XMM/mem128[64-127] |
vpclmulqdq
работает аналогично, только числа берутся из второго и третьего операнда.
Пример умножения 64-разрядных чисел:
.globl main .data nums0: .quad 5, 6 nums1: .quad 3, 4 format_str: .asciz "%d\n" .text main: subq $8, %rsp movaps nums0, %xmm0 vpclmulqdq $0x00, nums1, %xmm0, %xmm0 # XMM0[0:63] = 15 # выводим данные на консоль movq %xmm0, %rsi movq $format_str, %rdi call printf addq $8, %rsp ret