In this part we’ll properly cover the MIPS architecture in depth.
Parameter transferring
When calling a function in C code, what is happening at the assembler level is, calling a subroutine (usually).
What we need to do before a subroutine call, is the following:
- Place the input parameters into their respective registers
- Call the function/subroutine
- Allocate memory for eventual saved and local variables
- Execute the function/procedure
- Place the result in a register
- Reset the stack, if needed
- Return to where the function/subroutine got called.
Register conventions
Since we now need to store our input parameters into register before we call a function, we need to create a convention to where we expect things to be.
MIPS has this following convention:
-
$a_0 - a_3$: Input parameters
-
$v_0, v_1$: Result values
-
$t_0 - t_9$: Temporary variables
-
$s_0 - s_7$: “Saved” registers
- Must be saved and reset by each respective subroutine.
-
$gp$: global pointer
-
$sp$: stack pointer
-
$fp$: frame pointer
-
$ra$: return address
We discussed a lot of subroutines, but how do we actually call one?
jal Label
will jump to a label (starting point of a subroutine).
To return from a subroutine call jr $ra
.
A very simple function like:
int example(int g, int h, int i, int j) {
int f;
f = (g + h) - (i + j);
return f;
}
Given that the arguments are in $a_0, \ldots, a_3$, f is in $s_0$ (therefore we must save $s_0$ to the stack before!).
The result will be stored in $v_0$
MIPS Code:
example:
addi $sp, $sp, -4 # Allocate space for one variable, s_0
sw $s0, 0($sp) # Save s_0
add $t0, $a0, $a1 # (g + h)
add $t1, $a2, $a3 # (i + j)
sub $s0, $t0, $t1 # f = (g + h) - (i + j)a
add $v0, $s0, $zero # Store value
lw $s0, 0($sp) # Reset s_0 value
addi $sp, $sp, 4 # Reset stack pointer
jr $ra # Return
Let’s now take a recursive
int fact(int n) {
if(n < 1) {
return 1;
} else {
return n * fact(n - 1);
}
}
input parameter n in $a_0$, result in $v_0$
MIPS:
fact:
addi $sp, $sp, -8 # Adjust stack so we can store 2 variables
sw $ra, 4($sp) # Save return address
sw $a0, 0($sp) # Save input register
slti $t0, $a0, 1 # test for n < 1
beq $t0, $zero, L1
addi $v0, $zero, 1 # If n < 1, result = 1
addi $sp, $sp, 8 # Pop stack
jr $ra # Return
L1: addi $a0, $a0, -1 # Decrement n
jal fact # Recursive call
lw $a0, 0($sp) # restore orignal n
lw $ra 4($sp) # and return address
addi $sp, $sp, 8 # Pop stack
mul $v0, $a0, $v0 # n * fact(n - 1)
jr $ra # return
Less than a word
Sometimes we want to only access a byte or half-word:
lb rt, offset(rs)
lh rt, offset(rs)
# Unsigned
lbu rt, offset(rs)
lhu rt, offset(rs)
# Save
sb rt, offset(rs)
sh rt, offset(rs)
Indexing vs Pointers
-
Vector indexing requires:
-
Multiplication of the index with length
-
Summation of this product to the vectors start address
-
-
Pointer corresponds directly to a memory address
- Therefore, it’s faster
Common mistakes
Common mistakes when writing programs in MIPS are:
-
Incrementing with 1 and not 4!
-
MIPS uses byte addressing