assemblymipsstack-pointerframe-pointer

Storing and Loading $ra


I'm currently having trouble writing this recursive factorial assembly code in MIPS. I do not want to change the format of this code, as it is for a project, but I want to understand how to store or call $ra so that it will return to "branch:" in order to output the calculated factorial.

## 
## 

.data
prompt1:    .asciiz "\nPlease enter an integer: \n"
negativePrompt: .asciiz "\nYour input must be greater than or equal to 0 in order to function.\n"
factorialIs:    .asciiz "\nThe factorial of your input number is: \n"

.globl main
.text
main:

    subu $sp, $sp, 32   # push memory for 8 values
    sw $ra, 20($sp)     # save return address
    sw $fp, 16($sp)     # save old frame pointer 
    addiu $fp, $sp, 28  # set up frame pointer

branch:

    li $v0, 4           # load macro for print_str 
    la $a0, prompt1     # pass argument to string
    syscall             # print string prompt1

    li $v0, 5           # load macro for read_int
    syscall             # get N value from user 

    move $a0, $v0               # store N as arg
    blt $a0, 0, negBranch       # if N is negative, handles case    
    jal factorial               # calls factorial function

    li $v0, 4               # macro for print string
    la $a0, factorialIs     # print string
    syscall

    li $v0, 1               # macro for print int
    move $a0, $v0           # print output int in v0
    syscall 

    lw $ra, 20($sp)         # restore return address of caller
    lw $fp, 16($sp)         # restore frame pointer
    addiu $sp, $sp, 32      # pop stack
    jr $ra                  # return to caller 
#exit:
#   "would you like to calculate again?" 
#   li $v0, 10              # load macro to exit
#   syscall                 # execute exit
    
negBranch: 
    li $v0, 4                   # load macro for print_str 
    la $a0, negativePrompt              # pass argument to string
    syscall                     # print string prompt1  
    li $t0, 0                   # initialize input value to 0
    j branch                        # ask user to input new number
    
factorial: 
    subu $sp, $sp, 32       # pushes memory for variables
    sw $ra, 12($sp)         # stores return address of recursion
    sw $fp, 8($sp)          # save frame pointer
    addiu $fp, $sp, 28      # set up frame pointer
    sw $a0, 0($fp)          # stores n in frame pointer

    lw $v0, 0($fp)
    bgtz $v0, zeroBranch        # if N is greater than 0, recurse
    li $v0, 1                   # return 1
    jr end

zeroBranch:
    lw $v1, 0($fp)          # load n value
    subu $v0, $v1, 1        # v0 = n - 1 
    move $a0, $v0           # a0 = v0 = n - 1 
    jal factorial           # recursive function call
    lw $v1, 0($fp)          # load original n value to v1
    mul $v0, $v0, $v1       # v0 = n * fact(n-1)    

end: 
    lw $ra, 12($sp)         # loads return address of main call
## this ^ line does not access the address from "jal factorial"
    lw $fp, 16($sp)         # loads initial n value
    addiu $sp, $sp, 32      # collapses stack frame
    jr $ra                  # return to main then exit

When I enter factorial(1), it functions recursively as expected; however, when I get to my "end:" branch, the return address is not returning back to my "branch:" branch, which would output the result of the factorial input.


Solution

  • Why do you think that the function should return to label branch: — that is essentially incorrect and so you need to rethink this.  The function should return to the instruction immediately after jal.


    This code has assembly time errors, so I don't know how you're able to run it.


    That code is messing up the $fp register in epilogue of factorial — the old $fp is saved at 8($sp) but restored from 16($sp), so it doesn't come back to where the caller needs it to be.  Change the restore to use 8($sp).


    The syscall sequence used to print the factorial result in main is:

    li $v0, 1               # macro for print int
    move $a0, $v0           # print output int in v0
    

    Can you see how these two lines have clobbered the value in $v0 that you want to print, so will always print 1?

    But it's worse than that, since you also print a prompt (syscall #4, a few lines prior), that moves a 4 (syscall code for print string) into $v0 wiping out the function return value.

    So, what to do is: immediately after the jal instruction in main, copy the return value $v0, in a different location — suggest $t0 as it will survive a syscall (if it was a function call then would suggest memory or $s0 instead).

    Then, at the syscall #1 for print integer copy $t0 into $a0 for the parameter.


    You can see all of this by using single step in the debugger.  Debugging is a critical skill and anyone attempting assembly language programming should have it or acquire it asap.  Single step and watch each line, verify the program state (registers and memory) in between each line.