What Does a Branch Do?
A branch, quite simply, is a break in the sequential flow of instructions that the processor is executing. Some other architectures call them jumps, but they're essentially the same thing. The following is a trivial, and hopefully familiar example of a branch:
entry_point: mov r0, #0 @ Set r0 to 0. b target @ Jump forward to 'target'. mov r0, #1 @ Set r0 to 1. target: ... @ At this point, r0 holds the value 0. ... @ The second mov instruction did not execute.
There are several variants of branches in the ARM and Thumb instruction sets. Several of these variants are in common with many other CPU architectures, but there are also a few branch variants specific to ARM. Each variant is explained in detail below:
Relative and Absolute Branch Targets
A relative branch is one where the target address is calculated based on the value of the current
pc (program counter). Given the example above, an assembler would work out that the
target label is eight bytes ahead of the
b target instruction (in ARM code) and then generate a relative branch which means 'jump forward by eight bytes'. Relative branches are essential for position-independant code, which is expected to run correctly at any location in memory. The most common relative branches on ARM are single instructions and tend to be the most efficient branches available, though they have limited range.
An absolute branch will always jump to the specified address, regardless of the current
pc. Absolute branches are used when the address of the target is provided as a function pointer, for example. However, because an absolute branch requires a full 32-bit target address, absolute branches usually require a load or some other constant-loading mechanism in addition to the branch instruction itself.
In many cases, the programmer (or compiler) may not actually care whether a branch is relative or absolute, and might just use whichever is most efficient on a case-by-case basis.
Because the ARM instruction set is fixed-width at 32 bits (and Thumb has either 16 or 32 bits), it is not possible to encode a full 32-bit branch offset in a single instruction. Relative branches can be encoded using a limited-range offset from the current
pc. In assembly code, this is usually written as a branch to a label (as in the example above). The assembler will work out the required offset.
The range available varies between ARM and Thumb (and in a few cases also between instruction variants) but is usually very large and quite sufficient for most branches within a program. By using various combinations or additional instructions and literal pool loads, it is also possible to construct arbitrary full-range branches in case the single-instruction range is not sufficient. All practical absolute branches are necessarily full-range, since a 32-bit target address needs to be loaded.
Almost every modern programming language has some concept of functions. Any given function can (in general) be called from any part of a program, so processor architectures need some way to store the address of the caller. On ARM processors, this return address is stored in
lr (the link register). Branch instructions with an
l suffix -- like
blx -- work just like a standard
bx branch, but also store a return address in
If a function does not modify
lr, then the return sequence can (and should) be a simple "
bx lr". Otherwise, the
lr can be pushed onto the stack at function entry. From here, the best return sequence is usually to
pop directly into
pc, though a number of other options are possible depending on the situation.
Interworking Branches (Between ARM and Thumb Code)
Programs on ARM processors can use either the ARM or Thumb instruction set, or both. Whilst ARM and Thumb instructions cannot be directly interleaved, it is possible to switch (or interwork) between ARM and Thumb states at run-time. This interworking is most notably achieved using special branch instructions with an
x suffix, like
blx. Several other branch mechanisms are also capable of interworking. For example, the return sequence which writes to the
pop (or any other memory access) can interwork, and will always return in the appropriate state.
Branch instructions fall into three classes: Instructions that never change state (like "
b label"), instructions that always change state (like "
blx label"), and instructions that automatically change state based on the target address (like "
Address-based interworking uses the lowest bit of the address to determine the instruction set at the target. If the lowest bit is 1, the branch will switch to Thumb state. If the lowest bit is 0, the branch will switch to ARM state. Note that the lowest bit is never actually used as part of the address as all instructions are either 4-byte aligned (as in ARM) or 2-byte aligned (as in Thumb).
ARM Branch Instructions
The following table lists the branch instructions commonly used on ARM processors:
|Absolute||Simple (none)||Address-based |
|Relative||Function call (||Never||Note that assemblers will generally select between |
|Relative||Function call (||Always||Note that assemblers will generally select between |
|Absolute||Function call (||Address-based |
|Absolute||Simple (none)||Address-based  (since ARMv5T)||A common return sequence in cases where |
|Absolute||Simple (none)||Address-based  (since ARMv5T)||Load from a literal pool directly into |
It is also possible to write into the
pc using arithmetic instructions, but this is useful only in specific cases 1, and use of the normal branch instructions is advisable where possible.
Most of the interworking branches were added on ARMv5T. The only way to interwork on ARMv4T was to use the
bx instruction. ARMv4T interworking branch sequences are often much less efficient than the ARMv5T versions, so it's best to use ARMv5T branches unless you really need ARMv4T compatibility.
Using More Complex Branches
To encode more complex branches than those listed above, a combination of instructions must be used. In cases like this, where the target address must be calculated in advance of the branch instruction, normal methods for loading and calculated values are used. Arithmetic might be used for long-range relative branches, for example, and a constant pool load might be used for an absolute branch.
Thumb-2 Special-Purpose Branches
Finally, there are a few branches available specifically in the Thumb-2 instruction set that are designed for specific use-cases. These are not available to the ARM instruction set (or to the original Thumb instruction set), and so I will give them only a brief mention, but if you're writing Thumb-2 code they can be very useful. For further details, refer to the ARMv7-A/R Architecture Reference Manual.
For each special-purpose branch, I will also give a roughly equivalent ARM implementation. The ARM implementations have different limitations (such as branch range) and have other side effects (such as requiring a scratch register). Nevertheless, they should serve to clarify the behaviour of the Thumb-2 instructions.
cbnz (compare, branch on non-zero) and
cbz (compare, branch on zero) instructions are useful for very short-range forward branches, such as loop terminations, that would otherwise require two or more instructions. The two-instruction version is still available, of course, and may be useful if more range is required, or if a more complicated comparison is required.
ARM Implementation Thumb-2 Implementation cmp rA, #0 cbz rA, label beq label cmp rA, #0 cbnz rA, label bne label
tbb (table branch byte) and
tbh (table branch halfword) instructions are useful for the implementation of jump tables. One argument register is a base pointer to a table, and the second argument is an index into the table. The value loaded from the table is then doubled and added to the
ARM Implementation Thumb-2 Implementation ldrb ip, [rA, rB] tbb rA, rB add pc, pc, ip, lsl #1 ldrh ip, [rA, rB, lsl #1] tbh rA, rB, lsl #1 add pc, pc, ip, lsl #1
1A typical example of where arithmetic-based branches are useful is in the implementation of jump tables, but they are occasionally useful in other cases.
2Bit 0 of the address indicates the instruction set of the target. If 1, the target is Thumb. If 0, the target is ARM.