Code in ARM Assembly: Bit operations

In the previous article I looked at the rich families of instructions to perform arithmetic on integer values in the general-purpose registers. This article completes most of this survey of instructions by considering those which operate with bits and other representations of data.

Move as bits

In addition to the `MOV` instruction to move the contents of a register to another register, there are three variants which perform bit operations as part of the move:

• `MOVK` – move with keep, which inserts the first operand (a 16-bit immediate value) into an X or W register without changing the bits outside it,
• `MOVN` – move with NOT, which performs a one’s complement (bitwise NOT) on the first operand (a 16-bit immediate value) as it’s moved,
• `MOVZ` – move with zero, which inserts the first operand (a 16-bit immediate value) into an X or W register, zeroing the other bits.

Some examples explain these better:
`MOVK W0, #0x1234, LSL #16`
moves the halfword 0x1234 which has been left-shifted (see below) 16 places to form the most significant bytes in the register W0.

Because of the limitation on size of immediate values, you can load a 64-bit immediate using a sequence of one MOV followed by three MOVKs:
`MOV X0, #0xDEF0` // this is assembled as MOVZ to zero the other bits in the register
```MOVK X0, #0x9ABC, LSL #16 MOVK X0, #0x5678, LSL #32 MOVK X0, #0x1234, LSL #48```
which sets X0 to 0x123456789ABCDEF0.

Shifting bits

There are four ways in which the bits in a register can be shifted and rotated: LSL (logical shift left), LSR (logical shift right), ASR (arithmetic shift right) and ROR (rotate right).

`LSL` is an alias for LSLV, which shift bits to the left by moving in zero bits at the right:
`LSL W0, W1, #1`
shifts the contents of W1 one place to the left and puts the result in W0. The shift can be in a register, and for W registers can be 0-31, for X registers 0-63 in places.

For example, if W1 contained 0x0000 00F0 = 1111 0000, then shifting it left by one place converts it to 1 1110 0000 = 0x0000 01E0. Logical shift right moves the bits in the other direction.

If the integer being shifted is signed and negative, then LSR unfortunately changes its sign. To accommodate that, when shifting signed values to the right, you should use `ASR`, which is an alias of `ASRV`. This shifts in copies of the sign bit of the number rather than zeros. LSL and ASR are handy quick methods of multiplying and dividing integers by powers of 2.

LSL, LSR and their relatives all move in zeros. The other option is to move in the bits that are shifted out, which is performed by `ROR`, and its relative `RORV`.

Bitwise operations

The most common operations are:

• `AND` performs a bitwise AND, and has a sibling `ANDS` which does the same and sets NZCV flags;
• `BIC` and `BICS` perform a bitwise AND with the complement of a register value, which can be shifted;
• `ORR` performs a bitwise (inclusive) OR of a register value; `ORN` performs a bitwise (inclusive) OR of a register value and the complement of a register value;
• `EOR` performs a bitwise exclusive OR (XOR) of a register value; `EON` performs a bitwise exclusive OR (XOR) of the complement of a register value;
• `MVN` performs a bitwise inverse of a register value.

Miscellaneous operations you should be aware of, which are detailed in the instruction set reference, include:

• `BFC, BFI, BFM, BFXIL` – these clear, insert and move bitfields within registers;
• `REV` reverses the byte order in a register, and has relatives which work within sub-units: `REV16` for each halfword, and `REV32` for each word.

As promised, here’s a cheat sheet summary of the main operations using these registers:

and a tear-out PDF is here: armgpinstructions1

The next article will tackle the remaining topic involving the general-purpose registers, that of conditional operations, and their use to avoid branching. After that, I’ll move on to floating point.

Previous articles in this series:

1: Building an app to develop assembly routines, including an explanation of calling assembly language from Swift, with a complete Xcode project
2: Registers explained
3: Working with pointers
4: Controlling flow
5: Conditional loops
6: Flow, pipelines and performance
7: Moving data around
8: Integer arithmetic

ARM register summary
ARM operand architecture
Conditions and conditional branching instructions
Control Flow
ARM instructions for GP registers
AsmAttic 2, a complete Xcode project (version 2)
AsmAttic, a complete Xcode project (version 1)

References

Procedure Call Standard for the Arm 64-bit Architecture (ARM) from Github
Writing ARM64 Code for Apple Platforms (Apple)
Stephen Smith (2020) Programming with 64-Bit ARM Assembly Language, Apress, ISBN 978 1 4842 5880 4.
Daniel Kusswurm (2020) Modern Arm Assembly Language Programming, Apress, ISBN 978 1 4842 6266 5.
ARM64 Instruction Set Reference (ARM).