为LLVM添加简易RISCV后端(零):简介

LLVM
发布于

2020年12月3日

关键词

LLVM

为一个新的指令集编写编译器是一件复杂的事情,尽管LLVM的出现使这个过程比之前简单多了! 一个匪夷所思的困难是缺乏一个简单的、循序渐进的教程12。 因此,本系列博客试图提供一个从头开始编写LLVM后端的简易教程来解决(部分)问题。

简介

在本教程中,我将为RISC V指令集的基本32位版本(即RV32IM)开发一个后端。希望这能帮助那些不熟悉LLVM的人开始使用这个工具,并将其扩展到自己的项目中。看懂本教程不需要前置知识,但是如果你熟悉C++和RISC V,学习本教程会更容易。

在本文剩下的部分中,我将简要描述LLVM的体系结构和后端结构。不过,我不会在这里详细说明,因为,如果你像我一样,在5分钟(或5秒)之后,就会忘记读过的任何繁琐文档。LLVM的细节将在以后的帖子中根据需要提供。

注意:如果你想要更详细的版本,你可以自娱自乐地阅读LLVM User Guides

LLVM架构

传统上,编译过程分为三个阶段。 首先,编译器的前端将源代码转换为某种中间表示(IR); 然后,优化IR; 最后,编译器的后端将IR转换为机器代码。 传统的编译器通常仅支持一种编程语言和一种目标指令集,编译器的源代码很难重用,例如添加新的目标指令集。

LLVM模块化地实现了三个编译过程,可以解决重用问题。 其思想是LLVM的核心(即IR和优化器)是固定的,但是前端和后端可以被替换,以使编译器可以支持多种编程语言和指令集。 例如,我们可以使用Clang(LLVM的前端)和x86后端把C/C++代码编译成X86指令集上的可执行程序。 我们也可以用ARM后端替换X86后端,从而得到ARM指令集上的可执行程序。

注意:LLVM的设计师Chris Lattner撰写了这篇文章介绍LLVM的体系结构及其设计动机。

注意:LLVM的这三个阶段的每个阶段在都有一个专用的可执行文件。 clang是C/C++的前端(显然针对不同的编程语言有不同的前端),opt是优化程序,llc用于调用后端。 通常,我们使用clang作为驱动程序来执行前端,使用llc和opt配合适当的参数来生成IR,汇编,可执行文件等。

代码生成

LLVM后端将IR编译为目标代码或汇编代码。 每个后端都只支持单一平台,但可以支持多个指令集。 例如,LLVM只有一个ARM后端,但该后端可以为ARMv6和ARMv7等指令集生成代码。 每个后端都建立在LLVM的目标无关代码生成器之上。 目标无关代码生成器是一个框架,可实现诸如寄存器分配之类的关键算法。 从广义上讲,后端的任务是配置该框架并使之适应其目标指令集的特定需求。

代码生成具有以下阶段:

  1. 指令选择 映射LLVM IR到目标指令集中的指令。 此阶段使用无限数量的虚拟寄存器和函数调用堆栈的抽象引用。

  2. 计划和编排 确定指令的顺序。 需要明确的是,在指令选择阶段已经对指令进行了排序,但这里可以根据寄存器分配策略或指令等待时间来对其中的一些指令的排序进行优化。

  3. 基于SSA的机器代码优化 执行诸如peephole优化之类的工作。

  4. 寄存器分配 将虚拟寄存器映射到物理寄存器。

  5. Prolog / Epilog插入 在每个函数的开头(或prolog)和结尾(或epilog)插入机器指令。 这些通常是在进入或退出函数时扩展堆栈的指令。 由于当前已经知道堆栈大小,因此也可以解析抽象堆栈引用。

  6. 后期机器代码优化 可能不言自明。

  7. 代码发射 发出目标代码或汇编代码。

接下来,我将看一下构建LLVM以及如何设置开发/调试环境…

注意:您可以阅读这篇文章了解LLVM目标无关代码生成器的更多信息。

注释

脚注

  1. 公平地说,有不少关于LLVM的书籍和网站,但大多数都是对这个工具的一般性描述,还有是关于如何编写新前端的实践教程,但后端的教程非常少。↩︎

  2. 这个教程描述了如何开发LLVM后端,但我发现很难理解。↩︎