Indirect branching allows jumping to an address stored in a register, providing flexibility in control flow. The Arm instruction set includes two main indirect branch instructions – BX and BLX. This article will explain how they work and when to use each one.
What is an Indirect Branch?
An indirect branch occurs when the destination address for a jump or call instruction is computed at runtime rather than encoded directly in the instruction. For example: MOV R0, #0x100 BX R0
Here BX jumps to the address contained in R0, which could be set to any value at runtime. This contrasts with a direct branch like: B 0x100
Which always jumps to the fixed address 0x100.
BX – Branch and Exchange Instruction Set
The BX instruction performs an indirect branch to the address in a register, and can optionally switch between Arm and Thumb state. Its syntax is: BX Rn
BX jumps to the address in Rn. If bit 0 of Rn is 0, the processor remains in the current state (Arm or Thumb). If bit 0 is 1, the processor switches state.
For example: // Remain in Arm state MOV R0, #0x100 BX R0 // Switch to Thumb state MOV R0, #0x101 BX R0
This ability to conditionally change instruction set state is useful for interfacing between Arm and Thumb code.
Uses of BX
Some common uses of the BX instruction include:
- Returning from a function call
- Jumping to an address in a register for position-independent code
- Switching between Arm and Thumb states
- Implementing a primitive form of switch/case by jumping through a jump table
Function Returns
BX makes returning from a function easy: the return address was stored in LR during the BL call, so BX LR will jump back to the caller. This even works across changes in instruction set state thanks to the conditional switch ability of BX.
Position Independence
Code that computes jump targets at runtime can use BX to jump to those locations in a position independent way. For example: ADR R0, Function1 // R0 now contains address of Function1 BX R0 // Jump to address in R0
This allows the code to work regardless of where it is located in memory.
Switching Instruction Sets
BX provides a simple way to transfer control between Arm and Thumb code. For example: // Start in Arm state MOV R0, #1 // Set bit 0 BX R0 // Switch to Thumb // Now in Thumb state
Jump Tables
BX can be used to implement jump tables for primitive switch/case constructs. The table contains addresses to jump to indexed by case value: JUMPTABLE: DCD CASE1 DCD CASE2 DCD CASE3 // Load case value into R0 LDR R1, =JUMPTABLE LDR R0, [R1, R0, LSL #2] // Get address for case from table BX R0 // Jump
BLX – Branch Link Exchange
BLX performs similarly to BX but also stores the return address in LR, making it suitable for calling functions and subroutines. Its syntax is: BLX Rn
BLX branches to the address in Rn, switching instruction set state if bit 0 of Rn is 1. It also stores the address of the next instruction in the link register LR.
Uses of BLX
BLX is commonly used for:
- Calling functions indirectly
- Calling Thumb functions from Arm code and vice versa
- Calling code via function pointers
- Position independent calls
Indirect Calls
BLX allows computed destinations for calls: // R0 contains function address BLX R0 // Call through pointer MOV R0, pFunction BLX R0
This is useful any time the target of a call is not known at compile time.
Interworking Calls
BLX can call between Arm and Thumb thanks to its state switch ability: // Start in Arm state MOV R0, #1 // Set bit 0 BLX R0 // Switch to Thumb and call // Function is Thumb code
This facilitates interaction between the two instruction sets.
Function Pointers
BLX enables calling functions via C-style function pointers in Arm assembly: // R0 contains a function pointer BLX R0
Function pointers are useful for implementing callbacks, virtual functions, and more.
Position Independence
Like BX, BLX supports position independent calls: ADR R0, Function BLX R0
So the code works regardless of where it is located.
BX vs BLX
While BX and BLX are similar, there are some key differences:
- BLX stores a return address in LR, BX does not
- Use BX for jumps, BLX for calls
- BLX takes an extra cycle compared to BX
- BX can conditionally switch instruction sets, BLX always does
In general, use BX for simple jumps and BLX when you need to call a subroutine and be able to return.
Guidelines for Indirect Branches
Here are some guidelines for working with indirect branches on Arm:
- Use BX for position independent jumps and returns
- Use BLX for position independent calls
- Take care that register alignment requirements are met
- Beware of switching CPU states accidentally with BX
- Use BLX to call between Arm and Thumb code
- Indirect branches can make code harder to follow – use judiciously
Conclusion
BX and BLX provide flexible control flow on Arm using computed jump targets. BX performs simple indirect jumps, while BLX adds call abilities. Both can optionally switch instruction set state. Indirect branches enable position independence, call virtul functions, implement jump tables, and interact cleanly between Arm and Thumb. Used properly, they are powerful tools for advanced control flow.
This article has covered the key aspects of indirect branch instructions on Arm. BX and BLX provide a useful addition to direct branches, enabling flexible runtime-driven code execution.