寄存器名 | ABI名(编程用名) | 用途约定 | 谁负责在函数调用过程中维护这些寄存器 |
---|---|---|---|
x0 | zero | 读取时总为 0, 写入时不起任何效果 | N/A |
x1 | ra | 存放函数返回值(return address) | Caller |
x2 | sp | 存放栈指针(stack pointer) | Callee |
x5~x7, x28~x31 | t0~t2, t3~t6 | 临时(temporaries)寄存器,Callee 可能会使用这些寄存器,所以Callee 不保证这些寄存器中的值在函数调用过程中保持不变,这意味着对于 Caller 来说,如果需要的话,Caller 需要自己在调用 Callee 之前保存临时寄存器中的值。 | Caller |
x8, x9, x18~x27 | s0, s1, s2~s11 | 保存(saved)寄存器,Callee 需要保证这些寄存器的值在函数返回后仍然维持函数调用之前的原值,所以一旦 Callee 在自己的函数中会用到这些寄存器则需要在栈中备份并在退出函数时进行恢复。 | Callee |
x10 , x11 | a0 , a1 | 参数(argument)寄存器,用于在函数调用过程中保存第一个和第二个参数,以及在函数返回时传递返回值。 | Caller |
x12 ~ x17 | a2 ~ a7 | 参数(argument)寄存器,如果函数调用时需要传递更多的参数,则可以用这些寄存器,但注意用于传递参数的寄存器最多只有 8 个(a0 ~ a7),如果还有更多的参数则要利用栈。 | Caller |
void _start()
{
// calling nested routine
aa_bb(3, 4);
}
int aa_bb(int a, int b)
{
return square(a) + square(b);
}
int square(int num)
{
return num * num;
}
_start:
la sp, stack_end # 首先初始化堆栈指针
li a0, 3 # 第一个参数放入寄存器a0
li a0, 4 # 第二个参数放入寄存器a1
call aa_bb # Caller中没用到什么临时寄存器,所以不需要保存,直接跳转; call伪指令会将跳转地址放到寄存器ra中
stop:
j stop # 死循环结束代码
aa_bb:
addi sp, sp, -16 # 栈指针初始化时指在栈去末尾,要存放4个word的数据,所以减16
sw s0, 0(sp) # 考虑到后面会用到保存寄存器,s0~s2,需要Callee进行保存
sw s1, 4(sp)
sw s2, 8(sp)
sw ra, 12(sp) # ra中目前存放的是_start函数下一条指令的地址,由于在aa_bb函数中还要调用别的函数,会覆盖掉ra寄存器的值,因此也要存下来
mv s0, a0 # a0寄存器的值挪到s0中
mv s1, a1 # a1寄存器的值挪到s1中
li s2, 0 # 将最终结果放在s2中,因此先将s2清零
mv a0, s0 # 准备调用square函数啦,传参放入寄存器a0; 作为一个Caller没有需要保存的临时寄存器
jal square # 跳转到square函数中执行,返回地址会放在ra寄存器,覆盖了原来的ra
add s2, s2, a0 # 计算的返回参数在a0中,加到s2上
mv a1, s1 # 与上面计算同理
jal square
add s2, s2, a0
mv a0, s2 # 最终的返回参数防止到a0寄存器
lw s0, 0(sp) # 从栈区中恢复各个保存寄存器的值
lw s1, 4(sp)
lw s2, 8(sp)
lw ra, 12(sp) # ra寄存器的值恢复回来
addi sp, sp, 16 # 释放栈空间,栈指针依旧指向进入函数前的位置
ret
square:
addi sp, sp, -8
sw s0, 0(sp)
sw s1, 4(sp)
mv s0, a0
mul s1, s0, s0
mv a0, s1
lw s0, 0(sp)
lw s1, 4(sp)
addi sp, sp, 8
ret
stack_start:
.rept 10 # 定义10个word大小的栈空间
.word 0
.endr
stack_end:
定期以通俗易懂的方式分享嵌入式知识,关注公众号,加星标,每天进步一点点。
声明:
本号原创、转载的文章、图片等版权归原作者所有,如有侵权,请联系删除。