While loop through array, MIPS assembly - c

I want to convert while (w[i] == x) i += j; into MIPS assembly code.
Assuming INTEGERS i,j, and x are in $3,$4 and $5. Also assume i=0 initially before the while loop.
w => array of integers and its base address is stored in $6. So far I have this.
Loop:
sll $10, $3, 2 # $10 = i* 4
add $10, $6, $10 # $10 has address of w[i]
lw $11, 0($10) # $11 = w[i]
bne $11, $5, Exit # exit from loop if w[i]!= x
add $3, $3, $4 # i= i+ j
j Loop
Exit:
Is it possible to optimize this code by moving the base address itself by j*4 and also get rid of the multiple branch instructions? Cause I have no clue on how to do that.
Thanks in advance!

To get rid of the multiple branch instructions, this trick can be used:
WARNING: NOT exactly equivalent to your code
Loop:
sll $10, $3, 2 # $10 = i* 4
add $10, $6, $10 # $10 has address of w[i]
lw $11, 0($10) # $11 = w[i]
add $3, $3, $4 # i = i + j
beq $11, $5, Loop # keep looping if w[i] == x
Exit:
sub $3, $3, $4 # i = i - j
The trick is to perform i += j before testing for keep looping or not.
This do introduce a problem sometimes: it may trigger an additional integer overflow when your code doesn't.
EDITED:
It's something like rewriting this:
while (some_condition())
do_something();
into this:
do
do_something();
while (some_condition());
undo_something();
EDITED:
Well, let me try to "move the pointer from the base address itself by j*4" this time :)
Start:
sll $11, $3, 2 # $11 = i * 4
add $10, $11, $6 # Let $10 be a "cursor" pointing to w[i]
Loop:
lw $11, 0($10) # $11 = w[i]
sll $12, $4, 2 # $12 = j * 4
add $10, $10, $12 # update $10 by 4 * j
add $3, $3, $4 # update i by j
beq $11, $5, Loop # keep looping if w[i] == x
Exit:
sub $3, $3, $4 # i = i - j
However it's not more optimized than the version I gave above: both of them uses 5 instructions inside the loop body.

To make the comparison easier, I've written a small dummy function:
#include <stdint.h>
#include <stdlib.h>
uint32_t fun1(uint32_t const *in, uint32_t cmp, size_t j)
{
size_t i = 0;
while (in[i] == cmp)
{
i += j;
}
return in[i];
}
this can be compiled, and the output can be compared to an equivalent function:
uint32_t fun2(uint32_t const *in, uint32_t cmp, size_t j)
{
while (*in == cmp)
{
in += j;
}
return *in;
}
For both functions, gcc (4.8 on x86-64) generates a loop with only 4 instructions.
For the second function it essentially goes:
temp1 = (in)
compare temp1, cmp
if not equal, return temp1
temp2 = j*sizeof(uint32_t)
loop:
in += temp2 #\
temp1 = (in) # \
compare temp1, cmp # - 4 instructions loop
if equal, goto loop # /
return temp1
This pseudo-assembly can probably be implemented for MIPS like so:
lw $v0, 0($a0)
beq $a1, $v0, end
sll $t1, $a2, 2
loop:
add $a0, $a0, $t1 #\
lw $v0, 0($a0) # - only 3 instructions in loop, due to test-and-branch
bne $a1, $v0, loop #/
end:
jr $ra

Related

Simple Array Manipulation MIPS Assembly

