RISC-V GNU工具链优化方案

RISC-V GNU工具链是一套用于RISC-V架构的交叉编译工具集,它使得开发者能够在一个宿主系统上编译出适用于RISC-V处理器的程序。这个工具链包括了编译器、汇编器、链接器、调试器及其他辅助工具。编译器优化对程序运行性能提升是有很大的帮助的,根据CPU的指令发射宽度、各种资源的个数(加法单元,乘法单元的数量等)、流水线深度、指令的列表、每条指令的使用的资源以及延迟信息等对编译器进行优化可以显著提升程序的性能。还可以根据数据依赖约束、功能部件约束、寄存器约束等降低指令流水间的stall,也可以提升程序的运行效率。调试器提供了对硬件资源的深入访问和控制,是RISC-V开发过程中不可或缺的工具,高效的调试器可以快速定位问题,减少调试时间,调试器辅助进行性能分析的能力对优化程序性能至关重要,目前RISC-V调试器对固件和操作系统内核的调试支持明显不够,增加在这一方面的优化可以提高固件和操作系统的快速开发及稳定性的支持。
riscv-gnu-toolchain 是一个用于 RISC-V 架构的 GNU 工具链,包含了多个工具和库,主要用于编译、调试和运行 RISC-V 程序。如下表所示为 riscv-gnu-toolchain 包含的主要工具和组件:
表 2 riscv-gnu-toolchain的主要工具及组件
在这里插入图片描述

工具和组件 作用
gcc 用于将 C、C++ 等高级语言编译成 RISC-V 汇编代码。
ld GNU链接器
as GNU汇编器
gdb 用于调试 RISC-V 程序
newlib 一个适用于嵌入式系统的 C 标准库
qemu 一个开源的模拟器,支持 RISC-V 架构的仿真
spike RISC-V ISA 模拟器,通常用于仿真和测试
pk(Proxy Kernel) 一个轻量级的内核,用于在模拟环境中运行用户态程序
dejagnu 一个用于测试套件的框架,通常与 GCC 和 GDB 一起使用
objdump 主要用于显示编译后的对象文件、可执行文件、共享库等二进制文件的详细信息
objcopy 目标文件的复制,可以完成目标文件格式的转换
readelf ELF文件通常用于存储二进制可执行文件、共享库和目标文件的信息
其中最主要的是gcc (GNU Compiler Collection):用于将 C、C++ 等高级语言编译成 RISC-V 汇编代码。是一个由GNU项目开发的编译器集合,它支持多种编程语言和处理器架构。GCC最初是为C语言设计的,但随着时间的发展,它已经扩展到支持C++、Objective-C、Fortran、Ada、Go、D、Pascal和Java等多种编程语言。GCC是自由软件,遵循GNU通用公共许可证(GPL)发布。
gdb (GNU Debugger):它允许开发者对程序的执行进行控制、检查和修改,以便于查找和修复软件中的缺陷,它提供了对 RISC-V 架构的深入洞察和控制能力,有助于提高开发效率和软件质量。
这些工具和库共同构成了一个完整的开发环境,支持从代码编写、编译、链接到调试和运行的整个过程。

编译器整体架构讲解

要优化GCC编译器在RISC-V上的性能,可以从以下几个方面入手:

  1. 指令优化:根据RISC-V的指令集特点,优化GCC生成的指令。例如,RISC-V支持大量的自定义扩展指令,可以通过调整GCC的后端来充分利用这些指令,提高代码执行效率。

  2. 寄存器分配优化:RISC-V架构拥有大量的寄存器,优化寄存器分配策略可以减少内存访问,提高程序运行速度。可以通过改进寄存器分配算法,使得更多的变量能够存储在寄存器中,减少访问内存的次数。

  3. 窥孔优化:窥孔优化是一种在编译器的后端进行的优化,它检查生成的汇编代码,寻找可以优化的模式。例如,可以通过移位操作替代乘法和除法操作,减少指令数量和执行时间。

  4. 代码布局优化:优化代码的物理布局可以减少跳转指令和提高缓存命中率。例如,可以将频繁访问的函数和数据放置在内存中的连续位置,以提高访问速度。

  5. 循环优化:循环是程序中常见的结构,对循环进行优化可以显著提高性能。可以通过循环展开、循环交换等技术,减少循环的开销,提高循环的执行效率。

  6. 内存访问优化:RISC-V的内存访问指令相对简单,可以通过优化内存访问模式,如使用预取技术,来减少内存访问延迟。

  7. 代码压缩:对于资源受限的嵌入式系统,可以通过代码压缩技术减少程序的大小,同时保持性能。这可以通过使用RISC-V的压缩指令集或者在GCC中启用代码压缩选项来实现。

  8. 分支预测优化:RISC-V架构支持多种分支指令,可以通过优化分支预测逻辑,减少分支错误预测带来的性能损失。

  9. 向量化优化:对于支持向量扩展的RISC-V处理器,可以通过向量化优化,将标量操作转换为向量操作,提高数据吞吐量。

  10. 编译器参数调优:合理设置GCC编译器参数,如-O优化级别、-march指定目标架构、-mtune调整目标处理器的微架构等,以生成更高效的代码。

