1

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.

kaili
  • 13
  • 3

1 Answers1

0

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.

  • jr end is not a valid instruction — I think you want j end there.

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.

Erik Eidt
  • 23,049
  • 2
  • 29
  • 53
  • Thank you so much! I didn't even notice I wrote "jr end". Obviously I'm new to this, so your pointers are greatly appreciated. – kaili Mar 30 '23 at 19:24
  • fyi, using a frame pointer is unnecessary, but I guess it is meant as a learning exercise to understand the concept. However, sadly this particular example doesn't really motivate the frame pointer, but oh well. Also, let's note that the calling convention, stack frame, etc.. are all required even if the function called `factorial2`, i.e. `factorial` itself was not recursive. – Erik Eidt Mar 30 '23 at 19:29
  • yeah I agree that this doesn't emphasize the frame pointer concept; frankly I still don't quite get what it's supposed to do or what it's purpose is. – kaili Mar 30 '23 at 20:34
  • The frame pointer is good when you push a variable/unknown number of items on the stack. Then there is an anchor, the frame pointer, that doesn't move, even as the stack pointer is moving around. In C, the way to access this is `alloca` but it only offers limited capabilities compared to what can be done in assembly. That's one use case. – Erik Eidt Mar 30 '23 at 20:38
  • The other use case is stack unwind, which is important for languages that have exceptions (throw,catch) like C++, others. Stack unwind is difficult without some extra information. One way it can work is if all functions use a frame pointer in a very regular way, this then enables finding frame after frame that is on the stack. However, static data declarations can also provide that information to go frame to frame on the stack, and that tends to be the preferred method since it incurs less runtime overhead (no frame pointer needed). – Erik Eidt Mar 30 '23 at 20:40
  • I see. so, with what I wrote and what you're saying in mind, could I potentially store the address of the exit status on the frame pointer while I use the stack frame to allocate data in "factorial:" and "end:"...? the address, that is, being used to exit at the end of main and after factorial(n) has exited? – kaili Mar 31 '23 at 00:39
  • What I'm saying is that the frame pointer mechanism allows following from one frame to another as you might a linked list. – Erik Eidt Mar 31 '23 at 01:19
  • @kaili I think of `jr` as standing for "jump to register" so I don't mix them up. Most likely that's what it stands for but I'm not 100% sure on that. – puppydrum64 Mar 31 '23 at 11:32