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

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

Инструкции (v)pcmpeqb, (v)pcmpeqw, (v)pcmpeqd, (v)pcmpeqq, (v)pcmpgtb, (v)pcmpgtw, (v)pcmpgtd и (v)pcmpgtq сравнивают целые числа в соответствующих дорожках операндов. Результат сравнения сохраняется в первом операнде.

Инструкции pcmpeqX, сравнивают на равенство соответственно два байта, два слова, два двойных и два четверных слова из соответствуюших дорожек.

pcmpeqb xmmdest, xmmsrc/mem128 ; сравнивает 16 байтовых дорожек
pcmpeqw xmmdest, xmmsrc/mem128 ; сравнивает 8 дорожек с числами word
pcmpeqd xmmdest, xmmsrc/mem128 ; сравнивает 4 дорожек с числами dword
pcmpeqq xmmdest, xmmsrc/mem128 ; сравнивает 2 дорожек с числами qword

Инструкции устанавливают значение всех битов 1, если два значения в одной дорожке равны. Если значения не равны, то устанавливается 0 для всех бит. Например, возьмем следующую программу на Linux:

global main

extern printf

section .data
nums0 dd 1, 2, 4, 8 
nums1 dd 1, 2, 3, 8
format_str db "%d, %d, %d, %d", 10, 0

section .text
main: 
    sub rsp, 8

    movaps xmm0, [nums0]
    movaps xmm1, [nums1]
    pcmpeqd xmm0, xmm1       ; XMM0 = XMM0 == XMM1
    ; XMM0 = -1, -1, 0, -1

    ; выводим данные на консоль
    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

Здесь сравниваются соответствуюшие элементы векторов из XMM0 и XMM1, которые представляют 32-разрядные целые числа. Для упрощения результат сравнения с помощью функции printf языка Си выводится на консоль. Поскольку первые два и последний элементы обоих векторов равны, а третьи элементы не равны, то в результате получим вектор 0xFFFFFFFF, 0xFFFFFFFF, 0, 0. Компиляция и консольный вывод программы:

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, 0, -1
root@Eugene:~/asm#

Аналогичный пример на Windows:

global main

extern printf

section .data
nums0 dd 1, 2, 4, 8 
nums1 dd 1, 2, 3, 8
format_str db "%d, %d, %d, %d", 10, 0

section .text
main: 
    sub rsp, 40
    movaps xmm0, [rel nums0]
    movaps xmm1, [rel nums1]
    pcmpeqd xmm0, xmm1       ; XMM0 = XMM0 == XMM1
    ; XMM0 = -1, -1, 0, -1
    
    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

Инструкции pcmpgtX проверят, больше ли первый операнд чем второй:

pcmpgtb xmmdest, xmmsrc/mem128 ; сравнивает байты в 16 дорожках
pcmpgtw xmmdest, xmmsrc/mem128 ; сравнивает числа word в 8 дорожках
pcmpgtd xmmdest, xmmsrc/mem128 ; сравнивает числа dword в 4 дорожках
pcmpgtq xmmdest, xmmsrc/mem128 ; сравнивает числа qword в 2 дорожках

Инструкции устанавливают значение всех битов в 1, если значение из первого операнда больше значения из второго. Иначе устанавливается 0 для всех бит. Пример применения на Linux:

global main

extern printf

section .data
nums0 dd 0, 2, 4, 5
nums1 dd 0, 1, 2, 7
format_str db "%d, %d, %d, %d", 10, 0

section .text
main: 
    sub rsp, 8

    movaps xmm0, [nums0]
    movaps xmm1, [nums1]
    pcmpgtd xmm0, xmm1       ; XMM0 = XMM0 > XMM1
    ; XMM0 = 0, -1, -1, 0

    ; выводим данные на консоль
    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

128-варианты инструкций vpcmpeqX и vpcmpgtX работают аналогично SSE-инструкциям, только сравнивают второй и третий операнды и результат помещают в первый операнд.

