Для сложения соответствующих элементов векторов применяются следующие инструкции:
paddb
: сложение байтов в 16 дорожках
vpaddb
: сложение байтов в 16 дорожках (для 128-битной версии) и в 32 дорожках (для 256-битной версии)
paddw
: сложение слов (значений .short/.word) в 8 дорожках
vpaddw
: сложение слов в 8 дорожках (для 128-битной версии) и в 16 дорожках (для 256-битной версии)
paddd
: сложение двойных слов (значений .long) в 4 дорожках
vpaddd
: сложение двойных слов в 4 дорожках (для 128-битной версии) и в 8 дорожках (для 256-битной версии)
paddq
: сложение четверных слов (тип .quad) в 2 дорожках
vpaddq
: сложение четверных слов в 2 дорожках (128-битной версии) и в 4 дорожках (для 256-битной версии)
Синтаксис инструкций:
paddb xmmsrc/mem128, xmmdest vpaddb xmmsrc2/mem128, xmmsrc1, xmmdest vpaddb ymmsrc2/mem256, ymmsrc1, ymmdest paddw xmmsrc/mem128, xmmdest vpaddw xmmsrc2/mem128, xmmsrc1, xmmdest vpaddw ymmsrc2/mem256, ymmsrc1, ymmdest paddd xmmsrc/mem128, xmmdest vpaddd xmmsrc2/mem128, xmmsrc1, xmmdest vpaddd ymmsrc2/mem256, ymmsrc1, ymmdest paddq xmmsrc/mem128, xmmdest vpaddq xmmsrc2/mem128, xmmsrc1, xmmdest vpaddq ymmsrc2/mem256, ymmsrc1, ymmdest
Инструкции с двумя операндами складывают соответствующие дорожки двух операндов и результат помещают в первый операнд. Инструкции с тремя операндами складывают второй и третий операнды и результат помещают в первый. Пример сложения:
.globl main .data nums0: .long 1, 2, 4, 8 nums1: .long 2, 3, 5, 9 format_str: .asciz "%d, %d, %d, %d\n" .text main: subq $8, %rsp movaps nums0, %xmm0 movaps nums1, %xmm1 paddd %xmm1, %xmm0 # XMM0 = XMM0 + XMM1 movd %xmm0, %esi psrldq $4, %xmm0 # сдвиг вправо в 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
В данном случае в регистре XMM0 окажется вектор, элементы которого представляют сумму соответствующих дорожек двух регистров, то есть вектор 3, 5, 9, 17
.
После сложения для проверки выводим вектор на консоль. Пример компиляции и выполнения программы:
root@Eugene:~/asm# gcc -static hello.s -o hello root@Eugene:~/asm# ./hello 3, 5, 9, 17 root@Eugene:~/asm#
Эти инструкции не влияют ни на какие флаги и, таким образом, не указывают, когда во время выполнения этих инструкций происходит переполнение (со знаком или без знака). Если перенос происходит во время добавления, перенос теряется. Это может привести к неправильным результатм при рассчетах, например:
.globl main .data nums0: .short -32768, 0, 0, 0, 0, 0, 0, 0 nums1: .short -10, 0, 0, 0, 0, 0, 0, 0 format_str: .asciz "%d\n" .text main: subq $8, %rsp movaps nums0, %xmm0 movaps nums1, %xmm1 paddw %xmm1, %xmm0 # XMM0 = XMM0 + XMM1 movd %xmm0, %esi movsx %si, %esi # расширение знаком 16-рарядного числа до 32 разрядов movq $format_str, %rdi call printf addq $8, %rsp ret
Здесь складываем два вектора и для демонстрации проблемы выводим первый элемент результирующего вектора. Первый элемент вектора nums0 (-32768) складывается с первым элементом вектора nums1 (-10), результатом будет число 32758. Определенно это не тот результат, который мы бы хотели видеть. Пример работы программы:
root@Eugene:~/asm# gcc -static hello.s -o hello root@Eugene:~/asm# ./hello 32758 root@Eugene:~/asm#
Программа сама должна проверять, что складываемые операнды находятся в соответствующем диапазоне. Но дополнительно SSE/AVX предоставляют инструкции, которые используют так называемую "арифметику насыщения" (saturation arithmetic). Арифметика насыщения хорошо работает для обработки мультимедиа - аудио, видео, изображений. Это следующие инструкции:
paddsb
: сложение байтов со знаком в 16 дорожках
vpaddsb
: сложение байтов со знаком в 16 дорожках
vpaddsb
: сложение байтов со знаком в 32 дорожках
paddsw
: сложение слов со знаком в 8 дорожках
vpaddsw
: сложение слов со знаком в 8 дорожках
vpaddsw
: сложение слов со знаком в 16 дорожках
paddusb
: сложение беззнаковых байтов в 16 дорожках
vpaddusb
: сложение беззнаковых байтов в 16 дорожках
vpaddusb
: сложение беззнаковых байтов в 32 дорожках
paddusw
: сложение беззнаковых слов в 8 дорожках
vpaddusw
: сложение беззнаковых слов в 8 дорожках
vpaddusw
: сложение беззнаковых слов в 16 дорожках
Синтаксис инструкций:
paddsb xmmsrc/mem128, xmmdest vpaddsb xmmsrc2/mem128, xmmsrc1, xmmdest vpaddsb ymmsrc2/mem256, ymmsrc1, ymmdest paddsw xmmsrc/mem128, xmmdest vpaddsw xmmsrc2/mem128, xmmsrc1, xmmdest vpaddsw ymmsrc2/mem256, ymmsrc1, ymmdest paddusb xmmsrc/mem128, xmmdest vpaddusb xmmsrc2/mem128, xmmsrc1, xmmdest vpaddusb ymmsrc2/mem256, ymmsrc1, ymmdest paddusw xmmsrc/mem128, xmmdest vpaddusw xmmsrc2/mem128, xmmsrc1, xmmdest vpaddusw ymmsrc2/mem256, ymmsrc1, ymmdest
Для сложения без знака переполнение усекается до максимально возможного значения, которое может выдержать размер инструкции. Например, если сложение двух байтовых значений превышает 0xFF, арифметика насыщения дает 0xFF — максимально возможное 8-битное значение без знака. Аналогично, если при вычитании произойдет потеря значимости (underflow), то результат округляется до 0. Для арифметики насыщения со знаком отсечение происходит при наибольшем положительном и наименьшем отрицательном значениях (например, для чисел размером с 1 байт это 0x7F/+127 для положительных значений и 0x80/–128 для отрицательных значений).
Так, изменим предыдущий пример, применив сложение чисел со знаком:
.globl main .data nums0: .short -32768, 2, -4, 8, -16, 32, -64, 128 nums1: .short -10, -3, 5, -9, 15, -31, 55, -112 format_str: .asciz "%d\n" .text main: subq $8, %rsp movaps nums0, %xmm0 movaps nums1, %xmm1 paddsw %xmm1, %xmm0 # XMM0 = XMM0 + XMM1 # XMM0 = -32768, -1, 1, -1, -1, 1, -9, 16 movd %xmm0, %esi movsx %si, %esi # расширение знаком 16-рарядного числа до 32 разрядов movq $format_str, %rdi call printf addq $8, %rsp ret
В данном случае результат сложения первых элементов векторов - -32768 + -10
выходит за минимальные границы диапазона типа sword, но теперь минимальное значение
- -32768
, что, возможно, в каких ситуациях может быть приемлимым результатом.
root@Eugene:~/asm# gcc -static hello.s -o hello root@Eugene:~/asm# ./hello -32768 root@Eugene:~/asm#
Расширения e SSE/AVX также предоставляют инструкции для так называемого "горизонтального сложения":
(v)phaddw: сложение 16-битных чисел
(v)phaddd: сложение 32-битных чисел
(v)phaddsw: сложение 16-битных чисел с насыщением
Они имеют аналогичный синтаксис:
phaddw xmmsrc/mem128, xmmdest vphaddw xmmsrc2/mem128, xmmsrc1, xmmdest vphaddw ymmsrc2/mem256, ymmsrc1, ymmdest phaddd xmmsrc/mem128, xmmdest vphaddd xmmsrc2/mem128, xmmsrc1, xmmdest vphaddd ymmsrc2/mem256, ymmsrc1, ymmdest phaddsw xmmsrc/mem128, xmmdest vphaddsw xmmsrc2/mem128, xmmsrc1, xmmdest vphaddsw ymmsrc2/mem256, ymmsrc1, ymmdest
Инструкции горизонтального сложения складывают соседние слова или двойные слова обоих операндов и сохраняют сумму в дорожке регистра из первого операнда. В случае с инструкцией
phaddw
сложение будет выполняться следующим образом:
temp[0-15] = xmmdest[0-15] + xmmdest[16-31] temp[16-31] = xmmdest[32-47] + xmmdest[48-63] temp[32-47] = xmmdest[64-79] + xmmdest[80-95] temp[48-63] = xmmdest[96-111] + xmmdest[112-127] temp[64-79] = xmmsrc/mem128[0-15] + xmmsrc/mem128[16-31] temp[80-95] = xmmsrc/mem128[32-47] + xmmsrc/mem128[48-63] temp[96-111] = xmmsrc/mem128[64-79] + xmmsrc/mem128[80-95] temp[112-127] = xmmsrc/mem128[96-111] + xmmsrc/mem128[112-127] xmmdest = temp
4 слова из младших 64 битов результата являются суммой соседних слов первого операнда, а 4 слова из старших 64 бит результата - сумма соседних слов из второго операнда.
Инструкция phaddw
не затрагивает старшие 128 бит перекрывающего регистра YMM.
Инструкция vphaddw
складывает слова из второго и третьего операнда и результат помещает в первый. При этом старшие 128 бит перекрывающего регистра YMM заполняются нулями:
xmmdest[0-15] = xmmsrc1[0-15] + xmmsrc1[16-31] xmmdest[16-31] = xmmsrc1[32-47] + xmmsrc1[48-63] xmmdest[32-47] = xmmsrc1[64-79] + xmmsrc1[80-95] xmmdest[48-63] = xmmsrc1[96-111] + xmmsrc1[112-127] xmmdest[64-79] = xmmsrc2/mem128[0-15] + xmmsrc2/mem128[16-31] xmmdest[80-95] = xmmsrc2/mem128[32-47] + xmmsrc2/mem128[48-63] xmmdest[96-111] = xmmsrc2/mem128[64-79] + xmmsrc2/mem128[80-95] xmmdest[111-127] = xmmsrc2/mem128[96-111] + xmmsrc2/mem128[112-127]
256-разрядная версия инструкции vphaddw
выполняет вычисления следующим образом:
ymmdest[0-15] = ymmsrc1[16-31] + ymmsrc1[0-15] ymmdest[16-31] = ymmsrc1[48-63] + ymmsrc1[32-47] ymmdest[32-47] = ymmsrc1[80-95] + ymmsrc1[64-79] ymmdest[48-63] = ymmsrc1[112-127] + ymmsrc1[96-111] ymmdest[64-79] = ymmsrc2[16-31] + ymmsrc2[0-15] ymmdest[80-95] = ymmsrc2[48-63] + ymmsrc2[32-47] ymmdest[96-111] = ymmsrc2[80-95] + ymmsrc2[64-79] ymmdest[112-127] = ymmsrc2[112-127] + ymmsrc2[96-111] ymmdest[128-143] = ymmsrc1[144-159] + ymmsrc1[128-143] ymmdest[144-159] = ymmsrc1[176-191] + ymmsrc1[160-175] ymmdest[160-175] = ymmsrc1[208-223] + ymmsrc1[192-207] ymmdest[176-191] = ymmsrc1[240-255] + ymmsrc1[224-239] ymmdest[192-207] = ymmsrc2[144-159] + ymmsrc2[128-143] ymmdest[208-223] = ymmsrc2[176-191] + ymmsrc2[160-175] ymmdest[224-239] = ymmsrc2[208-223] + ymmsrc2[192-207] ymmdest[240-255] = ymmsrc2[240-255] + ymmsrc2[224-239]
Горизонтальное сложение двойных слов с помощью инструкции phaddd
:
temp[0-31] = xmmdest[0-31] + xmmdest[32-63] temp[32-63] = xmmdest[64-95] + xmmdest[96-127] temp[64-95] = xmmsrc/mem128[0-31] + xmmsrc/mem128[32-63] temp[96-127] = xmmsrc/mem128[64-95] + xmmsrc/mem128[96-127] xmmdest = temp
Сложение с помощью 128-битной инструкции vphaddd
xmmdest[0-31] = xmmsrc1[0-31] + xmmsrc1[32-63] xmmdest[32-63] = xmmsrc1[64-95] + xmmsrc1[96-127] xmmdest[64-95] = xmmsrc2/mem128[0-31] + xmmsrc2/mem128[32-63] xmmdest[96-127] = xmmsrc2/mem128[64-95] + xmmsrc2/mem128[96-127] (ymmdest[128-255] = 0)
Сложение с помощью 256-битной инструкции vphaddd
ymmdest[0-31] = ymmsrc1[32-63] + ymmsrc1[0-31] ymmdest[32-63] = ymmsrc1[96-127] + ymmsrc1[64-95] ymmdest[64-95] = ymmsrc2/mem128[32-63] + ymmsrc2/mem128[0-31] ymmdest[96-127] = ymmsrc2/mem128[96-127] + ymmsrc2/mem128[64-95] ymmdest[128-159] = ymmsrc1[160-191] + ymmsrc1[128-159] ymmdest[160-191] = ymmsrc1[224-255] + ymmsrc1[192-223] ymmdest[192-223] = ymmsrc2/mem128[160-191] + ymmsrc2/mem128[128-159] ymmdest[224-255] = ymmsrc2/mem128[224-255] + ymmsrc2/mem128[192-223]
Если при горизонтальном сложеним с помощью инструкций (v)phaddw
и (v)phaddd
происходит переполнение, то оно просто игнорируется.
При горизонтальном сложении с насыщением с помощью phaddsw
любое (положительное) переполнение приводит к значению 0x7FFF, независимо от фактического результата.
Аналогично, любое отрицательное значение потери значимости приводит к значению 0x8000.