自编教材分享:第五章—编译与运行优化(一)


编译流程
编译器的工作流程可分为预编译、编译、汇编以及链接四个阶段,其中,预编译又称预处理,是整个编译过程最先做的工作,主要是代码级的文本处理工作;编译过程是把预编译生成代码文件翻译为目标机器汇编代码;汇编过程是将汇编代码翻译为机器相关的二进制目标文件,并最终由链接器对目标文件、操作系统的启动代码和用到的库文件进行组织,将各个模块的代码链接到一起,最终生成可执行程序。
目前市面上较为流行的编译器有GCC、LLVM,以及Intel-ICC编译器,其中GCC是由GNU开发的开源编译器,LLVM是伊利诺伊大学发起的一个开源项目,相较于GCC,LLVM的模块化和复用性更好,且速度更快。

LLVM编译器架构
本次分享内容以LLVM编译器为主,LLVM是构架编译器的框架系统,以C++编写而成,支持多种语言和后端。
LLVM的编译流程分为前中后三个部分,前端的工作主要针对源语言,通过分析高级语言代码的文本,相应的进行预编译、词法分析、语法分析、语义分析到生成中间代码,编译器中端主要对前端生成的中间代码进行优化,其中包括常规编译优化、过程间优化、循环优化、自动向量化及其它优化。编译器后端重点关注目标机器,对中间代码实施面向目标机器特征的优化,生成符合目标机器运行需要的汇编代码,这些代码最终通过汇编器和链接器生成在目标机器上可执行的二进制程序。
接下来会展开介绍每个阶段所做的工作。

编译器前端
预编译
前端要做的第一个工作为预编译,完成文件包含的插入、宏展开、条件编译展开和删除注释等任务。
(1)文件包含:指源代码文件中的#include文件包含声明。例如,当源代码文件中含有语句#include <stdio.h>时,预处理器会在系统标准路径下搜索C的标准输入输出头文件stdio.h,将文件stdio.h中的代码复制到当前文件以来代替上述文件扩展声明语句。
(2)宏展开:C程序中可以使用#define来定义宏,一个宏定义给出一段C代码的缩写。预处理器将源程序文件中出现的、对宏的引用展开成相应的宏定义。
(3) 条件编译:处理#if和#ifdef等条件编译指令,将源代码中的某部分代码包含进来或排除在外。
(4) 删除注释:删除所有的注释“//”和“/* */”。
下边代码为生成的部分预编译文件,可以明显看出在主程序部分,已经没有注释,并且数组d[N]也被替换为了d[1024]。
词法分析
词法分析器读入程序的源代码字符流,扫描、分解字符串,识别出一个个的单词,包括如下类型:
关键字,如int、float、if、 sizeof等。
标识符,用来表示各种名字,如变量、数组名、函数名等。
运算符,包括算术运算符、逻辑运算符、关系运算符等。
分解符,包括“,、;”等符号。
常数,包括整形、浮点型、字符型等。

查看LLVM词法分析过程的编译命令:
语法分析
语法分析的任务是将词法分析生成的单词组合成语法短语,同时分析这些短语是否符合高级程序设计语言中的语法规则。
有下面的规则来定义表达式:
标识符是表达式。
常数是表达式。
若表达式1和表达式2都是表达式,那么表达式1+表达式2以及表达式1 * 表达式2也都是表达式。
有下面的规则来定义赋值语句:
<表达式> = n
<表达式> = <表达式>“+”<表达式>
<表达式> = <表达式>“* ”<表达式>
<赋值语句> = <标识符>“=”<表达式>

以语句c = a + b * 3为例,依据高级程序设计语言中约定的赋值语句及表达式的定义规则表示为抽象语法树形式。将字符串格式的源代码转化为树状的数据结构,更容易被计算机理解和处理。

语义分析
语义分析阶段的任务是审查源代码有无语义错误,源代码中有些语法成分,按照语法规则去判断是正确的,但不符合语义规则,比如使用了没有声明的变量。
语义分析主要的任务可归结为以下四类:
完成静态语义审查和处理;
上下文相关性审查;
类型匹配审查;
类型转换。
比如语句c=a+b*3中,运算符*的两个运算对象分别是b和3,如果b是实型变量,3是整型常数,语义分析阶段执行类型审查之后,会自动地将整型量转换为实型量以完成同类型的数据运算。效果会体现在语法分析所得到的语法树上,即增加一个运算符结点(inttoreal)。

编译器前端-相关选项
编译器前端将高级程序设计语言编写的源代码翻译到统一中间表示,优化人员可以通过编译选项指定预处理、语言和模式等对程序的前端编译过程予以干预。