vpcmpeqb xmmdest, xmmsrc1, xmmsrc2/mem128 ; сравнивает байты в 16 дорожках
vpcmpeqw xmmdest, xmmsrc1, xmmsrc2/mem128 ; сравнивает числа word в 8 дорожках
vpcmpeqd xmmdest, xmmsrc1, xmmsrc2/mem128 ; сравнивает числа dword в 4 дорожках
vpcmpeqq xmmdest, xmmsrc1, xmmsrc2/mem128 ; сравнивает числа qword в 2 дорожках

vpcmpgtb xmmdest, xmmsrc1, xmmsrc2/mem128 ; сравнивает байты в 16 дорожках
vpcmpgtw xmmdest, xmmsrc1, xmmsrc2/mem128 ; сравнивает числа word в 8 дорожках
vpcmpgtd xmmdest, xmmsrc1, xmmsrc2/mem128 ; сравнивает числа dword в 4 дорожках
vpcmpgtq xmmdest, xmmsrc1, xmmsrc2/mem128 ; сравнивает числа qword в 2 дорожках

256-разрядные варианты инструкций vpcmpeqX и vpcmpgtX сравнивают в два раза больше дорожек в 256-битных операндах:

vpcmpeqb ymmdest, ymmsrc1, ymmsrc2/mem256 ; сравнивает байты в 32 дорожках
vpcmpeqw ymmdest, ymmsrc1, ymmsrc2/mem256 ; сравнивает числа word в 16 дорожках
vpcmpeqd ymmdest, ymmsrc1, ymmsrc2/mem256 ; сравнивает числа dword в 8 дорожках
vpcmpeqq ymmdest, ymmsrc1, ymmsrc2/mem256 ; сравнивает числа qword в 4 дорожках

vpcmpgtb ymmdest, ymmsrc1, ymmsrc2/mem256 ; сравнивает байты в 32 дорожках
vpcmpgtw ymmdest, ymmsrc1, ymmsrc2/mem256 ; сравнивает числа word в 16 дорожках
vpcmpgtd ymmdest, ymmsrc1, ymmsrc2/mem256 ; сравнивает числа dword в 8 дорожках
vpcmpgtq ymmdest, ymmsrc1, ymmsrc2/mem256 ; сравнивает числа qword в 4 дорожках

Сравнение целых чисел со знаком SSE/AVX не влияет на флаги состояния (поскольку они сравнивают несколько значений, и только одно из этих сравнений может быть перемещено во флаги).

(v)pmovmskb

Инструкция (v)pmovmskb позволяет проверить результат сравнения. Она извлекает старший бит из всех байтов в регистре XMM или YMM и сохраняет 16 или 32 бита (соответственно) в регистре общего назначения. Эта инструкция устанавливают все старшие биты регистра общего назначения в 0 (помимо тех, которые необходимы для хранения битов маски):

pmovmskb reg, xmm
vpmovmskb reg, xmm
vpmovmskb reg, ymm

Первым операндом инструкции является 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. Например, получим результат сравнения в программе на Linux:

global main

extern printf

section .data
nums0 dd 0, 2, 4, 5
nums1 dd 0, 1, 2, 7
format_str db "%#x", 10, 0

section .text
main: 
    sub rsp, 8

    movaps xmm0, [nums0]
    movaps xmm1, [nums1]
    pcmpgtd xmm0, xmm1       ; XMM0 = XMM0 > XMM1
    ; XMM0 = 0, -1, -1, 0

    pmovmskb esi, 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
0xff0
root@Eugene:~/asm#

При сравнении регистр xmm0 хранит следующий вектор: 0, -1, -1, 0. То есть первые 4 байта равны 0, следующие 8 байт равны -1, и последние 4 байта равны 0. При выполнении инструкции pmovmskb esi, xmm0 страшие биты из первых 4 байтов xmm0 (первое число dword) копируются в первые 4 бита в esi, страшие биты из вторых 4 байтов xmm0 (второе число dword) копируются во вторые 4 бита в esi и так далее. В итоге в rsi будет хранится значение 0x0ff0 - каждая шестнадцатеричная цифра будет представлять старшие биты одного 4-байтного числа из xmm0. И таким образом, по числу 0x0ff0 мы можем увидеть, что в xmm0 хранится вектор чисел 0, -1, -1, 0 (или 0, 0xffffffff, 0xffffffff, 0)

