Kevin Boone

ARM assembly-language programming for the Raspberry Pi

3. Using branch instructions

This example builds on the previous one by illustrating a simple branch ("goto", "jump") instruction. It doesn't do anything more useful -- it just does it in a slightly different way. The ARM instruction set has many different branch instructions and, unlike in most CPUs, we have to use branches to implement function calls as well as jumps.

The example

Here is the example. You can build and execute it exactly the same as the previous example (as followed by ld).

// Set exit value to a literal number, by jumping to code that
//  invokes sys_exit
.text

.global _start

// Exit the program.
//   The exit code is always 42
exit:
    mov    %r0, $42    
    mov    %r7, $1     // sys_exit is syscall #1
    swi    $0          // invoke syscall 

_start:
    b      exit

New ideas in this example

Notice first of all that the program does not start execution with the first instruction in the source, as the previous one did. Execution starts at the address labeled _start. At this address there is only one instruction:

    b      exit

b is the assembly code for branch -- the most basic way to move program execution from one address to another. exit is a label -- it was defined earlier in the code.

Branching as a means to control program flow

If we label a piece of self-contained code, then we can jump to it from anywhere. In this example, the code that exits the program is labeled exit, and it's certainly plausible that we might need to exit the program from a number of different places. Grouping instructions that form a self-contained unit of functionality is one of the most basic of all programming techniques, regardless of the language.

In this example, the block of code that exits the program never completes -- execution stops at the syscall. So the question what to do afterwards never arises. Being able to execute a self-contained piece of code, and then continue with a previous sequence of operations, allows us to implement function calls (or subroutines, or procedures -- these terms are essentially synonymous in assembly programming). I'll have a lot more to say about functions later.

Conditional branches -- a preview

The b branch is unconditional -- the program execution moves to the specified location, and that's that. The real power of branching comes from conditional branches. These are branches that may, or may not, be executed, depending on certain conditions. Conditional branches are usually preceded by some kind of test on a register -- is it greater than some number, or less; is it zero or not. Conditional branches will become increasingly important in later, more complex examples.

A digression -- ARM machine code

There are a number of different 32-bit ARM cores on the market, and 64-bit cores are now available, too. There are subtle variations even between the different 32-bit devices. In these articles I will be focusing on the 'traditional' ARMv7 cores, which are found in most mobile devices and many desktop ones. The instruction set is usually just known as "ARM", although some writers use the term "AArch32" to distinguish it from other instructions sets that are available. All instructions in the AArch32 set are exactly 32 bits (four bytes) in length; this is understandable, but not a necessity, for a CPU with a 32-bit register size.

Many ARM devices support an alternative instruction set called "Thumb" and a few, mostly for the microcontroller market, support Thumb exclusively. The Thumb instruction set uses predominantly 16-bit instructions. Originally, Thumb was devised as more space-efficient subset of AArch32, but Thumb has expanded so that it offers overlapping functionality with AArch32. For reasons of space, and to avoid unnecessary duplication, I won't be dealing with Thumb at all in these articles.

If you don't like looking at hexadecimal code, or have no interest in the low-level design of the ARM instruction set, you won't lose much by skipping the rest of this section.

It can be illustrative to look at the executable machine code using a disassembler like objdump. This will show the source code alongside the machine code generated by the assembler.

$ objdump -d 02_first_jump.o 
00000000 :
   0:	e3a0002a 	mov	r0, #42	; 0x2a
   4:	e3a07001 	mov	r7, #1
   8:	ef000000 	svc	0x00000000

0000000c <_start>:
   c:	eafffffb 	b	0 

It should be clear that each instruction is exactly 32 bits (eight hex digits) long. The first digit is the condition code, which determines whether or not the instruction will actually be executed. The value 0xE means that the instruction should be executed unconditionally. Nearly every instruction in every example in this series will be unconditional, except for conditional branches.

The actual instruction code is split between the second and third hex digits, which makes it hard to pick out. The instruction code is only four bits long, so the ARM instruction set has only sixteen basic operations. Each of these is modified in various ways by the condition code and immediate flag -- this is why there are (many) more than sixteen assembly language instructions.

The lower 5 digits encode the registers that the operation should apply to, and/or an immediate number. The immediate number is only 11 bits long, which partly -- but not completely -- explains why the range of immediate values is limited in the way it is. You should be able to pick out the immediate values 42 (0x2A) and 1 in the hex dump.

Summary