I'm new to MIPS assembly and I'm trying to learn how to use arrays. I understand that arrays are declared in the .data segment and then you load the address of the array to the register. However, I'm confused on accessing elements of the the array and changing the values. Here is a simple program I wrote in C that I'm trying to convert into assembly. Any explanation/code is greatly appreciated!
void addArray (int arrA[], int arrB[], int i, int j) {
arrB[j] = arrA[i] + arrA[i + 1];
printf("%d", arrB[j]);
}
int main(void) {
int arrA [] = {1,2,3,4,5,6};
int arrB [] = {1,1,1,1,1,1};
int i = 2;
int j = 3;
addArray(arrA, arrB, i, j);
return 0;
}
Try this code
.data
arrA: .word 1,2,3,4,5,6
arrB: .word 1,1,1,1,1,1
.text
.globl main
main:
la $a0,arrA #load address of arrA into $a0 register (first parameter)
la $a1,arrB #load address of arrB into $a1 register (second parameter)
li $a2,4 # load $a2 register with 3 (threeth parameter)
li $a3,3 # load $a3 register with 3 (fourth parameter)
jal addArray # call addArray function
exit: # syscall to exit the programm
li $v0, 10
syscall
addArray:
addi $sp,$sp,-4 #allocate space in stack
sw $ra,0($sp)
move $t0,$a2 #copy a content of $a2 register into $t0
move $t1,$a3 #copy a content of $a3 register into $t1
mulu $t0,$t0,4 # multiplication by 4 with the value of $t0, because each number have 4 bytes as offset
addu $a0,$a0,$t0 #
lw $t3,($a0) # arrA[i]
addi $a0,$a0,4 # i+1
lw $t4,($a0) # arrA[i+1]
add $t5,$t3,$t4 # arrA[i] + arrA[i + 1]
mul $t1,$t1,4 # j
addu $a1,$a1,$t1
sw $t5,($a1) # arrB[j] = arrA[i] + arrA[i + 1];
print_out:
lw $s0,($a1)
li $v0, 1 #print the input number
move $a0, $s0
syscall
lw $ra,0($sp) # free stack and return to main
addi $sp,$sp,4
jr $ra

Translating C function with args to MIPS: loop over an int array and count negatives

