RISC-V汇编程序设计-计组

汇编程序

一、赋值语句

(一)寄存器操作数赋值

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

(二)内存操作数赋值

1. 数组

注意课本中数组的单位元素大小为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)
2.堆栈

堆栈存储在内存中,其中堆自底向上生长,栈自顶向下生长
在使用栈的过程中,注意使用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)

Example One:

将如下的 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)

Example Two:

将下面的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)

你可能感兴趣的:(risc-v,笔记,汇编)