Loops
The overview of this topic is in Assembly basics. The same topic in M68K, RISC-V, Z80, x86.
A loop sends execution backwards so a group of instructions can run again. MIPS has no single
instruction that means while or for; you build the repetition from branches, jumps, and labels.
Most loops have the same five parts:
- Initialization gives the counter and any result registers their starting values.
- Test decides whether the loop is finished.
- Body does the work for one pass.
- Update changes the value used by the test.
- Jump sends execution back to the test.
Keeping those parts visible makes a loop easier to write and debug.
Test before the body
This loop adds the numbers from 1 through 10. Its test is at the top, so execution checks the counter before every pass through the body.
.text
main:
li $t0, 0 # running total
li $t1, 1 # counter: first number to add
li $t2, 10 # last number to add
loop_test:
bgt $t1, $t2, loop_end # finished when the counter is past 10
add $t0, $t0, $t1 # body: add this number
addi $t1, $t1, 1 # update: move to the next number
j loop_test # test again
loop_end:
li $v0, 10
syscall
The first test sees $t1 equal to 1, so the branch is not taken and the body adds 1. After the
update, the jump returns to loop_test with $t1 equal to 2. This continues through 10. The update
then makes $t1 equal to 11, and the next test branches to loop_end.
For this loop, $t0 finishes at 55 and $t1 finishes at 11. That final 11 follows from this
particular test and update: 10 is used, then the counter advances once before the loop discovers it
is finished.
A loop with its test at the top can run its body zero times. If the counter had started at 11, the
first instruction at loop_test would branch straight to loop_end.
Test after the body
The test can also go at the bottom. Here is the same sum with a counter that starts at 10 and counts down to zero:
.text
main:
li $t0, 0 # running total
li $t1, 10 # numbers still to add
beq $t1, $zero, loop_end # a zero count skips the body
loop:
add $t0, $t0, $t1 # body
addi $t1, $t1, -1 # update
bne $t1, $zero, loop # repeat while the count is not zero
loop_end:
li $v0, 10
syscall
Once execution reaches loop, the body runs before the next test. That gives this shape a useful
rule: the count is positive at the start of every pass. The beq before loop establishes that
rule by handling zero separately. At the bottom, bne performs the test and the backward jump
together.
Without the guard, a starting count of zero would still run the body once. The update would then change the count to -1, so the bottom test would send execution backwards again. A bottom-tested loop therefore needs an explicit guard whenever zero is a valid starting count.
Nested loops
A loop can contain another loop. The inner loop must receive a fresh starting value for every pass through the outer loop.
This example visits two rows with three columns in each row. $t0 counts the visits.
.text
main:
li $t0, 0 # cells visited
li $t1, 0 # rows completed
li $t3, 2 # total rows
li $t4, 3 # columns in each row
outer_test:
bge $t1, $t3, loop_end
li $t2, 0 # reset columns for this row
inner_test:
bge $t2, $t4, outer_update
addi $t0, $t0, 1 # visit one cell
addi $t2, $t2, 1 # one more column completed
j inner_test
outer_update:
addi $t1, $t1, 1 # one more row completed
j outer_test
loop_end:
li $v0, 10
syscall
Each time execution reaches outer_test, $t1 is the number of complete rows. After the outer
test, li $t2, 0 resets the column counter before the inner loop begins. Inside that loop, $t2 is
the number of columns already visited in the current row. These are useful invariants: facts
that remain true whenever execution reaches the same point in a loop.
The inner loop adds 3 to $t0 before reaching outer_update. The outer loop does that twice, so
$t0 finishes at 6. If the reset of $t2 were outside the outer loop, the second row would begin
with $t2 still equal to 3 and would visit no columns.
Write two loops
Add the numbers from 1 through 10 with a top-tested loop. Leave the sum in $t0. Keep the counter in
$t1; after the loop it should be 11. The stop sequence is already in place.
.text
main:
# initialize the loop here
# write the test, body, update, and backward jump here
loop_end:
li $v0, 10
syscall
Show solution
.text
main:
li $t0, 0
li $t1, 1
li $t2, 10
loop_test:
bgt $t1, $t2, loop_end
add $t0, $t0, $t1
addi $t1, $t1, 1
j loop_test
loop_end:
li $v0, 10
syscall
For the second exercise, $t0 and $t1 hold two repeat counts. Use a separate guarded,
bottom-tested loop for each count:
- add 1 to
$s0exactly$t0times; - add 1 to
$s1exactly$t1times.
Initialize both result registers to zero. The supplied inputs make the first loop skip its body and the second loop run four times. Finish with the stop sequence shown in the examples.
.text
main:
# initialize the results and write both loops here
Show solution
.text
main:
li $s0, 0
li $s1, 0
beq $t0, $zero, first_end
first_loop:
addi $s0, $s0, 1
addi $t0, $t0, -1
bne $t0, $zero, first_loop
first_end:
beq $t1, $zero, second_end
second_loop:
addi $s1, $s1, 1
addi $t1, $t1, -1
bne $t1, $zero, second_loop
second_end:
li $v0, 10
syscall