Инструкция SUB выполняет вычитание данных. По синтаксису она похожа на инструкцию ADD
и имеет аналогичные формы:
SUB Xd, Xn, Xm // Xd = Xn - Xm SUB Xd, Xn, #imm // Xd = Xn - #imm SUB Xd, Xn, #imm, shift // вычитание со сдвигом - непосредственный операнд #imm сдвигается с помощью выражения shift SUB Xd, Xn, Xm, shift #N // вычитание со сдвигом - регистр Xm сдвигается с помощью выражения shift #N SUB Xd, Xn, Xm, extend #N // вычитание с расширением - регистр Xm расширяется с помощью выражения extend #N
Инструкция вычитает третий операнд из второго и результат помещает в регистр из первого операнда. Первые два операнда - регистры. В качестве третьего операнда, как и в случае инструкцией
ADD
, может выступать значение из другого регистра или непосредственный операнд.
Например, вычтем из значения из регистра X1 число 22 и поместим результат в регистр X0
.global _start _start: MOV X1, 22 SUB X0, X1, 2 // X0 = X1 - 2 = 22 - 2 = 20 // X0 = X2 + младший байт из X1, сдвинутый влево на 4 бита = 0x2 + (0xFD << 4) =0x2 + 0xD0 = 0xD2 MOV X8, #93 // номер функции Linux для выхода из программы - 93 SVC 0 // вызываем функцию и выходим из программы
Передаваемый в качестве третьего параметра непосредственный операнд может иметь длину 12 бит (0-4096)
Вместо непосредственного операнда можно использовать значение из другого регистра:
MOV X1, 20 MOV X2, 5 SUB X0, X1, X2 // X0 = X1 - X2 = 20 - 5 = 15
Используя операцию вычитания из нулевого регистра, мы можем получить отрицательное значение числа (фактически умножить на -1):
MOV X1, 2 SUB X0, XZR, X1 // X0 = 0 - X1 = 0 - 2 = -2
Можно применять сдвиг и поворот. При этом если сдвиг применяется к непосредственному операнду, то значение сдвига (количество разрядов, на которые происходит сдвиг) должно быть равно 0 или 12:
SUB X2, X1, #0x10, LSL 12 // X2 = X1 - 0x10000
При сдвиге значения из регистра такого ограничения нет:
MOV X1, 20 MOV X2, 2 SUB X0, X1, X2, LSL #3 // X0 = X1 - (2 << 2) = 20 - 26 = 4
Для вычитания с расширением применяются ранее рассмотренные операции, которые позволяют извлечь один байт, полслова (2 байта) или слово (4 байта) из одного регистра
uxtb
: извлекает один младший байт, который рассматривается как число без знака
uxth
: извлекает беззнаковые полслова - два младших беззнаковых байта
uxtw
: извлекает беззнаковое слово - четыре младших беззнаковых байта
sxtb
: извлекает один младший байт, который рассматривается как число со знаком
sxth
: извлекает младшие полслова (2 байта) со знаком
sxtw
: извлекает младшее слово (4 байта) со знаком
Например, выполним вычитание одного байта
.global _start _start: MOV X1, #0x12FA // X1 = 4861 MOV X2, #0xFE // X2 = 2 SUB X0, X2, X1, UXTB // X0 = X2 - младший байт из X1 = 0xFE - 0xFA = 0x04 MOV X8, #93 // номер функции Linux для выхода из программы - 93 SVC 0 // вызываем функцию и выходим из программы
Здесь извлекается младший байт из регистра X1 и вычитается из регистра X2. То есть младший байт числа 0x12FA
равен FA
. Он вычитается из
0xАУ
, и получаем 0x04
или 4 в десятичной системе.
Пример с расширением и сдвигом влево:
.global _start _start: MOV X1, #0x12FA // X1 = 4861 MOV X2, #0xFE // X2 = 2 SUB X0, X2, X1, UXTB #4 // X0 = X2 - младший байт из X1 = 0xFE - 0xFA = 0x04 // X0 = X2 - младший байт из X1, сдвинутый влево на 4 бита = 0xFE - (0xFA << 4) =0xFE - 0xA0 = 0x5E = 94 MOV X8, #93 // номер функции Linux для выхода из программы - 93 SVC 0 // вызываем функцию и выходим из программы