编译器实现之旅——第七章 编译器后端概观
在前面的旅程中,我们已经实现了整个的编译器前端。我们也知道,前端的目标是将源代码转变为抽象语法树,以供后端使用。从这一章开始,我们就要前往后端世界一探究竟了,现在,就让我们来看看编译器后端到底由哪些组件组成,其分别又是在做什么吧。
1. 编译器后端的结构组成
不同于编译器前端,编译器后端并不是一个严格的流水线式结构,我想,用"A服务于B"这样的形容是更为贴切的。在CMM编译器的后端中,主要包含了语义分析器和代码生成器这两个组件;此外,由于虚拟机套件和后端关系密切,故我们这里也将其一起展示。请看下图:
+-----------+
抽象语法树 -> | 语义分析器 | -> 符号表
| +-----------+ |
| |
| v
| +----------+ +-------+
+------------------> | 代码生成器 | -> 低级指令 -> | 虚拟机 |
+----------+ +-------+
2. 什么是语义分析器
要回答这个问题,首先要讨论的是:什么是语义?语义,说白了就是"一句话的意思"。在日常生活中,我们往往会听到"每个字我都看得懂,但是我不知道这是在说什么"这样的形容,这就是语义出现了问题导致的。在编程语言中也是一样,有的代码可能完全符合语法,但其仍然是错误的,比如:将两个不能相加的类型进行相加,就属于语义错误,因为在语法分析器看来,只要代码满足"A + B",而不是"+ A B"、"A B +"或是什么别的错误写法,就是符合语法的。也就是说,语法分析器对代码的检查能力并不能满足编程语言中的全部要求,缺失的部分就需要由语义分析器来弥补。
语义分析器不仅仅是一个用于错误检查的组件,其还负责另一项非常重要的工作:生成符号表。什么是符号表呢?符号表就是记录抽象语法树中任何你想额外记录下的东西的表,这主要包括变量名、函数名、数组大小等,以服务于代码生成器。
我们将在语义分析器的相关章节进一步讲述语义分析器和符号表的故事。
3. 什么是代码生成器
代码生成器,顾名思义:生成代码的组件。代码生成器一手拿着前端生成的抽象语法树,一手拿着语义分析器生成的符号表,背包里还装着诸如语法定义,指令集定义等物品,最终生成了可供虚拟机执行的低级指令。代码生成器是整个编译器中实现最为复杂,需要考虑的方面最多的部分,我们也将在代码生成器的相关章节中走过相当长的一段旅程。
4. 什么是虚拟机
代码生成器生成的代码多种多样:对于有的编译器实现,其代码生成器直接生成汇编语言代码;而对于包括CMM编译器在内的另一类编译器,其代码生成器生成的是编译器作者自行设计的低级指令,这些低级指令就需要专门的虚拟机来执行。计算机执行机器指令,而虚拟机执行类似于机器指令的另一套指令,"虚拟机"因此得名。对于一个虚拟机来说,其自身模仿了计算机的硬件结构,也具有和管理寄存器、内存等"物理设备"。在真实的编译器设计中,虚拟机是一个高度复杂的组件,但在CMM的编译器实现中,我们设计了一套极为精简的指令集和一个非常简单的虚拟机。
我们将在虚拟机的相关章节进一步讲述指令集和虚拟机的故事。
5. 给读者的阅读建议
不同于编译器前端,编译器后端的各组件之间相互联系,共同完成目标。故编译器后端各章节的阅读是没有严格的顺序之分的,而更像是一种"并行阅读"。如果你对某个地方为什么要这样实现,或为什么需要某个东西不能理解,那就看看其他组件的相关章节吧。由于代码生成器是整个编译器后端的核心,故出于逻辑顺序考虑,本文作者将这部分章节放在了最后讲述。
接下来,就让我们首先来看看语义分析器是怎么实现的,符号表又是什么样子的吧。请看下一章:《实现语义分析器》。