Search code examples
mips

Recursive power function in mips


The signature of the function in a high-level language (C) would be double power(double x, int n){ } Here x is the parameter of type double, which must be passed in $f14, and n is the parameter of type int, which must be passed in $a1, and the procedure should calculate and return the result of the expression in $f0 - $f2. Since it’s a procedure, it must be called from main using jal power and since it’s also a recursive procedure, inside the implementation of power you must have jal power as well, which will turn it into a recursive function.

.data
array1: .double 2.0 2.0 3.0 3.0 4.2 5.0 2.0 0.0 0.0
array2:  .word 5 4 2 1 0 2 3 0 10
array_size: .word 9 
newLine: .asciiz "\n"
one: .double 1.0
.text

main:
    la $s1 array1
    la $s2 array2
    lw $s3 array_size #s3 = 9
    
    li $s4, 0
loop: 
    beq $s4 $s3 exit 

    l.d $f14 0($s1)
    lw $a2 0($s2)
    
    jal power
    
    li $v0, 3           
        mov.d $f12, $f20   
        syscall             

    addi $s1 $s1 8  
    addi $s2 $s2 4  
    
    addi $s4, $s4, 1 #index
    j loop

power: 
    andi $sp 0xfffffff8 
    addi $sp $sp -16
    s.d $f20 ($sp)  
    sw $ra 8($sp)
      
        s.d $f14, 8($sp)
    l.d $f20 one
    beq $s2 $zero done
    
    addi $a2 $a2 -1
    jal power
    
    mul.d $f20 $f14 $f20
    
done:
    lw $ra 8($sp)
    l.d $f14, 8($sp)
    l.d $f20 ($sp)
    addi $sp $sp 16
    jr $ra  
exit: 
    li $v0, 10         
    syscall

The logic seems okay to me, but the output gives me all zeros. I do not know what is wrong :(


Solution

  • Looks like the whole class is here on stack overflow with this assignment.  The template and instructions you've been given are arguably lacking and/or incorrect.

    1. For one, a function with signature

      double power ( double x, int n );

      should pass x in $f12 and n in $a2 for 32-bit MIPS an $f12 and $a1 for 64-bit MIPS.  Your template code is 32-bit MIPS.  Suspect your instructors are taking a page from 64-bit MIPS here, and also avoiding use of $f12, since that's handy to use with the syscall to print a float/double.  Obviously, you should go with what's been given in your template, just realize perhaps that this is not quite the official way.

    2. It is incorrect to align the stack upon function entry without also making special provisions to restore the stack pointer to the caller-provided value.  It is mandatory to restore the stack point to the caller-provided value, though in this particular case, won't crash the program because the main does not use the stack pointer (but this cannot be relied upon in general).  Solutions here are to either:

      (a) Align the stack pointer once in the startup code (which here is main) before doing any function calling — this means moving the andi sp 0xfffffff8 into the main.  Also then ensure that all non-leaf functions allocate stack space in multiples of 8 bytes rather than the sometimes seen multiples of 4 bytes.

      (b) Or else align the stack pointer upon function entry but make provisions to save and restore the original stack pointer value upon function exit.  That means capturing the original stack pointer value before aligning it, then saving that in the stack after aligning and restoring the stack pointer from that saved memory location rather than simply adding a fixed amount to the stack pointer (as that won't necessarily restore the stack pointer to the caller-provided value).  Your template fails to restore the stack pointer to that provided by main, but it since main doesn't consult the stack, it doesn't crash or get incorrect program output despite this flaw.

    3. The standard MIPS calling convention requires reservation of stack memory locations for the parameters by the caller to be provided to the callee.  This is not being done in your template, however, is often not done for these kinds of assignments.

    4. Since the function signature has power returning a double, you must consistently return a double from all return ...; statements.  Here you're returning an integer 1 in $v0 from the base recursive case as if the signature was int power ( double x, int n), though the callers of power expecting a double of value 1.0 to be returned instead (in the case the n is zero).  Failing to do this means that $f0 holds the simulator startup value of 0.0 and as a result the multiplications simply fail to go to any non-zero value.

    5. By the standard calling convention, you cannot rely on parameter (here in $f14 though should be $f12) to survive a function call, even to itself.  There are several possible solutions here:

      (a) Use a call-preserved register, meaning, using the given $f20 register that ensures survival across a function call.  This means moving the parameter from $f14 into $f20 before the recursive call, and then using it from that call-preserved register after the recursive function call.  The save & restore of $f20 that is in the template is necessary for this.  Since your template already has this boilerplate, might as well go that route.

      (b) Or else, use stack memory directly to preserve the incoming parameter value, omitting saving and restoring of $f20 and instead to save the incoming $f14 parameter value in that stack slot at 0($sp).  After the recursive call, restore the incoming parameter value from the stack slot, to a scratch register, like $f2, and use it from there for the multiplication.  This is actually the most efficient approach with this problem when using the standard calling convention, since there is one less instruction than with approach (a) — no need to move $f14 to $f20, just store $f14 directly to memory and reload when needed.  That makes one save, one restore, whereas with (a) there's one save, one move, and one restore.

      (c) Create a non-standard calling convention for this function that preserves the incoming argument $f14 for callers to use after.  This is an advanced technique that can work in this case because we can know the implementation of both caller and callee.  In this case there is no requirement to (a) align the stack, or (b) save and restore any floating point register (be it $f20 or $f14).  While this will work, you'll likely get points off for this approach, since we should probably be following the standard calling convention and only making deviations with more experience.

    As far as the rest goes, you need to be able to handle the case of negative powers, so you'll need more code.  Find or make a C version that includes that case so you know what it needs to do, and then code up the same in assembly.