Below is the Fibonacci value expressed in MIPS.
fib: addi $sp, $sp, -24
sw $ra, 16($sp)
sw $a0, 20(sp) # recursive calls will overwrite original $a0
sw $s0. 0($sp) # holds fib(n-1)
# end prologue
slti $t0, $a0, 4 # fib(i) = i for i = 1, 2, 3; fib(0) = 0 by C code
beq $t0, $zero, L1
addi $v0, $a0, 0 # see prior comment (assumes $a0 non-negative integer)
j exit
# fib(n) = fib(n-1) fib(n-2)
L1: addi $a0, $a0, -1
jal fib
addi $s0, $v0, 0 # $s0 = fib(n-1) <-----how can use $v0?
addi $a0, $a0, -1
jal fib # upon return, $v0 holds fib(n-2)
add $v0, $v0, $s0
exit: # unwind stack and return
lw $s0, 0($sp)
lw $a0, 20($sp)
lw $ra, 16($sp)
addi $sp, $sp, 24
jr $ra
But there is something I don't quite understand here. As far as I know, the values of the registers other than $s
disappear when the function ends.
Looking at the fib function, when n
is 1
or 0
, 1
or 0
is stored in the value of $v0
. After that, when the function ends, shouldn't the value of $v0
be deleted too? So, before calling fib(n-2)
, the return value of fib(n-1)
is deleted, so I thought that the code to be saved in $s
should be written in the fib
function.
However, in the code above, the return value of the fib(n-1)
function is used by the next fib(n-2)
function. I don't know how this is possible.
Exactly how long are non preserved registers preserved and when will they be deleted?
CodePudding user response:
Please be aware that the fib
you're quoting is following custom and non-standard calling convention. I don't recommend it for learning about call preserved vs. call clobbered registers.
In this section here:
L1: addi $a0, $a0, -1
jal fib
addi $s0, $v0, 0 # $s0 = fib(n-1) <-----how can use $v0?
addi $a0, $a0, -1 <------------- **HERE** --------
jal fib # upon return, $v0 holds fib(n-2)
At the line marked **HERE**
, the code expects $a0
to have survived the function call. This non-standard, and a disingenuous illustration of the proper MIPS calling convention.
While this code works, it does not follow the MIPS calling convention register usage. It has made a custom alteration, that is only possible by knowing the implementation of both caller and callee. Thinking this way is false for the general case.
exit: # unwind stack and return
lw $s0, 0($sp)
lw $a0, 20($sp) <----------- **ALSO**
lw $ra, 16($sp)
addi $sp, $sp, 24
jr $ra
This part marked **ALSO**
is where the code is restoring $a0
for the caller! This is unconventional and in the general case not to be relied upon. In the general case, no one shall rely on $a0
being preserved, and so no one should bother to restore it.
Here's a proper (and more efficient) version of recursive fib
:
fib:
beq $a0, $0, return0 # goto return 0 if n == 0
li $v0, 1 # load $v0 with 1 for comparison and return
beq $a0, $v1, return # if n == 1 return leaving 1 in $v0
addiu $sp, $sp -8 # allocate two words of stack space
sw $ra, 4($sp) # save $ra
sw $a0, 0($sp) # save n
addi $a0, $a0, -1
jal fib # fib(n-1)
lw $a0, 0($sp) # restore $a0 for next call to fib
sw $v0, 0($sp) # store return value of fib(n-1) in stack
addi $a0, $a0, -2
jal fib # fib(n-2)
lw $t0, 0($sp) # reload return value from fib(n-1)
add $v0, $t0, $v0 # fib(n-1) fib(n-2)
lw $ra, 4($sp) # restore our return address
addiu $sp, $sp, 8 # release stack
jr $ra # and use return address
return0:
li $v0, 0
return:
jr $ra
This version actually follows the MIPS calling convention. It does not convert $a0
into a call preserved register, reloading $a0
before the 2nd recursive call instead of in epilogue.
This version does not use $s
registers, as they are actually a disadvantage in the circumstances of this particular function. Stack memory is used instead. An $s
register version would be (just slightly) longer, because the same number of loads & stores would be involved (but for different purpose of preserving $s register) though a extra register-to-register copy instruction would be needed making it longer.
CodePudding user response:
As far as I know, the values of the registers other than $s disappear when the function ends.
All the registers are permanent and accessible to all machine code in the program, and that includes both $t, $a, $v, and $s registers, for example.
The register values only change by executing machine code instructions that target them. Whether you're allowed (or supposed) to do that is a matter of software convention, which tells us how to share a mere 31 registers in a program that may have thousands of functions.
By software agreement, $a0
is used to pass the first (integer or pointer) parameter, and $v0
is used to return a function's (integer or pointer) return value. Once a register is set it doesn't change unless some machine code instruction changes it. Thus, register values have continuity and are under total control of the machine code program.