你好,我是编辑王惠。
阶段性的总结复习和验证成果是非常重要的。所以,在8月7日到8月12日这为期一周的期中复习时间里,我们先来巩固一下“真实编译器解析篇”中的重点知识。你可以通过学习委员朱英达总结梳理的**划重点内容,以及涵盖了关键知识点的7张思维导图**,来回顾7种语言编译器的核心概念与算法。
另外,宫老师还精心策划了10道考试题,让你能在行至半程之时,做好自检,及时发现知识漏洞,到时候一起来挑战一下吧!
在期中复习周的最后,我还会邀请一位优秀的同学来做一次学习分享。通过他的学习故事,你也可以借此对照一下自己的编译原理学习之路。
好,下面我们就一起来复习这些核心的编译原理概念与算法知识吧。
## Java编译器(javac)
Java是一种广泛使用的计算机编程语言,主要应用于企业级Web应用开发、大型分布式系统以及移动应用开发(Android)。到现在,Java已经是一门非常成熟的语言了,而且它也在不断进化、与时俱进,泛型、函数式编程、模块化等特性陆续都增加了进来。与此同时,Java的编译器和虚拟机中所采用的技术,也比 20 年前发生了天翻地覆的变化。
Java的字节码编译器(javac)是用Java编写的,它实现了自举。启动Java编译器需要Java虚拟机(默认是HotSpot虚拟机,使用C++编写)作为宿主环境。
javac编译器的编译过程,主要涉及到了这样一些关键概念和核心算法:
- 词法分析阶段:基于有限自动机的理论实现。在处理标识符与关键字重叠的问题上,采用了先都作为标识符识别出来,然后再把其中的关键词挑出来的方式。
- 语法分析阶段:使用了自顶向下的递归下降算法、LL(k)方式以及多Token预读;处理左递归问题时,采用了标准的改写文法的方法;处理二元表达式时,采用了自底向上的运算符优先级解析器。
- 语义分析阶段:会分为多个小的阶段,且并不是顺序执行的,而是各阶段交织在一起。
- 语义分析阶段主要包含:ENTER(建立符号表)、PROCESS(处理注解)、ATTR(属性分析)、FLOW(数据流分析)、TRANSTYPES(处理泛型)、TRANSPATTERNS(处理模式匹配)、UNLAMBDA(处理 Lambda)和 LOWER(处理其他所有的语法糖,比如内部类、foreach 循环等)、GENERATE 阶段(生成字节码)等。在ATTR和FLOW这两个阶段,编译器完成了主要的语义检查工作。
- 注意:生成字节码是一个比较机械的过程,编译器只需要对 AST 进行深度优先的遍历即可。在这个过程中会用到前几个阶段形成的属性信息,特别是类型信息。
参考资料:
1. 关于注解的官方教程,参考[这个链接](https://docs.oracle.com/javase/tutorial/java/annotations/)。
1. 关于数据流分析的理论性内容,参考龙书(Compilers Principles, Techniques and Tools)第二版的9.2和9.3节。也可以参考《编译原理之美》 的第[27](https://time.geekbang.org/column/article/155338)、[28](https://time.geekbang.org/column/article/156878)讲,那里进行了比较直观的讲述。
1. 关于半格这个数学工具,可以参考龙书第二版的9.3.1部分,也可以参考《编译原理之美》的[第28讲](https://time.geekbang.org/column/article/156878)。
1. Java语言规范第六章,参考[Java虚拟机指令集](https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-6.html)。
## Java JIT编译器(Graal)
对于编译目标为机器码的Java后端的编译器来说,主要可以分AOT和JIT两类:如果是在运行前一次性生成,就叫做提前编译(AOT);如果是在运行时按需生成机器码,就叫做即时编译(JIT)。Java以及基于JVM的语言,都受益于JVM的JIT编译器。
在[JDK的源代码](https://hg.openjdk.java.net/jdk/jdk11/raw-file/tip/doc/building.html)中,你能找到src/hotspot目录,这是 JVM 的运行时:HotSpot虚拟机,它是用C++编写的,其中就包括JIT编译器。
Graal是Oracle公司推出的一个完全用Java语言编写的JIT编译器。Graal编译器有两个特点:内存安全(相比C++实现的Java JIT编译器而言);与Java配套的各种工具(比如ID)更友好、更丰富。
Java JIT编译器的编译过程,主要涉及到了这样一些关键概念和核心算法:
- 分层编译:C0(解释器)、C1(客户端编译器)、C2(服务端编译器)。不同阶段的代码优化激进的程度不同,且存在C2降级回C1的逆优化。
- IR采用了“节点之海(Sea of Nodes)”,整合了控制流图与数据流图,符合 SSA 格式,有利于优化算法的编写和维护。
- 两个重要的优化算法:内联优化和逃逸分析。
- 几个重要的数据结构:HIR(硬件无关的IR)、LIR(硬件相关的IR)、CFG(控制流图)。
- 寄存器分配算法:LinearScan。
**金句摘录:“编译器开发的真正的工作量,都在中后端。”**
参考资料:
1. GraalVM项目的[官方网站](https://www.graalvm.org/);Graal的[Github地址](https://github.com/oracle/graal);Graal项目的[出版物](https://github.com/oracle/graal/blob/master/docs/Publications.md)。
1. 基于图的IR的必读论文:程序依赖图-[J. Ferrante, K. J. Ottenstein, and J. D. Warren. The program dependence graph and its use in optimization](https://www.cs.utexas.edu/users/less/reading/spring00/ferrante.pdf). July 1987;Click的论文-[A Simple Graph-Based Intermediate Representation](https://www.oracle.com/technetwork/java/javase/tech/c2-ir95-150110.pdf);介绍Graal IR的论文-[Graal IR: An Extensible Declarative Intermediate Representation](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.726.5496&rep=rep1&type=pdf)。
1. 关于优化算法:多态内联-[Inlining of Virtual Methods](http://extras.springer.com/2000/978-3-540-67660-7/papers/1628/16280258.pdf);逃逸分析-[Escape Analysis for Java](https://www.cc.gatech.edu/~harrold/6340/cs6340_fall2009/Readings/choi99escape.pdf);部分逃逸分析-[Partial Escape Analysis and Scalar Replacement for Java](http://www.ssw.uni-linz.ac.at/Research/Papers/Stadler14/Stadler2014-CGO-PEA.pdf)。
## Python编译器(CPython)
Python诞生于上个世纪90年代初,作者是荷兰计算机程序员吉多·范罗苏姆(Guido van Rossum)。Python语言的特点是:自身语法简单,容易掌握,强调一件事情只能用一种方法去做;具备丰富的现代语言特性,如OOP、FP等;其实现机制决定了易于集成C++扩展,不仅便于利用一些已有的、经典开源的高性能的C/C++库,同时也可以很方便地编写自己的C++扩展,实现一些高性能模块。
另外,Python使用了pgen这样的生成编译器的工具。pgen能够基于语法规则生成解析表(Parse Table),供语法分析程序使用。你可以通过修改规则文件来修改Python语言的语法,pgen能给你生成新的语法解析器。它是把EBNF转化成一个NFA,然后再把这个NFA转换成DFA。基于这个DFA,在读取Token的时候,编译器就知道如何做状态迁移,并生成解析树。Python用的是 LL(1) 算法。
CPython编译器编译器的编译过程,主要涉及到了这样一些关键概念和核心算法:
- 语法分析:首先是生成CST(Concret Syntax Tree,具体语法树),接着生成AST(抽象语法树)。CST的特点是完全基于源程序结构构建出树结构,它比AST啰嗦,但更精确地反映了语法推导的过程。而AST的特点是去掉了树结构上繁杂冗余的树枝节点,更简明扼要,更准确地表达了程序的结构。
- 语义分析:Python通过一个建立符号表的过程来做相关的语义分析,包括做引用消解等。Python语言使用变量的特点是不存在变量声明,每个变量都是赋值即声明,因此在给一个变量赋值时需要检查作用域,确认当前操作是给全局的变量赋值,还是在局部给声明新变量。
- 生成字节码:这个工作实际上包含了生成 CFG、为每个基本块生成指令,以及把指令汇编成字节码,并生成 PyCodeObject 对象的过程。另外,生成的字节码在最后放到解释器里执行之前,编译器还会再做一步窥孔优化工作。
- 运行时机制:Python的运行时设计的核心,就是PyObject对象,Python对象所有的特性都是从PyObject的设计中延伸出来的。其虚拟机采用了栈机的架构。
参考资料:
1. [python.org网站](https://www.python.org/):下载[3.8.1版本的源代码](https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tgz)。
1. GDB的安装和配置:参考[这篇文章](https://github.com/RichardGong/CompilersInPractice/edit/master/python/GDB.md)。
1. Python的[开发者指南](https://devguide.python.org/)网站。