通过上述方法,可以针对RISC-V架构的特点对GCC编译器进行优化,提高其在RISC-V上的运行效率。

根据搜索结果,以下是一些GCC编译器针对RISC-V的优化案例:

  1. SPEC CINT2006基准测试性能提升

    • 在SiFive HiFive Unleashed平台上运行SPEC CINT2006基准测试时,使用GCC编译的测试运行速度比使用Clang编译的快18%。
    • GCC编译的基准测试执行的指令数量比Clang编译的少了31%,指令字节数少了35%。
    • 这表明GCC在生成RISC-V代码方面通常比Clang更优。
  2. RISC-V Bitmanip扩展指令集优化

    • 研究发现,使用RISC-V Bitmanip扩展指令集可以减少基准测试的动态指令数量高达17%,这与之前使用宏操作融合技术达到的减少量相当。
  3. GCC针对RISC-V的特定选项

    • GCC提供了针对RISC-V的特定编译选项,如-mbranch-cost=n用于设置分支的成本,-mplt用于控制在生成PIC代码时是否使用PLTs,-mabi=ABI-string用于指定整数和浮点类型的调用约定,-mfdiv用于决定是否使用硬件浮点除法和平方根指令等。
    • 这些选项可以帮助开发者根据RISC-V的具体架构和应用需求进行优化。
  4. 位运算优化

    • 在GCC中,针对RISC-V 64位的位运算优化问题,例如在特定条件下生成的位移指令对SI-Mode寄存器的操作可能超出其宽度,导致寄存器被优化为常数零,这与原始RTX的行为不匹配。
    • 通过修改GCC的内部模式,可以解决这类优化问题,确保位运算的正确性和效率。

这些案例展示了GCC编译器在RISC-V架构上的性能优化潜力,以及如何通过特定的编译选项和内部模式调整来提高代码的执行效率。

编译器架构分为前端、中端、后端三个部分,如下图所示。每个阶段都有一组不同的问题要解决,用于解决这些问题的方法也各有不同。
在这里插入图片描述

图 6 编译器架构三段论划分
前端,front end:解析源码,检查错误(词法分析(lexical analysis), 语法分析(syntax analysis), 语义分析(semantic analysis)),所以前端又称为分析阶段。并且构建一个特定于语言的抽象语法树(Abstract Syntax Tree,AST)来代表输入的代码。
优化器,optimizer:负责进行各种转换尝试,改善代码的执行时间,比如消除冗余计算。通常或多或少与语言及目标无关。
后端,back end:代码产生器,将代码映射到目标指令集,除了编写正确的代码外,它还负责生成利用所支持体系结构的不寻常特性的良好代码。一个编译器后端的通用部分包含指令选择,寄存器分配,及指令调度(instruction selection, register allocation, and instruction scheduling)。更详细的设计框图如下图所示。
在这里插入图片描述

