SM213 Stack-Based Calling Convention

This document describes the conventions to use when writing SM213 code.

Basic Stack Usage

r5 is used for the stack pointer, and should not be used for anything else. When calling a function, its value immediately before the call must be the same as after the call returns. The stack is predecrement, postincrement: when pushing a value on the stack, decrease the stack pointer before writing the value, and when popping, increase the stack pointer after reading the value. This means the stack pointer will always point to the last pushed value, the value pushed before that will be at r5+4, the one before that at r5+8, etc.

Calling and Returning

A call is performed by setting the return address in r6 with gpc, then jumping to the function to be called. Since r6 is a callee-save register, ensure that its value has been saved on the stack (see below). On entry to the callee, r6 will contain the return address, so a return is done with a j 0(r6) instruction. Return only after restoring the stack to the state it was in upon entering the function.

Register Usage

r0-r3 are caller-save registers: This means that you may use them freely in your function, but if you call any other functions, do not assume that they will retain the values they had before the call. These registers are ideal for values that do not need to be preserved across function calls.

r4-r7 are callee-save registers: You may use them in your function, but must save their values on the stack upon entering, and restore their values before returning. This also means that you can depend on their values not changing after making another call from within your function (since that function must save them if it uses them, and your function will have saved them if it uses them, so that your function's caller can also depend on its values being preserved.) Note that although r5 is included in this set for completeness, it is the stack pointer and so should not be used for anything else, as described above. Because a stack is LIFO (last-in first-out), ensure that the you restore registers by popping from the stack in the reverse order to how they were pushed.

Passing Parameters and Returning Values

If your function returns a value, ensure that value is in r0 before the j 0(r6) that returns to the caller. Similarly, if you call a function that returns a value, its return value will be in r0 after the jump that called it.

Parameters are passed on the stack and should be present in right-to-left order, pushed immediately before making the call. Thus, immediately after entering the callee, the first (leftmost) parameter will be at 0(r5), the second at 4(r5), etc. Since these are relative to the stack pointer, if you push any other values in the callee, the offsets to the parameters will increase appropriately. The caller is responsible for removing the parameters from the stack after the call returns. Note that in some cases you can reuse the area allocated for parameters by simply storing the new values there, without needing to move the stack pointer up and down unnecessarily (as long as the stack is restored to the state it was before returning).

Here is an example of the C code and corresponding Asm that illustrates this:


int bar(int a, int b, int c) {

 return a + b + c;

}



int foo(int a) {

 return bar(1, 2, 3) + bar(a, 3, 4);

}


bar:

    ld 0(r5), r0     # r0 = a

    ld 4(r5), r1     # r1 = b

    add r1, r0       # r0 = a + b

    ld 8(r5), r1     # r1 = c

    add r1, r0       # r0 = a + b + c

    j 0(r6)          # return

foo:

    deca r5

    st r6, 0(r5)     # save return address

    deca r5

    st r4, 0(r5)     # save r4

    ld $3, r0

    deca r5

    st r0, 0(r5)     # push 3

    dec r0           # r0 = 2

    deca r5

    st r0, 0(r5)     # push 2

    dec r0           # r0 = 1

    deca r5

    st r0, 0(r5)     # push 1

    gpc $6, r6

    j bar            # call bar

    mov r0, r4       # r4 = bar(1, 2, 3)

    ld $4, r0        # r0 = 4

    st r0, 8(r5)     # write parameter 4 (reuse parameter area)

    dec r0           # r0 = 3

    st r0, 4(r5)     # write parameter 3

    ld 20(r5), r0    # get parameter a (note offset: 20 = 3*4 parameter bytes + 2*4 saved registers)

    st r0, 0(r5)     # write parameter a

    gpc $6, r6

    j bar            # call bar

    add r4, r0       # r0 = bar(1, 2, 3) + bar(a, 3, 4)

    inca r5

    inca r5

    inca r5          # discard parameters

    ld 0(r5), r4     # restore r4

    inca r5

    ld 0(r5), r6     # restore r6

    inca r5

    j 0(r6)          # return