9.3. Arithmetic Instructions
9.3.1. Common Arithmetic Instructions
The A64 ISA implements several instructions that correspond to arithmetic operations performed by the ALU. Table 1 lists several arithmetic instructions that one may encounter when reading ARM assembly.
Instruction | Translation |
---|---|
|
D = O1 + O2 |
|
D = O1 - O2 |
|
D = -(O1) |
The add
and sub
instructions correspond to addition and subtraction and
require two operands in addition to the destination register. In contrast,
the neg
instruction requires only one operand in addition to the destination
register.
The three instructions in Table 1 also have carry forms that
enable the instruction to use the optional carry condition flag, C
.
The one-bit carry flag is set when an unsigned operation overflows. We
cover other condition control flags in the following section, but describe the
carry flag here to introduce the additional arithmetic instructions. The
carry forms and their rough translation are shown in Table 2.
Instruction | Translation |
---|---|
|
D = O1 + O2 + |
|
D = O1 - O2 - |
|
D = -(O1) - |
The above instructions also have an optional s
suffix. When the s
suffix
is used (e.g., adds
), it indicates that the arithmetic operation is setting
condition flags.
Multiplication and Division
Instruction | Translation |
---|---|
|
D = O1 × O2 |
|
D = O1 / O2 (32-bit unsigned) |
|
D = O1 / O2 (64-bit signed) |
The most common multiplication and division instructions are shown in
Table 3. The mul
instruction operates on two operands
and places the product in the destination D. The division operation
does not have a generic form; the udiv
and sdiv
instructions
operate on 32-bit and 64-bit data, respectively. Note that you cannot
multiply 32-bit registers with 64-bit registers.
In addition, ARMv8-A provides composite forms for multiplication, allowing the CPU to perform more sophisticated operations in a single instruction. These instructions are shown in Table 4.
Instruction | Translation |
---|---|
|
D = O3 + (O1 × O2) |
|
D = O3 - (O1 × O2) |
|
D = -(O1 × O2) |
9.3.2. Bit Shifting Instructions
Bit shifting instructions enable the compiler to perform bit shifting
operations. Multiplication and division instructions typically take a long
time to execute. Bit shifting offers the compiler a
shortcut for multiplicands and divisors that are powers of 2. For example, to
compute 77 * 4
, most compilers will translate this operation to 77 << 2
to avoid the use of a mul
instruction. Likewise, to compute 77 / 4
, a
compiler typically translates this operation
to 77 >> 2
to avoid using the sdiv
instruction.
Keep in mind that left and right bit shifts translate to different instructions based on whether the goal is an arithmetic (signed) or logical (unsigned) shift.
Instruction | Translation | Arithmetic or Logical? |
---|---|---|
|
D = R |
logical or arithmetic |
|
D = R |
logical |
|
D = R |
arithmetic |
|
D = R |
neither (rotate) |
In addition to the destination register, each shift instruction takes two operands; one is usually a register (denoted by R) and the other is a 6-bit shift value (v). On 64-bit systems, the shift value is encoded as a single byte (since it doesn’t make sense to shift past 63). The shift value v must either be a constant or stored in a component register.
The last bit shifting instruction, ror
, requires special discussion. The
ror
instruction rotates the bits, replacing the most significant bits
with the least significant bits. We represent the rotate shift instruction
using the >>>
symbol.
Different versions of instructions help us distinguish types at an assembly level
At the assembly level, there is no notion of types. However, recall that the compiler can choose to use component registers based on the types present at the code level. Similarly, recall that shift right works differently depending on whether the value is signed or unsigned. At the assembly level, the compiler uses separate instructions to distinguish between logical and arithmetic shifts! |
9.3.3. Bitwise Instructions
Bitwise instructions enable the compiler to perform bitwise operations on data.
One way in which the compiler uses bitwise operations is for certain optimizations. For
example, a compiler may choose to implement 77 mod 4 with the operation
77 & 3
in lieu of the more expensive sdiv
instruction.
Table 6 lists common bitwise instructions, and composite bitwise instructions that utilize negation.
Instruction | Translation |
---|---|
|
D = O1 |
|
D = O1 |
|
D = O1 |
|
D = |
|
D = O1 |
|
D = O1 |
|
D = O1 |
Remember that bitwise not is distinct from negation (neg
). The mvn
instruction flips the bits of the operand but does not add 1. Be careful not
to confuse these two instructions.
Use bitwise operations only when needed in your C code!
After reading this section, it may be tempting to replace common arithmetic operations in your C code with bitwise shifts and other operations. This is not recommended. Most modern compilers are smart enough to replace simple arithmetic operations with bitwise operations when it makes sense, making it unnecessary for the programmer to do so. As a general rule, programmers should prioritize code readability whenever possible and avoid premature optimization. |