图 7 一个编译器的各个步骤/GCC编译器架构
词法分析
通常,编译器的第一项工作叫做词法分析。就像阅读文章一样,文章是由一个个的中文单词组成的。程序处理也一样,只不过这里不叫单词,而是叫做“词法记号”,英文叫Token。
语法分析
编译器下一个阶段的工作是语法分析。词法分析是识别一个个的单词,而语法分析就是在词法分析的基础上识别出程序的语法结构。这个结构是一个树状结构,是计算机容易理解和执行的。
语义分析
语义分析的任务是对结构上正确的源程序进行上下文有关性质的审查,进行类型审查。语义分析是审查源程序有无语义错误,为代码生成阶段收集类型信息。语义分析的任务是按照语法分析器识别的语法范畴进行语义检查和处理,产生相应的中间代码或目标代码。
以上三部分为编译器的前端,前端专注于将源代码转换为中间表示,前端依赖与形式语言理论和类型理论的结果,以及若干健壮的算法和数据结构。
中间部分或优化器,会将一个IR程序转换为另一个,其目标是生成执行更高效的IR程序。优化器分析程序得出关于其运行时行为的知识,而后利用此项知识来变换代码并改进其行为。
在GCC编译器的中端,GIMPLE是一种中间表示(IR),它从高级语言的AST(抽象语法树)转换而来,并且可以进一步转换为更低级的IR,如RTL(寄存器传输语言)。GIMPLE的转换过程涉及多个阶段,如下表所示:
表 3 GIMPLE的转换过程

GIMPLE转换过程 作用
GIMPLE生成 GCC前端将高级语言的源代码解析成AST/GENERIC,然后通过一系列的过程转换成GIMPLE。GIMPLE是一种三地址代码形式,它是一种与前端语言无关的中间表示,引入了临时变量来保存中间值。GIMPLE的生成分为高级GIMPLE和低级GIMPLE两个阶段。高级GIMPLE是从AST/GENERIC转换而来,而低级GIMPLE则是通过进一步的优化和转换得到的。
GIMPLE遍历 在GCC中,GIMPLE可以通过遍历来进行分析和转换。遍历GIMPLE树可以对每个GIMPLE语句进行处理,例如,可以检查函数返回值是否被存储,或者进行其他优化。
GIMPLE到低级IR的转换 这个过程涉及到将高级GIMPLE转换为低级GIMPLE,然后再转换为RTL。在低级GIMPLE中,所有的GIMPLE_BIND被移除,多个GIMPLE_RETURN语句被合并,所有的if分支被转化成then和else两个分支,GIMPLE_TRY和GIMPLE_CATCH被转化为异常控制流。具体操作可在 lower_function_body 函数中查看。
构建控制流图(CFG) 在低级GIMPLE阶段,GCC会构建控制流图(CFG),这是将GIMPLE转换为RTL的一个关键步骤。CFG将GIMPLE中的控制流结构转换为基本块(basic blocks),每个基本块包含一系列顺序执行的指令。
SSA转换 在GIMP LE到RTL的转换过程中,GCC还会进行静态单赋值(SSA)形式的转换。SSA是一种特殊的GIMPLE形式,它通过引入Phi函数来表示变量的多个定义点,从而简化了后续的优化过程。
优化Pass 在GIMPLE到低级IR的转换过程中,GCC会执行一系列的优化Pass。这些Pass可以对GIMPLE进行各种优化,如死代码消除、循环优化、指令组合等。
GIMPLE到RTL的最终转换 在所有优化Pass执行完毕后,GCC会将GIMPLE(或SSA GIMPLE)转换为RTL。RTL是一种更低级的IR,它更接近于目标机器的指令集,为代码生成阶段做准备。
pk(Proxy Kernel) 一个轻量级的内核,用于在模拟环境中运行用户态程序
dejagnu 一个用于测试套件的框架,通常与 GCC 和 GDB 一起使用
objdump 主要用于显示编译后的对象文件、可执行文件、共享库等二进制文件的详细信息
objcopy 目标文件的复制,可以完成目标文件格式的转换
readelf ELF文件通常用于存储二进制可执行文件、共享库和目标文件的信息
整个GIMPLE到低级IR的转换过程是GCC编译器中端优化的关键部分,它涉及到多个复杂的步骤和优化策略,以确保生成的机器代码既高效又符合目标架构的要求。后端将IR 程序映射到指定处理器的指令集。对寄存器分配和指令调度方面的困难问题,后端会近似求解,其近似解的质量对于编译后代码的速度和大小有着直接影响。
测例源码不更改的情况下,前端不需要修改。如果修改过程中增加编译选项或者编译指示语言(类似硬件的约束文件),仅需要Parse支持这些,但不需要大改。因此词法分析、语法分析、语义分析不作为重点研究对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值