Search code examples
assemblymips

MIPS - How do I branch and link if $a0 is greater than or equal to 1?


My program has a recursive funciton that takes an integer as input and prints n, n-1...1, 0, 0, 1 ...n

Here is the recursive function:

RDemo:
    
    addi $sp, $sp -4
    sw $ra, 0($sp)
    
    #print $a0
    li $v0, 1
    syscall
    
    addi $a0, $a0, -1
    bgezal $a0, RDemo
    addi $a0, $a0, 1
    li $v0, 1
    syscall
    
    lw $ra, 0($sp)
    addi $sp, $sp, 4
    jr $ra

the problem is that I need it to stop at 1, not 0.

For example, with an input of 3 it should output ( 3, 2, 1, 1, 2, 3) instead of (3, 2, 1, 0, 0, 1, 2, 3)

I am using the bgezal instruction ( branch if greater than or equal to 0 and link) because it is closest to what I need ( I need branch if grater than or equal to 1 and link).

As far as I can tell, there are only two MIPS branch and link instructions. begzal and bltzal (branch if less than 0 and link)

Is there a way I can accomplish what I want to without creating any other subroutines?


Solution

  • You can always use a normal blez over an unconditional bal (relative) or jal (section-absolute). Or offset your counter by one (into another register) in order to get something to branch on. Or in this case optimize your code into 2 loops instead of recursion, using no stack space instead of O(n) space.

    Unfortunately slt doesn't work directly with bgezal / bltzal. The both only check the sign bit, but slt generates a 0 or 1, both of which are non-negative.

    RDemo:
        addi $sp, $sp -4
        sw $ra, 0($sp)
        
        #print $a0
        li $v0, 1
        syscall
    
        addi $t0, $a0, -2       # tmp = n-2
        addi $a0, $a0, -1       # n--
        bgezal $t0, RDemo       # if (n-1 >= 0) RDemo(n);
             # returns with $a0 unchanged, custom calling convention
        addi $a0, $a0, 1
        li $v0, 1
        syscall
        
        lw $ra, 0($sp)
        addi $sp, $sp, 4
        jr $ra
    

    tmp = n-2 on the original n, instead of tmp = new_n - 1, has better instruction-level parallelism. A CPU able to run multiple instructions per cycle could run those both in parallel, since there's no RAW dependency between them.

    In this case, where integer overflow is probably not a concern for n-1, we can get away with just one extra instruction vs. your original code and still use a conditional-b-and-link, instead of branching around an unconditional call. If a0 = INT_MIN was a possibility, a0-1 >= 0 would be different from a0 >= 1. Since we used addi instead of the normal addiu, this will actually trap on signed-overflow and only print -2147483647 twice if called with that arg, not print 2^31 times (or actually stop on stack overflow from recursion.)


    BTW, your "recursion" is using a custom calling convention where the call "returns" n in $a0 vs. the standard MIPS calling convention where return values are in $v0, and $a arg-passing registers are call-clobbered. https://godbolt.org/z/hrx4rdqaM shows how GCC uses stack space to save the incoming arg across a call if you don't write it with a return value.

    This is kind of half way in between iterative and recursive, keeping registers across a call. But you can describe it to a C compiler with a0 = RDemo(a0) since there's only one arg.


    MIPS32r6

    If you were programming for MIPS32r6 (MIPS manual from dec 2016), BGTZALC rt, offset would be available, a full set of branch-and-link with any of the usual comparison predicates. The "C" is for "compact" - no branch delay slot.

    Real MIPS does have a branch delay slot, but your code looks like it's for a simplified MIPS without one, otherwise the addi $a0, $a0, 1 would run before the callee. And MIPS32r6 removed the old bgezal except for the bgezal $zero case, unconditional bal.

    MARS may simulate MIPS32, but not MIPS32r6 which reorganized some opcodes. (MIPS32r6 is of limited interest these days except for the architecture design choices; the company developing that owns the ISA has switched over to RISC-V.)