9/24/02 Tue
1) Copy the arguments (registers $a0, $a1, $a2, and $a3)
2) Copy the return address onto the return address register ($ra) and jump to the procedure.
3) Inside the procedure
4) Access the return value (registers $v0, $v1)
1) Copy the result onto the return value registers $v0 and $v1
2) Jump to the return address using the return address register ($ra)
main() {
int i,j,k,m; /* $s0, $s1, $s2, $s3 */
...
i = mult(j,k); ...
m = mult(i,i); ...
}
/* really dumb mult function */
int mult (int mcand, int mlier){
int product;
product = 0;
while (mlier > 0) {
product = product + mcand;
mlier = mlier -1;
} return product; }
------------------------------------- __start:
add $a0, $s1, $zero add $a1, $s2, $zero jal mult add $s0, $v0, $zero
add $a0, $s0, $zero add $a1, $s0, $zero jal mult add $s3, $v0, $zero
done
mult: add $t0, $zero, $zero ... jr $ra
No. The example above is an extremely simple one. The caller (main) doesn't have a procedure which called it. And the callee (mult) doesn't make any procedure calls. In general, a procedure can be called from a procedure (it is considered the callee in this case) and it can also call other procedures (it is considered the caller in this case).
No. The example above is extremely simple. main just ends the program without returning to another procedure and the mult doesn't make any procedure calls. In general, a procedure can be called from another procedure and can call other procedures. In that case, we need to save registers to prevent the registers from being hampered a procedure call.
Registers can be categorized as follows.
| Registers | Purposes | Save/Restore | Description |
| $sp | Stack pointer | Callee | Needs to be grown when some registers need to be saved/restored. |
| $s0 - $s7 | Local variables. | Callee | Need to be saved when the procedure modifies them. |
| $ra | Return address | Caller | Need to be saved when the procedure calls another procedure |
| $a0 - $a3 | Arguments | Caller | Need to be saved if they are used after a procedure call. |
| $v0, $v1 | Return values | Caller | |
| $t0 - $t9 | Temporary variables. | Caller |
|
1) Grow the stack by subtracting the stack pointer ($sp) as much as the procedure needs (frame size). Save the registers the procedure should not change ($s0 -
$s7 and $ra) using store instruction (sw). |
|
|
2) Do the operations (return values are copied onto $v0 and $v1). |
This can be repeated. |
|
3) Copy arguments onto registers $a0, $a1, $a2 and $a3. Save the registers ($t0 - $t9, $a0 - $a3) the procedure wants to preserve after call using store instruction. A procedure call Restore the registers ($t0 - $t9, $a0 - $a3) if necessary. |
|
|
4) Restore the registers ($s0-$s7, $ra). Shrink the stack by adding the stack pointer with the frame size. Return using return address register ($ra). |
int fib(int n) {
if(n == 0) { return 1; }
if(n == 1) { return 1; }
return (fib(n - 1) + fib(n - 2));
}
Is this called by another procedure?
Yes.
It needs to save the local variables it modifies.
It needs to grow stack frame by subtracting stack pointer.
Does this call other procedures?
Yes.
It needs to copy arguments and make a procedure call.
If necessary it needs to save return address, arguments or temporary variables.
Apply the general case conventions.
| fib: addi $sp, $sp, -12 sw $ra, 8($sp) sw $s0, 4($sp) |
Step (1) Grows stack and saves the return address ($ra) and a local variable ($s0) which will be modified within the procedure. |
| addi $v0, $zero, 1 beq $a0, $zero, fin addi $t0, $zero, 1 beq $a0, $t0, fin |
Step (2) |
| addi $a0, $a0, -1 sw $a0, 0($sp) jal fib lw $a0, 0($sp) |
Step (3) Copy an arguement onto $a0. Saves and restores $a0 across the procedure call to reuse $a0 later. |
| addi $a0, $a0, -1 add $s0, $v0, $zero jal fib |
Step (3) Copy an arguement onto $a0. Doesn't need to save arguments because the arguments are not needed after the call. |
| add $v0, $v0, $s0 | Step (2) |
| fin: lw $s0, 4($sp) lw $ra, 8($sp) addi $sp, $sp, 12 jr $ra |
Step (4) Restore the return address ($ra) and a local variable ($s0). Jump to the return address. |
int sum(int n) {
if(n == 1) { return 1; }
return (n + sum(n - 1));
}
Is this called by another function? Yes. It needs to save the return address and the local variables.
Does it call other function? Yes, but it calls a procedure only once. Thus it doesn't need to save and restore arguments across the call.
sum: addi $sp, $sp, -8 sw $ra, 4($sp) sw $s0, 0($sp)
addi $v0, $zero, 1 addi $t0, $zero, 1 beq $a0, $t0, fin
add $s0, $a0, $zero add $a0, $a0, -1 jal sum add $v0, $v0, $s0
fin: lw $ra, 4($sp) lw $s0, 0($sp) addi $sp, $sp, 8
The basic data operations in MIPS are done in 32-bit units. Bitwise operations allow us to access the data in smaller units.
AND operation takes two operands (either two registers or one register and immediate value) and does logical AND operation for each bit. The Logical AND operation returns 1 when both bits are 1. Otherwise the operation returns 0.
AND operation is used for masking. If we want to take the rightmost byte from 4-byte word, we can AND a register with 0x000000ff.
addi $t0, $zero, $0xff # set $t0 as 0x000000ff
andi $t1, $t1, $t0 # and $t1 with 0x000000ff
The resulting value in register $t1 contains the rightmost byte of the original value.
AND operation is also used to selectively clear a bit in a register.
OR operation takes two operands (either two registers or one register and immediate value) and does logical OR operation for each bit. The Logical OR operation returns 1 when either of the bits are 1. Otherwise the operation returns 0.
OR operation is used to selectively set a bit in a register.
Logical shift left (sll) operation shift the bits in a register to the left by shift amount and fills rightmost bits with zeros.
Logical shift right (sll) operation shift the bits in a register to the right by shift amount and fills leftmost bits with zeros.
Arithmetic shift right (sll) operation shift the bits in a register to the right by shift amount and fills leftmost bits with sign extending.
MIPS has a coprocessor 0 to manage the processor states. Status register ($12) of coprocessor 0 allows us enable or disable interrupts.
| 15 | 8 | 5 | 4 | 3 | 2 | 1 | 0 | ||||||||
| Interrupt mask | Old | Previous | Current | ||||||||||||
| Kernel /user |
Int enable |
Kernel /user |
Int enable |
Kernel /user |
Int enable |
||||||||||
To check whether the current interrupt is enabled,
mfc0 $k0, $12 # copy the status register to $k0
andi $k0, $k0, 0x0001 # $k0 is not zero if the current interrupt is enabled.
To set a bit in the status register.
ori $k0, $k0, 0x0001
mtc0 $k0, $12
To clear a bit in the status register.
andi $k0, $k0, 0xfffe
mtc0 $k0, $12