sections in this module City College of San Francisco - CS270
Computer Architecture

Module: MIPS-II (Decisions)
module list

Loops

There is nothing special about translating loops for MIPS code. It proceeds the same as it did for the Simple Machine, but it is again much easier to generate MIPS code than it is to generate Simple Machine code.

First, we will do a couple of simple loops. Then we will get right into a mid-level example to give us some practice. Before we do that, however, we will change how we use MARS:

Using the trap handler

Until now we have been using MARS to assemble raw programs with no support. This means we have defined the overall starting point for our program __start and we have had no support if we encountered an error.

Beginning now, we will use the trap handler. The trap handler (/usr/local/lib/exceptions.s on our linux machines) defines __start and calls a function named main(), which we will now define as the beginning of our code. At the end of our function we should return to the trap handler (return from main() ). This is done by simply jumping to the address stored in the register $ra (return address)

Simple:

  1. load the trap handler with your code. This is done by checking the Use the exception handler box under Settings->Exception Handler in MARS. Use the location of the trap handler above.
  2. define main as a label at the start of your .text segment (where __start used to be) and declare it .globl just like you did for __start
  3. At the end of your code, return from main by including the instruction  jr  $ra

A simple loop for practice

First, let's start with a simple while loop

int A[N], i;
i=0;
while (i<N) {
if (A[i] < 0) A[i] = -A[i];
i++;
}

In our first pass, we will translate the loop "stupidly" - i.e., just as it appears. First, let's transform it to our ugly version that uses gotos

i=0;
loop: if (i>=N) goto loopdone;
if (A[i] >= 0) goto loopskip
A[i] = -A[i];
loopskip: i++;
goto loop;
loopdone:

Now we will write the code, interspersing the original source code. We will assume that A and N are global variables.

# i=0; // keep i in $t0
move   $t0,$zero
# loop: if (i>=N) goto loopdone;
loop:
lw     $t1,N
bge    $t0,$t1,loopdone
# if (A[i] >= 0) goto loopskip
la     $t2,A
sll    $t3,$t0,2
add    $t2,$t2,$t3
lw     $t4,0($t2)
bge    $t4,$zero,loopskip
# A[i] = -A[i];
la     $t2,A
sll    $t3,$t0,2
add    $t2,$t2,$t3
lw     $t4,0($t2)
sub    $t4,$zero,$t4
sw     $t4,0($t2)
# loopskip: i++;
loopskip:
addi   $t0,$t0,1
# goto loop;
b      loop
# loopdone:
loopdone:

This loop is whileloop1.s in the online/mipsII directory.

You probably noticed that there was some redundancy here. This loop requires us to calculate the address of A[i] several times each iteration. So let's add a couple of additional variables to it to see if we can optimize it. Here is the modified original code:

int A[N], i, element, *elptr;
i=0;
while (i<N) {
elptr = &A[i];
element = *elptr;
if (element < 0) *elptr = -element;

i++;
}

We introduced integer pointers when we were covering the Simple Machine. Let's take a moment and clarify something about C syntax before we continue.

If iptr is a pointer that points to an integer, and we want it to point to an integer i (that is, iptr holds the address of i), the declaration of iptr is

int *iptr, i;

this means that iptr is a pointer to an int. (Note that the * only applies to iptr, not to i. i is just an int.) The * in a declaration means 'is a pointer to'.

You initialize iptr to point to an integer using the addressof operator (&) like this

iptr = &i;

Now the confusing part. To use the address in iptr and get the integer it points to you use * in an assignment statement like this

int j = *iptr;

In an assignment statement, * means dereference, or go through the pointer and get what it points to.

Let's go ahead now and change this to our ugly version of code:

i=0;
loop: if (i>=N) goto loopdone;
elptr=&A[i];
element=*elptr;
if (element >= 0) goto loopskip
*elptr = -element;
loopskip: i++;
goto loop;
loopdone:

And, finally, to our MIPS code:

# i=0; // keep i in $t0
move   $t0,$zero
# loop: if (i>=N) goto loopdone;
loop:
lw     $t1,N
bge    $t0,$t1,loopdone
#elptr = &A[i];
la     $t2,A
sll    $t3,$t0,2
add    $t2,$t2,$t3 # $t2 is elptr
#element = *elptr;
lw     $t4,0($t2)  # $t4 is element
# if (element >= 0) goto loopskip
bge    $t4,$zero,loopskip
# *elptr = -element;
sub    $t4,$zero,$t4
sw     $t4,0($t2)
# loopskip: i++;
loopskip:
addi   $t0,$t0,1
# goto loop;
b      loop
# loopdone:
loopdone:

This loop is whileloop2.s in the online/mipsII directory.

The last optimization that could be done to this is noticing that it is not necessary to recalculate &A[i] each iteration. Each iteration just adds 4 to the pointer. Can you modify this program for that optimization?

Now, let's proceed with a more complicated loop example:

A moderately-complex loop

Here is the code we want to translate:

for (i=0,i<N,i++) {
for (j=i+1,j<N,j++) {
if (A[j] > A[i]) {
temp=A[i];
A[i] = A[j];
A[j] = temp;
}
}

Some of you may recognize this as a bubblesort routine, where A is an integer array of N elements. You may immediately realize that this is going to be a bit complicated. Again, take it one step at a time. 

Start with the standard translation to gotos

i=0;
nextouter: if (i >= N) goto alldone;

j=i+1;
nextinner: if (j >= N) goto incrouter;

/* if (A[j] > A[i] ) { */
if (A[j] <= A[i]) goto incrinner;
temp=A[i];
A[i] = A[j];
A[j] = temp;
/* } */

incrinner: j=j+1;
goto nextinner;

incrouter: i=i+1;
goto nextouter;

alldone:

Ugly, isn't it? (Believe me, it's more ugly without the indentation and blank lines!)

The pre- and post- transformed C code are in the files bubblesort.c and bubblesort1.c in the online/mipsII directory on hills.

Last step, let's translate this to MIPS. Before we do that, let's decide how to use our registers:

First, the data region. We will initialize a simple integer array of 10 elements:

.data
A:  .word 5,3,-3,54,2,-56,7,9,11,11
N:  .word 10

Now the .text region with the initialization of our variables (above)

.text
.globl main
main:
# $t8 will be N; $t9 will be &A[0] 
la   $t9, A
lw   $t8, N

# $t0 will be i,  $t1 will be j
# i=0;
move  $t0,$zero

# nextouter: if (i >= N) goto alldone;
nextouter:
bge  $t0,$t8,alldone

# j=i+1;
addi  $t1,$t0,1

# nextinner: if (j >= N) goto incrouter;
nextinner:
bge  $t1,$t8,incrouter

# if (A[j] <= A[i]) goto incrinner;
sll  $t6,$t1,2
add  $t6,$t6,$t9    # &A[j]
lw   $t4,0($t6)    # A[j]
sll  $t5,$t0,2
add  $t5,$t5,$t9    # &A[i]
lw   $t3,0($t5)    # A[i]
ble  $t4,$t3,incrinner

# temp=A[i];
move $t2,$t3

# A[i] = A[j];
sw   $t4,0($t5)

# A[j] = temp;
sw   $t2,0($t6)

# incrinner: j=j+1;
incrinner: 
addi $t1,$t1,1

# goto nextinner;
b    nextinner

# incrouter: i=i+1;
incrouter: 
addi $t0,$t0,1

# goto nextouter;
b    nextouter

alldone: 
jr  $ra


Prev This page was made entirely with free software on linux:  
Kompozer, the Mozilla Project
and Openoffice.org    
Next

Copyright 2015 Greg Boyd - All Rights Reserved.