AArch64 Playground
4.7 · Arithmetic, and the missing remainder

Arithmetic, and the missing remainder

AArch64 has an instruction for each of the four basic operations: add, sub, mul, and two for division, sdiv and udiv. It also has instructions that multiply and add in one step, which keeps a formula short. What it does not have is an instruction for the remainder. This lesson goes through each instruction, uses them to work out a formula, and then builds the remainder out of a divide and a multiply.

Destination first

Every arithmetic instruction names the register that gets the answer first, then the registers it reads, called its sources. sub w0, w1, w2 means "w0 becomes w1 minus w2". The destination can also be one of the sources, so add w19, w19, w20 adds w20 onto w19.

Use the w registers for int values, which are 32 bits, and the x registers for long values, which are 64 bits. In these instructions every register is the same width: all w or all x.

The eight instructions

Every row of this table starts from w1 = 7, w2 = 3 and w3 = 10.

InstructionWhat it computesResult
add w0, w1, w2w1 + w210
sub w0, w1, w2w1 - w24
mul w0, w1, w2w1 * w221
madd w0, w1, w2, w3w1 * w2 + w331
msub w0, w1, w2, w3w3 - w1 * w2-11
mneg w0, w1, w2-(w1 * w2)-21
sdiv w0, w1, w2w1 / w2, signed2
udiv w0, w1, w2w1 / w2, unsigned2

madd is short for multiply-add, msub for multiply-subtract, and mneg for multiply-negate. In madd and msub the register that is added to, or subtracted from, comes last: msub w0, w1, w2, w3 is w3 - w1 * w2, not w1 * w2 - w3.

sdiv reads the bits as a signed number, one that can be negative, and udiv reads them as an unsigned number, one that cannot. For 7 and 3 they agree. The last section shows where they do not.

pitfall

Only add and sub accept an immediate, a constant written into the instruction itself, in place of their last source register: add w0, w1, 10 works, and any value from 0 to 4095 is always accepted. The other six need every source in a register, so mul w0, w1, 3 does not assemble. Put the constant in a register first with mov w9, 3, then multiply by w9.

Working out a formula

To work out a formula, split it into steps of one instruction each and keep each partial answer in a register. A register used this way is called a temporary: it holds a value only until a later step uses it. For y = 3x^2 - 5x + 7, where x^2 means x times x, the steps are:

  1. sq = x * x, with mul
  2. y = 3 * sq + 7, with madd
  3. y = y - 5 * x, with msub

The constants 3, 5 and 7 go into w9, w10 and w11 first, because madd and msub take no immediates. Those three are scratch registers, which means a function you call, such as printf, is allowed to change them. That is fine here, because nothing reads them after the call.

The program prints 3x^2 - 5x + 7 at x = 4 is 35. Change X_VALUE to 5 and run it again: the answer becomes 57.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 1try it: run it, or step one instruction at a timeOpen in playground

note

This main keeps its named values in w19 to w21 without saving those registers first. Course programs do this in main: when main returns, the program ends, so nothing is left to notice the change. A function of your own must save any of x19 to x28 that it changes and put them back before it returns, which a later lesson shows.

Division drops the fraction

sdiv gives a whole number and drops the fraction, always rounding toward zero: 38 / 7 is 5, not 5.43, and -38 / 7 is -5, not -6. The part that was dropped is the remainder, and no AArch64 instruction hands it to you.

Building the remainder

The remainder is what is left after taking away as many whole copies of the divisor as fit:

remainder = a - (a / b) * b

That is the shape of msub, so a divide followed by a multiply-subtract gives it:

        sdiv    quot_r, a_r, b_r            // quot = a / b        msub    rem_r, quot_r, b_r, a_r     // rem = a - quot * b

The program below shares 38 cookies among 7 friends. 38 / 7 is 5, and 38 - 5 * 7 is 3, so it prints 38 cookies, 7 friends: 5 each, 3 left over. Set COOKIES to 40 and run it again: the last number becomes 5.

With a negative number to divide, the remainder comes out negative too: -38 and 7 give -5 each and -3 left over, the same answer as the % operator in C.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 2try it: run it, or step one instruction at a timeOpen in playground

Dividing by zero

On many machines, dividing by zero stops the program with an error. AArch64 does not: sdiv and udiv with a zero divisor write 0 and carry on. The program keeps running with a wrong answer and nothing warns you, so when a divisor could be zero, compare it with zero before dividing. Conditionals with cmp and b.cond shows how to act on that comparison.

Signed or unsigned division

A register holds bits, and the same bits can be read two ways. Read as a signed number, the 32 bits of -8 mean -8. Read as an unsigned number, the same bits mean 4294967288, because an unsigned number has no negative values. sdiv uses the signed reading and udiv the unsigned one, so they agree on positive numbers and disagree as soon as one is negative. Use sdiv for int and long values, and udiv only for values that can never be negative.

The program below divides 9 by 0, then divides -8 by 2 both ways. The %u specifier prints the unsigned reading of a w register. It prints:

sdiv 9 / 0 = 0
sdiv -8 / 2 = -4
udiv -8 / 2 = 2147483644 (it read -8 as 4294967288)

Binary, hex, and octal explains why -8 and 4294967288 share one bit pattern.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 3try it: run it, or step one instruction at a timeOpen in playground

Check yourself

  1. After mov w1, 4, mov w2, 6 and mov w3, 30, what does msub w0, w1, w2, w3 leave in w0?
  2. Which two instructions leave the remainder of w19 divided by w20 in w21, using w22 for the quotient?
  3. Why does mul w0, w1, 10 fail to assemble, and what is the fix?
  4. What does sdiv w0, w1, w2 leave in w0 when w2 holds 0?

answers

show answers
  1. 6, because 30 - 4 * 6 = 6.
  2. sdiv w22, w19, w20, then msub w21, w22, w20, w19.
  3. mul takes registers only, so move 10 into a register first: mov w9, 10, then mul w0, w1, w9.
  4. 0, and the program carries on with no error.

Practice