Search code examples
mipscallprocedure

What is mistake of this MIPS procedure call?


this MIPS code contain a function int func(int a, int b) that calls another function foo(a,b).

func: addi $sp,$sp,-4    
      sw $ra, 0($sp)    
      addi $a0,$a0,1    
      addi $a1,$a1,2    
      jal foo
      addi $a0,$v0,4    
      add $v0,$a0,$a1    
      lw $ra,0($sp)    
      addi $sp,$sp,4    
      jr $ra

This code contains some mistakes but I don't know where wrong.


Solution

  • The most serious violation is that func expects $a1 to survive the function call to foo — which is incorrect.  By the calling convention, $a1 is an argument register, and is not preserved by a function call.  $a1 is also not a return value, so it should be considered uninitialized after a function call, i.e. it contains uselss garbage.

    func: addi $sp,$sp,-4    
          sw $ra, 0($sp)    
          addi $a0,$a0,1    
          addi $a1,$a1,2    
          jal foo           # this call effectively wipes out argument registers (ok)
          addi $a0,$v0,4    # here the function re-initializes $a0 from return value $v0 (ok)
          add $v0,$a0,$a1   # but here it uses uninitliazed $a1 (not ok)
          lw $ra,0($sp)    
          addi $sp,$sp,4    
          jr $ra
    

    The proper way to preserve funcs $a1 argument is to store it in memory and load it back later, after the function call; this would require an additional word in the stack frame.

    func: addiu $sp,$sp,-8    
          sw $ra, 4($sp)    
          addi $a0,$a0,1    
          addi $a1,$a1,2 
          sw $a1, 0($sp)    # save a1 in stack before call
          jal foo           # this call effectively wipes out argument registers
          lw $a1, 0($sp)    # restore a1 from stack after call
          addi $a0,$v0,4    # here the function re-initializes $a0 from return value $v0
          add $v0,$a0,$a1   # now using re-initialized $a1 (ok)
          lw $ra,4($sp)    
          addiu $sp,$sp,8    
          jr $ra
    

    In the above, I saved the updated $a1 register, but to be sure, it is not clear from merely reading the code whether the original, un-incremented $a1 or the updated $a1 is actually desired.  It looks like $a1 is being passed as a parameter to foo, so it could be that foo wants the incremented value but func later doesn't.

    Alternately, one could store $a1 in a $s register, as these are ensured to be preserved across a function call by the calling convention — however, by this very definition, $s registers themselves, if used by a callee, have to be saved and restored, so an additional word in the stack frame would still be necessary.


    The other violations of the calling convention for MIPS are that:

    • The MIPS CC requires stack frames to be 8 byte aligned so even if you only want one word, we're supposed to round up the stack frame size to a multiple of 8 bytes.  Many assembly programmers ignore this, however, without serious consequences.

    • The MIPS CC also dictates space for the 4 argument registers to be saved in the stack and that this space is provided by the caller so the callee can use it (without requiring any stack allocation by the callee).  This is almost never done by assembly programmers in simple assignments, though technically required.  If a callee takes advantage of these 4 words that are supposed to be there when they are not, bad things will happen.  (I did not address this in the above solution.)  I would minimally follow this requirement when writing a function that calls a varargs function, like printf, sprintf, scanf, etc..   This save area for the 4 argument registers is contiguous with the memory for the 5th, 6th, etc.. arguments, making the entire parameter block contiguous in memory, which is useful within the implementation of varargs functions.

    https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf


    Also, the addi used to allocated and deallocate stack space is better as addiu — since this is pointer arithmetic (addresses are unsigned), signed integer overflow is uninteresting at best, and harmful at worst (by causing an irrelevant overflow exception).