Для сравнения значений применяется инструкция CMP, которая имеет следующий формат:
CMP A, B
Инструкция сравнивает значение A со значением B. В качестве A могут выступать 64-разрядные регистры X0-X30 или 32-разрядные регистры W0-W30 или регистр указателя стека SP/WSP. В качестве B - те же регистры или непосредственный операнд.
В реальности эта инструкция вычитает B из значения A с помощью инструкции SUBS
инструкции и обновляет флаги состояния NZCV. Фактически она эквивалентна
SUBS XZR, A, B // для 64-разрядных операндов SUBS WZR, A, B // для 32-разрядных операндов
Выполнив сравнение инструкцией CMP, мы можем проверить флаги с помощью условий:
EQ проверяет A == B
NE проверяет A != B
HI проверяет A > B
(если числа беззнаковые)
LS проверяет A <= B
(если числа беззнаковые)
GE проверяет A >= B
(если числа со знаком)
LT проверяет A < B
(если числа со знаком)
GT проверяет A > B
(если числа со знаком)
LE проверяет A <= B
(если числа со знаком)
Например, сравним два числа на равенство
.global _start _start: mov x0, #3 mov x1, #3 cmp x0, x1 // сравним Х0 и Х1 b.eq equal // если они равны - переход к метке equal mov x0, #255 // если не равны, Х0=255 b exit equal: mov x0, #1 // если равны, Х0=1 exit: mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
Здесь мы сравниваем значения регистров Х0 и Х1. Фактически CMP вычитает Х1 из Х0. Если оба значения равны, то результат равен нулю, и соответственно Z-флаг будет установлен.
И далее команда B.EQ
выполняет переход к метке equal
, если Z-флаг установлен.
Кроме инструкции CMP также можно использовать инструкцию CMN (compare negative). Она имеют аналогичную форму:
CMN A, B
Эта инструкция принимает те же операнды, только вместо вычитания выполняет сложение. То есть фактически эквивалентна
ADDS XZR, A, B // для 64-разрядных ADDS WZR, A, B // для 32-разрядных
Соответственно и логика проверки операндов будет отличаться:
EQ проверяет A == -B
NE проверяет A != -B
HI проверяет A > -B
(если числа беззнаковые)
LS проверяет A <= -B
(если числа беззнаковые)
GE проверяет A >= -B
(если числа со знаком)
LT проверяет A < -B
(если числа со знаком)
GT проверяет A > -B
(если числа со знаком)
LE проверяет A <= -B
(если числа со знаком)
Как видно, из логики условий, инструкцию CMN удобно применять для сравнения с отрицательным значением второго операнда:
.global _start _starts: mov w1, #-1 cmn w1, #1 // сравним W0 == -1 b.eq equal // если они равны - переход к метке equal mov x0, #1 // если не равны, Х0=1 b exit equal: mov x0, #255 // если равны, Х0=255 exit: mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
Поразрядная инструкция TST применяется, чтобы узнать, содержат ли определенные биты 1. Она нередко применяется для проверки условия. Фактически
она применяет операцию ANDS (логическое И) для передаваемых ей двух операндов, только в отличие от инструкции ANDS
не изменяет значения регистров.
TST Xn, Xm/#imm TST Wn, Wm/#imm
В качестве первого операнда применяется 64- или 32-разрядный регистр, в качестве второго операнда может выступать регистр или непосредственный операнд. Эта инструкция также является псевдонимом для инструкции ANDS
, которой в качестве первого операнда передается нулевой регистр.
ANDS XZR, Xn, Xm/#imm ANDS WZR, Wn, Wm/#imm
TST устанавливает флаг N, если знаковый бит результата равен 1, и также устанавливает флаг Z, если результат равен 0. Флаги C и V сбрасываются в 0 и в контекста данной инструкции не играют никакой роли.
При сравнении TST A, B
мы можем использовать два условия:
EQ: биты B в A не найдены или (A & B) == 0
NE: биты B в A найдены или (A & B) != 0
Например, определим, установлен ли в регистре бит 2 (0b0100):
.global _start _start: mov x1, #0b1101 tst x1, #0b0100 b.ne found // если установленные биты 0b0100 НАЙДЕНЫ в Х0 - переход к метке found mov x0, #1 // если биты не найдены, Х0=1 b exit found: mov x0, #2 // если биты найдены, Х0=2 exit: mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
В данном случае в регистр Х1 помещаем двоичное число 0b1101
. Затем инструкция TST
проверяет установку бита номер 2. Поскольку в данном случае такой бит установлен -
"0b1101", то результат будет не равен 0, поэтому флаг нуля будет сброшен. Соответственно выполнение перейдет к метке found.
Другой более практичный пример. Как известно, буквы в нижнем регистре отличаются от заглавных букв в таблице ASCII установленным 6-тым битом. Например
Бинарный код симвоа "A": 01000001 Бинарный код симвоа "a": 01100001
Поэтому с помощью инструкции TST можно легко проверить, является символ буквой с верхнем или нижнем регистре (для простоты примера опустим тот факт, что 6 -1 бит может быть установлен не только у символов):
.global _start _start: mov x1, #'b' // помещаем символ для теста tst x1, #0b00100000 // проверка шестого бита b.ne low // если установлен бит 00100000 - переход к метке low mov x0, #8 // если буква в верхнем регистре, Х0=8 b exit low: mov x0, #16 // буква в нижнем регистре, Х0=16 exit: mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы