Нередко сравнение двух значений произодится, чтобы в зависимости от результатов сравнения загрузить в регистр некоторое значение или, наоборот,
не загружать это значение, если проверка или сравнение не пройдены. Для упрощения подобных действий процессоры x86-64 поддерживают набор инструкций
условного копирования cmov
. Инструкции перемещения с проверкой флагов:
cmovc / cmovb / cmovnae: копирует значение, если флаг переноса CF = 1
cmovnc / cmovnb / cmovae: копирует значение, если флаг переноса CF = 0
cmovz / cmove: копирует значение, если флаг нуля ZF = 1
cmovnz / cmovne: копирует значение, если флаг нуля ZF = 0
cmovs: копирует значение, если флаг знака SF = 1
cmovns: копирует значение, если флаг знака SF = 0
cmovo: копирует значение, если флаг переполнения OF = 1
cmovno: копирует значение, если флаг переполнения OF = 0
Отдельная группа инструкций предназначена для сравнения с копированием. Здесь есть инструкции для сравнения беззнаковых чисел:
cmova: копирует значение, если первый операнд больше второго (CF=0, ZF=0
)
cmovnbe: копирует значение, если первый операнд не меньше и не равен второму (CF=0, ZF=0
)
cmovae / cmovnc / cmovnb: копирует значение, если первый операнд больше или равен второму (CF=0
)
cmovnb / cmovnc / cmovae: копирует значение, если первый операнд не меньше второго (CF=0
)
cmovb / cmovc / cmovnae: копирует значение, если первый операнд меньше второго (CF=1
)
cmovnae / cmovc / cmovb: копирует значение, если первый операнд не больше и не равен второму (CF=1
)
cmovbe: копирует значение, если первый операнд меньше или равен второму (CF=1
или ZF=1
)
cmovna: копирует значение, если первый операнд не больше второго (CF=1
или ZF=1
)
И инструкции для сравнения чисел со знаком:
cmovg: копирует значение, если первый операнд больше второго (SF=OF
или ZF=0
)
cmovnle: копирует значение, если первый операнд не меньше и не равен второму (SF=OF
или ZF=0
)
cmovge: копирует значение, если первый операнд больше или равен второму (SF=OF
)
cmovnl: копирует значение, если первый операнд не меньше второго (SF=OF
)
cmovl: копирует значение, если первый операнд меньше второго (SF != OF
)
cmovnge: копирует значение, если первый операнд не больше и не равен второму (SF != OF
)
cmovle: копирует значение, если первый операнд меньше или равен второму (SF != OF
или ZF=1
)
cmovng: копирует значение, если первый операнд не больше второго (SF != OF
или ZF=1
)
И две общие инструкции как для чисел со знаком, так и для беззнаковых чисел:
cmove: копирует значение, если первый операнд равен второму (ZF=1
). Аналогичен инструкции cmovz
cmovne: копирует значение, если первый операнд не равен второму (ZF=0
). Аналогичен инструкции cmovnz
Первый параметр этих инструкций (куда копируем) представляет либо регистр, либо переменную (16, 32 или 64-битные). Второй параметр (что копируем) - регистр общего назначения(также 16, 32 или 64-битные). Например, возьмем следующую программу для Linux:
global _start section .text _start: mov al, 255 mov bl, 3 add al, bl ; складываем AL и BL jc carry ; если флаг переноса установлен, переходим к метке carry mov rdi, 2 ; если флаг переноса НЕ установлен jmp exit carry: mov rdi, 4 ; если флаг переноса установлен exit: mov rax, 60 syscall
Здесь складываем 2 числа (регистры AL и BL). И если при сложении устанавливается флаг переноса, то осуществляется переход к метке carry, где в регистр RDI помещается 4. Если флаг переноса не установлен, то в регистр RDI помещается 2, и выполняется завершение программы. С помощью инструкций условного перемещения эту программу можно упростить так:
global _start section .text _start: mov al, 255 mov bl, 3 add al, bl ; складываем AL и BL mov rcx, 2 ; вариант, если флаг переноса сброшен (CF = 0) mov rdx, 4 ; вариант, если флаг переноса установлен (CF = 1) cmovnc rdi, rcx ; Если CF = 0 cmovc rdi, rdx ; Если CF = 1 mov rax, 60 syscall
В регистры RCX и RDX помещаем варианты на случай, если флаг CF установлен и не установлен соответственно. И с помощью инструкций cmovnc/cmovc выполняем копирование:
cmovnc rdi, rcx ; Если CF = 0 cmovc rdi, rdx ; Если CF = 1
Аналогичный пример для Windows:
global _start section .text _start: mov al, 255 mov bl, 3 add al, bl ; складываем AL и BL mov rcx, 2 ; вариант, если флаг переноса сброшен (CF = 0) mov rdx, 4 ; вариант, если флаг переноса установлен (CF = 1) cmovnc rax, rcx ; Если CF = 0 cmovc rax, rdx ; Если CF = 1 ret
Другой пример. Поместим в регистр RDI определенное значение в зависимости от результата сравнения (пример для Linux):
global _start section .text _start: mov rcx, 2 mov rdx, 2 cmp rcx, rdx ; сравниваем RCX и RDX, устанавливается флаг нуля ZF mov rcx, 8 ; вариант, если флаг нуля сброшен (ZF = 0) mov rdx , 16 ; вариант, если флаг нуля установлен (ZF = 1) cmovne rdi, rcx ; Если ZF = 0 cmove rdi, rdx ; Если ZF = 1 mov rax, 60 syscall
В данном случае если числа в регистрах RCX и RDX равны, в регистр RDI помещается число 16, иначе помещается число 8. Аналогичный пример для Windows:
global _start section .text _start: mov rcx, 2 mov rdx, 2 cmp rcx, rdx ; сравниваем RCX и RDX, устанавливается флаг нуля ZF mov rcx, 8 ; вариант, если флаг нуля сброшен (ZF = 0) mov rdx , 16 ; вариант, если флаг нуля установлен (ZF = 1) cmovne rax, rcx ; Если ZF = 0 cmove rax, rdx ; Если ZF = 1 ret