Сравнение целых чисел

Последнее обновление: 04.10.2023

Инструкции (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

Инструкция (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#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850