Last time, we saw how to use assembly to perform arithmetic calculations. We saw the following instructions:
We saw the following shifts which can be applied to the last operand:
And we saw how immediate values can be placed into instructions. Next we will look at how to control execution flow.
Assembly languages do not have control structures like if and else blocks, for loops or while loops. Instead, these can be built using branch instructions which are like goto statements that jump to a particular point in the program.
The simplest branch is done with the b instruction which always branches to its destination. The destinations are specified with labels which are just symbolic names inserted into the code followed by a colon.
For instance, the following function just loops forever:
.global forever forever: .top: @ a label for the top of the function b .top @ branch to the top label
By convention, labels used for branches (as opposed to functions) begin with a '.' symbol.
In addition to making unconditional branches like this, we can make the branches depend on some condition. This is done with the cmp instruction which takes two arguments and compares them. It sets a "status register" in the processor which can be checked by a branch instruction. The following branches check the status register:
For instance, the following function returns the absolute value of its argument:
@ abs.s /* function to find the absolute value of a number */ .global abs abs: cmp r0, #0 bge .end mvn r1, r0 add r0, r1, #1 .end: mov pc, lr
This program uses cmp to compare r0 to 0. It then uses bge to branch to the .end label when the number is 0 or greater. Otherwise, the branch has no effect, so the program just moves to the next two lines which serve to negate the number. The mvn instruction moves and does a bitwise inversion into r1. We then add 1 to the value. Together they do the "invert and add one" necessary to negate a two's complement number.
We can use these compare and branch instructions to write loops in assembly programs. For example, we can calculate the factorial of a number in assembly with the following function:
@ fact.s /* function to calculate the factorial of a number */ .global fact fact: sub r1, r0, #1 .top: cmp r1, #0 beq .done mul r0, r1, r0 sub r1, r1, #1 b .top .done: mov pc, lr
This program uses r0 to store the factorial as its being calculated, starting with the number passed in. It uses r1 to store the next value to be multiplied in. The initial sub instruction starts r1 at one less than the number passed in.
Inside the loop, we compare r1 with 0. When they are equal, the function jumps to the end of the function and returns. Otherwise, it multiplies r0 by r1, storing the result back into r0, then subtracts 1 from r1 and jumps back to the top of the loop.
Copyright © 2022 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.