1

I am having a lot of trouble doing modulus on ARM7.

Currently, I have this code:

ADD R0,R0,R1
MOV R0, R0 MOD 2
BX LR

But it doesn't work at all.

It seems from what my classmates have done that we are supposed to do it by bit shifts, but I don't understand how would this work.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
thedogs
  • 11
  • 1
  • 2
  • “It doesn't work” is not an error description. Always say what error you get! – fuz Mar 01 '19 at 23:17

1 Answers1

4

Indeed, the syntax you have is not correct. Although most (all?) ARM assemblers support the MOD operator, it only works when both operands are assembly-time constants. It just does assembly-time arithmetic and constant expression folding. So, you could do:

mov  r0, #11 MOD 3     ; R0 = 2 = (11 % 3)

which would essentially be transformed into:

mov  r0, #2

thus moving the value 2 into the R0 register.

This is nice, because it allows you to perform modulus on declared constants (which you use for readability), and also to write expressions so that they are human-readable and thus more maintainable.

It doesn't, however, work when you are dealing with registers, variables, or anything which is not an assembly-time constant.


Based on the code you have in the question, it looks like you are adding the contents of the R1 register to the R0register, and then trying to compute R0 modulo 2.

Assuming that the integers are unsigned, that is as simple as:

add  r0, r0, r1     ; R0 = (R0 + R1)
and  r0, r0, #1     ; R0 = (R0 & 1)
bx   lr

This works because x % 2 is equivalent to x & 1 for unsigned integers. In general, x % n is equivalent to x & (n - 1) as long as n (the divisor) is a power of two. This not only easier to write, but it is also a performance optimization, because bitwise operations are faster than division.

Now that you know the pattern for modulo by powers of two, you can easily do (r0 + r1) % 4:

add  r0, r0, r1     ; R0 = (R0 + R1)
and  r0, r0, #3     ; R0 = (R0 & 1)
bx   lr

If you want to do modulo by a constant that is not a power of two, then things get more complicated. I would not try to write this out by hand in assembly. Instead, I'd look to see what a compiler would generate. Here's the way you'd perform (r0 + r1) % 3 in assembly:

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

The compiler has generated optimized code to compute the integer modulus. Instead of doing a full division, it has transformed this into multiplication by a magic number (the multiplicative inverse). This is a standard trick from Hacker's Delight, and a common strength-reduction optimization used by many compilers.


So far, we've looked at modulo operations on unsigned integer types. What about when you want to do modular arithmetic on signed integers? Well, you need to take the sign bit (which is the MSB) into account.

For (r0 + r1) % 2, where r0 and r1 are signed, and thus r0 + r1 produces a signed result:

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

This is very similar to the code we had for unsigned modulus, except for the IT+RSBMI instructions to conditional negate, based on whether or not the input value is negative (in other words, to take the absolute value).

(You only specified ARMv7 in the question, not which profile you were targeting. If your chip has the "A" (application) profile, you can omit the IT instruction. But otherwise, you are targeting the Thumb-2 instruction set, which doesn't support conditional execution of non-branch instructions, so you need the IT before the RSBMI instruction. See Conditional Execution in Thumb-2.)

Unfortunately, calculating (r0 + r1) % 4 isn't a simple matter of changing the constant operand of the AND instruction. You need more code, even for modulo by constants powers of two. Again, ask a compiler how to do it. Definitely ask a compiler for signed modulo of non-powers of two.


If you want to do a general modulus operation on two variables, things are much more difficult, because you can't simply use bit-twiddling. C compilers are going to emit a call to a library function:

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}

Here, GCC dispatched to the __aeabi_uidivmod library function for unsigned and the __aeabi_idivmod library function for signed modulo/division. Other compilers will have their own library functions.

Don't try and write this sort of code by hand in assembly. It simply isn't worth the effort. If necessary, extract the function from a C compiler's standard library, and call it to do the heavy lifting. (Your teacher is not expecting you to do this.)

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • If you compile for a specific ARMv7 CPU that *does* support [`sdiv`](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289894402.htm) (and [`mls`](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289878654.htm)), e.g. `-mcpu=cortex-a15`, gcc will use it/them https://gcc.godbolt.org/z/CdJWrg. GCC `-march=armv7` is the common baseline for *all* ARMv7 cores, and doesn't include ARM-mode, `sdiv`, or many other things that you'll definitely find on a cell-phone CPUs for example. Even cortex-m3 has `sdiv` (but not m0). `-march=armv7ve` has it, but IDK what that is. – Peter Cordes Mar 02 '19 at 03:34
  • tl;dr: use `sdiv` for division, and `mls` to multiply-and-subtract to get the remainder. (If you want to be mathematically precise, modulus and remainder aren't identical. Modulus is what Python does, where it's always positive even for negative dividends. [What's the difference between “mod” and “remainder”?](//stackoverflow.com/q/13683563) and [What is the result of % in Python?](//stackoverflow.com/posts/comments/36649721). C does plain remainder.) But probably the OP actually wants or would be ok with remainder, not modulus. – Peter Cordes Mar 02 '19 at 03:39