RISC-V寄存器功能表(简化版)
寄存器编号 | 用途 |
---|---|
x0 | 硬连接为0,表示常数0 |
x1 | 经常称作ra,存储返回地址 |
x2 | 经常称作sp,称作栈指针,存储栈顶位置 |
x5~x7 | 存储临时变量 |
x10 | 函数参数/返回值 |
x12~x17 | 函数参数 |
X18~x27 | 可以灵活使用的寄存器 |
所要操作的数据均存储于寄存器中,可以直接使用
Example:
f = (g + h) - (m + n)
其中,f,g,h,m,n逐个存储于x18~x22中
add x5, x19, x20
add x6, x21, x22
sub x18, x5, x6
注意课本中数组的单位元素大小为8Byte,而RISC-V的寻址单位是Byte
事实上,数组索引左移的位数取决于数组元素的类型,以下是常见的数组类型以及其所需要左移的位数:
数组类型 | 左移位数 |
---|---|
char | 0 |
short | 1 |
int | 2 |
double | 3 |
long long int | 3 |
Example:
A[12] = A[i] + A[0]
其中,A[]地址存储在x22,i存储在x23
// 第一种数组寻址:直接写出偏移量
ld x17, x22
// 第二种数组寻址:将索引左移3位(乘以8)后与基址相加得到偏移地址
slli x23, 3
add x24, x22, x23
ld x18, 0(x24)
add x19, x17, x18
sd x19, 96(x22)
堆栈存储在内存中,其中堆自底向上生长,栈自顶向下生长
在使用栈的过程中,注意使用x2(栈指针寄存器)存储栈顶地址,使用x8(帧指针寄存器)存储栈底地址
内存地址 | 存储数据 |
---|---|
0x7fffffc | (sp的初始位置) |
0x7fffff4 | x5 |
0x7ffffec | x1(栈顶,该地址存储在x2) |
代码示例等详情参照函数与递归部分
在严格的分支结构中,应当先写分支跳转指令,然后写分支中不发生跳转的分支,最后再写分支跳转的分支,这样如果分支发生则将跳过分支不跳转的分支,可以避免先写的分支执行后由于顺序执行特点继续执行后写的分支。
分支语句中除了数据运算类指令和数据传送类指令外,还要用到流程控制类指令。
Example:
if(i > j) i += j;
else i -= j;
其中i,j存储在x17,x18中
blt x18, x17, Label
// 先写分支不发生跳转的分支
sub x17, x17, x18
// 后写分支跳转的分支
Label:add x17, x17, x18
一般选择“往回”(程序总体前进方向的反方向)跳转,利用程序顺序执行的特点自然地构成循环。
Example:
long long int i = 0;
for(; i < n; i++) {
if(nums[i] == k) break;
}
其中i,n,k存放在x5,x6,x7
la x18, nums // 取数组首地址
addi x5, x0, 0
Label:bge x5, x6, Exit
// i++
addi x5, x5, 1
// 将 nums[i] 存入x21
slli x19, x5, 3
add x20, x18, x19
ld x21, 0(x20)
beq x21, x7, Exit
// 无条件跳转,实现循环
jal x0, Label
Exit: // 后续的指令
在调用函数时通常使用以下跳转指令实现调用功能
注意程序将返回地址存入x1(也称ra寄存器中)当需要返回主函数时就通过jalr指令跳转回主调用程序
//主程序
jal x1, FunctionNameLabel
// 主程序中后续的指令
FunctionNameLabel:
// 先使用栈将函数所使用的值传递参数保存起来
addi sp,sp,-8
sd x12, 0(sp)
// 函数体
// 函数结束,返回结果,恢复栈。并将PC的值调整回主函数
// 示例:
ld x12,0(sp)
addi sp, sp, 8
jalr x0, 0(x1)
将如下的 C 代码中的函数 f 翻译转换成 RISC-V 汇编代码
要求:分别用 x12-x15 传递入口参数 a-d,并转移至 x18-x21;用 x10 传递出口参数。
int funct(int a, int b);
int f(int a, int b, int c, int d)
{ return funct(funct(a,b),c+d); }
f: addi sp, sp, -32
// 用栈保存现场
sw x18, 0(sp)
sw x19, 8(sp)
sw x20, 16(sp)
sw x21, 24(sp)
// 转移赋值,接收输入数值
add x18, x0, x12
add x19, x0, x13
add x20, x0, x14
add x21, x0, x15
addi sp, sp, -8
sw x1, 0(sp)
jal x1, funct
add x12, x0, x10
add x5, x0, x20
add x5, x5, x21
add x13, x0, x5
jal x1, funct
lw x1, 0(sp)
addi sp, sp, 8
lw x18, 0(sp)
lw x19, 8(sp)
lw x20, 16(sp)
lw x21, 24(sp)
addi sp, sp, 32
jalr x0, 0(x1)
将下面的C代码翻译转换成RISC-V汇编代码,假设t存储在x9寄存器中,a[0]存储在x28寄存器中:
int abc(int a[]) {
int t=0;
if (a[0]==0) return t;
t=abc(a+1);
if (a[0]>0) t=t+1;
return t;
}
代码分析: 注意函数中使用了递归,递归调用的参数使用了a+1,而a正是数组的基址,故此处a+1并不是指将数组基址加上一个Byte再作为参数输入,而是表示将a[1]的地址作为基址输入给被调用的函数
abc:
addi sp,sp,-8 // 开辟一个双字空间
sd x9, 0(sp) // 后续会对t赋初值0,所以要先将t保存下来
add x9,x0,x0
ld x12, 0(x28)
beq x12,x0,ret
addi sp,sp,-8
sd x12,0(sp) // 因为后续还要用a[0]的值,所以要用栈存储起来
addi sp,sp,-8
sd x1,0(sp) // 将返回地址保存起来
addi x28,x28,4
jal x1,abc // 调用递归函数
addi x9,x10,0 // 将返回值传给变量t
ld x1,0(sp)
addi sp,sp,8
ld x12,0(sp)
addi sp,sp,8
blt x12,x0,ret
addi x9,x9,1
ret:
add x10,x9,x0 // 将局部变量t存入返回值寄存器中
ld x9, 0(sp) // 恢复t的值
addi sp,sp,8
jalr x0,0(x1)