My question is about procedures in MIPS and using arguments.
I'm trying to translate this small C Function to MIPS and I wasn't sure if I was in the right track. This is the C function:
0 int countNegatives(int table[] , int n) {
1 int count = 0;
2 int i;
3
4 for (i=0; i<n; i++) {
5 if (table[i] <0) {
6 count++;
7 }
8 }
9
10 return count;
11 }
And this what I have on MIPS
main:
jal countNegatives
countNegatives:
li $t0, 0 #count = 0
li $t1, 0 #i = 0
loop:
bge $t1, $a1, endloop
sll $t2, $t1, 2 #$t2 = 4*i
add $t2, $a0, $t2 #$t2 = &table[i]
lw $t3, 0($t2) #temp = table[i]
bge $t3, $zero, endif
addi $t0, $t0, 1 #counter++
endif:
addi $t1, $t1, 1 #i++
endloop:
jr $ra
My code doesn't really run on QTSpim, and so I'm also trying to know if I'm missing any MIPS convention, and if I'm using the arguments in the procedure in a correct manner.
Thanks in advance if anyone can check the code out and see if something is wrong.
Except for some missing boilerplate, you were very close. Here's a version annotated with the bugs:
main:
# NOTE/BUG: a0/a1 are _not_ set up for the call
jal countNegatives
# NOTE/BUG: we just fall through into countNegatives again [which is bad]
countNegatives:
li $t0,0 # count = 0
li $t1,0 # i = 0
loop:
bge $t1,$a1,endloop
sll $t2,$t1,2 # $t2 = 4*i
add $t2,$a0,$t2 # $t2 = &table[i]
lw $t3,0($t2) # temp = table[i]
bge $t3,$zero,endif
addi $t0,$t0,1 # counter++
endif:
addi $t1,$t1,1 # i++
# NOTE/BUG: we need to loop here
endloop:
jr $ra
Here's a working version [with the added boilerplate]:
.data
arr: .word 10 20 -5 7 -6 0 1 -1 37
.text
.globl main
main:
la $a0,arr # point to array
li $a1,9 # array count
jal countNegatives
move $a0,$v0
li $v0,1
syscall
li $v0,10
syscall
# countNegatives -- count number of negatives
#
# RETURNS:
# v0 -- number of negative numbers found
#
# arguments:
# a0 -- pointer to array
# a1 -- array count
#
# temporaries:
# t1 -- index variable "i"
# t2 -- array offset / &table[i]
# t3 -- temp (value of table[i])
countNegatives:
li $v0,0 # count = 0
li $t1,0 # i = 0
loop:
bge $t1,$a1,endloop # i >= count? if yes, fly
sll $t2,$t1,2 # $t2 = 4*i
addu $t2,$a0,$t2 # $t2 = &table[i]
lw $t3,0($t2) # temp = table[i]
bge $t3,$zero,endif
addi $v0,$v0,1 # counter++
endif:
addi $t1,$t1,1 # i++
j loop
endloop:
jr $ra
Here's a just for fun version that uses slt instead of a conditional branch [and eliminates an extra jump inside the loop]:
.data
arr: .word 10 20 -5 7 -6 0 1 -1 37
.text
.globl main
main:
la $a0,arr # point to array
li $a1,9 # array count
jal countNegatives
move $a0,$v0
li $v0,1
syscall
li $v0,10
syscall
# countNegatives -- count number of negatives
#
# RETURNS:
# v0 -- number of negative numbers found
#
# arguments:
# a0 -- pointer to array
# a1 -- array count
#
# temporaries:
# t1 -- index variable "i"
# t2 -- array offset / &table[i]
# t3 -- temp (value of table[i])
countNegatives:
li $v0,0 # count = 0
li $t1,0 # i = 0
j loop_start # start the loop
loop:
sll $t2,$t1,2 # $t2 = 4*i
addu $t2,$a0,$t2 # $t2 = &table[i]
lw $t3,0($t2) # temp = table[i]
slt $t3,$t3,$zero # temp = (temp < 0)
add $v0,$v0,$t3 # counter += temp
addi $t1,$t1,1 # i++
loop_start:
blt $t1,$a1,loop # i < count? if yes, fly
jr $ra
Here's another version that uses pointer arithmetic instead of index variables.
Note that under the mips ABI, only the s* regs must be preserved by callee, so a0 and a1 are used as temporaries.
Also note that when adding addresses/pointers, as good practice, we want to use the unsigned versions of the add instructions (i.e. addu and addiu) to prevent [the unlikely possibility of] an overflow exception.
.data
arr: .word 10 20 -5 7 -6 0 1 -1 37
.text
.globl main
main:
la $a0,arr # point to array
li $a1,9 # array count
jal countNegatives
move $a0,$v0
li $v0,1
syscall
li $v0,10
syscall
# countNegatives -- count number of negatives
#
# RETURNS:
# v0 -- number of negative numbers found
#
# arguments:
# a0 -- pointer to array (ptr)
# a1 -- array count
#
# temporaries:
# a1 -- array limit (endp)
# t3 -- temp (value of table[i])
countNegatives:
li $v0,0 # count = 0
sll $a1,$a1,2 # get byte offset
addu $a1,$a0,$a1 # endp = &arr[count]
j loop_start # start the loop
loop:
lw $t3,0($a0) # temp = *ptr
slt $t3,$t3,$zero # temp = (temp < 0)
add $v0,$v0,$t3 # counter += temp
addiu $a0,$a0,4 # ptr += 4
loop_start:
bne $a0,$a1,loop # ptr != endp? if yes, fly
jr $ra
So, the final asm version, translated back into C would look something like this:
int
countNegatives(int *table, int n)
{
int *endp;
int count = 0;
endp = &table[n];
for (; table != endp; ++table)
count += (*table < 0);
return count;
}
It is not too difficult I think to see what decent compiler does:
https://godbolt.org/g/PiR8Ds
And you will have all the call conventions and another stuff toking by themselves.
#include <stdio.h>
int __attribute__((noinline)) countNegatives(int table[] , int n) {
int count = 0;
int i;
for (i=0; i<n; i++) {
if (table[i] <0) {
count++;
}
}
return count;
}
volatile int x[] = {454,-3,-5343,-3434,4534};
int main(void)
{
printf("%d\n",countNegatives((int *)x, sizeof(x)/sizeof(x[0])));
}
countNegatives:
blez $5,$L6
sll $5,$5,2
addu $5,$4,$5
move $2,$0
$L5:
lw $3,0($4)
addiu $4,$4,4
slt $3,$3,0
bne $4,$5,$L5
addu $2,$2,$3
j $31
nop
$L6:
j $31
move $2,$0
$LC0:
.ascii "%d\012\000"
main:
lui $4,%hi(x)
addiu $sp,$sp,-32
li $5,5 # 0x5
sw $31,28($sp)
jal countNegatives
addiu $4,$4,%lo(x)
lui $4,%hi($LC0)
move $5,$2
jal printf
addiu $4,$4,%lo($LC0)
lw $31,28($sp)
move $2,$0
j $31
addiu $sp,$sp,32
x:
.word 454
.word -3
.word -5343
.word -3434
.word 4534

