-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5bc9d3b
commit 3b8bf3d
Showing
1 changed file
with
81 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
= Branches and Conditionals = | ||
|
||
So far the instructions we have seen only allow us to give the computer a fixed-size list of things to do. Once all of the instructions have run, the program is done. What if we wanted to do something simple like subtract 6 from r0 the number of times that are stored in r1? | ||
|
||
Well, first we will need a way to run instructions more than once. To do this, we use the `b` instruction: | ||
|
||
doing: | ||
sub r0, r0, #6 | ||
b doing | ||
|
||
What is this `doing:`? Any word at the start of a line followed by a colon is a "label". A "label" marks a position in the code so that we can refer to it elsewhere. This is because instructions may get loaded into the computer's RAM anywhere, and so we might not know ahead of time where and given instruction is in order to jump to it. Labels solve this problem, and also let us use nice names that make sense to us instead of obscure numbers representing locations in RAM. | ||
|
||
If you run this program, it will never finish. That's because the `b` instruction just causes the CPU to jump (or "branch") to the `doing` label. So it will just keep running that subtract instruction forever. | ||
|
||
To solve this problem, ARMv6 allows us to put a specifier on the end of any instruction to say that some condition must be true in order for the instruction to run. For example, to branch only if something is equal to something else, we can use `beq` instead of just `b`, or `bne` to branch if they are not equal. | ||
|
||
We also need a way to say what things we are testing for equality. The simplest instruction for doing this is `cmp`. | ||
|
||
doing: | ||
sub r0, r0, #6 | ||
sub r1, r1, #1 | ||
cmp r1, #0 | ||
bne doing | ||
|
||
This code will keep subtracting 6 from r0, and 1 from r1, until r1 equals 0. What if r1 starts at 0? Then the first subtraction (which runs before we check for equality) will cause it to no longer equal 0, and it only goes down from there, so the program will run forever. Lets fix that: | ||
|
||
b loopcond | ||
doing: | ||
sub r0, r0, #6 | ||
sub r1, r1, #1 | ||
loopcond: | ||
cmp r1, #0 | ||
bne doing | ||
|
||
Now we check the condition before starting the loop. We could also write it like this: | ||
|
||
doing: | ||
cmp r1, #0 | ||
subne r0, r0, #6 | ||
subne r1, r1, #1 | ||
bne doing | ||
|
||
This way, the subtractions and branch will never happen if r1 is equal to 0, and we have less branches happening (since we don't need to jump to the end to do the comparison). This can matter because branch instructions cause CPUs to lose some of the smart things they do in order to go faster (because they suddenly are executing code from somewhere else than they might have expected). | ||
|
||
So now we have a program that can do something to r0 based on r1. What if we wanted to do this many times in our program? Would we have to copy this code everywhere? | ||
|
||
Well, we can run this code from anywhere in our program (with `b doing`), but how would it get back to us? Right now it would just keep running whatever instructions came next. | ||
|
||
Maybe we could store the location to come back to in a register? But how do we know where we are? Well, we could use labels, like so: | ||
|
||
mov r14, comeback | ||
b doing | ||
comeback: ...rest... | ||
|
||
This would work, but then we have to come up with a unique label name for every place we want to do this. Luckily, the CPU also needs to know where we are in order to load and run the next instruction. It stores the location in register 15, which we can also call "pc" (for "Program Counter"): | ||
|
||
mov r14, pc | ||
b doing | ||
|
||
But of course, we don't want to come back to exactly our current instruction, since then we will just branch to `doing` again right away! | ||
|
||
mov r14, pc | ||
add r14, r14, #8 | ||
b doing | ||
|
||
It turns out that wanting to do this is common enough, that there is a special instruction name for it: `bl` (or "Branch and Link"). It always stores the place we want to come back to in register 14, sometimes called `lr` (for "Link Register"). | ||
|
||
bl doing | ||
|
||
Of course, we would also need to modify `doing` to come back. | ||
|
||
doing: | ||
sub r0, r0, #6 | ||
sub r1, r1, #1 | ||
cmp r1, #0 | ||
bne doing | ||
b lr | ||
|
||
Now we can set r0 and r1 from anywhere in our code, and then cause `doing` to operate on them. But what if we didn't want doing to modify r1? What if we still need that value? Well, we could just `mov r3, r1` or something, but what if we were using r3 for something? `doing` can't know what registers the code that calls it is using, and even we as the programmer can't know for sure how we will use it in the future. We could be careful to use only certain registers, but what if we needed those in a new place later on? What if we had some code that needed almost *all* of the registers? | ||
|
||
The solution to this, as we will see, is to store things in RAM and not just in registers. |