In this article, we are going to learn MIPS calling conventions by examples. And we will focus on the one used by GCC. All the examples will be compiled with GCC flag -mno-abicalls not generating SVR4-style position-independent code. Normally -mabicalls should be used, we disable it for the simplification of assembly codes.
First up, take a look at the most simple leaf function without any arguments.
int empty(void) { return 0; } 1 empty: 2 addiu $sp,$sp,-8 3 sw $fp,0($sp) 4 move $fp,$sp 5 move $2,$0 6 move $sp,$fp 7 lw $fp,0($sp) 8 addiu $sp,$sp,8 9 j $31 10 nopline 2: reserve 8 bytes stack space
line 3: push the previous $fp to stack
line 4: take current $sp as the subroutine's frame pointer
line 5: set $v0 to zero ($v0 stores the return value)
line 6,7,8: pop out $fp from stack, restore back original $sp
line 9: jump to return address
line 10: branch delay slot
The function has its own stack frame. It must save the old frame pointer ($fp) before using it, and restore it back before returning to the caller. Generally speaking, if any of $16..$23 or $29..$31 is changed within the called function, it must be saved in the stack frame before use and restored from the stack frame before return from the function. It is called general register save area. As to the example, 4 bytes is enough for saving $fp, why does it reserve 8 bytes stack space? It is because that the register save area must be doubleword (8 byte) aligned.
Besides saving $fp, a non-leaf function must save return address ($ra) as well. When it calls other subroutines, the $ra will be modified for returning from the subroutines. The non-leaf function has to save $ra in order to return to its caller.
int emptycaller(void) { empty(); return 0; } 1 emptycaller: 2 addiu $sp,$sp,-24 3 sw $31,20($sp) 4 sw $fp,16($sp) 5 move $fp,$sp 6 jal empty 7 nop 8 move $2,$0 9 move $sp,$fp 10 lw $31,20($sp) 11 lw $fp,16($sp) 12 addiu $sp,$sp,24 13 j $31 14 nopline 3: push $ra onto the stack
line 4: push $fp onto the stack
line 10: pop $ra from the stack
line 11: pop $fp from the stack
In the example, 8 bytes stack space should be enough for saving $ra and $fp. The additional 16 bytes (total 24 bytes) is the function call arguemnt area, and we will dicuss about it later.
We know that arguments are passed to subroutines in argument registers ($a0, $a1, $a2, and $a3). Let's take a look at the simple leaf function with 3 arguments.
int args3(int x, int y, int z) { return (x + y + z); } 1 args3: 2 addiu $sp,$sp,-8 3 sw $fp,0($sp) 4 move $fp,$sp 5 sw $4,8($fp) 6 sw $5,12($fp) 7 sw $6,16($fp) 8 lw $3,8($fp) 9 lw $2,12($fp) 10 nop 11 addu $2,$3,$2 12 lw $3,16($fp) 13 nop 14 addu $2,$2,$3 15 move $sp,$fp 16 lw $fp,0($sp) 17 addiu $sp,$sp,8 18 j $31 19 nopline 2~4: the function prologue as described previously
line 5~7: push the arguments from $a0, $a1, and $a2 to the stack
line 8~14: add the 3 arguments, and put the sum in $v0 for the return value
line 15~17: the function epilogue as described previously
line 18~19: return to the caller
The arguments are pushed to 8($fp) to 16($fp), which are located in the caller's stack frame. A non-leaf function (Caller) will always reserve the top 4 words (16 bytes) at least to storing arguments to called functions. It is called the function call argument area.
If the maximum number of arguments to the called function is fewer than 4 words, the caller still has to reserve 4 words. Take the followed non-leaf function for example. The function reserved 24 bytes stack space instead of 8, and the additional 16 bytes is the function call argument area.
int arg3caller(void) { args3(5, 6, 7); return 0; } 1 arg3caller: 2 addiu $sp,$sp,-24 3 sw $31,20($sp) 4 sw $fp,16($sp) 5 move $fp,$sp 6 li $4,5 # 0x5 7 li $5,6 # 0x6 8 li $6,7 # 0x7 9 jal args3 10 nop 11 move $2,$0 12 move $sp,$fp 13 lw $31,20($sp) 14 lw $fp,16($sp) 15 addiu $sp,$sp,24 16 j $31 17 nopline 2: reserve 24 bytes stack space
line 3~4: Since 0($sp) ~ 12($sp) is function call argument area, the old $fp will be pushed onto 16($sp), and $ra will be pushed onto 20($sp).
line 6~10: pass the arguments in $a0, $a1, and $a2
There are four argument registers ($a0, $a1, $a2, and $a3) totally. When calling a function with more than 4 arguemnts, the first 4 arguments are passed in registers, and the others are passed on the stack.
int args6(int x, int y, int z, int a, int b, int c) { return (x + y + z + a + b + c); } 1 args6: 2 addiu $sp,$sp,-8 3 sw $fp,0($sp) 4 move $fp,$sp 5 sw $4,8($fp) 6 sw $5,12($fp) 7 sw $6,16($fp) 8 sw $7,20($fp) 9 lw $3,8($fp) 10 lw $2,12($fp) 11 nop 12 addu $2,$3,$2 13 lw $3,16($fp) 14 nop 15 addu $2,$2,$3 16 lw $3,20($fp) 17 nop 18 addu $2,$2,$3 19 lw $3,24($fp) 20 nop 21 addu $2,$2,$3 22 lw $3,28($fp) 23 nop 24 addu $2,$2,$3 25 move $sp,$fp 26 lw $fp,0($sp) 27 addiu $sp,$sp,8 28 j $31 29 nopline 2~4: function prologue
lin 5~8: push the first 4 arguments to the stack
line 9~18: add the first 4 arguments, and put the sum in $v0
line 19~21: fetch the 5th argument from 24($fp), and add it to $v0
line 22~24: fetch the 6th argument from 28($fp), and add it to $v0 for the return value
line 25~27: function epilogue
line 28~29: return to the caller
In order to call the 6-argument function, the caller has to push the 5th and 6th arguments onto the stack. The function call argument area becomes of length 6 words (24 bytes) instead of 4 words. It reserves total 32 bytes, 8 bytes for registers $ra and $fp, and 24 bytes for function call argument area. Note that the area will also be doubleword aligned.
int arg6caller(void) { args5(5, 6, 7, 8, 9, 10); return 0; } 1 arg6caller: 2 addiu $sp,$sp,-32 3 sw $31,28($sp) 4 sw $fp,24($sp) 5 move $fp,$sp 6 li $2,9 # 0x9 7 sw $2,16($sp) 8 li $2,10 # 0xa 9 sw $2,20($sp) 10 li $4,5 # 0x5 11 li $5,6 # 0x6 12 li $6,7 # 0x7 13 li $7,8 # 0x8 14 jal args5 15 nop 16 move $2,$0 17 move $sp,$fp 18 lw $31,28($sp) 19 lw $fp,24($sp) 20 addiu $sp,$sp,32 21 j $31 22 nopline 2: reserve 32 bytes stack space
line 3~4: Since 0($sp) ~ 20($sp) is function call argument area, the old $fp will be pushed onto 16($sp), and $ra will be pushed onto 20($sp).
line 6~9: pass the 5th and 6th arguments on the stack
line 10~13: pass the first 4 arguments in argument registers
Next, we are going to see a little bit complicated example. We declare 2 variables stored in registers $s0 and $s1. The function must save them in the general register save area before using them. Besides, we declare 2 local variables (automatic variables), and they will also be saved on the top of the general register save area.
int comp(int m, int n) { int a = 1, b = 2; register int x = 5, y = 6; args6(a, b, m, n, x, y); return (x + y); }Therefore, the stack frame will be like:
The assembly code line by line:
1 comp: 2 addiu $sp,$sp,-48 3 sw $31,44($sp) 4 sw $fp,40($sp) 5 sw $17,36($sp) 6 sw $16,32($sp) 7 move $fp,$sp 8 sw $4,48($fp) 9 sw $5,52($fp) 10 li $2,1 # 0x1 11 sw $2,24($fp) 12 li $2,2 # 0x2 13 sw $2,28($fp) 14 li $16,5 # 0x5 15 li $17,6 # 0x6 16 sw $16,16($sp) 17 sw $17,20($sp) 18 lw $4,24($fp) 19 lw $5,28($fp) 20 lw $6,48($fp) 21 lw $7,52($fp) 22 jal args6 23 nop 24 addu $2,$16,$17 25 move $sp,$fp 26 lw $31,44($sp) 27 lw $fp,40($sp) 28 lw $17,36($sp) 29 lw $16,32($sp) 30 addiu $sp,$sp,48 31 j $31 32 nopline 3~4: save previous $ra and $fp
line 5~6: save $s0 and $s1 before using it
line 8~9: save $a0 and $a1 (arguments m and n)
line 10~13: storing local variable a and b
line 14~15: variable x and y are saved in registers $s0 and $s1
line 16~17: pass arguments x and y on stack
line 18~21: pass arguments a, b, m, and n in argument registers
line 22~23: call the subroutine
line 24: put the sum of x + y to $v0 for the return value
line 26~30: restore registers back
line 31~32: return to the caller
References:
2 comments:
Hi,
as I can see you hack gcc with mipsel. I have a problem. Can you take a look on my message at gcc-help mailing list? http://gcc.gnu.org/ml/gcc-help/2010-10/msg00056.html
I could not find out your email address, therefore, I write this comment.
Thanks,
Csaba
Thank you very much for this straightforward tutorial on a poorly documented topic.
Post a Comment