So far we have seen how to move data between memory and processor registers, and how to do arithmetic in the registers. All are programs have been linear, a list of instructions carried out from start to exit. In this chapter we look at the assembly language instructions that alter the control flow of a program, and then at how these can be used to implement the high-level language constructs of if, while, & for.
The order of program execution is determined by the PC (program counter). It can be altered by changing the PC. This can be done by jump and branch instructions. Jump is just the notorious go to that has been supressed from high level languages. Branch instructions are so named because they offer an alternative, to branch or not. A "flow chart" of a program "branches" where there is a branch instruction.
A jump instruction puts the address of a label into the PC. Execution continues from that point. jar also saves the old PC value as a "return address" in register $ra ($31). This is used for procedure call, see chapter 8.
Instruction | Example | Meaning |
j label | j do_it_again | next instruction is at the label do_it_again: |
jal label (jump and link) |
jal my_proc | execution of procedure my_proc will start. $ra holds address of instruction following the jal |
jr Rsrc (jump register) |
jr $ra | Retrun from procedure call, by putting $ra value back into the PC |
These instructions branch, that is, change the PC to the label, when a condition is true. The condition is always a comparison of 2 registers, or a register and aconstant. Normally, they are compared as signed numbers. Where it matters, you get an unsigned compare by ending the operation code with a u.
Instruction | Branch to label if | Unsigned |
beq Rsrc1, Src2, label | Rsrc1 = Src2 | |
bne Rsrc1, Src2, label | Rsrc1 <> Src2 | |
blt Rsrc1, Src2, label | Rsrc1 < Src2 | bltu |
bgt Rsrc1, Src2, label | Rsrc1 > Src2 | bgtu |
ble Rsrc1, Src2, label | Rsrc1 <= Src2 | bleu |
bge Rsrc1, Src2, label | Rsrc1 >= Src2 | bgeu |
Comparison with zero is useful, and can be done simply by using $0 as Src2 (remember, it always holds the value 0). For your convenience, you can write comparison with zero by putting a z at the end of the operation code. Thus these two instructions are the same:
blt $t0, $0, cold bltz $t0, cold
(You can't use both u and z. There would be no point.)
These are all the instructions you need to write very complicated programs. In fact, you can write programs that are too complicated with them, so complicated that you have to explain the flow with so many arrows that your paper looks like a bunch of spaghetti. You must avoid "spaghetti programs" and use the well-known, disciplined constructs you have learned in your other programming classes.
In an IF statement, you "branch around" a section of code. In an IF - THEN - ELSE construct, one of two alternatives is executed. Here are some pseudo code examples, with assembly language translations. (Assume that swim and cycle are labels of .asciiz strings containing the phrases.)
IF num < 0 THEN num := - num ENDIF {absolute value} |
IF temperature >= 25 THEN activity := "go swimming" ELSE activity := "ride bicycle" ENDIF print (activity) |
lw $t0, num bgez $t0, endif0 # must be ltz------ sub $t0,$0,$t0 # 0-num sw $t0, num endif0: # num is now guaranteed non-negative |
lw $t0, temperature blt $t0, 25, else25 la $a0, swim j endif25 else25: la $a0, cycle endif25: li $v0, 4 # print string call code syscall |
Note that the assembly "branch around" is the opposite of the high-level condition. Also note the jump instruction needed before ELSE.
If a jump (or branch) reloads the PC with a lower address, that instruction will be reached again, and again... A loop results, as the same block of instructions is repeated. One must be careful to avoid an infinite loop by providing a branch out of the repetitive region that will eventually be taken. Two well-known and well-behaved constructs are WHILE and FOR. FOR is actually a special case of WHILE that uses a counter. Both are pre-test loops, the test (branch instruction) comes at the beginning of the loop body, and there is a jump instruction at the end.
{sum non-negative numbers} sum := 0 num := 0 WHILE num >= 0 DO sum := sum + num read (num) ENDWHILE |
{write 10 lines} FOR countdown := 10 downto 1 DO print ("Hello again...") ENDFOR |
# add $t0, $0, $0 really: move $t0, $0 #sum=0 move $v0, $0 #num=0 while5: bltz $v0, endwhile5 add $t0, $v0 # ---- $v0 switches role here li $v0, 5 #read int call syscall j while5 enendwhile5: |
# set up print arguments before loop la $a0, hello_again li $v0, 4 #print string call li $t0, 10 # countdown for10: beqz $t0, endfor10 syscall #print another line sub $t0, 1 #decr. countdown j for10 endfor10: |
A REPEAT loop is a post-test loop. It may be used in special situations when it is known that the loop body should be executed at least once.
Embarrasing historical note: The first version of FORTRAN implmented FOR loops as post-test loops. As a result, they always executed once even if it made no sense. It took 20 years to correct the situation.