RISC-V GCC工具链与普通的GCC工具链基本相同,用户可以遵照开源的riscv-gnu-toolchain项目(请在Github中搜索riscv-gnu-toolchain)中的说明自行生成全套的GCC工具链。
由于GCC工具链支持各种不同的处理器架构,因此不同处理器架构的GCC工具链会有不同的命名。遵循GCC工具链的命名规则,当前RISC-V GCC工具链有如下几个版本:
1、以“riscv64-unknown-linux-gnu-”为前缀的版本,譬如riscv64-unknown-linux-gnu-gcc、riscv64-unknown-linux-gnu-gdb、riscv64-unknown-linux-gnu-ar等。
2、以“riscv64-unknown-elf-”为前缀的版本,则表示该版本为非Linux(Non-linux)版本的工具链。注意:
3、以“riscv-none-embed-”为前缀的版本,则表示是最新为裸机(bare-metal)嵌入式系统而生成的交叉编译工具链,所谓裸机(bare-metal)是嵌入式领域的一个常见形态,表示不运行操作系统的系统。该版本使用新版本的newlib作为C运行库,并且支持newlib-nano,能够为嵌入式系统生成更加优化的代码体积(Code Size)。开源的蜂鸟E203 MCU系统是典型的嵌入式系统,因此将使用“riscv-none-embed-”为前缀的版本作为RISC-V GCC交叉工具链。注意:
对于riscv-none-embed版本的工具链而言,为了方便用户直接使用预编译好的工具链,Eclipse开源社区会定期更新发布最新版本的预编译好的RISC-V嵌入式GCC工具链,包括Windows版本和Linux版本。请在谷歌中搜索“releases gnu-mcu-eclipse/riscv-none-gcc”进入网页下载Windows版本或者Linux版本,如下图所示。对于Linux和Windows版本均只需在相应的操作系统中解压即可使用。
由于RISC-V的指令集是模块化的指令集,因此在为目标RISC-V平台进行交叉编译之时,需要通过选项指定目标RISC-V平台所支持的模块化指令集组合,该选项为(-march=),有效的选项值如下:
注意:在上述选项中rv32表示目标平台是32位架构,rv64表示目标平台是64位架构,其他i/m/a/f/d/c/g分别代表了RISC-V模块化指令子集的字母简称。请参见RISC-V中文书籍《手把手教你设计CPU——RISC-V处理器篇》中附录A.1节中关于RISC-V架构指令集的详细中文介绍。
本节后文会介绍(-march=)选项使用的具体实例。
由于RISC-V的指令集是模块化的指令集,因此在为目标RISC-V平台进行交叉编译之时,需要通过选项指定嵌入式RISC-V目标平台所支持的ABI函数调用规则(有关ABI函数调用规则的相关信息请参见RISC-V中文书籍《手把手教你设计CPU——RISC-V处理器篇》中附录A的图A-1)。RISC-V定义了两种整数的ABI调用规则和三种浮点ABI调用规则,通过选项(-abi=)指明,有效的选项值如下:
在上述选项中两种前缀(ilp32和lp64)表示的含义如下:
上述选项中的三种后缀类型(无后缀、后缀f、后缀d)表示的含义如下:
本节后文会介绍(-mabi=)选项使用的具体实例。
为了便于读者更加形象地理解(-march=)和(-mabi=)选项的具体含义,下面以一个实例加以介绍。
假设有一段C语言函数代码,如下所示:
//这是一个名为dmul的函数,其有两个参数,为double类型的双精度浮点数
double dmul(double a, double b) {
return b * a;
}
$ riscv64-unknown-elf-gcc test.c -march=rv64imafdc -mabi=lp64d -o- -S -O3
//所生成的汇编代码如下,从中可以看出,浮点数乘法操作直接使用了RISC-V的fmul.d指令进行支持,且函数的两个double类型的参数直接使用浮点通用寄存器fa0和fa1进行传递。这是因为:
- -march选项指明了目标平台支持的模块化指令子集为imafdc,其中包含了F和D指令子集,即支持单精度和双精度浮点指令,因此可以直接使用RISC-V的浮点指令来支持浮点数的操作。
- -mabi选项指明了后缀“d”,表示当浮点数作为函数参数进行传递之时,无论单精度还是双精度浮点数都可以直接通过寄存器传递。
dmul:
fmul.d fa0,fa0,fa1
ret
$ riscv64-unknown-elf-gcc test.c -march=rv32imac -mabi=ilp32 -o- -S -O3
//所生成的汇编代码如下,从中可以看出,浮点数乘法操作由C库函数(__muldf3)进行支持,这是因为:
- -march选项指明了目标平台支持的模块化指令子集为I/M/A/C,其中未包含了F和D指令子集,即不支持单精度和双精度浮点指令,因此无法直接使用RISC-V的浮点指令来支持浮点数的操作。
dmul:
mv a4,a2
mv a5,a3
add sp,sp,-16
mv a2,a0
mv a3,a1
mv a0,a4
mv a1,a5
sw ra,12(sp)
call __muldf3
lw ra,12(sp)
add sp,sp,16
jr ra
$ riscv64-unknown-elf-gcc test.c -march=rv32imafdc -mabi=ilp32 -o- -S -O3
//所生成的汇编代码如下,从中可以看出,浮点数乘法操作直接使用了RISC-V的fmul.d指令进行支持,但是函数的两个浮点类型的参数均通过堆栈进行的传递,这是因为:
- -march选项指明了目标平台支持的模块化指令子集为I/M/A/F/D/C,其中包含了F和D指令子集,即支持单精度和双精度浮点指令,因此可以直接使用RISC-V的浮点指令来支持浮点数的操作。
- -mabi选项指明了“无后缀”,表示当浮点数作为函数参数进行传递之时,无论单精度还是双精度浮点数都需要通过堆栈进行传递。
dmul:
add sp,sp,-16 //对堆栈指针寄存器(sp)进行调整,分配堆栈空间
sw a0,8(sp) //将函数参数寄存器a0中的值存入堆栈
sw a1,12(sp) //将函数参数寄存器a1中的值存入堆栈
fld fa5,8(sp) //从堆栈中取回双精度浮点操作数
sw a2,8(sp) //将函数参数寄存器a2中的值存入堆栈
sw a3,12(sp) //将函数参数寄存器a3中的值存入堆栈
fld fa4,8(sp) //从堆栈中取回双精度浮点操作数
fmul.d fa5,fa5,fa4 //调用RISC-V的浮点指令进行运算
fsd fa5,8(sp) //将计算结果存回堆栈
lw a0,8(sp) //通过堆栈将结果赋值给函数结果返回寄存器a0
lw a1,12(sp) //通过堆栈将结果赋值给函数结果返回寄存器a1
add sp,sp,16 //对堆栈指针寄存器(sp)进行调整,回收堆栈空间
jr ra
$ riscv64-unknown-elf-gcc test.c -march=rv32imac -mabi=ilp32d -o- -S -O3
cc1: error: requested ABI requires -march to subsume the 'D' extension
//从中可以看出报非法错误,这是因为:
- -march选项指明了目标平台支持的模块化指令子集为I/M/A/C,其中未包含了F和D指令子集,即不支持单精度和双精度浮点指令,因此无法直接使用RISC-V的浮点指令来支持浮点数的操作。
- -mabi选项指明了后缀“d”,表示目标平台支持硬件浮点指令。这一点与-march选项中指明的指令集子集产生了冲突。
虽然(–march=)和(–mabi=)选项理论上可以组成很多种不同的组合,但是目前并不是所有的(–march=)和(–mabi=)选项组合都是合法,目前的riscv-none-embed工具所支持的组合如下:
注意:上述有效组合来自于本文撰写时的信息。随着时间推移,新发布的riscv-none-embed工具可能会支持更多的组合,请读者以其最新的发布说明(Release Notes)为准。
目前RISC-V GCC工具链认为,在实际的情形中,一个程序的大小一般不会超过4GB的大小,因此在程序内部的寻址空间不能超过4GB的空间。而在64位的架构中,地址空间的大小远远的大于4GB的空间,因此针对RV64架构而言,RISC-V GCC工具链定义了(–mcmodel=)选项用于指定寻址范围的模式,使得编译器在编译阶段能够按照相应的策略编译生成代码。其有效的选项值如下:
注意:
medlow和medany两个选项的含义分别解释如下。
(-mcmodel==medlow)选项用于指示该程序的寻址范围固定只能在-2GB至+2GB的空间内。注意:地址区间没有负数可言,-2GB是指整个64位地址空间最高2GB地址区间。
由于此模式的寻址空间是固定的-2GB至+2GB的空间内,因此编译器能够相对生成比较高效的代码,但是由于寻址空间固定,对于整个64位的大多数地址空间都无法访问到,用户需小心使用。
(-mcmodel==medlow)选项用于指示该程序的寻址范围可以在任意的一个4G空间内。由于此模式的寻址空间不是固定的,所以相对比较灵活。
本章仅介绍了RISC-V GCC工具链几个特别的选项,有关RISC-V GCC工具链的完整选项列表和解释,感兴趣的读者可以在谷歌输入“gcc/RISC-V-Options”关键字进行搜索进入相关网页查询,如下图所示。
RISC-V GCC会根据编译生成若干预定义的宏,在Linux操作环境中可以使用如下方法查看和RISC-V相关的宏:
//首先创建一个空文件
touch empty.h
//使用RISC-V GCC的-E选项对empty.h进行预处理,有关“预处理”的背景知识请参见本号文章《编译过程简介》
//通过grep命令对于处理后的文件搜索riscv的关键字
//如果使用-march=rv32imac -mabi=ilp32选项可以看出生成如下预定义宏
riscv-none-embed-gcc -march=rv32imac -mabi=ilp32 -E -dM empty.h | grep riscv
#define __riscv 1
#define __riscv_atomic 1
#define __riscv_cmodel_medlow 1
#define __riscv_float_abi_soft 1
#define __riscv_compressed 1
#define __riscv_mul 1
#define __riscv_muldiv 1
#define __riscv_xlen 32
#define __riscv_div 1
//如果使用-march=rv32imafdc -mabi=ilp32f选项可以看出生成如下预定义宏
riscv-none-embed-gcc -march=rv32imafdc -mabi=ilp32f -E -dM empty.h | grep riscv
#define __riscv 1
#define __riscv_atomic 1
#define __riscv_cmodel_medlow 1
#define __riscv_float_abi_single 1
#define __riscv_fdiv 1
#define __riscv_flen 64
#define __riscv_compressed 1
#define __riscv_mul 1
#define __riscv_muldiv 1
#define __riscv_xlen 32
#define __riscv_fsqrt 1
#define __riscv_div 1