本文结合编译原理理论和GCC实践做了一个总结,希望能给需要了解编译原理和底层知识的同学一个更快的学习路径。
了解事物的本质,是一件非常愉快的事情。
学习编译原理好处:
编译原理可以说是一个计算机科学的缩影,在学习寄存器分配中会使用到贪心算法,死代码消除中会用到图论算法,数据流分析中使用到Fixed-PointAlgorithm,词法分析和语法分析中会使用到有限状态机和递归下降等重要思想。可见编译原理是值得学习的。
源程序到目标代码的过程要经历如下四个步骤:
首先是源程序到抽象语法树:
需要经历词法分析,也就是将程序中的一个一个字符按照单词识别出来。
然后是语法分析,将词法分析阶段的单词构成短语,将短语以抽象语法树的形式存储起来。
接下来是语义分析,语义分析是审查源程序有无语义错误,为代码生成阶段收集类型信息。
从源程序到抽象语法树的过程称为编译器前端。
目标代码生成:中间代码到目标代码会经过大量的代码优化,例如死代码删除、指令调度等。这个过程称为编译器后端。
编译器后端是整个编译器中最精华的地方,如果想要提升程序性能,研究编译器后端算法绝对会让你受益良多。
下面是一条语句i=j+k*10编译过程的具体实例:
程序=数据结构+算法
相信很多关心程序效率的同学都有这样的体验:
算法和数据结构密不可分,一个高效的算法必然有合适的数据结构作为支撑。高效的算法与合理的数据结构同样重要。
对于算法,通常仔细读一读论文就可以弄清楚原理,但是去看一些开源代码具体实现的时候往往又一头雾水。
那是因为我们刚接触开源代码的时候并不清楚它的数据结构是如何设计的。
本文从GCC的数据结构开始入手,为想要快速上手GCC的同学提供一个捷径。
即使不关心GCC源码,也可以从数据结构设计中获得启发,毕竟合理的数据结构和高效的算法一样重要。
我们通常认为GCC是一个编译器,然而官方的解释是这样的:
GCCisnotacompiler.
GCCisacompilercollectionthatconsistsofthreecomponents.
Afrontendforeachprogramminglanguage,amiddleend,andabackendforeacharchitecture.
也就是说GCC是一个编译器集合,支持多种语言和多种硬件架构。
下图是GCC的一个整体结构图
GCC整体结构图
图中的绿色的部分Generic、GIMPLE、RTL是本文要介绍的,看懂这三个数据结构之后离看懂GCC源码基本就成功了一半。
GCC中的Generic其实也是一种抽象语法树(AST)。
从GCC整体结构图中我们可以看到,在Generic前面已经生成了AST,为啥这儿的Generic也是一种AST呢?
原因是这样的,从GCC整体结构图中我们可以看到,C语言会生成一种AST,C++也会生成一种AST,Java还会生成一种AST。这三种AST其实还是会有一些细微的差别,因此设计了一种通用的AST去统一所有的语言生成的AST,这个通用的AST就是Generic。
有了统一的AST后,就不用针对多种语言的AST写多份code去生成目标代码。只需要针对统一的AST写一份code去生成目标代码。
将每一种节点统一到一个联合体中的好处就是便于代码阅读和代码的编写与维护。
structtree_var_declvar_decl;
两者交叉的部分为变量的类型,因为a与b都是int类型,所以用指针指向同一个int类型节点。
变量a与变量b通过var_decl的chan字段以链的方式连接起来。
在GCC中很多前端处理并不包含AST到GENERIC的转换,而是直接将AST转换成与语言无关的另外一种中间表示,即GIMPLE。
从GCC整体框架图可以看到,AST转换成GIMPLE之后首先进行静态单赋值(SSA),然后进行各种优化pass。
gimplify_function_tree是生成GIMPLE的入口函数。
其作用是通过扫描函数的AST,分别对函数的返回值、函数参数、函数中的变量以及函数体的语句序列进行处理,并将其转换成对应的GIMPLE序列。
gimplify_body函数,对函数的内容进行GIMPLE转换。
gimplify_parameters对函数的参数列表进行GIMPLE转换。
gimplify_stmt,函数体的GIMPLE生成是通过调用gimplify_stmt完成的。
gimplify_expr函数是GIMPLE生成的核心函数,由gimplify_stmt调用。
另外一个需要注意的是,对于带有操作数的GIMPLE语句,这些操作数的节点指针(类型为tree)将被连续存放在从该结构体最后一个成员treeop[1]开始的连续地址中。
RTL中文叫做寄存器传输语言(RegisterTransferLanguage)。RTL是一种非常接近汇编指令的中间表示。RTL采用了类似LISP语言的列表形式,描述了每一条指令的语义动作。
刚接触RTL的时候对其含义并不是太了解,导致代码难理解,因此在这儿对RTL的含义进行简要介绍,方便初学者能够快速入门GCC。
RTL是下面这样子的:
别以为是乱码,刚开始见的时候确实非常奇怪,但弄清楚之后非常简单。
其中set表示等号或者说是赋值,plus表示加法。SI表示寄存器存取的模式,SI表示该寄存器以32位整形的模式存取。
整个RTL的意思是:将寄存器139与寄存器138相加的值赋给寄存器140.
用一张图表示就是:
其中的XEXP(x,0)是GCC源码中用来取第一个操作数的代码。
知道了RTL表示后阅读源码就会轻松许多,当然还有一些细节没有介绍,需要了解的可以后台回复GCC,下载我收集的几份比较好的国外PPT,再配合源码看起来会方便很多。