the jr $ra
will jump to the address stored in register $ra
, which was not set by your code, so it is still zero (initial register value from MARS before running your code). The CPU will then jump to address zero, which is failure, and reports that.
To use jr $ra
to "return" from some code, you have to first use jal
instruction (or other instruction modifying $ra
content) to set up $ra
. Also when you want to nest several jal
"subroutines calls", you have to store/restore the $ra
of the outer calls around to not lose the value by the next nested jal
.
Following code is example of both using paired jal
+jr $ra
for "subroutine"-like call, and also example how straightforward the implementation of such algorithm is in assembly (because actually that C source is more like assembly in C, so your inexperience made you take a very convoluted and complex approach of something what can be implemented in assembly almost 1:1 with the original C source):
.text
main: # just minimal "main" to verify the code works
# read two integers as input
li $v0,5
syscall
move $a0, $v0 # $a0 = a
li $v0,5
syscall
move $a1, $v0 # $a1 = b
# call the russianPeasant subroutine
jal russianPeasant # $v0 = a * b
nop
# output result
move $a0, $v0
li $v0, 1
syscall
# terminate
li $v0, 10
syscall
And the subroutine itself, which can be called with arguments in a0
and a1
and returns result in v0
.
# input: $a0 = a, $a1 = b
# output: $v0 = a * b
russianPeasant:
li $v0, 0 # res = 0
beq $a1, $zero, russianPeasant_b_zero
nop # neutralize "Delayed branching" setting ("nop" works with both ON/OFF setting)
russianPeasant_while_b:
andi $at, $a1, 1 # test if b is odd, for even b skip the res = res + a
beq $at, $zero, russianPeasant_b_even
nop # but such neutralization comes with performance cost of course
add $v0, $v0, $a0 # res = res + a
russianPeasant_b_even:
sll $a0, $a0, 1 # a = a << 1
srl $a1, $a1, 1 # b = b >> 1
bne $a1, $zero, russianPeasant_while_b # repeat when (b != 0)
nop # on real MIPS production code the instructions are reordered to avoid useless nop
russianPeasant_b_zero:
jr $ra # return res
nop
The code is intentionally putting after each branch nop
instruction to make it work with both delayed branching setting ON or OFF.
Try to use MARS help (F1) with instruction description to figure out, how it works, also use the single-step feature of the debugger to watch out the code in action, one instruction at time, observing all the register values and code flow.
On real MIPS CPU with the delayed branching the code can be optimized like this (you can switch "Delayed branching" ON in MARS settings to make it simulate real MIPS CPU with this somewhat confusing behaviour .. from your original source it looks like you are learning the MIPS assembly without delayed branching, which is certainly reasonable approach for somebody just starting with assembly, but that's not how real MIPS CPU works):
# input: $a0 = a, $a1 = b
# output: $v0 = a * b
russianPeasant_real_MIPS: # second variant supporting delayed branching like real MIPS CPU
beq $a1, $zero, russianPeasant2_b_zero
li $v0, 0 # res = 0
russianPeasant2_while_b:
andi $at, $a1, 1 # test if b is odd, for even b skip the res = res + a
beq $at, $zero, russianPeasant2_b_even
srl $a1, $a1, 1 # b = b >> 1
add $v0, $v0, $a0 # res = res + a
russianPeasant2_b_even:
bne $a1, $zero, russianPeasant2_while_b # repeat when (b != 0)
sll $a0, $a0, 1 # a = a << 1
russianPeasant2_b_zero:
jr $ra # return res
nop