这些指令按照功能可以分为如下几类:
整数运算指令:算术、逻辑、比较等基础运算功能。
分支转移指令:实现条件转移、无条件转移操作
加载存储指令:实现字节、半字(half word)、字(word)、双字(RV64I)的加载,存储操作,采用的都是寄存器相对寻址方式
RISC-V有六种基本指令格式:
R 类型指令,用于寄存器-寄存器操作
I 型指令,用于短立即数和访存 load 操作
S 型指令,用于访存 store 操作
B 类型指令,用于条件跳转操作
U 型指令,用于长立即数
J 型指令,用于无条件跳转
示例代码
int main() {
long long a=0xffffffffffe54b31 , b=0x0000000000004d0e;
int shift=0x0000000000000004 ;
long long res1, res2, res3;
res1 = a >> shift; //FFFF FFFF FFFE 54B3
res2 = (unsigned long long) a >> shift; //FFF FFFF FFFE 54B3
res3 = b << shift;//4 D0E0
return 0;
}
汇编指令
# (1)a, b, shift 分别存放在寄存器 x28, x29, x30 中
# (2)res1, res2, res3 存放在寄存器 x5, x6, x7 中
sra x5, x28, x30 #算术右移,高位补原来高位
srl x6, x28, x30 #逻辑右移, 高位补零
sll x7, x29, x30 #逻辑左移,低位补零
int abs(int value) {
int res;
if (value >= 0)
res = value;
else
res = -value;
return res;
}
对应汇编语句(借助符号)
# value 存储在寄存器 x10 中,res 存储在寄存器 x6 中
blt x10, x0, negative
positive:
addi x6, x10, 0
jal x0, finish
negative:
sub x6, x0, x10
finish:
# ...
nagative 标号表示 sub x6, x0, x10 指令的地址,sub x6, x0, x10 指令为 blt x10, x0, negative 之后的第 3 条指令,由于一条指令的长度为 4 字节,故目标地址偏移量为 3∗4=12,因此,上述 RISC-V 汇编指令段的无标号表达形式为:
blt x10, x0, 12
addi x6, x10, 0
jal x0, 8
sub x6, x0, x10
# ...
int main(){
long long a, b, c;
long long res;
//**************************
if (a + b > c && a < c)
res = c - a;
else
res = c & a;
//***************************
return 0;
}
仅标记部分对应汇编语句(借助符号)
# a, b, c 分别存放在寄存器 x28, x29, x30 中
# res 存放在寄存器 x6 中
add x7, x28, x29
slt x1, x30, x7 #a+b>c
slt x2, x28, x30 #a<c
and x1, x1, x2 #(a + b > c && a < c)是否为0
beq x0, x1, negative #if (0) ,跳转到negative
positive:
sub x6, x30, x28
jal x0, 8
negative:
and x6, x30, x28
finish:
# ...
无标号表达形式
add x7, x28, x29
slt x1, x30, x7
slt x2, x28, x30
and x1, x1, x2
beq x0, x1, 12
sub x6, x30, x28
jal x0, 8
and x6, x30, x28
# ...
使用 a、b 两个变量作为斐波那契数列的前两个元素,经过 10 次计算后,将斐波那契数列的第 22 个元素赋值给 res
#include <stdio.h>
int main(){
long long a, b;
long long res;
int i;
//
for(i = 0; i < 10; ++i)
{
a = a + b;
b = b + a;
}
res = b;
/
return 0;
}
仅标记部分对应汇编语句(借助符号)
# (1)a, b, i 分别存放在寄存器 x28, x29, x30 中
# (2)res 存放在寄存器 x6 中
star:
addi x7, x30, -10
beq x0, x7, oout
addi x30, x30, 1
inn:
add x28, x28, x29
add x29, x29, x28
jal x0, star
oout:
add x6, x29, x0
无标号表达形式
addi x7, x30, -10
beq x0, x7, 20
addi x30, x30, 1
add x28, x28, x29
add x29, x29, x28
jal x0, -20
add x6, x29, x0
while (save[i]==k)
i+=1;
#假设i和k对应于x22和x24,数组的基址保存在x25
Loop:
slli x10, x22, 3
add x10, x10, x25 //x10 = address of save[i]
ld x9, 0(x10) //reg x9=save[i]
bne x9, x24, Exit //循环判断:是否退出
addi x22, x22, 1
beq x0, x0, Loop
Exit:
#......
寄存器 x0 被硬编码为常量 0
寄存器 x1 用于保存返回地址,即子程序调用完成后从被调用方返回到调用方
寄存器 x2 为栈指针,存储当前栈顶的内存地址
寄存器 x5-x7、x28-x31 为临时寄存器,由调用方按需保存
寄存器 x8-x9、x18-x27 为保存寄存器,由被调用方按需保存和恢复
寄存器 x10-x11 用于传递函数返回值
寄存器 x10-x17 用于传递函数参数
函数 P 中调用函数 Q,函数 Q 接收一个整型变量作为参数,将该参数 “加 10”后作为返回结果,该过程需要完成下列步骤:
函数 P 将变量 val 作为参数传递给函数 Q
函数 P 将程序执行控制流转给函数 Q (转到函数 Q 执行)
函数 Q 获取用于存储局部变量等数据的内存空间(例如用于存放变量 tmp 的空间)
函数 Q 获取由函数 P 传递的参数并执行函数功能
函数 Q 将返回结果传递回函数 P
函数 Q 将程序执行控制流转回函数 P(返回到函数 P 中调用 Q 的位置)
函数 P 获取由函数 Q 传递的返回值,继续完成函数 P 其他功能
long long callee(long long val_a, long long val_b){
long long val_a_tmp = val_a & 0xffff0000;
long long val_b_tmp = val_b & 0xffff;
return val_a_tmp + val_b_tmp;
}
//
int main(){
long long a, b;
long long res;
res = callee(a, b);
return 0;
}
仅标记部分对应汇编语句 (测评通过,但有点冗余,后续会简化)
callee:
addi sp,sp,-48
sw s0,44(sp)
addi s0,sp,48
sw a0,-40(s0)
sw a1,-48(s0)
lw a4,-40(s0)
li a5,65535
slli a5, a5, 16
and a5,a4,a5
sw a5,-24(s0)
lw a4,-48(s0)
li a5,65536
addi a5,a5,-1
and a5,a4,a5
sw a5,-32(s0)
lw a5,-24(s0)
lw a1,-32(s0)
add a3,a5,a1
mv a0,a3
lw s0,44(sp)
addi sp,sp,48
jr ra
long long callee(long long val_a, long long val_b){
long long val_a_tmp = val_a & 0xffff0000;
long long val_b_tmp = val_b & 0xffff;
return val_a_tmp + val_b_tmp;
}
//
long long caller(long long val_a, long long val_b){
return callee(val_a, val_b);
}
///
int main(){
long long a, b;
long long res;
res = caller(a, b);
return 0;
}
仅标记部分对应汇编语句
caller:
addi sp, sp, -8
sd ra, 0(sp)
jal ra, callee
ld ra, 0(sp)
addi sp, sp, 8
jr ra
long long callee(long long val_a, long long val_b){
long long val_a_tmp = val_a & 0xffff0000;
long long val_b_tmp = val_b & 0xffff;
return val_a_tmp + val_b_tmp;
}
int main(){
long long a, b;
long long res;
res = callee(a, b);
return 0;
}
long long callee(long long val_a, long long val_b){
long long val_a_tmp = val_a & 0xffff0000;
long long val_b_tmp = val_b & 0xffff;
return val_a_tmp + val_b_tmp;
}
int main(){
long long a, b;
long long res;
res = callee(a, b);
return 0;
}
首先要理解 栈帧,我也难讲呜呜,,,
#include <stdio.h>
int n = 10;
int fibonacci_array[100];
int main() {
fibonacci_array[0] = 1;
fibonacci_array[1] = 1;
int i;
for(i = 2; i <= n; ++ i) {
fibonacci_array[i] = fibonacci_array[i-2] + fibonacci_array[i-1];
}
printf("%d", fibonacci_array[n]);
return 0;
}
汇编语句
.data
n: .word 10
fibonacci_array: .zero 400
.text
main:
addi x5, x0, 1
la x6, fibonacci_array
sw x5, 0(x6)
sw x5, 4(x6)
la x7, n
lw x7, 0(x7)
addi x28, x0, 2
for_loop:
blt x7, x28, end_loop
# load fibonacci_array[i-2] to x29
addi x29, x28, -2
slli x29, x29, 2
add x29, x6, x29
lw x29, 0(x29)
# load fibonacci_array[i-1] to x30
addi x30, x28, -1
slli x30, x30, 2
add x30, x6, x30
lw x30, 0(x30)
# add x29 (fibonacci_array[i-2]) and x30 (fibonacci_array[i-1]) to x31
add x31, x29, x30
# store x31 to fibonacci_array[i]
addi x29, x28, 0
slli x29, x29, 2
add x29, x6, x29
sw x31, 0(x29)
# i = i + 1
addi x28, x28, 1
j for_loop
end_loop:
addi x29, x7, 0
slli x29, x29, 2
add x29, x6, x29
lw x29, 0(x29)
addi a7, x0, 1
addi a0, x29, 0
ecall
addi a7, x0, 10
ecall
项目
全局变量的定义通过汇编指示语句实现,即在 .data 段定义初始化的全局变量,在该例子中,.data 段内定义了整型变量 n,整型类型为 4 字节,对应 RISC-V 中的一个“字”,因此其定义语句为 n: .word 10,该指示语句由三部分组成,即标签、数据类型、变量值,分别对应变量名称、变量大小、变量数值;.data 段还定义了整型数组 fibonacci_array,该数组类型为整型、长度为 100,故其大小为 400 字节,其定义语句为 fibonacci_array: .zero 400,该指示语句表示为 fibonacci_array 初始化 400 字节的内存空间,每个字节数值设置为 0,即定义长度为 100 的整型数组,并将数组元素值初始化为 0。
变量的赋值通过组合一系列指令实现,该例子中,局部变量 i 直接存储在 x28 寄存器中,因此 i 的赋值可直接通过运算类指令 addi 实现;实际情况下的变量通常存储在内存区域中,则此时变量的运算和赋值需要通过访存指令和运算类指令实现,如例子中对 fibonacci_array[0]=1 的实现可分为如下步骤:
#include <stdio.h>
long long int n = VALUE;
long long int fact (long long int n) {
if (n < 1) return 1;
else return n * fact(n - 1);
}
int main() {
long long int res = fact(n);
printf("%lld", res);
return 0;
}
汇编语句
.data
n: .dword VALUE
.text
main:
addi sp, sp, -32
sd ra, 24(sp)
sd s0, 16(sp)
addi s0, sp, 32
la a0, n
lw a0,0(a0)
call fact
addi a7, x0, 1
ecall
addi a7, x0, 10
ecall
fact:
addi sp sp -32
sd ra 24(sp)
sd s0 16(sp)
addi s0 sp 32
sd a0 -24(s0)
ld x15 -24(s0)
blt x0 x15 L1
addi x15 x0 1
jal x0 L2
L1:
ld x15 -24(s0)
addi x15 x15 -1
addi a0 x15 0
jal ra fact
addi x14 a0 0
ld x15 -24(s0)
mul x15 x14 x15
L2:
addi a0 x15 0
ld ra 24(sp)
ld x8 16(sp)
addi sp sp 32
jalr x0 ra 0
没完全理解,后面会加进理解后持续更新