-1

So, I have been trying for two days to write a program in mips assembly to exercise for my exams in a few days but yeah, my brain keeps lagging and I don't understand how it should work and what I should do, very confusing. All the versions of the programs I wrote would definitely not calculate the fibonacci unfortunately.

So this is my code as of now    
        .data
prompt: .asciiz "Give me a number to find its fibonacci: "
result: .asciiz "The fibonacci is: "
number: .word   0
answer: .word   0
        .text
        .globl  main
        .globl  fib
main:   li      $v0, 4
        la      $a0, prompt
        syscall
        
        li      $v0, 5
        syscall
        
        sw      $v0, number
        lw      $a0, number
        
        li      $v0, 0
        jal     fib

        sw      $v0, answer
        
        li      $v0, 4
        la      $a0, result
        syscall
        
        li      $v0, 1
        lw      $a0, answer
        syscall
        
        li      $v0, 10
        syscall
        
fib:    addi    $sp, $sp, -8
        sw      $ra, 4($sp)
        sw      $a0, 0($sp)
        
        slti    $t0, $a0, 2         # if (t0=(a0 < 2))
        beqz    $t0, fibB           # if (t0 == 0) / a0 < 2 false goto fibB
        # if (t0 == 1)
        add     $v0, $a0, $v0
        addi    $sp, $sp, 8
        jr      $ra
        
fibB:   addi    $a0, $a0, -1
        jal     fib
        addi    $a0, $a0, -2
        jal     fib
        lw      $a0, 0($sp)
        lw      $ra, 4($sp)
        addi    $sp, $sp, 8
        jr      $ra
        
  • [How can I convert recursive C++ code to RISC-V assembly code and Printing both strings and numbers in RISC-V assembly code for recursive C++ code?](https://stackoverflow.com/a/76347719) is a good explanation of how to think about writing recursive functions in asm. If it doesn't need to be recursive, a simple loop is a lot easier for Fibonacci, both easier to debug and way more efficient. You are single-stepping this with a debugger, right? What exactly do you see going wrong when you execute this for an input like `1` (base case) vs. an input like `3`. – Peter Cordes May 29 '23 at 15:35
  • @PeterCordes for the base case 1 it prints 1 but for 3 it prints 0 and for 10 it prints 6. I just tried a recursive function because my professor said those are SOS and I struggle with recursive functions even in other languages like c++ (I understand the concept but I fail during the implementation). – mariajuana May 29 '23 at 15:39

1 Answers1

1

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:

  1. 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:

  1. 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).

  2. 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.

Erik Eidt
  • 23,049
  • 2
  • 29
  • 53