mipsmips32mips64smips

Mips: going back to incorrect return address


I have a mini bank program written that goes calls multiple functions inside the subrouting deposit, this is the subroutine

deposit:
    addi $sp, $sp, -8 #save space on stack
    addi $s3, $0, 1 #trigger s3
    sw $s3, 0($sp)
    sw $ra, 4($sp)
    .....
    jal AsciiConvert #convert ascii of deposited amount to integer
    beq $v0, $0, Err_ACC #if no value to be deposited was inputed print error message
    beq $t0, $0, deposit_checking #if check exists returns a 0
    beq $t0, 1, deposit_saving #if check exists returns a 1
    jal printarray
    lw $s3, 0($sp)
    lw $ra, 4($sp)      # reload $ra so we can return to caller
    addi $sp, $sp, 8   # restore $sp, freeing the allocated space
    jr $ra   
    
deposit_checking:
    ... arithmetic operations...
    jr $ra   

the ascii convert subroutine:

AsciiConvert:
    ...normal arithemtics...
    j ConvertOP
ConvertOP:
    lb $s0, 0($a1)
    beq $s0, $0, endConvert #end at null terminating
    beq $s0,32,endConvert #if found a space
    addi $s0, $s0, -48 #convert to int
    mul $s2, $s1, 10 #multiply sum by 10
    add $s2, $s2, $s0 #sum = sum + previous number
    add $s1, $s2, $0 #s1 holds previous value
    addi $a1, $a1, 1 #increment adress
    add $v0, $s2, $0 #store the number in the return adress
    j ConvertOP
endConvert:
    jr $ra

When I go into deposit, I jal AsciiConvert and then I go into the deposit_Checking subroutine, however the return address of that deposit_Checking returns me back to the line of jal AsciiConvert and not to the line where I called the deposit_Checking subroutine, leading to an infinite loop between Ascii convert subroutine and deposit_Checking subroutine...can someone please help me?


Solution

  • deposit_checking looks like a subroutine, and you identify it as a subroutine in your post, but, we don't enter subroutines with beq instruction, you're supposed to use jal to call a subroutine.

    In machine code, the return address, $ra for MIPS, is effectively a parameter to the subroutine — it tells the subroutine where to resume execution in the caller, where to return to.  There are several ways to set the $ra register with a meaningful return address, though of course jal is by far the most common way.

    beq transfers control of the processor to the target label (when eq is true) by changing the program counter (pc) though does not provide a (new) $ra value.

    By not setting $ra to a new value, its old value is retained.  Since the $ra register was last set by the jal AsciiConvert, the jr $ra for the other function goes back there, none the wiser that this was not the right call — as it is the caller's job to set that parameter properly.

    And while some instruction sets allow calling on a condition, we wouldn't necessarily want all beqs to capture a return address, because then any function that used beq would have to concern itself with preserving $ra.

    Let's also note that these behaviors are all visible during debugging.  jr $ra, for example will transfer control to whatever machine instruction is addressed by the value in the $ra register.  When you first enter a subroutine/function, there should be a proper return address parameter provided in $ra register, and by the time you get to the end of the function with its final instruction jr $ra, if the value in the $ra register's value has changed from entry, then it won't go back to where it was called from — so we'd be looking for somewhere in between that changes $ra, without restoring it back.