MIPS调用约定要求前四个函数参数在寄存器a0到a3中,如果还有更多的参数,则放在堆栈中。此外,它还要求函数调用者为前四个参数在堆栈上分配四个插槽,尽管这些参数已经传递给了寄存器。
因此,如果您想访问第五个(及更多)参数,需要使用sp。如果该函数反过来调用其他函数并在调用后使用其参数,则需要将a0到a3存储在堆栈上的这四个插槽中,以避免它们丢失/被覆盖。同样,您可以使用sp将这些寄存器写入堆栈。
如果您的函数具有局部变量,并且无法将所有变量都保留在寄存器中(例如,当它调用其他函数时无法保留a0到a3时),则必须使用on-stack空间来存储这些局部变量,这再次需要使用sp。
例如,如果您有以下内容:
int tst5(int x1, int x2, int x3, int x4, int x5)
{
return x1 + x2 + x3 + x4 + x5;
}
它的反汇编大概会是这样:
tst5:
lw $2,16($sp) # r2 = x5
addu $4,$4,$5 # x1 += x2
addu $4,$4,$6 # x1 += x3
addu $4,$4,$7 # x1 += x4
j $31 # return
addu $2,$4,$2 # r2 += x1
注意,sp
用于访问x5
。
如果您有以下代码:
int binary(int a, int b)
{
return a + b;
}
void stk(void)
{
binary(binary(binary(1, 2), binary(3, 4)), binary(binary(5, 6), binary(7, 8)));
}
这是编译后的反汇编结果:
binary:
j $31 # return
addu $2,$4,$5 # r2 = a + b
stk:
subu $sp,$sp,32 # allocate space for local vars & 4 slots
li $4,0x00000001 # 1
li $5,0x00000002 # 2
sw $31,24($sp) # store return address on stack
sw $17,20($sp) # preserve r17 on stack
jal binary # call binary(1,2)
sw $16,16($sp) # preserve r16 on stack
li $4,0x00000003 # 3
li $5,0x00000004 # 4
jal binary # call binary(3,4)
move $16,$2 # r16 = binary(1,2)
move $4,$16 # r4 = binary(1,2)
jal binary # call binary(binary(1,2), binary(3,4))
move $5,$2 # r5 = binary(3,4)
li $4,0x00000005 # 5
li $5,0x00000006 # 6
jal binary # call binary(5,6)
move $17,$2 # r17 = binary(binary(1,2), binary(3,4))
li $4,0x00000007 # 7
li $5,0x00000008 # 8
jal binary # call binary(7,8)
move $16,$2 # r16 = binary(5,6)
move $4,$16 # r4 = binary(5,6)
jal binary # call binary(binary(5,6), binary(7,8))
move $5,$2 # r5 = binary(7,8)
move $4,$17 # r4 = binary(binary(1,2), binary(3,4))
jal binary # call binary(binary(binary(1,2), binary(3,4)), binary(binary(5,6), binary(7,8)))
move $5,$2 # r5 = binary(binary(5,6), binary(7,8))
lw $31,24($sp) # restore return address from stack
lw $17,20($sp) # restore r17 from stack
lw $16,16($sp) # restore r16 from stack
addu $sp,$sp,32 # remove local vars and 4 slots
j $31 # return
nop
我希望我已经在不出错的情况下对代码进行了注释。
请注意,编译器选择在函数中使用r16
和r17
,但将它们保留在堆栈上。由于该函数调用另一个函数,因此还需要将其返回地址保存在堆栈中,而不是只将其保留在r31
中。
PS 请记住,在MIPS上,所有分支/跳转指令在实际传输控制到新位置之前有效地执行紧随其后的指令。这可能会让人感到困惑。