Arithmetic, and the missing remainder
prerequisite
Read these first:
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.
| Instruction | What it computes | Result |
|---|---|---|
add w0, w1, w2 | w1 + w2 | 10 |
sub w0, w1, w2 | w1 - w2 | 4 |
mul w0, w1, w2 | w1 * w2 | 21 |
madd w0, w1, w2, w3 | w1 * w2 + w3 | 31 |
msub w0, w1, w2, w3 | w3 - w1 * w2 | -11 |
mneg w0, w1, w2 | -(w1 * w2) | -21 |
sdiv w0, w1, w2 | w1 / w2, signed | 2 |
udiv w0, w1, w2 | w1 / w2, unsigned | 2 |
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:
sq = x * x, withmuly = 3 * sq + 7, withmaddy = y - 5 * x, withmsub
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.
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 * bThe 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.
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.
pitfall
Common mistakes from this lesson, each with a broken program and its fix that you can run:
Check yourself
- After
mov w1, 4,mov w2, 6andmov w3, 30, what doesmsub w0, w1, w2, w3leave inw0? - Which two instructions leave the remainder of
w19divided byw20inw21, usingw22for the quotient? - Why does
mul w0, w1, 10fail to assemble, and what is the fix? - What does
sdiv w0, w1, w2leave inw0whenw2holds 0?
answers
show answers
- 6, because 30 - 4 * 6 = 6.
sdiv w22, w19, w20, thenmsub w21, w22, w20, w19.multakes registers only, so move 10 into a register first:mov w9, 10, thenmul w0, w1, w9.- 0, and the program carries on with no error.
Practice
- Warm up the registers: combine three registers with an add and a subtract.
- Change for a twenty: split change into coins with
sdivandmsub. - Fizzbuzz, by hand: the divide-then-
msubremainder inside a loop. Try it after the pre-test loop lesson. - Intermediate quiz: ARMv8 assembly, Fill in the blank: ARMv8 assembly (core) and Predict: ARMv8 assembly (challenge): questions on these instructions and the registers around them.