一个简单编译器的实现过程

一、编译器的实现步骤

1、词法分析,将原文件划分为单独的单词或关键字。

2、语法分析,利用上下文无关文法分析程序的短语结构。

3、语义分析,根据短语建立抽象语法树,确定短语含义、变量关联声明、检查表达式类型。

4、翻译,根据抽象语法树翻译成中间表示树不依赖任何特定的程序语言和目标机器结构。

5、指令选择,根据目标机器的指令体系,对中间表示树的节点进行划分。

6、控制流分析。

7、数据流分析。

8、寄存器分配。

9、代码生成。

在上编译原理课程的时候,我们完成了一个基于miniJava的编译器和垃圾回收器


代码:https://github.com/xuhaoavl/tiger

二、词法分析器

1、词法记号,编程语言语法单元的一系列字符,词法记号一般分为有限的几种。

例如:ID(字符串)、NUM(整数)、REAL(实数)、IF(if)、VOID(void)、COMMA(逗号)。。。。

2、正则表达式,一般用正则表达式匹配字符窜的方式检查字符串类型。

符号(a,v,h,b等),或(a|b)、并(a·b)、空(ε)、重复(克林闭包,所有可能的字符串)

有一些可能会产生模糊性,例如if8是字符串还是if和8,对此有以下两种解决方法

a、最长匹配,选择可以匹配正则表达式的最长输入字符串。

b、优先匹配,选择最先与之匹配的正则表达式,这时候正则表达式的顺序很重要。

3、有限自动机DFA,不存在两个从统一状态出发且标记完全相同的边,不存在空串。

一般使用DFA来实现词法分析。

4、非确定有限自动机NFA,可能存在多条从一个状态出发且标记一样的边,也可能有空边。

一般使用正则表达式表示一个语言的词法,然后将其转换为NFA(正则表达式转NFA比较容易),最后再将NFA转DFA,并用代码实现。

三、语法分析

1、上下文无关文法

使用上下文无关文法描述词法结构。

推导形式有最左推导和最右推导,可以使用一颗分析树表示推导的过程,每一步推导都对分析树中的某一个节点进行一步拓展。

二义性文法是指能够产生两颗不同的分析树。,可以通过改写文法消除二义性。

2、预测分析的过程

递归下降,每个递归下降分析器对应一个非终结符号的推导。

但是在推导的过程中有可能会产生冲突,即在某一步有多个候选式。

这时候可以通过计算当前非终结符的first集和follow集来解决冲突。

LL(k):从左到右分析,最左推导,k个lookahead符号。

LR(k):从左到右分析,最右推导,k个lookahead符号。

消除左递归,根据first集和follow集,左递归会导致多重定义入口。

消除左因子,用一个新的非终结符替代。

四、抽象语法

1、在一个递归下降分析器中,语义动作是分析函数返回的值。

2、抽象语法树,其实也就是前面所说的分析树

在Java中其实也就是一颗对象树,main函数所在的类也就是树根,树上有表达式对象,语句对象等。

一个简单编译器的实现过程_第1张图片

如图所示,在语法分析的过程中,已经构建出了一颗抽象语法树。

3、抽象语法树的遍历

从根步遍历语法分析树的时候并不知道每个子节点的类型,可以通过instanceof解决,但是更好的一个解决方法是使用访问者模式。

意图:主要将数据结构与数据操作分离。

主要解决:稳定的数据结构和易变的操作耦合问题。

一个简单编译器的实现过程_第2张图片

一个简单编译器的实现过程_第3张图片

五、语义分析

把变量的定义与使用联系到一起,检查表达式的类型。通过符号表进行。

1、符号表,将标识符与其类型和位置进行映射。实际中可能含有很多个符号表,每一个类、每一个方法都有一个符号表,第一次遇到该变量的时候会将其加入到符号表中,作用域结束后将其删除。

符号表主要用于检查变量的使用和定义、检查表达式类型。

六、翻译成中间表示


虽然可以直接翻译成机器码,但是这将阻碍可移植性并且无助于进行模块化设计。

中间表示是一种抽象的机器语言,无需过多考虑机器特性就可以对目标机进行表达。但是,它同样独立于源语言。编译器的前端进行词法分析、语法分析、语义分析并负责生成中间表示,后端负责对中间表示进行优化,同时翻译成机器语言。

1、使用中间树表示中间表示

递归遍历前面生成的抽象语法树将其转换成中间树。中间树与抽象语法树的区别是抽象语法树和源语言相关,而中间树则与源语言无关,与机器也无关,是一种中间表示形式,例如三地址指令、四地址指令等。

中间树要尽量能够满足不同语言的要求,又不能与机器底层有太多关系。

七、指令选择


中间表示树的每一个树节点仅能表示一种操作:从内存中读,从内存中写,加,减等。找到一些恰当的指令去构造一颗中间表示树是指令选择阶段要完成的工作。

将机器指令表示成中间树的片段,称为树模式。这样指令选择的任务就是构造一颗包含最小树模式集所组成的树。

八、活性分析


编译器需要分析中间表示程序来确定哪些临时变量同时被使用,如果一个变量中保存的值会被使用,则变量是活跃的。如果两个临时变量不是同时使用,则他们可以共用寄存器。通过活性分析可以确定出一个最少使用的寄存器数,最大化寄存器的使用。

控制流图:每一条语句都是一个执行块,按照程序执行的顺序可以画出程序的控制流。

通过控制流分析出每个变量的活性,再通过活性画出干扰图。

干扰图可以是一个邻接矩阵或则一个无向图,哪些点之间有边表示那些变量之间不能分配给同一个寄存器。

九、寄存器分配


寄存器分配其实是一个着色问题,使用k(k个寄存器)种颜色给干扰图着色。

十、垃圾收集


标记清扫、复制、标记整理、分代收集。

十一、数据流分析


数据流分析主要是收集程序运行时可能发生的信息,并针对该信息进行一些优化:

1、寄存器分配,同一个寄存器保存两个不重叠的临时变量。

2、消除共用子表达式,如果一个表达式被多次计算,消除其中一个计算。

3、消除死代码

4、合并常量,如果一个表达式的操作数是常量,那么在编译时执行该计算。

除此之外,还有其他的一些优化策略,如循环优化、静态单赋值、流水线和调度策略。

还有一些利用计算机cache的优化,如指令cache对齐,预取指令循环交换,分块等。


你可能感兴趣的:(编译)