Стандартная инструкция сложения ADD
никак не изменяет флаги. Однако установка флагов может быть необходима, особенно если мы работаем с большими числами и
хотим избежать некорректных результатом. И для сложения с установкой флагов архитектура ARM64 предоставляет дополнительную инструкцию - ADDS
ADDS Xn, Xs, Operand
Эта инструкция, аналогично инструкции ADD
, вычисляет сумму Xs + Operand
и помещает результат в регистр Xs. Но при этом также устанавливает флаги.
Рассмотрим пример
.global _start _start: ldr x0, =0xffffffffffffffff mov x1, #2 adds x0, x0, x1 // x0 = 0xffffffffffffffff + 2 = 0x00000000000000001 mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
Итак, в регистр Х0 загружается число 0xffffffffffffffff
. Данное число рассматривается можно рассматривать как беззнаковое число 18446744073709551615, либо как число со
знаком -1. В Х1 помещаем число 2.
Затем складываем оба числа и помещаем результат в Х0. Как будет происходить сложение
Или для большей наглядности в бинарном виде:
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 + 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 = 1 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001
Формально результатом бы было 65-разрядное число 0x1_0000000000000001
, однако регистр Х0 у нас 64-разрядный, поэтому фактическим результатом является число
0x0000000000000001
или просто 1. В итоге в результате сложения образуется перенос. В итоге флаги устанавливаются следующим образом:
Z=0. Результат не равен 0, флаг нуля Z не устанавливается.
N=0. Поскольку старший (знаковый) бит результата не равен 1, то флаг знака N не устанавливается.
C=1. Поскольку в результате операции имеется перенос, устанавливается флаг переноса C.
V=0. Переполнения знака тут нет, как минимум потому, что знаковые биты у обоих операндов разные, поэтому флаг переполнения V не устанавливается.
Рассмотрим другой пример:
.global _start _start: ldr x0, =0x7fffffffffffffff // 9223372036854775807 ldr x1, =0x7fffffffffffffff adds x0, x0, x1 // x0 = 0xfffffffffffffffe mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
Здесь в регистры Х0 и Х1 загружается одно и то же число 0x7fffffffffffffff
или 9223372036854775807 в десятичной системе. Инструкция ADDS выполняет
сложение этих чисел и помещает их в Х0. В результате сложения мы получим число 0xfffffffffffffffe
. Для наглядности процесс сложения в бинарной форме
0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 + 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 = 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110
В данном случае происходит следующая установка флагов:
Z=0. Результат не равен 0, флаг нуля Z не устанавливается.
N=1. Старший (знаковый) бит результата равен 1, поэтому устанавливается флаг знака N.
C=0. В результате операции нет переноса, поэтому флаг переноса C не установлен.
V=1. Операнды инструкции имеют один и тот же знаковый бит. Результат имеет другой знаковый бит, поэтому устанавливается флаг переполнения V.
Третий пример:
.global _start _start: ldr x0, =0x8000000000000000 // 9223372036854775808 ldr x1, =0x8000000000000000 adds x0, x0, x1 // x0 = 0x0 mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
Здесь складываются два числа 0x8000000000000000
(9223372036854775808 в десятичной системе). То есть мы получим следующую операцию:
1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 + 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 = 1_0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
Здесь опять же образуется перенос, а фактический результат равен 0. В итоге состояние флагов будет следующим:
Z=1. Результат равен 0, поэтому устанавливается флаг нуля Z.
N=0. Старший (знаковый) бит результата равен 0, поэтому флаг знака N не устанавливается.
C=1. В результате операции произошел перенос, поэтому устанавливается флаг переноса C.
V=1. Операнды инструкции имеют один и тот же знаковый бит. Результат имеет другой знаковый бит, поэтому устанавливается флаг переполнения V.