Для сравнения отдельных чисел с плавающей точкой расширения SSE предоставляют набор инструкций сравнения для конкретных условий, которые сохраняют значение true (все биты 1) или false (все биты 0) во втором операнде:
<cmpss xmmn, xmmm/mem32, imm8 cmpsd xmmn, xmmm/mem64, imm8 cmpeqss xmmn, xmmm/mem32 ; операнды равны cmpltss xmmn, xmmm/mem32 ; первый операнд меньше второго cmpless xmmn, xmmm/mem32 ; первый операнд меньше или равен второму cmpunordss xmmn, xmmm/mem32 ; неупорядоченное сравнение (один из операндов или оба равны NaN) cmpneqss xmmn, xmmm/mem32 ; операнды не равны cmpnltss xmmn, xmmm/mem32 ; первый операнд НЕ меньше второго cmpnless xmmn, xmmm/mem32 ; первый операнд НЕ меньше или равен второму cmpordss xmmn, xmmm/mem32 ; упорядоченное сравнение cmpeqsd xmmn, xmmm/mem64 ; операнды равны cmpltsd xmmn, xmmm/mem64 ; первый операнд меньше второго cmplesd xmmn, xmmm/mem64 ; первый операнд меньше или равен второму cmpunordsd xmmn, xmmm/mem64 ; неупорядоченное сравнение (один из операндов или оба равны NaN) cmpneqsd xmmn, xmmm/mem64 ; операнды не равны cmpnltsd xmmn, xmmm/mem64 ; первый операнд НЕ меньше второго cmpnlesd xmmn, xmmm/mem64 ; первый операнд НЕ меньше или равен второму cmpordsd xmmn, xmmm/mem64 ; упорядоченное сравнение
Первые две инструкции могут в качестве третьего операнда (imm8) принимать значение от 0 до 7, которое указывает на принцип сравнения:
0: Первый операнд == второй операнд
1: Первый операнд < второй операнд
2: Первый операнд <= второй операнд
3: Неупорядоченное сравнение
4: Первый операнд ≠ второй операнд
5: Первый операнд не меньше второго операнда (>=)
6: Первый операнд не меньше или равен второму операнду (>)
7: Упорядоченное сравнение
Эти инструкции устанавливают в 0 (неверно, ложь) или 1 (верно, истина) все биты в первом операнде. После сравнения значение из регистра XMM можно поместит в регистр общего назначения и проверить этот регистр на наличие нуля/не нуля. Для этого можно использовать инструкции movq или movd. Например, возьмем следующую программу на Linux:
global _start section .data num0 dq 3.4 num1 dq 3.4 section .text _start: movsd xmm0, [num0] ; помещаем в xmm0 число num0 movsd xmm1, [num1] ; помещаем в xmm1 число num1 cmpeqsd xmm0, xmm1 ; проверяем, равны ли операнды movq rdi, xmm0 ; для проверки помещаем результат сравнения в rdi mov rax, 60 syscall
Здесь помещаем в XMM0 и XMM1 два числа .double
и инструкцией cmpeqsd
сравниваем их на равенство. Если они равны, то в xmm0 все биты будут равны 1,
если нет - то 0. Для проверки значения помещаем его в регистр rdi. Поскольку при равенстве все биты будут установлены в 1, то фактически в десятичной системе это будет число -1.
Аналогичная программа на Windows:
global _start section .data num0 dq 3.4 num1 dq 3.4 section .text _start: movsd xmm0, [rel num0] ; помещаем в xmm0 число num0 movsd xmm1, [rel num1] ; помещаем в xmm1 число num1 cmpeqsd xmm0, xmm1 ; проверяем, равны ли операнды movq rax, xmm0 ; для проверки помещаем результат сравнения в rax ret
Получив результат проверки в целочисленный регистр, мы можем использовать уже стандартные инструкции перехода, чтобы определить условные конструкции. Так, возьмем следующую программу на Linux:
global _start section .data num0 dq 3.6 num1 dq 3.5 str_equal db "Equal", 10, 0 str_equal_len equ $ - str_equal str_notequal db "Not equal", 10, 0 str_notequal_len equ $ - str_notequal section .text _start: movsd xmm0, [num0] ; помещаем в xmm0 число num0 movsd xmm1, [num1] ; помещаем в xmm1 число num1 cmpeqsd xmm0, xmm1 ; проверяем, равны ли операнды movq rdi, xmm0 test rdi, rdi ; проверяем на равенство - на единицы jnz equal ; если xmm0 == xmm1 mov rsi, str_notequal ; адрес строки mov rdx, str_notequal_len ; размер строки jmp exit equal: mov rsi, str_equal ; адрес строки mov rdx, str_equal_len ; размер строки exit: mov rax, 1 ; номер системной функци mov rdi, 1 ; дескриптор стандартного (консольного) вывода syscall mov rax, 60 syscall
Здесь по сравнению с предыдущим примером добавили проверку на 0 с помощью инструкции test - если результат этой инструкии равен 0, то будет установлен
флаг нуля ZF, который мы можем проверить с помощью инструкции jnz
- если флаг не установлен (то есть по сути если xmm0 и xmm1 были равны),
то далее выполняем переход на метку equal. И в зависимости от результата выводим на консоль то или иное сообщение.
Аналогичная программа на Windows:
global _start extern WriteFile ; подключем функцию WriteFile extern GetStdHandle ; подключем функцию GetStdHandle section .data num0 dq 3.6 num1 dq 3.5 str_equal db "Equal", 10, 0 str_equal_len equ $ - str_equal str_notequal db "Not equal", 10, 0 str_notequal_len equ $ - str_notequal section .text _start: sub rsp, 40 ; Для параметров функций WriteFile и GetStdHandle резервируем 40 байт (5 параметров по 8 байт) movsd xmm0, [rel num0] ; помещаем в xmm0 число num0 movsd xmm1, [rel num1] ; помещаем в xmm1 число num1 cmpeqsd xmm0, xmm1 ; проверяем, равны ли операнды movq rdi, xmm0 test rdi, rdi ; проверяем на равенство - на единицы jnz equal ; если xmm0 == xmm1 mov rdx, str_notequal ; адрес строки mov r8d, str_notequal_len ; размер строки jmp exit equal: mov rdx, str_equal ; адрес строки mov r8d, str_equal_len ; размер строки exit: ; выводим строку на консоль mov rcx, -11 ; Аргумент для GetStdHandle - STD_OUTPUT call GetStdHandle ; вызываем функцию GetStdHandle mov rcx, rax ; Первый параметр WriteFile - в регистр RCX помещаем дескриптор файла - консоли xor r9, r9 ; Четвертый параметр WriteFile - адрес для получения записанных байтов mov qword [rsp + 32], 0 ; Пятый параметр WriteFile call WriteFile ; вызываем функцию WriteFile add rsp, 40 ret