Для вычитания соответствующих элементов векторов применяются следующие инструкции:
psubb
: вычитание байтов в 16 дорожках
vpsubb
: вычитание байтов в 16 дорожках (для 128-битной версии) и в 32 дорожках (для 256-битной версии)
psubw
: вычитание слов (значений .short/.word) в 8 дорожках
vpsubw
: вычитание слов в 8 дорожках (для 128-битной версии) и в 16 дорожках (для 256-битной версии)
psubd
: вычитание двойных слов (значений .long) в 4 дорожках
vpsubd
: вычитание двойных слов в 4 дорожках (для 128-битной версии) и в 8 дорожках (для 256-битной версии)
psubq
: вычитание четверных слов (тип .quad) в 2 дорожках
vpsubq
: вычитание четверных слов в 2 дорожках (128-битной версии) и в 4 дорожках (для 256-битной версии)
Синтаксис инструкций:
psubb xmmdest, xmmsrc/mem128 vpsubb xmmdest, xmmsrc1, xmmsrc2/mem128 vpsubb ymmdest, ymmsrc1, ymmsrc2/mem256 psubw xmmdest, xmmsrc/mem128 vpsubw xmmdest, xmmsrc1, xmmsrc2/mem128 vpsubw ymmdest, ymmsrc1, ymmsrc2/mem256 psubd xmmdest, xmmsrc/mem128 vpsubd xmmdest, xmmsrc1, xmmsrc2/mem128 vpsubd ymmdest, ymmsrc1, ymmsrc2/mem256 psubq xmmdest, xmmsrc/mem128 vpsubq xmmdest, xmmsrc1, xmmsrc2/mem128 vpsubq ymmdest, ymmsrc1, ymmsrc2/mem256
Инструкции с двумя операндами вычитают из первого операнда соответствующие дорожки второго операнда и результат помещают в первый операнд. Инструкции с тремя операндами вычитают из второго соответствующие дорожки третьего операнда и результат помещают в первый. Пример вычитания в программе на Linux:
global main extern printf section .data nums0 dd 1, 4, 3, 9 nums1 dd 2, 3, 5, 6 format_str db "%d, %d, %d, %d", 10, 0 section .text main: sub rsp, 8 movaps xmm0, [nums0] movaps xmm1, [nums1] psubd xmm0, xmm1 ; XMM0 = XMM0 - XMM1 ; XMM0 = -1, 1, -2, 3 ; выводим данные на консоль movd esi, xmm0 psrldq xmm0, 4 movd edx, xmm0 psrldq xmm0, 4 movd ecx, xmm0 psrldq xmm0, 4 movd r8d, xmm0 mov rdi, format_str call printf add rsp, 8 ret
Результат работы программы:
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o root@Eugene:~/asm# gcc -static hello.o -o hello root@Eugene:~/asm# ./hello -1, 1, -2, 3 root@Eugene:~/asm#
Аналогичный пример на Windows:
global main extern printf section .data nums0 dd 1, 4, 3, 9 nums1 dd 2, 3, 5, 6 format_str db "%d, %d, %d, %d", 10, 0 section .text main: sub rsp, 40 movaps xmm0, [rel nums0] ; вектор num0 в регистр xmm0 movaps xmm1, [rel nums1] ; вектор num1 в регистр xmm1 psubd xmm0, xmm1 ; XMM0 = XMM0 - XMM1 ; XMM0 = -1, 1, -2, 3 movd edx, xmm0 ; помещаем первое число в rdx psrldq xmm0, 4 ; сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0 movd r8d, xmm0 ; помещаем первое число в r8 psrldq xmm0, 4 ; сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0 movd r9d, xmm0 ; помещаем первое число в r9 psrldq xmm0, 4 ; сдвигаем вправо на 4 бита - к следующему числу в векторе в xmm0 movd [rsp+32], xmm0 ; помещаем первое число в стек mov rcx, format_str call printf add rsp, 40 ret
Инструкции вычитания не влияют ни на какие флаги, и любая информация о переносе, заимствовании, переполнении или недостатке будет потеряна.
И аналогично сложению расширения SSE/AVX предоставляют инструкции для вычитания с насыщением:
psubsb
: вычитание байтов со знаком в 16 дорожках
vpsubsb
: вычитание байтов со знаком в 16 дорожках
vpsubsb
: вычитание байтов со знаком в 32 дорожках
psubsw
: вычитание слов со знаком в 8 дорожках
vpsubsw
: вычитание слов со знаком в 8 дорожках
vpsubsw
: вычитание слов со знаком в 16 дорожках
psubusb
: вычитание беззнаковых байтов в 16 дорожках
vpsubusb
: вычитание беззнаковых байтов в 16 дорожках
vpsubusb
: вычитание беззнаковых байтов в 32 дорожках
psubusw
: вычитание беззнаковых слов в 8 дорожках
vpsubusw
: вычитание беззнаковых слов в 8 дорожках
vpsubusw
: вычитание беззнаковых слов в 16 дорожках
Синтаксис инструкций:
psubsb xmmdest, xmmsrc/mem128 vpsubsb xmmdest, xmmsrc1, xmmsrc2/mem128 vpsubsb ymmdest, ymmsrc1, ymmsrc2/mem256 psubsw xmmdest, xmmsrc/mem128 vpsubsw xmmdest, xmmsrc1, xmmsrc2/mem128 vpsubsw ymmdest, ymmsrc1, ymmsrc2/mem256 psubusb xmmdest, xmmsrc/mem128 vpsubusb xmmdest, xmmsrc1, xmmsrc2/mem128 vpsubusb ymmdest, ymmsrc1, ymmsrc2/mem256 psubusw xmmdest, xmmsrc/mem128 vpsubusw xmmdest, xmmsrc1, xmmsrc2/mem128 vpsubusw ymmdest, ymmsrc1, ymmsrc2/mem256
Инструкций для вычитания однобайтовых чисел со знаком насыщают положительное переполнение до 0x7F (+127) и отрицательное переполнение до 0x80 (-128). Инструкции для вычитания 2-байтных чисел (слов) насыщаются до 0x7FFF (+32 767) и до 0x8000 (-32 768) соответственно. Инструкции насыщения без знака насыщают до 0xFFFF (+65 535) и 0 соответственно.
Посмотрим, как насыщение может изменить результат. Сначала выполним вычитание без насыщения:
global main extern printf section .data nums0 dw -32768, 0, 0, 0, 0, 0, 0, 0 nums1 dw 10, 0, 0, 0, 0, 0, 0, 0 format_str db "%d", 10, 0 section .text main: sub rsp, 8 movaps xmm0, [nums0] movaps xmm1, [nums1] psubd xmm0, xmm1 ; XMM0 = XMM0 - XMM1 ; выводим данные на консоль movd esi, xmm0 movsx esi, si ; расширение знаком 16-рарядного числа до 32 разрядов mov rdi, format_str call printf add rsp, 8 ret
Здесь вычитаются векторы 16-разрядных целых чисел. Для демонстрации нас интересуют только первые дорожки векторов. В частности, мы имеем операцию -32768 - 10
.
Математически результат был бы -32778
, но этот результат выходит за пределы диапазона чисел для типа word
. Посмотрим, что нам покажет вывод программы
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o root@Eugene:~/asm# gcc -static hello.o -o hello root@Eugene:~/asm# ./hello 32758 root@Eugene:~/asm#
Число 32758 - определенно это не правильный результат. Теперь применим операцию вычитания чисел с насыщением:
global main extern printf section .data nums0 dw -32768, 0, 0, 0, 0, 0, 0, 0 nums1 dw 10, 0, 0, 0, 0, 0, 0, 0 format_str db "%d", 10, 0 section .text main: sub rsp, 8 movaps xmm0, [nums0] movaps xmm1, [nums1] psubsw xmm0, xmm1 ; Вычитание с насыщением movd esi, xmm0 movsx esi, si ; расширение знаком 16-рарядного числа до 32 разрядов mov rdi, format_str call printf add rsp, 8 ret
Вывод программы
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o root@Eugene:~/asm# gcc -static hello.o -o hello root@Eugene:~/asm# ./hello -32768 root@Eugene:~/asm#
Число -32768 с математической точки зрения также не является правильным результатом, но этот результат ближе к желательному и укладывается в диапазон чисел типа .short
. И в ряде
сценариев такой результат может быть допустимым.