《计算系统基础》DLX 寄存器的分配约定及简介
★R0寄存器保存的是数值0,且该寄存器是不可被加载为其他值的。
基于以上性质,在BEQZ指令中,如果你的源寄存器选择的是R0,就代表此寄存器保存的值必定为0,那么就形成了无条件分支指令。
★R2、R3是作为函数的返回值使用。在栈中,当一个函数运行完毕,会把对应的返回值加载进R2或者是R3
汇编指令为:
ADDI R2 , Rx(你需要返回的值所在的寄存器) ,#0
接着,程序跳转到main函数。此时,再把返回值传给你期望传入的寄存器 ,汇编指令为
ADDI Ry(接收返回值的寄存器) , R2, #0
★R8-R15,R24,R25是用来保存临时值的。什么是临时值呢?
比如
void main()
{
int object;
object = 4;
int* ptr;
ptr = &object;
*ptr = *ptr + 1;
}
假设object位于栈中,且偏移量为-3,那么object=4这个操作就需要用到临时寄存器
ADDI R8 R0 #4
SW -12(R30) R8
上述汇编语言实现了object=4
假设ptr位于R16中,那么ptr=&object是如何实现的呢?
SUBI R16 R30 #12
那么*ptr=*ptr+1又是如何实现的呢?
实际上,*ptr的值就是object 。但是,在汇编语言中,二者的操作方式是不一样的
先把 *ptr移动到临时寄存器
SW R8 0(R16)
接着向临时寄存器内载入 *ptr+1的值
ADDI R8 R8 #1
最后将此值赋给 *ptr
SW 0(R16) R8
上述不仅介绍了临时寄存器的用处,同时也对指针操作的底层实现进行了简单的介绍
★R4——R7用于参数的保存。这个参数指传入函数的参数。比如,
#include <stdio.h>
void fun(int a,int b,int c,int d,int e)
{
}
void main()
{
int x1 = 0;
int x2 = 0;
int x3 = 0;
int x4 = 0;
int x5 = 0;
int x6 = 0;
int x7 = 0;
int x8 = 0;
int x9 = 0;
fun(x1,x2,x3,x4,x5);
}
这段代码没有意义,但可以借此研究函数传参的底层实现。
在main函数中,int xn=0;这个举动实际上代表着为xn分配空间。在上表我们可以看到,R16-R23保存的是局部变量。因此,x1-x8的值被存入R16-R23
ADDI R(16-23),R0,b(赋予的值)
那么x9呢?没有为它准备的寄存器了。这时就要用到存储器。存储器是以栈的方式实现的,因此
ADDI Rx(保存临时值的寄存器), R0, b(赋予的值)
SUBI R29 , R29, #4
SW 0(R29), Rx
这样就把 局部变量存入栈中
而调用fun函数时传入的参数需要用R4-R7保存
ADDI R(4-7), R(16-23), #0
R4-R7只有4个寄存器,而参数有五个,多出来的一个怎么办呢?
很简单,压入栈中
SUBI R29 R29 #1
SW 0(R29) Rx(多出来的一个参数所在的临时寄存器)
★ R26、R27,操作系统,用于保存EPC的值,EPC是出现 异常时的指令地址,比如在中断服务例程中就使用到了EPC保存返回地址。在异常处理结束,使用JR
R26/R27返回
★R28是全局变量的起始地址
★R29是栈指针,始终指向栈顶
压栈:SUBI R29 R29 #4
SW 0(R29) Rx
出栈
LW Rx 0(R29)
ADDI R29 R29 #4
★ R30是框架指针(帧指针、动态链接),始终位于参数之上。使用它访问参数及局部变量会十分方便。如果不需要使用存储器存储的局部变量,就不用设置框架指针。其一般位于活动记录的底部
★ R31用来保存返回地址