MIPS array update

I have a problem in converting my C code to MIPS code. The original C code is below:
int A[4];
int i;
int diff;
for(i=0; i<3; i++){
diff = A[i+1] - A[i];
if (diff > 0)
A[i] = 5*A[i];
else
A[i+1] = -5*A[i];
}
And here is my MIPS Code below:
### MIPS PROJECT PART 1: ARRAY USING FOR LOOPS
.data
# allocate 16 bytes memory for 4 integer array
A: .space 16
.text
# Store array values in registers
addi $s0,$zero,2
addi $s1,$zero,4
addi $s2,$zero,6
addi $s3,$zero,8
# Index = $t0
addi $t0,$zero,0
# Store the first index and then store others by increasing $t0 by 4 bytes
sw $s0,A($t0)
addi $t0,$t0,4
sw $s1,A($t0)
addi $t0,$t0,4
sw $s2,A($t0)
addi $t0,$t0,4
sw $s3,A($t0)
main:
li $s4,0 # counter i = $s4
li $t2,3 # constant $t2
loop:
beq $s4, $t2, end # if t1 == 3 we are done
# diff = A[i+1] - A[i]
lw $t3,A($zero) # $t3 = A[i]
addi $t6, $zero,4 # next element A[i+1]
lw $t4,A($t6) # $t4 = A[i+1]
sub $t5,$t4,$t3 # dif = $t5 = A[i+1] - A[i]
# if (diff > 0)
bltz $t5, Else
# A[i] = 5*A[i]
add $t5,$zero,$t5
add $t5,$zero,$t5
add $t5,$zero,$t5
add $t5,$zero,$t5
sw $t5 A($zero)
Else:
# else A[i+1] = -5 * A[i];
addi $zero, $zero,4 #next element A[i+1]
#Loop body
addi $s4, $s4, 1 # add 1 to $s4
j loop # jump back to the top
end:
li $v0,10
syscall
The thing is that program does not update array values. Additionally I am restricted to use multiplication commands (mult or mull), that is why I am trying just to add it 5 times in control statement.
Your program has some bugs and is incomplete. I've created two versions. One with annotations for the bugs and a refactored one. Please pardon the gratuitous style cleanup.
Here's your asm program with annotations:
### MIPS PROJECT PART 1: ARRAY USING FOR LOOPS
.data
A: .space 16
# allocate 16 bytes memory for 4 integer array
.text
# Store array values in registers
addi $s0,$zero,2
addi $s1,$zero,4
addi $s2,$zero,6
addi $s3,$zero,8
# Index = $t0
addi $t0,$zero,0
# Store the first index and then store others by increasing $t0 by 4 bytes
sw $s0,A($t0)
addi $t0,$t0,4
sw $s1,A($t0)
addi $t0,$t0,4
sw $s2,A($t0)
addi $t0,$t0,4
sw $s3,A($t0)
# NOTE/BUG: this should go after the .text as the above code may be skipped
main:
li $s4,0 # counter i = $s4
li $t2,3 # constant $t2
loop:
beq $s4,$t2,end # if t1 == 3 we are done
# diff = A[i+1] - A[i]
# NOTE/BUG: this always loads from A[0]
lw $t3,A($zero) # $t3 = A[i]
# NOTE/BUG: this always loads from A[1]
addi $t6,$zero,4 # next element A[i+1]
lw $t4,A($t6) # $t4 = A[i+1]
sub $t5,$t4,$t3 # dif = $t5 = A[i+1] - A[i]
# if (diff > 0)
bltz $t5,Else
# NOTE/BUG: this is _not_ A[i] but is diff
# NOTE/BUG: this is _not_ 5* but higher (see below)
# A[i] = 5*A[i]
add $t5,$zero,$t5 # diff*2
add $t5,$zero,$t5 # diff*4
add $t5,$zero,$t5 # diff*8
add $t5,$zero,$t5 # diff*16
# NOTE/BUG: this always stores to A[0]
sw $t5,A($zero)
# NOTE/BUG: this falls through to the else case
Else:
# else A[i+1] = -5 * A[i];
# NOTE/BUG: this is just a nop because a $zero as destination reg does
# nothing
addi $zero,$zero,4 # next element A[i+1]
# Loop body
addi $s4,$s4,1 # add 1 to $s4
# NOTE/BUG: this stores nothing
j loop # jump back to the top
end:
li $v0,10
syscall
I rewrote the C code slightly to make the asm easier:
int A[4] = { 2, 4, 6, 8 };
void
calc(int *arr,int cnt)
{
int tmp;
int diff;
int *endp;
endp = &arr[cnt];
for (; arr < endp; arr += 1) {
tmp = arr[0];
diff = arr[1] - tmp;
if (diff > 0) {
tmp = 5 * tmp;
arr[0] = tmp;
}
else {
tmp = -5 * tmp;
arr[1] = tmp;
}
}
}
Here's the refactored asm code:
### MIPS PROJECT PART 1: ARRAY USING FOR LOOPS
.data
# allocate 16 bytes memory for 4 integer array
A: .word 2,4,6,8
space: .asciiz " "
nl: .asciiz "\n"
.text
main:
la $a0,A
li $a1,4
jal print
la $a0,A
li $a1,3
jal calc
la $a0,A
li $a1,4
jal print
li $v0,10
syscall
# calc -- calculate results
#
# arguments:
# a0 -- pointer to array
# a1 -- count of array
calc:
# get endp
sll $a1,$a1,2 # convert int count to byte count
addu $a1,$a1,$a0 # add in array base
j calc_start # start loop
calc_loop:
lw $t3,0($a0) # tmp = A[i]
lw $t4,4($a0) # A[i + 1]
sub $t5,$t4,$t3 # get diff
bltz $t5,calc_neg # < 0
add $t5,$t3,$t3 # tmp = A[i]*2
add $t5,$t5,$t5 # tmp = A[i]*4
add $t5,$t5,$t3 # tmp = A[i]*5
sw $t5,0($a0)
b calc_next
calc_neg:
neg $t3,$t3
add $t5,$t3,$t3 # tmp = A[i]*2
add $t5,$t5,$t5 # tmp = A[i]*4
add $t5,$t5,$t3 # tmp = A[i]*5
sw $t5,4($a0)
calc_next:
addiu $a0,$a0,4
calc_start:
bne $a0,$a1,calc_loop # more to do? if yes, fly
jr $ra # return
# print -- print results
#
# arguments:
# a0 -- pointer to array
# a1 -- count of array
#
# temporaries:
# a2 -- array pointer
# a3 -- array count
print:
move $a2,$a0
move $a3,$a1
j print_next
print_loop:
li $v0,4
la $a0,space
syscall
li $v0,1
lw $a0,0($a2) # get current value
syscall
addiu $a2,$a2,4
addi $a3,$a3,-1
print_next:
bnez $a3,print_loop
li $v0,4
la $a0,nl
syscall
jr $ra # return

