Действительно, у вас неправильный синтаксис.Хотя большинство (все?) Сборщиков ARM поддерживают оператор MOD
, он работает только тогда, когда оба операнда являются константами времени сборки.Он просто выполняет арифметику во время сборки и свертывание константных выражений.Таким образом, вы можете сделать:
mov r0, #11 MOD 3 ; R0 = 2 = (11 % 3)
, который по существу будет преобразован в:
mov r0, #2
, таким образом, переместив значение 2 в регистр R0
.
Thisэто хорошо, потому что он позволяет вам выполнять модуль для объявленных констант (которые вы используете для удобочитаемости), а также писать выражения, чтобы они были удобочитаемыми и, следовательно, более удобными для обслуживания.
Однако это не так.работать, когда вы имеете дело с регистрами, переменными или чем-то, что не является константой времени сборки.
Исходя из кода, который у вас есть в вопросе, похоже, что вы добавляете содержимоерегистр R1
к регистру R0
, а затем пытается вычислить R0
по модулю 2.
Предполагая, что целые числа равны unsigned , это так просто:
add r0, r0, r1 ; R0 = (R0 + R1)
and r0, r0, #1 ; R0 = (R0 & 1)
bx lr
Это работает, потому что x % 2
эквивалентно x & 1
для целых чисел без знака.В общем, x % n
эквивалентно x & (n - 1)
, пока n
(делитель) - это степень двойки.Это не только легче написать, но и оптимизировать производительность, потому что побитовые операции выполняются быстрее, чем деление.
Теперь, когда вы знаете шаблон для модуля по степеням двух, вы можете легко выполнить (r0 + r1) % 4
:
add r0, r0, r1 ; R0 = (R0 + R1)
and r0, r0, #3 ; R0 = (R0 & 1)
bx lr
Если вы хотите сделать по модулю постоянную, которая является не степенью двойки, то все становится сложнее.Я бы не пытался выписать это вручную при сборке.Вместо этого я бы посмотрел на , чтобы увидеть, что компилятор сгенерирует .Вот как вы будете выполнять (r0 + r1) % 3
в сборке:
add r0, r0, r1 ; R0 = (R0 + R1)
movw r3, #43691 ; \ R3 = 0xAAAAAAAB
movt r3, 43690 ; /
umull r2, r3, r3, r0 ; R3:R2 = (R3 * R0) [R3 holds upper and R2 holds lower bits of result]
lsrs r3, r3, #1 ; R3 = (R3 >> 1)
add r3, r3, r3, lsl #1 ; R3 = (R3 + R3 * 2)
subs r0, r0, r3 ; R0 = (R0 - R3)
bx lr
Компилятор сгенерировал оптимизированный код для вычисления целочисленного модуля.Вместо полного деления он превратил это в умножение на магическое число (обратное умножение).Это стандартный прием от Хакера Delight и общая оптимизация снижения прочности, используемая многими компиляторами .
Пока что мы рассмотрелиОперации по модулю на беззнаковых целочисленных типах.Как насчет того, когда вы хотите сделать модульную арифметику для знаковых целых чисел?Ну, вам нужно принять во внимание бит знака (который является MSB).
Для (r0 + r1) % 2
, где r0
и r1
подписаны, и, таким образом, r0 + r1
дает подписанный результат:
adds r0, r0, r1 ; R0 = (R0 + R1) <-- note "s" suffix for "signed"
and r0, r0, #1 ; R0 = (R0 & 1) <-- same as before for unsigned
it mi ; conditionally execute based on sign bit (negative/minus)
rsbmi r0, r0, #0 ; negate R0 if signed (R0 = abs(R0))
bx lr
Это очень похоже на код, который мы использовали для модуля без знака, за исключением инструкций IT
+ RSBMI
для условного отрицания, основанных на том, является ли входное значение отрицательным (в другихслова, чтобы принять абсолютное значение).
(Вы указали в вопросе только ARMv7, а не профиль, на который вы нацеливались. Если у вашего чипа есть профиль "A" (приложение), вы можете опустить IT
инструкция. Но в противном случае вы ориентируетесь на набор инструкций Thumb-2, который не поддерживает условное выполнение инструкций, не связанных с ветвлением, поэтому вам нужно IT
перед инструкцией RSBMI
. См. Условное выполнение вThumb-2 .)
К сожалению, вычисление (r0 + r1) % 4
не является простым вопросом изменения постоянного операнда инструкции AND
.Вам нужно больше кода, даже по модулю с постоянными степенями два.Опять спросите у компилятора как это сделать.Определенно спросите у компилятора о знаковом модуле не степеней двойки.
Если вы хотите выполнить общую операцию модуля над двумя переменными, все гораздо сложнее, потому что выне могу просто использовать бит-тиддлинг. Компиляторы C собираются передать вызов библиотечной функции :
UnsignedModulo(unsigned int i, unsigned int j, unsigned int m):
push {r3, lr}
add r0, r0, r1
mov r1, r2
bl __aeabi_uidivmod
mov r0, r1
pop {r3, pc}
SignedModulo(int i, int j, int m):
push {r3, lr}
add r0, r0, r1
mov r1, r2
bl __aeabi_idivmod
mov r0, r1
pop {r3, pc}
Здесь GCC отправил библиотечную функцию __aeabi_uidivmod
для unsigned и библиотеку __aeabi_idivmod
функция для подписанного модуля / деления.Другие компиляторы будут иметь свои собственные библиотечные функции.
Не пытайтесь писать такого рода код вручную при сборке.Это просто не стоит усилий.При необходимости извлеките функцию из стандартной библиотеки компилятора C и вызовите ее, чтобы выполнить тяжелую работу.(Ваш учитель не ожидает, что вы это сделаете.)