Умножение. Инструкция MUL

Последнее обновление: 11.07.2023

Инструкция MUL выполняет умножение. Она имеет следующую форму:

MUL Xd, Xn, Xm
MUL Wd, Wn, Wm

Инструкция фактически вычисляет выражение Xd = Xn ∗ Xm (для 32-разрядных регистров Wd = Wn ∗ Wm). Пример применения инструкции MUL:

.global _start
 
_start: 
    mov x0, #5
    mov x1, #2
    mul x0, x0, x1  // X0 = X0 * X1 = 5 * 2 = 10
 
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Но при умножении следует учитывать ряд ограничений:

  • Регистры являются 64-битными, и если результат умножения больше 64 бит (например, 128-битное число), то в регистр Xd помещаются только младшие 64 разрядов числа. А старшие 64 разряда отбрасываются

  • В отличие от операций ADD или SUB для инструкции MUL нет S-версии, которая бы устанавливала флаги при переполнении. Поэтому при умножении нельзя отследить переполенение.

  • У инструкции MUL нет отдельных версий, которые умножают числа со знаком и без знака.

  • Оба умножаемых числа должны помещаться в регистры, нельзя использовать произвольное число вне регистра, как для инструкций сложения и вычитания.

  • Если перемножаются два 32-битных W-регистра, то результат помещается только в W-регистр, соответственно опять же мы можем столкнуться с потерей точности при умножении, а старшие разряды будут отброшены.

Для преодоления этих ограничений архитектура ARM64 предоставляет еще 4 инструкции, которые выполняют умножение:

  • SMULH: при использовании в паре с инструкцией MUL получает старшие 64 разрядов результата умножения 64-битных чисел со знаком

    SMULH Xd, Xn, Xm
  • UMULH: при использовании в паре с инструкцией MUL получает старшие 64 разрядов результата умножения беззнаковых 64-битных чисел

    UMULH Xd, Xn, Xm
  • SMULL: выполняет умножение 32-битных чисел со знаком и возвращает в регистр Xd 64-битный результат

    SMULL Xd, Wn, Wm
  • UMULL: выполняет умножение 32-битных чисел без знака и возвращает в регистр Xd 64-битный результат

    UMULL Xd, Wn, Wm

Умножение 32-битных регистров без знака:

mov w1, #22
mov w2, #4
umull x0, w1, w2  // X0 = W1 * W2 = 22 * 4 = 88

Умножение 62-битных регистров без знака с переносом старших разрядов:

// в X2 #0x8000000000000002
mov  x2, #0x00000002
movk x2, #0x8000, LSL #48   // начиная с 48-го бита

mov x3, #0x0010
 
// умножаем X2*X3, результат в X0 и X1
mul x0, x2, x3      // В X0 младшие 64 бита - 0x0000000000000020 или 32
umulh x1, x2, x3    // В X1 старшие 64 бита - 0x0000000000000008

Умножение 62-битных регистров со знаком с переносом старших разрядов:

// в X2 #0x8000000000000002
mov  x2, #0x00000002
movk x2, #0x8000, LSL #48   // начиная с 48-го бита

mov x3, #0x0010
 
// X2 * X3 = X1:X0
mul x0, x2, x3      // В X0 младшие 64 бита - 0x0000000000000020 или 32
smulh x1, x2, x3    // В X1 старшие 64 бита - 0xFFFFFFFFFFFFFFF8

Обратите внимание на разницу между умножением без знаком и со знаком - разница в том, как интерпретируются старшие 64 разряда. Почему так происходит? Дело в том, что старший бит при умножении со знаком интепретируется как индикатор знака: если он равен 0, то число положительное, если равен 1 - то число отрицательное. Посмотрим внимательно на перемножаемые числа в регистрах.

Число в регистре X2 начинается на 8 или в двоичной системе 10008. Инструкция SMULH трактует старший бит числа как индикатор отрицательного числа. Поэтому число, которое получается в результате произведения, то же будет отрицательным (учитывая, что число в регистре X3 положительное).

Вычисление отрицательного значения

В дополнение ассемблер предоставляет три инструкции, которые выполняют умножение двух чисел с последующим дополнительным умножением результата на -1:

  • MNEG: выполняет умножение значений 64-битных регистров и результат умножает на -1

    MNEG Xd, Xn, Xm

    Что фактически эквивалентно Xd = -(Xn * Xm)

  • SMNEGL: выполняет умножение значений 32-битных регистров со знаком и результат умножает на -1

    SMNEGL Xd, Wn, Wm
  • UMNEGL: выполняет умножение значений 32-битных регистров без знака и результат умножает на -1

    UMNEGL Xd, Wn, Wm

Пример использования инструкций:

mov x1, #0x2
mov x2, #0x3
mneg x0, x1, x2     // x0=-1*(x1 * x2) = 0xFFFFFFFFFFFFFFFA

В данном случае мы имеем следующую ситацию

-(0x0000000000000002 * 0x0000000000000003) =
- (0x0000000000000006) =
0xFFFFFFFFFFFFFFFA

результат умножения числа 0x0000000000000006 на -1 (дополнение до двух) равно 0xFFFFFFFFFFFFFFFA.

Инструкция SMNEGL учитывает старший разряд числа, который трактуется как указатель знака числа:

mov w1, #0x1
mov w2, #0xfffffff3     // w2 = -13
umnegl x0, w1, w2      // 0xFFFFFFFF0000000D

mov w1, #0x1
mov w2, #0xfffffff3     // w2 = -13
smnegl x0, w1, w2      // 0x000000000000000D = 13

В данном случае число в регистре W2 в качестве старшего разряда имеет значение 1. Соответственно инструкция SMNEGL трактует это число как отрицательное. Поэтому при умножении на -1 результат будет положительный (учитывая, что число в регистре W1 положительное).

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850