编译器基础
简介
编译器分为三个阶段
前端负责词法分析和语法分析,生成语法树。
优化器是在前端上,对得到的中间代码进行优化。让代码执行更加高效
后端是将优化后的中间代码转化为针对各个平台的机器码。
GCC起源于1987年,当时作为GNC的c语言编译器。
苹果最早用了GCC作为编译器。
GCC是一个可移植的编译器,支持多种前端平台,GCC还能进行跨平台编译,解析不同的语言。可以加入新语言和新CPU架构的支持,并且可以免费试用。
在早期阶段LLVM的前端使用了GCC。
GCC
GCC和LLVM本身都自带了前端后端优化器。而CLANG就是LLVNM后来的的前端。
前端的作用是编译成LLVM IR(Intermediate Representation,中间表示)。
GCC的编译流程如下
.i
文件是C语言源代码在编译过程的第一个阶段——预处理(Preprocessing) 之后生成的中间文件。它包含了所有预处理指令(如 #include, #define, #ifdef等)展开后的完整源代码。
预处理阶段包括宏定义,文件包含,条件编译。预处理过程读入源码,就爱你差包含指令的语句和宏定义,并对其进行响应和替换。预处理会删除程序中的注释和多余空白字符。编译完毕后,会在hello.s中出现很多的汇编指令。.o
中就是二进制代码。
链接阶段将一个或多个.o
文件和所需的库文件合并在一起,解析它们之间的相互引用,最终生成一个可执行文件。
预处理就是前端,编译的过程就是优化,后端就是汇编和链接的过程。
GCC早期还支持JAVA,(GCJ)但该项目已经被停止维护。 GCJ 是 GCC 家族的一部分,它是一个可以将 Java 源代码 (.java) 编译成字节码 (.class),或者更进一步直接编译成本地机器代码的编译器。后者是它最大的特点,可以让 Java 程序像 C/C++ 程序一样直接以原生方式运行,无需 Java 虚拟机 (JVM),只需运行时库支持。
GCC支持不同的架构和平台和操作系统。
然而GCC代码耦合度非常高,很难独立,如集成到专用IDE上,模块化使用GCC很难。GCC被构建成单一静态编译器,使得难以被API并集成到其他工具中。
GCC大约有35年多的历史,越到后期的版本,代码质量越差,目前一共有1500万行代码,是显存最大的自由程序之一。
最早苹果拉了一个GCC的分支,开源版给FST GCC维护,苹果维护Apple GCC。
实际GCC上前端、后端、优化器的分界线没有那么明显。
GCC是GNU项目的子项目,也是GNU项目的核心组成部分。GCC是GNU的编译套件。随着时代的发展,它现在已经能够编译非常多的编程语言。它的名字被重新定义为 “GNU Compiler Collection”(GNU编译器套件),但缩写保持不变。
rust和go也可以用gcc编译但是不是默认的推荐编译器。
LLVM
llvm提出了IR的概念。苹果不是LLVM的创始人,但是是LLVM的推动者。
LLVM是编译器,编译器前端,编译器的工具链。LLVM是一个工具的集合,支持的工具非常多。
在LLVM中如果新的语言只需要实现一个新的编译前端,支持已有的优化和后端都能实现复用。
LLVM组件交互发生在层次抽象,不同组件隔离为单独程序库,易于在整个编译流水线中集成转化和pass。现在被作为各种静态和运行时编译语言的通用基础结构。
GCC饱受封层和抽象漏洞的困扰:编译前端生成编译后端的结构,编译后端遍历前端抽象语法树来生成调试信息,整个编译器以来命令行设置的全局数据结构。
LLVM IR
CLANG是llvmc语言的前端
CLANG会编译为IR。
LLVM的IR是非常丰富的,有多重形式。会经历多次优化,先是AST,然后最后会将一个DAG传给后端。
LLLVM IR作为一种编译器IR,它的基本原则指导核心库开发:
- SSA表示,代码组织为三地址指令序列和无线寄存器让优化能够快速执行
- 整个程序的IR存储到磁盘让链接时优化易于实现。
LLVM采用静态单赋值的形式(static single assignment SSA)。程序中每个变量有且只有一个赋值语句,称一个程序是SSA形式的。每个变量都在事前被定义,并且只能赋值一次。
每个值只有单一赋值定义了它。每次使用一个值,可以立刻向后赘述到给出其定义的唯一指令集。极大简化优化,因为SSA形式建立了频繁的user-def链,也就是一个值到达使用指出的定义列表。
LLVM IR是类似于精简指令集的底层虚拟指令集,和真实精简指令集一样,支持简单指令的线性序列,如添加,相减,比较和分支。指令都是三地址形式,它们接受一定数量的输入然后在不同的寄存器中存储计算结果。与大多数精简指令集不同,LLVM使用强类型的简单类型系统,并剥离了机器差异。LLVMIR不适用固定的命名寄存器,使用%字符命名的临时寄存器。
每个三地址指令都可以被分解成一个四元组的形式。每个陈述都包含了三个变量,即每条指令最多有三个操作数,所以被称为三地址码。
LLVM IR有三种表示形式,它们是完全等价的
- 内存中的编译中间语言
- 硬盘上存储的中间语言
.bc
- 人类刻度的代码语言
.ll