If your choice is to use a custom/non-standard calling convention that both saves & restores $a0
, and passes $v0
as a accumulator parameter, then you are well off the mainstream track. It is possible that your instructors are telling you to do this, but then you need to go to them for help, or at least obtain knowledge of this alteration.
IMHO, at best this approach can be considered a very advanced optimization technique (though flawed or incomplete), so there's little advantage to learning this before you know how normal calling should work.
I also see that you've attempted to move the addition part of the expression fib(...) + fib(...)
into the base case — this is folly since it simply doesn't belong there, and is then missing from all the non-base cases, and the resulting function simply isn't a normal recursive fib()
.
That code is doing something like:
int altFib(int n, int acc) {
if (n <= 2) {
return n + acc;
}
acc = altFib(n-1, acc);
return altFib(n-2, acc);
}
So, this is only going to do addition at the base case and not at every level.
Also, to be clear, there are bad examples to be found on the internet, of fib()
using non-standard calling conventions that many educators would consider problematic, yet others don't even understand the issues.
The rest of this post will deal with standard calling convention and register usages.
First, your base case has a typo (or design flaw): add $v0, $a0, $v0
. In the base case, you want to simply copy $a0
into $v0
, not a sum of whatever happens to be in $v0
with $a0
, i.e. return n;
.
Always test your code with the smallest numbers first so, do fib(0)
, then fib)(1)
, then fib(2)
. However, you have cleverly cleared $v0
to 0 before invoking the function, as a non-standard band-aid — $v0
is not a parameter to the function, plus your band-aid is not being applied to the internal call sites within fib
. We are not supposed to rely on $v0
having some value upon function entry: simply set it, overwriting any old value, to return a desired value to the caller.
Second, within the fib
function, you are generally not following the standard calling convention with regard to the second invocation of fib
inside the function.
You are using a non-standard register save & restore, $a0
should not be restored, and callers should not rely on it being restored by callee. $a0
should be saved for your own function's benefit but there is no reason to restore it for the caller's benefit.
The return value from the first invocation to fib
is (supposedly) in $v0
, yet you make a second (recursive) call to fib
, which necessarily also returns its return value in $v0
, and as a result you no longer have access to the first invocation's return value; it is lost! You can observe this in debugging. (That non-standard calling convention is going to restore the $a0
that the invocation got, not the original parameter value you want meaning it will return to you in $a0
the -1 value, however the proper solutions is to use the standard calling convention.)
The parameter $a0
is being wiped out by the first invocation to fib
by your own code subtracting 1 from it in order to make that invocation=. You can observe this in debugging, with just fib(2)
.
You are missing the addition in fib(..) + fib(..)
. You are most likely missing this because you don't know what to add together (or perhaps due to the alternative design), which comes from not following the calling convention's requirements, and the associated loss of the first $v0
return value from the first invocation of fib
.
In order to fix this and follow the standard calling convention:
- You already have prologue and epilogue that is saving
$a0
, which is good. You should simply remove the restoration of $a0
in epilogue.
In between the two invocation's of fib:
You need to restore the original $a0
in order to subtract 2 from it for parameter passing to the second invocation of fib
. This requires a lw
instruction to fetch into $a0
from the stack memory location where it was saved in prologue (before the -2 subtraction).
You need to save $v0
(in between the two invocations to fib
), as it hold the return value from the first invocation, which will be needed to perform the proper addition. You can save it into the same stack location that you just loaded $a0
from, since by analysis of the remainder of the function, you'll no longer need the original parameter value.
(You could do items (2.) & (3.) in reverse order, but that would require an additional stack slot, since in that ordering item (3.) cannot save to the same stack slot, as the value there is not yet recovered for item (2.))
After the second invocation to fib
, you need to add the two return values together: one of them is in $v0
and the other has been saved as per item (3.). So, reload the value saved in (3.) into any available temporary register (i.e. other than $v0, say $t0
or even $a0
), and sum it's value to $v0
.