0

I'm quite new to MIPS so excuse me if this is a dumb question. I have the following exercise as an assignment and I would be very grateful if given any ideas, starting points.

I'm supposed to create a function that receives the arguments n(amount of numbers) and then n numbers, and then procedees to return the sum of said numbers and returns it in a stack. How would I start this? I'm thinking the function could have more than 4 numbers and the fact that the number of actual numbers varies is what gets me confused. The function argument should look like this:(int n, int number1, int number2....etc).

Could i store the numbers in a stack and then use the stack as a parameter in the function? If so, how would i do it?

UPDATE: So what i have in my mind as of now(with the help i received) looks like this:

sum:
addu $t3,$sp,16   #add to t3 address of sp+16
addu $a1,$a1,$a2   #adding sum to a1,a1 is first element a2 second and a3 third
addu $a1,$a1,$a3
li $t0,4          #start with i=4
bge $t0,$a0,end_for   #while i<n
lw $v0,0($t3)     #load v0 with value in stack
addu $a1,$v0,$a1  #add to sum
addi $t3,$t3,4   #increment stack and go up for next element
addi $t0,$t0,1
end_for:
li $v0,1
move $a0,$a0
syscall
jr $ra

I tried assembling it as it is but my MARS stops responding. Any clue as to why?

Michael
  • 57,169
  • 9
  • 80
  • 125
  • Are you sure the professor didn't meant you to use an array? You can do it with a varadic function but if the calling convention passes the first arguments in the registers and there is no homing in the stack, the implementation needs some pre-processing or it'll get messy. MIPS passes the first arguments in the registers, the rest on the stack without homing (I suppose), so it's better to first clarifying how the n numbers are passed before enrolling in a varadic implementation. – Margaret Bloom Jan 18 '19 at 10:28
  • @MargaretBloom Yes, he said it should be the equivalent of something like add(3,1,2,3), add (2,1,5) in C(where the first argument tells us how many numbers are gonna follow) – valentine george Jan 18 '19 at 10:37
  • @MargaretBloom: MIPS uses a link register instead of pushing a return address, so you could store the register args contiguously with the stack args to form an array of args. In fact it *also* leaves shadow space like Windows x64. Presumably so small functions can save manipulating the stack pointer? e.g. so they can store `$lr` there before calling another function? But no, they'd have to reserve shadow space for the callee, so it only helps for leaf functions that need to spill something, I think. Seems like a poor design (especially vs. having a red zone). Maybe some unwind convention? – Peter Cordes Jan 18 '19 at 10:38
  • @PeterCordes Good point about the `$lr`. I actually didn't expect MIPS to have a shadow/homing space. All in all, this turned out to be easier than expected. – Margaret Bloom Jan 18 '19 at 11:02
  • @MargaretBloom: yeah, I was also really surprised to find out it had shadow/home space, like I said in my answer. It was already trivial without it, unlike x64, so it's significantly less valuable (and I think a red-zone would have been a far better choice). But even without that, peeling the first 3 iterations would be easy, too. Either a separate array with 2 loops, or fully peel/unroll. – Peter Cordes Jan 18 '19 at 11:16
  • @PeterCordes It's true it's easy but peeling/unrolling or using two arrays makes the code longer. Being able to form a continuous array (either because there's no return address or thanks to a shadow space) shorten the code and uses few registers (especially when avoiding pseudo-instructions). But I stand by your point :) – Margaret Bloom Jan 18 '19 at 12:18

1 Answers1

4

In the normal MIPS calling convention, args after the 4th will already be stored on the call stack, placed there by your caller.

The standard calling convention leaves padding before stack args, where you could store the register args to create a contiguous array of all the args. This PDF has a diagram, and see also MIPS function call with more than four arguments

This is normally called "shadow space" in x86-64 Windows. But since MIPS jal doesn't store anything to memory (unlike x86 which pushes a return address on the stack, MIPS puts the return address in $lr), even if the calling convention didn't include this shadow space a function could still adjust SP first and then store register args contiguous with stack args. So the only benefit I can see is giving tiny functions extra scratch space without having to adjust the stack pointer. This is less useful than on x86-64, where it isn't easily possible to create an array of args without it.


Or you could peel the first 3 sum iterations that handle $a1 .. $a3 (again assuming the standard MIPS calling convention with the first 4 args in registers, $a0 being int n).

Then loop over stack args if you haven't got to n yet.


You could write a C function and look at optimized compiler output, like this

#include <stdarg.h>
int sumargs(int n, ...) {
    va_list args;
    va_start(args, n);

    int sum=0;
    for (int i=0 ; i<n ; i++){
        sum += va_arg(args, int);
    }
    va_end(args);
    return sum;
}

va_start and va_arg aren't real functions; they'll expand to some inline code. va_start(args,n) dumps the arg-passing registers after n into the shadow space (contiguous with stack args, if any).

MIPS gcc unfortunately doesn't support the -mregnames option to use names like $a0 and $t0, but google found a nice table of register name<->number

MIPS asm output from the Godbolt compiler explorer

# gcc5.4 -O3 -fno-delayed-branch  
sumargs(int, ...):
      # on entry: SP points 16 bytes below the first non-register arg, if there is one.
        addiu   $sp,$sp,-16  # reserve another 16 bytes
        addiu   $3,$sp,20    # create a pointer to the base of this array
        sw      $5,20($sp)   # dump $a1..$a3 into the shadow space
        sw      $6,24($sp)
        sw      $7,28($sp)    
        sw      $3,8($sp)    # spill the pointer into scratch space for some reason?
        blez    $4,$L4       # check if the loop should run 0 times.
        nop                  # branch-delay slot.  (MARS can simulate a MIPS without delayed branches, so I told gcc to fill the slots with nops)

        move    $5,$0        # i=0
        move    $2,$0        # $v0 = sum = 0
$L3:                         # do {
        lw      $6,0($3)
        addiu   $5,$5,1        # i++
        addu    $2,$2,$6       # sum += *arg_pointer
        addiu   $3,$3,4        # arg_pointer++  (4 bytes)
        bne     $4,$5,$L3    # } while(i != n)
        nop                  # fill the branch-delay slot

$L2:
        addiu   $sp,$sp,16
        j       $31          # return (with sum in $v0)
        nop

$L4:
        move    $2,$0                     # return 0
        b       $L2
        nop

Looping on do {}while(--n) would have been more efficient. It's a missed optimization that gcc doesn't do this when compiling the for loop.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847