Инструкции (v)pcmpeqb, (v)pcmpeqw, (v)pcmpeqd, (v)pcmpeqq, (v)pcmpgtb, (v)pcmpgtw, (v)pcmpgtd
и (v)pcmpgtq
сравнивают целые числа в соответствующих дорожках операндов. Результат сравнения сохраняется в первом операнде.
Инструкции pcmpeqX
, сравнивают на равенство соответственно два байта, два слова, два двойных и два четверных слова из соответствуюших дорожек.
pcmpeqb xmmsrc/mem128, xmmdest # сравнивает 16 байтовых дорожек pcmpeqw xmmsrc/mem128, xmmdest # сравнивает 8 дорожек с числами .short/.word pcmpeqd xmmsrc/mem128, xmmdest # сравнивает 4 дорожек с числами .long pcmpeqq xmmsrc/mem128, xmmdest # сравнивает 2 дорожек с числами .quad
Инструкции устанавливают значение всех битов 1, если два значения в одной дорожке равны. Если значения не равны, то устанавливается 0 для всех бит. Например:
.globl main .data nums0: .long 1, 2, 4, 8 nums1: .long 1, 2, 3, 8 format_str: .asciz "%d, %d, %d, %d\n" .text main: subq $8, %rsp movaps nums0, %xmm0 movaps nums1, %xmm1 pcmpeqd %xmm1, %xmm0 # XMM0 = XMM0 == XMM1 # XMM0 = -1, -1, 0, -1 # выводим данные на консоль movd %xmm0, %esi # помещаем число в регистр для передачи в функцию printf psrldq $4, %xmm0 # сдвиг вправо на 4 байта для получения следующего числа 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 и XMM1, которые представляют 32-разрядные целые числа. Для упрощения результат сравнения с помощью функции printf языка Си выводится на консоль. Поскольку первые два и последний элементы обоих векторов равны, а третьи элементы не равны,
то в результате получим вектор 0xFFFFFFFF, 0xFFFFFFFF, 0, 0
. Компиляция и консольный вывод программы:
root@Eugene:~/asm# gcc -static hello.s -o hello root@Eugene:~/asm# ./hello -1, -1, 0, -1 root@Eugene:~/asm#
Инструкции pcmpgtX
проверят, больше первый операнд чем второй:
pcmpgtb xmmsrc/mem128, xmmdest # сравнивает байты в 16 дорожках pcmpgtw xmmsrc/mem128, xmmdest # сравнивает 16-разрядные числа в 8 дорожках pcmpgtd xmmsrc/mem128, xmmdest # сравнивает 32-разрядные числа в 4 дорожках pcmpgtq xmmsrc/mem128, xmmdest # сравнивает 64-разрядные числа в 2 дорожках
Инструкции устанавливают значение всех битов в 1, если значение из второго операнда больше значения из первого. Иначе устанавливается 0 для всех бит. Например:
.globl main .data nums0: .long 0, 2, 4, 5 nums1: .long 0, 1, 2, 7 format_str: .asciz "%d, %d, %d, %d\n" .text main: subq $8, %rsp movaps nums0, %xmm0 movaps nums1, %xmm1 pcmpgtd %xmm1, %xmm0 # XMM0 = XMM0 > XMM1 # XMM0 = 0, -1, -1, 0 # выводим данные на консоль 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
128-варианты инструкций vpcmpeqX
и vpcmpgtX
работают аналогично SSE-инструкциям, только сравнивают второй и третий операнды и результат помещают в первый операнд.
vpcmpeqb xmmsrc2/mem128, xmmsrc1, xmmdest # сравнивает байты в 16 дорожках vpcmpeqw xmmsrc2/mem128, xmmsrc1, xmmdest # сравнивает числа word в 8 дорожках vpcmpeqd xmmsrc2/mem128, xmmsrc1, xmmdest # сравнивает числа dword в 4 дорожках vpcmpeqq xmmsrc2/mem128, xmmsrc1, xmmdest # сравнивает числа qword в 2 дорожках vpcmpgtb xmmsrc2/mem128, xmmsrc1, xmmdest # сравнивает байты в 16 дорожках vpcmpgtw xmmsrc2/mem128, xmmsrc1, xmmdest # сравнивает числа word в 8 дорожках vpcmpgtd xmmsrc2/mem128, xmmsrc1, xmmdest # сравнивает числа dword в 4 дорожках vpcmpgtq xmmsrc2/mem128, xmmsrc1, xmmdest # сравнивает числа qword в 2 дорожках
256-разрядные варианты инструкций vpcmpeqX
и vpcmpgtX
сравнивают в два раза больше дорожек в 256-битных операндах:
vpcmpeqb ymmsrc2/mem256, ymmsrc1, ymmdest # сравнивает байты в 32 дорожках vpcmpeqw ymmsrc2/mem256, ymmsrc1, ymmdest # сравнивает числа word в 16 дорожках vpcmpeqd ymmsrc2/mem256, ymmsrc1, ymmdest # сравнивает числа dword в 8 дорожках vpcmpeqq ymmsrc2/mem256, ymmsrc1, ymmdest # сравнивает числа qword в 4 дорожках vpcmpgtb ymmsrc2/mem256, ymmsrc1, ymmdest # сравнивает байты в 32 дорожках vpcmpgtw ymmsrc2/mem256, ymmsrc1, ymmdest # сравнивает числа word в 16 дорожках vpcmpgtd ymmsrc2/mem256, ymmsrc1, ymmdest # сравнивает числа dword в 8 дорожках vpcmpgtq ymmsrc2/mem256, ymmsrc1, ymmdest # сравнивает числа qword в 4 дорожках
Сравнение целых чисел со знаком SSE/AVX не влияет на флаги состояния (поскольку они сравнивают несколько значений, и только одно из этих сравнений может быть перемещено во флаги).
Инструкция (v)pmovmskb позволяет проверить результат сравнения. Она извлекает старший бит из всех байтов в регистре XMM или YMM и сохраняет 16 или 32 бита (соответственно) в регистре общего назначения. Эта инструкция устанавливают все старшие биты регистра общего назначения в 0 (помимо тех, которые необходимы для хранения битов маски):
pmovmskb xmm, reg vpmovmskb xmm, reg vpmovmskb ymm, reg
Вторым операндом инструкции является 32- или 64-битный регистр общего назначения.
Инструкция (v)pmovmskb
копирует знаковые биты из каждой байтовой дорожки в соответствующую битовую позицию регистра общего назначения. Так, она копирует бит 7
из регистра XMM (бит знака для дорожки 0) в бит 0 регистра назначения; она копирует бит 15 из регистра XMM (бит знака для дорожки 1) в бит 1 регистра назначения,
бит 23 из регистра XMM (бит знака для дорожки 2) в бит 2 регистра назначения и так далее.
128-битные инструкции заполняют только биты с 0 по 15 регистра назначения (обнуляя все остальные биты). 256-битная форма инструкции vpmovmskb
заполняет биты с 0 по 31 целевого регистра (при использовании 64-битного регистра все остальные биты обнуляются)
Можно использовать инструкцию pmovmskb для извлечения одного бита из каждой байтовой дорожки в регистре XMM или YMM после инструкций (v)pcmpeqb
или (v)pcmpgtb
.
.globl main .data nums0: .long 0, 2, 4, 5 nums1: .long 0, 1, 2, 7 format_str: .asciz "%#x\n" .text main: subq $8, %rsp movaps nums0, %xmm0 movaps nums1, %xmm1 pcmpgtd %xmm1, %xmm0 # XMM0 = XMM0 > XMM1 # XMM0 = 0, -1, -1, 0 pmovmskb %xmm0, %rsi # выводим данные на консоль movq $format_str, %rdi call printf addq $8, %rsp ret
Вывод программы:
root@Eugene:~/asm# gcc -static hello.s -o hello root@Eugene:~/asm# ./hello 0xff0 root@Eugene:~/asm#
При сравнении регистр %xmm0 хранит следующий вектор: 0, -1, -1, 0
. То есть первые 4 байта равны 0, следующие 8 байт равны -1, и последние 4 байта равны 0.
При выполнении инструкции pmovmskb %xmm0, %rsi
страшие биты из первых 4 байтов xmm0 (первое число .long) копируются в первые 4 бита в %rsi,
страшие биты из вторых 4 байтов xmm0 (второе число .long) копируются во вторые 4 бита в %rsi и так далее. В итоге в %rsi будет хранится значение 0x0ff0
-
каждая шестнадцатиричная цифра будет представлять старшие биты одного 4-байтного числа из %xmm0. И таким образом, по числу 0x0ff0
мы можем увидеть, что в %xmm0 хранится вектор чисел
0, -1, -1, 0
(или 0, 0xffffffff, 0xffffffff, 0
)
Подобные инструкции удобно применять для сравнения строк. Например:
.globl main .data str0: .asciz "Hello Word" .balign 16 str1: .asciz "Hello Word" .balign 16 mask: .short 0xffff equal_str: .asciz "String are equal\n" notequal_str: .asciz "String are not equal\n" .text main: subq $8, %rsp movdqa str0, %xmm0 movdqa str1, %xmm1 pcmpeqb %xmm1, %xmm0 # сравниваем байты (символы) строк vpmovmskb %xmm0, %rdi # копируем результат в %rdi cmpw mask, %di # с помощью маски проверяем равны ли строки jz equal # если результат - 0, строки равны movq $notequal_str, %rdi # если строки не равны jmp exit equal: movq $equal_str, %rdi # если строки равны exit: call printf addq $8, %rsp ret
Здесь сравниваем побайтно 16 первых символов двух строки - str1 и str2 с помощью инструкции pcmpeqb
.
pcmpeqb %xmm1, %xmm0
После сравнения копируем результат в регистр %rdi:
vpmovmskb %xmm0, %rdi
Поскольку из xmm0 в rdi копируется 16 бит, то в дальнейшем проверяем младшие 16 бит регистра rdi (регистр di) на соответствие маске:
cmpw mask, %di
Маска mask представляет 2 байта, где все биты установлены в 1. То есть маска предполагает ситуацию, когда все 16 байтов регистра xmm0 или все 16 битов регистра %di равны 1. И если маска равна числу из %di, то две строки равны.
Операции сравнения открывают нам и другую возможность - манипуляции с символами по условию. Например, нам надо перевести все строчные буквы в строке в верхний регистр. С точки зрения таблицы ASCII строчная буква отличается от заглавной тем, что у нее установлен 5-й бит (нумерация битов с нуля). Например:
Код буквы "A": 01000001 Код буквы "a": 01100001
То есть, чтобы перейти от буквы в верхнем регистре к букве в нижнем регистре, нам надо установить бит 5. А для обратного перехода - снять 5-й бит. Однако строка может содержать не только алфавитные символы, но и другие символы - цифры, скобки и т.д., в том числе непечатные символы типа перевода строки (символ \n). Поэтому просто снять или установить для всех символов строки определенный бит не получится. Нам надо манипулировать с битом только тогда, когда символ представляет букву. И для этого нам как раз нужны инструкции сравнения.
Так, определим следующую программу:
.globl _start .data str: .asciz "Hello World\n" strlen = .-str # длина строки .balign 16 maskgeA: .fill 16, 1, 96 # код символа равен букве "a" или больше maskleZ: .fill 16, 1, 123 # код символа равен букве "z" или меньше mask20: .fill 16, 1, 0b00100000 # для установки маски, чтобы убрать 5-й бит .text _start: movdqa str, %xmm0 # помещаем строку для преобразования в регистр xmm0 vpcmpgtb maskgeA, %xmm0, %xmm1 # находим символы, код которых равен "a" или больше movdqa maskleZ, %xmm2 vpcmpgtb %xmm0, %xmm2, %xmm2 # находим символы, код которых равен "z" или меньше vandpd %xmm1, %xmm2, %xmm1 # находим все строчные символы путем умножения результатов обоих сравнений # в xmm1 единицы в тех байтах, где в строке строчные буквы andpd mask20, %xmm1 # получаем маску для перевода из нижнего регистра в верхний vandnpd %xmm0, %xmm1, %xmm0 # применяем маску к строке - убираем 5-й бит movdqa %xmm0, str # сохраняем строку обратно в переменную str # собственно печать строки с помощью системного вызова write movq $1, %rax # номер системной функци movq $1, %rdi # дескриптор стандартного (консольного) вывода movq $str, %rsi # адрес строки movq $strlen, %rdx # размер строки syscall # выполняем системный вызов movq $strlen, %rdi movq $60, %rax # выход из программы syscall
Итак, наша оригинальная строка - str. Ее мы будем переводить в верхний регистр, то есть делать все буквы заглавными. И для этого определяем ряд масок. Первая маска - maskgeA
maskgeA: .fill 16, 1, 96
Это массив из 16 байтов, каждый из которых равен 96 - это код ascii косой кавычки "`", после которого идет символ "a". Мы будем сравнивать каждый символ строки с этим байтом. Если код символа в строке больше этого байта, то мы скорее всего имеем дело со строчной буквой.
Но после строчных букв в таблице символов еще есть символы. И чтобы ограничить верхний потолок и найти все символы, код которых равен "z" или меньше, определяем вторую маску - maskleZ
maskleZ: .fill 16, 1, 123 # код символа равен букве "z" или меньше
123 - это код символа "{", до которого идет код символа "z".
Третья маска нужна, чтобы сбросить 5-й бит, поэтому каждый ее байт содержит число 32 (число, где установлен только 5-й бит - 0b00100000):
mask20: .fill 16, 1, 0b00100000 # для установки маски, чтобы убрать 5-й бит
Исходная строка загружается в регистр %xmm0. Сначала мы сравниваем все ее символы с байтами из маски maskgeA:
vpcmpgtb maskgeA, %xmm0, %xmm1 # находим символы, код которых равен "a" или больше
В регистре %xmm1 все байтовые дорожки устанавливаются в -1 (0b11111111), если код символа из соответствующей байтовой дорожке в строке больше 96. То есть xmm1 выгдлядит типа следующего:
xmm1 = 0x0000000000FFFFFFFF0000FFFFFFFF00
Далее сравниваем все ее символы с байтами из маски maskleZ:
movdqa maskleZ, %xmm2 vpcmpgtb %xmm0, %xmm2, %xmm2 # находим символы, код которых равен "z" или меньше
В регистре %xmm2 все байтовые дорожки устанавливаются в -1 (0b11111111), если байт маски maskleZ - число 123 больше кода символа из соответствующей байтовой дорожке в строке. То есть после выполнения инструкции xmm2 выгдлядит типа следующего:
xmm2 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
По результату можно увидеть, что все символы строки иммеют код меньше 123.
Теперь нам надо найти только строчные буквы. То есть те, для которых в обоих регистрах - %xmm1 и %xmm2 установлены биты (то есть фактически код символа меньше 123, но больше 96). То есть нам надо получить пересечение множеств, и для этого выполняем логическое умножение:
vandpd %xmm1, %xmm2, %xmm1 # находим все строчные символы путем умножения результатов обоих сравнений
Результат помещается в xmm1, и он выглядит следующим образом:
xmm1 = 0x0000000000FFFFFFFF0000FFFFFFFF00 * xmm2 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF = xmm1 = 0x0000000000FFFFFFFF0000FFFFFFFF00
Фактически остаются те символы, которые были изначально отмечены в xmm1. Далее определяем собственно маску для перевода строчный букв в верхний регистр
andpd mask20, %xmm1 # получаем маску для перевода из нижнего регистра в верхний
В данном случае получаем байты, где установлены 5-е биты:
xmm1 = 0x0000000000FFFFFFFF0000FFFFFFFF00 * mask20 = 0x20202020202020202020202020202020 = xmm1 = 0x00000000002020202000002020202000
Последний этап - применение маски. Для этого инвертируем маску, то есть получаем те биты надо установить. То есть если каждый бит в маске сейчас равен 0b0010000
,
то после инверсии он равен 0b11011111
. Применяя эту маске к каждому символу, мы сбросим 5-й бит. Ассеммблер позволяет все эти действия выполнить одной инструкцией (операция NAND):
vandnpd %xmm0, %xmm1, %xmm0
Результат сохраняется в регистр %xmm, а затем обратно в строку str, которая в конце выводится на экран с помощью системной функции write.
Компиляция и результат работы программы:
root@Eugene:~/asm# as hello.s -o hello.o root@Eugene:~/asm# ld hello.o -o hello root@Eugene:~/asm# ./hello HELLO WORLD root@Eugene:~/asm# echo $? 13 root@Eugene:~/asm#