Сравнение строк

Подобные инструкции удобно применять для сравнения строк. Например, возьмем следующую программу на Linux:

global main

extern printf

section .data
str0 db "Hello Word",0
align 16
str1 db "Hello Word", 0
align 16   
mask dw 0xffff  
equal_str db "String are equal", 10, 0
notequal_str db "String are not equal", 10, 0

section .text
main: 
    sub rsp, 8

    movdqa xmm0, [str0]
    movdqa xmm1, [str1]
    pcmpeqb xmm0, xmm1      ; сравниваем байты (символы) строк
    vpmovmskb edi, xmm0     ; копируем результат в rdi (поскольку 16 битов сравнения, результат в DI)
    cmp di, [mask]          ; с помощью маски проверяем равны ли строки
    jz equal                ; если результат - 0, строки равны

    mov rdi, notequal_str  ; если строки не равны
    jmp exit

equal:
    mov rdi, equal_str      ; если строки равны

exit:    
    call printf
    add rsp, 8
    ret

Здесь сравниваем побайтно 16 первых символов двух строки - str1 и str2 с помощью инструкции pcmpeqb.

pcmpeqb xmm0, xmm1

После сравнения копируем результат в регистр rdi:

vpmovmskb edi, xmm0

Поскольку из xmm0 в edi копируется 16 бит, то в дальнейшем проверяем младшие 16 бит регистра edi (регистр di) на соответствие маске:

cmp di, [mask]

Маска mask представляет 2 байта, где все биты установлены в 1. То есть маска предполагает ситуацию, когда все 16 байтов регистра xmm0 или все 16 битов регистра di равны 1. И если маска равна числу из di, то две строки равны.

Аналогичный пример на Windows:

global main

extern printf

section .data
str0 db "Hello Word",0
align 16
str1 db "Hello Word", 0
align 16   
mask dw 0xffff  
equal_str db "String are equal", 10, 0
notequal_str db "String are not equal", 10, 0

section .text
main: 
    sub rsp, 40

    movdqa xmm0, [rel str0]
    movdqa xmm1, [rel str1]
    pcmpeqb xmm0, xmm1      ; сравниваем байты (символы) строк
    vpmovmskb edi, xmm0     ; копируем результат в rdi (поскольку 16 битов сравнения, результат в DI)
    cmp di, [rel mask]          ; с помощью маски проверяем равны ли строки
    jz equal                ; если результат - 0, строки равны

    mov rcx, notequal_str  ; если строки не равны
    jmp exit

equal:
    mov rcx, equal_str      ; если строки равны

exit:    
    call printf
    add rsp, 40
    ret

Преобразование символов

Операции сравнения открывают нам и другую возможность - манипуляции с символами по условию. Например, нам надо перевести все строчные буквы в строке в верхний регистр. С точки зрения таблицы ASCII строчная буква отличается от заглавной тем, что у нее установлен 5-й бит (нумерация битов с нуля). Например:

Код буквы "A": 01000001
Код буквы "a": 01100001

То есть, чтобы перейти от буквы в верхнем регистре к букве в нижнем регистре, нам надо установить бит 5. А для обратного перехода - снять 5-й бит. Однако строка может содержать не только алфавитные символы, но и другие символы - цифры, скобки и т.д., в том числе непечатные символы типа перевода строки (символ \n). Поэтому просто снять или установить для всех символов строки определенный бит не получится. Нам надо манипулировать с битом только тогда, когда символ представляет букву. И для этого нам как раз нужны инструкции сравнения.

Так, определим следующую программу:

global _start

section .data
msg db "Hello World", 10, 0
msglen equ $-msg      ; длина строки
align 16 
maskgeA times 16 db 96     ; код символа равен букве "a" или больше
maskleZ times 16 db 123    ; код символа равен букве "z" или меньше
mask20 times 16 db 0b00100000    ; для установки маски, чтобы убрать 5-й бит

section .text
_start:
    movdqa xmm0, [msg]               ; помещаем строку для преобразования в регистр xmm0
    vpcmpgtb xmm1, xmm0, [maskgeA]  ; находим символы, код которых равен "a" или больше - результат в xmm1
    movdqa xmm2, [maskleZ]
    vpcmpgtb xmm2, xmm2, xmm0   ; находим символы, код которых равен "z" или меньше - результат в xmm2
    vandpd xmm1, xmm1, xmm2    ; находим все строчные символы путем умножения результатов обоих сравнений
    ; в xmm1 единицы в тех байтах, где в строке строчные буквы
    
    andpd xmm1, [mask20]      ; получаем маску для перевода из нижнего регистра в верхний
    
    vandnpd xmm0, xmm1, xmm0     ; применяем маску к строке - убираем 5-й бит
    
    movdqa [msg], xmm0   ; сохраняем строку обратно в переменную msg 
    
    ; собственно печать строки с помощью системного вызова write
    mov rax, 1           ; номер системной функции
    mov rdi, 1           ; дескриптор стандартного (консольного) вывода
    mov rsi, msg          ; адрес строки
    mov rdx, msglen       ; размер строки
    syscall                 ; выполняем системный вызов

    mov rdi, msglen 
    mov rax, 60
    syscall             ; выход из программы

Итак, наша оригинальная строка - msg. Ее мы будем переводить в верхний регистр, то есть делать все буквы заглавными. И для этого определяем ряд масок. Первая маска - maskgeA

maskgeA times 16 db 96     ; код символа равен букве "a" или больше

Это массив из 16 байтов, каждый из которых равен 96 - это код ascii косой кавычки "`", после которого идет символ "a". Мы будем сравнивать каждый символ строки с этим байтом. Если код символа в строке больше этого байта, то мы скорее всего имеем дело со строчной буквой.

Но после строчных букв в таблице символов еще есть символы. И чтобы ограничить верхний потолок и найти все символы, код которых равен "z" или меньше, определяем вторую маску - maskleZ

maskleZ times 16 db 123

123 - это код символа "{", до которого идет код символа "z".

Третья маска нужна, чтобы сбросить 5-й бит, поэтому каждый ее байт содержит число 32 (число, где установлен только 5-й бит - 0b00100000):

mask20 times 16 db 0b00100000

Исходная строка загружается в регистр xmm0. Сначала мы сравниваем все ее символы с байтами из маски maskgeA:

vpcmpgtb xmm1, xmm0, [maskgeA]   ; находим символы, код которых равен "a" или больше

В регистре xmm1 все байтовые дорожки устанавливаются в -1 (0b11111111), если код символа из соответствующей байтовой дорожке в строке больше 96. То есть xmm1 выгдлядит типа следующего:

xmm1 = 0x0000000000FFFFFFFF0000FFFFFFFF00

Далее сравниваем все ее символы с байтами из маски maskleZ:

movdqa xmm2, [maskleZ]
vpcmpgtb xmm2, xmm2, xmm0   ; находим символы, код которых равен "z" или меньше - результат в xmm2

В регистре xmm2 все байтовые дорожки устанавливаются в -1 (0b11111111), если байт маски maskleZ - число 123 больше кода символа из соответствующей байтовой дорожке в строке. То есть после выполнения инструкции xmm2 выгдлядит типа следующего:

xmm2 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

По результату можно увидеть, что все символы строки иммеют код меньше 123.

Теперь нам надо найти только строчные буквы. То есть те, для которых в обоих регистрах - %xmm1 и %xmm2 установлены биты (то есть фактически код символа меньше 123, но больше 96). То есть нам надо получить пересечение множеств, и для этого выполняем логическое умножение:

