除了一些 CPU 关心的栈,还有我们关心的函数参数(例如这里的 a 和 b),RISC-V 提供了 a0-a7 总计 8 个寄存器储存函数的参数(其中 a0-a1 也被用作储存函数的返回值)
当然,假如函数有超过 8 个参数,这时就不可避免的要使用内存了
接受到到参数需要储存在栈内存中,这是因为寄存器是公用的(递归函数也可以操作寄存器)
sd a0, -40(s0) sd a1, -48(s0)
分支与循环
程序三大结构分别是顺序,分支和循环
在 RISC-V 中,我们通过比较指令和标签组合实现分支结构
比较指令就是常说的 B 型指令
ble a0, a1, .L1
beq 相等
bne 不相等
blt 小于
bgt 大于
ble 小于等于
bge 大于等于
标签就是对特定语句的标记,用点开头
.L1: # ...
因此,这样的指令对应了这样的 C 代码
if(a0 == a1) goto L1; // ... L1: // ...
通过向前回溯,我们就可以实现 for - while - goto - riscv 之间的改写
int n = 10; for(int i = 0; i < n; ++i){ // code } /**********/ int i, n; i = 0; n = 10; while(i < n){ // code ++i; } /**********/ int i, n; i = 0; n = 10; L1: { // code ++i; } if(i < n) goto L1;
fn: # ... ld a0, -32(s0) # load n into a0 li a1, 0# use a1 to store i .L1: # code addi a1, a1, 1 blt a1, a0, .L1
factor: addi sp, sp, -32 sd ra, 24(sp) sd s0, 16(sp) addi s0, sp, 32 sd a0, -24(s0) # a0 is callee’s parameter n mv a5, a0 # mv a0 to m5 bne a5, zero, .L1 # if n != 0, then go to .L1 j .L2 # else then go to .L2 .L1: # n != 0, recursively call self addi a5, a5, -1# n - 1 mv a0, a5 # IMPORTANT # the instruction puts parameter n - 1 in a0, # so that when calling factor recursively, # it can get its parameter from its caller call factor mv a4, a0 # after factor called, # a0 stores the return value ld a5, -24(s0) mul a5, a5, a4 j .L3 .L2: # recursive boundary li a5, 1 j .L3 .L3: # do return operations mv a0, a5 # use a0 to exchange return value ld ra, 24(sp) ld s0, 16(sp) addi sp, sp, 32 jr ra
sp:每次调用 factor,sp 会向下移动 32 字节,调用结束后回上 32 字节,这样的操作使得每次递归操作可以获得一份独立的栈帧(在这里主要是保存自己的 n 以及返回地址 ra)
a0:用于传递 n 和 n - 1,每次递归调用时,我们将递归转移函数计算出的 n - 1 移动到 a0 中,恰好 a0 本就是用于保存参数的寄存器,所以顺理成章的完成了递归调用
a4:用于传递返回值
实战
冒泡排序
.text .globl bubble_sort
bubble_sort: addi sp, sp, -128 sd s0, 120(sp) # s0 store at 8(sp) addi s0, sp, 128# s0 mean stack top sd a0, -112(s0) # arr sd a1, -102(s0) # len sd zero, -96(s0) # i sd zero, -88(s0) # j addi a1, a1, -2# change a1 to store len - 2 sd a1, -80(s0) # len - 2 sd a1, -72(s0) # len - 2 - i j .L2 # begin from loop i .L5: # the loop body ld a0, -88(s0) # load j to a0 ld a1, -112(s0) # load arr to a1 addi a6, zero, 8 mul a0, a0, a6 # cal shift value add a1, a1, a0 ld a3, 0(a1) # load value of arr[j] to a3 ld a4, 8(a1) # load value of arr[j + 1] to a4 # sd zero, 0(a1) ## ble a3, a4, .L4 # if arr[j] <= arr[j + 1], then skip the swap operations sd a4, 0(a1) sd a3, 8(a1) # do swap .L4: ld a0, -88(s0) # load j to a0 addi a0, a0, 1# add j with 1 sd a0, -88(s0) # store j to j + 1 .L3: # compare of loop j ld a1, -80(s0) # load len - 2 to a1 ld a0, -96(s0) # load i to a0 addi a6, zero, -1 mul a0, a0, a6 # i times -1 to -i store in a0 add a1, a1, a0 # cal a1 = len - 2 - i ld a2, -88(s0) # load j to a2 ble a2, a1, .L5 # if j <= len - 2 - i, them begin loop i ld a0, -96(s0) # load i to a0 addi a0, a0, 1# add i with 1 sd a0, -96(s0) # store i to i + 1 .L2: # compare of loop i ld a0, -96(s0) # load i to a0 ld a1, -80(s0) # load len - 2 to a1 sd zero, -88(s0) # initial j with 0 ble a0, a1, .L3 # if i <= len - 2, then begin loop j ld s0, 120(sp) # return s0
没啥好说的,如果看不懂可以试着依次改写出下面的 C 代码
// standard C voidbubble_sort(longlong* arr, longlong len){ longlong i, j, tmp; i = 0; while(i < len - 1){ j = 0; while(j < len - 1 - i){ if(arr[j] > arr[j + 1]){ tmp = a[j]; a[j] = a[j + 1]; a[j + 1] = tmp; } ++j; } ++i; } } /**********/ // C with goto voidbubble_sort(longlong* arr, longlong len){ longlong i, j, tmp; i = 0; L1: { j = 0; L2: { if(arr[j] > arr[j + 1]) goto L2_1; elsegoto L2_2; L2_1: { tmp = a[j]; a[j] = a[j + 1]; a[j + 1] = tmp; } L2_2: { ; } ++j; } if(j < len - i -1) goto L2; ++i; } if(i < len - 1) goto L1; }
递归计算斐波那契
.text .globl fibonacci
fibonacci: addi sp, sp, -64 sd ra, 56(sp) sd s0, 48(sp) addi s0, sp, 64 sd a0, -56(s0) # stone n(recuisive variable) li a1, 1# a1 = 1 ble a5, a1, .L2 # if n <= 2, then return 1 j .L1 # if n > 2, then do add f(n - 1) + f(n - 2)
.L1: ld a5, -56(s0) # load n to a5 addi a5, a5, -1# n - 1 sd a5, -48(s0) # store n - 1 mv a0, a5 # trans the parameter call fibonacci mv a3, a0 # store f(n - 1) sd a3, -36(s0) # store f(n - 1) ld a5, -56(s0) # load n addi a5, a5, -2# n - 2 mv a0, a5 # trans the parameter call fibonacci mv a4, a0 # store f(n - 2) ld a3, -36(s0) # load f(n - 1) add a5, a3, a4 # do add j .L3
.L2: li a5, 1 j .L3
.L3: # return value operations mv a0, a5 ld ra, 56(sp) ld s0, 48(sp) addi sp, sp, 64# move stack top to next 64 jr ra