MIPS 调用约定要求前四个函数参数位于寄存器a0 到a3 中,其余的(如果还有更多)位于堆栈中。更重要的是,它还要求函数调用者在堆栈上为前四个参数分配四个槽,尽管这些参数是在寄存器中传递的。
所以,如果你想访问参数五(以及更多参数),你需要使用sp。如果该函数依次调用其他函数并在调用后使用其参数,则需要将a0 到a3 存储在堆栈上的这四个槽中,以避免它们丢失/覆盖。同样,您使用sp 将这些寄存器写入堆栈。
如果你的函数有局部变量并且不能将它们全部保存在寄存器中(比如当它调用其他函数时不能保存a0到a3),它必须使用栈上这些局部变量的空间,这再次需要使用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; 4 slots are skipped
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 上的所有分支/跳转指令都会在实际将控制权转移到新位置之前有效地执行紧随其后的指令。这可能会令人困惑。