So I am writing a piece of code that prints out elements of an array within a for loop. The C version of this code looks like this:
for(i=0; i<arraySize; i++)
{
printf("Array[%i]=%i\n", i, array[i]);
}
The code I have in comparison in MIPS is this:
.data
array: .byte 2,3,5,7,11
array_size: .word 20
array_print1: .asciiz "Array["
array_print2: .asciiz "]="
newline: .asciiz "\n"
sum_print: .asciiz "Sum of the array is "
.text
main:
lw $s1, array_size #int arraySize = 5
la $s0, array #int array[] = {2, 3, 5, 7, 11}
add $t0, $zero, $zero #int i = 0
j for #go to for
for:
bge $t0, $s1, outsidefor #i < arraySize otherwise leave for
li $v0, 4 #printing 'Array['
la $a0, array_print1
syscall
div $t2, $t0, 4
mflo $t2
li $v0, 1 #printing index
move $a0, $t2
syscall
la $v0, 4 #printing ']='
la $a0, array_print2
syscall
lw $t1, 0($s0) #getting array[i]
li $v0, 1 #printing array[i]
move $a0, $t1
syscall
li $v0, 4
la $a0, newline #printing new line
syscall
addi $t0, $t0, 4 #incrementing i
addi $t1, $t1, 4 #incrementing $t1
j for
Currently what is being printed out is the address of the value at array[i] (or so I believe). What am I doing wrong here?
With your answer (addi $s0, $s0, 4
to increment the pointer), you now have a normal pointer-increment loop over the array. But for some reason, you were already incrementing two other loop counters by 4. You could addu $t3, $t0, $s0
to add that byte-offset to the array base and get &array[i]
, ready to use with lw
.
Using div
to get the array index is incredibly inefficient. It would be much more efficient to simply use another register for another loop counter that increments by 1, instead of 4.
Also, if your array elements are all small, then an array index is a byte offset, or you can just use one subu
instruction to convert a pointer to an element back to an index. (In C, i == &arr[i] - arr
.)
Another option, good if you had .word
elements, would be to run two counters: a pointer with add r,r,4
and an int index to print with add r,r,1
.
.data
array: .byte 2,3,5,7,11
array_end:
.eqv array_size, 5 # assemble-time constant. But can't get MARS to calculate it from the actual array size, and hard-coding sucks.
array_print1: .asciiz "Array["
array_print2: .asciiz "]="
# newline with print_char not print_string
sum_print: .asciiz "Sum of the array is "
.text
main:
# only use $t registers; we don't need them to survive across a function call
la $t4, array # int8_t *arr = array;
la $t5, array_end # endp
move $t0, $t4 # int8_t *p = array
# we assume size is non-zero so we only need to check the count at the bottom
loop: # do{
li $v0, 4 #printing 'Array['
la $a0, array_print1
syscall
li $v0, 1 #printing index
subu $a0, $t0, $t4 # i = p - arr
# srl $a0, $a0, 2 # if you had word elements, C pointer subtraction would involve scaling by the element size
syscall
li $v0, 4 #printing ']='. # This was la which works but isn't idiomatic
la $a0, array_print2
syscall
li $v0, 1 #printing array[i]
lb $a0, 0($t0) # sign_extend_int8_to_word (*p)
syscall
li $v0, 11
li $a0, '\n'
syscall # print_char(newline)
addiu $t0, $t0, 1
bne $t0, $t5, loop # }while(++p != endp);
li $v0, 10
syscall # exit()
Notice that I load into $a0
where I want the value, and otherwise don't waste move instructions. $a0
is a normal register just like $t1
; it's not "reserved" for use by syscall, you just need your value there when syscall
asks "the OS" (or in this case the emulator) to do something with the value there.
With all the code to print strings, the program doesn't look much smaller, but if you look at what's left after that, my version is much simpler:
main:
# only use $t registers; we don't need them to survive across a function call
la $t4, array # int8_t *arr = array;
la $t5, array_end # endp
move $t0, $t4 # int8_t *p = array
# we assume size is non-zero so we only need to check the count at the bottom
loop: # do{
subu $a0, $t0, $t4 # i = p - arr
# for print_int
...
lb $a0, 0($t0) # sign_extend_int8_to_word (*p)
# for print_int
...
addiu $t0, $t0, 1
bne $t0, $t5, loop # }while(++p != endp);
li $v0, 10
syscall # exit()
Just 4 instructions of real work inside the loop to have i
and arr[i]
where we want them at the right times. (bne
as the loop branch instead of j
means we avoid a bge
at the top). And all 4 instructions are simple efficient ones, not including div
.
The rest is just printing.
(bge
between two registers other than $zero
is a pseudo-instruction for slt/bne; that's why in MIPS it's usually a good idea to calculate your loop end condition or count something down towards zero so you can use bne
or beq
as the loop condition. Your loop could have used beq
since the count will be reached exactly.)
Also note that in my .data
section, I avoided a .word 5
because I put a label on the end of the array so I could use la
to get an end-pointer. Or I could have used .eqv array_size, 5
so I could later use li $t5, array_size
. (Note li
not lw
or la
. It will assemble to li $t5, 5
, or more specifically addiu $t5, $zero, 5
.
Putting a small integer constant into data memory and loading it from there with lw
is inefficient so I avoid it.