vandpd xmm1, xmm1, xmm2     ; находим все строчные символы путем умножения результатов обоих сравнений

Результат помещается в xmm1, и он выглядит следующим образом:

xmm1 = 0x0000000000FFFFFFFF0000FFFFFFFF00
*
xmm2 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
=
xmm1 = 0x0000000000FFFFFFFF0000FFFFFFFF00

Фактически остаются те символы, которые были изначально отмечены в xmm1, так как строка не содержит символов, чей код больше 123.. Далее определяем собственно маску для перевода строчный букв в верхний регистр

andpd xmm1, [mask20]      ; получаем маску для перевода из нижнего регистра в верхний

В данном случае получаем байты, где установлены 5-е биты:

xmm1   = 0x0000000000FFFFFFFF0000FFFFFFFF00
*
mask20 = 0x20202020202020202020202020202020
=
xmm1 =   0x00000000002020202000002020202000

Последний этап - применение маски. Для этого инвертируем маску, то есть получаем те биты надо установить. То есть если каждый бит в маске сейчас равен 0b0010000, то после инверсии он равен 0b11011111. Применяя эту маске к каждому символу, мы сбросим 5-й бит. Ассемблер позволяет все эти действия выполнить одной инструкцией (операция NAND):

vandnpd xmm0, xmm1, xmm0

Результат сохраняется в регистр xmm0, а затем обратно в строку msg, которая в конце выводится на экран с помощью системной функции write.

Компиляция и результат работы программы:

root@Eugene:~/asm# nasm -f elf64 hello.asm -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#

Аналогичный пример на Windows:

global _start

extern WriteFile        ; подключем функцию WriteFile
extern GetStdHandle     ; подключем функцию GetStdHandle

section .data
msg db "Hello World", 10, 0
msglen equ $-msg      ; длина строки
align 16 
maskgeA times 16 db 96     ; код символа равен букве "a" или больше
maskleZ times 16 db 123    ; код символа равен букве "z" или меньше
mask20 times 16 db 0b00100000    ; для установки маски, чтобы убрать 5-й бит

section .text
_start: 
    sub rsp, 40

    movdqa xmm0, [rel msg]               ; помещаем строку для преобразования в регистр xmm0
    vpcmpgtb xmm1, xmm0, [rel maskgeA]  ; находим символы, код которых равен "a" или больше - результат в xmm1
    movdqa xmm2, [rel maskleZ]
    vpcmpgtb xmm2, xmm2, xmm0   ; находим символы, код которых равен "z" или меньше - результат в xmm2
    vandpd xmm1, xmm1, xmm2    ; находим все строчные символы путем умножения результатов обоих сравнений
    ; в xmm1 единицы в тех байтах, где в строке строчные буквы
    andpd xmm1, [rel mask20]      ; получаем маску для перевода из нижнего регистра в верхний
    
    vandnpd xmm0, xmm1, xmm0     ; применяем маску к строке - убираем 5-й бит
    
    movdqa [rel msg], xmm0   ; сохраняем строку обратно в переменную msg     

    ; вывод строки на экран
    mov  rcx, -11  ; Аргумент для GetStdHandle - STD_OUTPUT
    call GetStdHandle ; вызываем функцию GetStdHandle
    mov  rcx, rax     ; Первый параметр WriteFile - в регистр RCX помещаем дескриптор файла - консоли
    mov  rdx, msg    ; Второй параметр WriteFile - загружаем указатель на строку в регистр RDX
    mov  r8d, msglen      ; Третий параметр WriteFile - длина строки для записи в регистре R8D 
    xor  r9, r9       ; Четвертый параметр WriteFile - адрес для получения записанных байтов
    mov  qword [rsp + 32], 0  ; Пятый параметр WriteFile
    call WriteFile ; вызываем функцию WriteFile

    add rsp, 40
    ret
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850