Checking number of zeros in a MIPS array

I have a question as follows:
Given an array of 32-bit signed integers in the memory and its length in one of the registers, write a MIPS program that counts how many zeros the array contains. Assume that the array starts at Ox12345678, and the length of the array is already stored in $1. The number of zeros should be stored in $2, which mayor may not be initialised with zero at the beginning. Refer to Table 1 for MIPS assembly instructions.
Here is where I have got to in my own head but I have one main question:
1) I think that if I have subroutines, I need to be pushing and then popping data from a stack by using sw $ra, 4($sp) and addi $sp,$sp,-8and sw $fp, 0($sp). However, using my program, I have a break clause which only moves to a subroutine on a condition (if something is $0). So I don't jal to the subroutine, I beq to the subroutine. How can I modify my code to do this?
Here is my current code:
Add $3, $0, $0 #Set a counter to 0, the start of the array
Lui $4, 0x1234
Ori $4, $4, 0x5678 #Store register $4 to start of the array so you can offset
Add $2, $0, $0 #Sets $2 to $0 which is the total number of zeros
Start_for: Beq $3, $1, end_for #If counter is equal to the array length, go to end
Lw $5, 0x0($4) Load the current value of array into temp register $5
Addi $4, $4, 4 #Increment array pointer to next value
Beq $5, $0, increment #Increment the sum by 1 if the array[i] is zero
Addi $3, $3, 1 #Increment counter by 1
J start_for
increment:
Addi $2, $2, 1 #Increment the number of zeros by 1 and add to sum
Jr $ra
End_for: Lui $8, 0xffff
Ori $8, $8, 0xf004 Load the outtray into $8
Lw $8, 0x0($2) Store the number of zeros in the array to the outtray

Dynamic allocation for arrays in MIPS

Our team is trying to create a compiler that's fed code and produces MIPS assembly from it.
To tackle array declaration at the global scope, we create a label for the array in .text and reserve 4 bytes to hold the address pointing to the start of the array in memory.
.text
arr: .space 4
.data
...
li $t0, N # (where N = number of elements in arr)
li $v0, 9 # Load system instruction to allocate dynamic memory
li $t1, 4 # 4 bytes per element
mult $t0, $t1 # Calculates how big the allocated memory has to be in bytes.
mflo $a0 # Loads this value into $a0
syscall # Allocates memory and returns address into $v0
la $s0, arr # load address of arr into $s0
sw $v0, ($s0) # Save allocated memory address into the space reserved in .text
However, the last instruction doesn't seem to be working properly for us.
This image shows exactly where the error occurs and the state of the registers at that time. I'm unsure why it's causing an error.
Edit: some more information on the error produced, updated to encompass the modified instructions at the end
[00400000] 8fa40000 lw $4, 0($29) ; 183: lw $a0 0($sp) # argc
[00400004] 27a50004 addiu $5, $29, 4 ; 184: addiu $a1 $sp 4 # argv
[00400008] 24a60004 addiu $6, $5, 4 ; 185: addiu $a2 $a1 4 # envp
[0040000c] 00041080 sll $2, $4, 2 ; 186: sll $v0 $a0 2
[00400010] 00c23021 addu $6, $6, $2 ; 187: addu $a2 $a2 $v0
[00400014] 0c100009 jal 0x00400024 [main] ; 188: jal main
[00400018] 00000000 nop ; 189: nop
[0040001c] 3402000a ori $2, $0, 10 ; 191: li $v0 10
[00400020] 0000000c syscall ; 192: syscall # syscall 10 (exit)
[00400024] 34080003 ori $8, $0, 3 ; 9: li $t0, 3 # Load immediate value into register $t0
[00400028] 34020009 ori $2, $0, 9 ; 10: li $v0, 9
[0040002c] 34090004 ori $9, $0, 4 ; 11: li $t1, 4
[00400030] 01090018 mult $8, $9 ; 12: mult $t0, $t1
[00400034] 00002012 mflo $4 ; 13: mflo $a0
[00400038] 0000000c syscall ; 14: syscall
[0040003c] 3c101001 lui $16, 4097 [arr0] ; 15: la $s0, arr0
[00400040] ae020000 sw $2, 0($16) ; 16: sw $v0, ($s0)
PC = 400040
EPC = 40003c
Cause = 1c
BadVAddr = 1004002f
Status = 3000ff12
The last instruction sw $v0, arr0($0) = MEM[$0 + arr0] = $v0 which isn't correct. However you'll be given the amount of memory requested and it is needed to save that memory pointer in a register until scope of program. The assignment might look like this,
arr: .space 4
syscall # Allocates memory and returns address into $v0
la $s0, arr # arr is pointer address
st $v0, 0($s0) # start base address of array
Now arr is base address of 4 bytes of memory holding the address of memory content. For good practice it is important to de-allocate the memory assigned back to OS as,
li $v0,10 # return back to OS
syscall

Resources