This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,211 @@
你好,我是编辑王惠。
阶段性的总结复习和验证成果是非常重要的。所以在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 进行深度优先的遍历即可。在这个过程中会用到前几个阶段形成的属性信息,特别是类型信息。
<img src="https://static001.geekbang.org/resource/image/3e/27/3e8d282f1479671407ab81b576fb0c27.jpg" alt="">
参考资料:
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。
**金句摘录:“编译器开发的真正的工作量,都在中后端。”**
<img src="https://static001.geekbang.org/resource/image/2b/6c/2b7478eyyf5422d8398a7ce3baac656c.jpg" alt="">
参考资料:
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 1987Click的论文-[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&amp;rep=rep1&amp;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编译器编译器的编译过程主要涉及到了这样一些关键概念和核心算法
- 语法分析首先是生成CSTConcret Syntax Tree具体语法树接着生成AST抽象语法树。CST的特点是完全基于源程序结构构建出树结构它比AST啰嗦但更精确地反映了语法推导的过程。而AST的特点是去掉了树结构上繁杂冗余的树枝节点更简明扼要更准确地表达了程序的结构。
- 语义分析Python通过一个建立符号表的过程来做相关的语义分析包括做引用消解等。Python语言使用变量的特点是不存在变量声明每个变量都是赋值即声明因此在给一个变量赋值时需要检查作用域确认当前操作是给全局的变量赋值还是在局部给声明新变量。
- 生成字节码:这个工作实际上包含了生成 CFG、为每个基本块生成指令以及把指令汇编成字节码并生成 PyCodeObject 对象的过程。另外,生成的字节码在最后放到解释器里执行之前,编译器还会再做一步窥孔优化工作。
- 运行时机制Python的运行时设计的核心就是PyObject对象Python对象所有的特性都是从PyObject的设计中延伸出来的。其虚拟机采用了栈机的架构。
<img src="https://static001.geekbang.org/resource/image/78/a6/7868ac14a4d585a1897c6aece4c3dda6.jpg" alt="">
参考资料:
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/)网站。
<li>pgen的工具程序[Parser/pgen](https://github.com/python/cpython/blob/3.9/Parser/pgen/pgen.py)。<br>
由于CPython最新的Master分支上的代码调整此处pgen的链接地址调整为CPython3.9版本分支上的pgen相关代码。</li>
1. Python的[字节码的说明](https://docs.python.org/zh-cn/3/library/dis.html#python-bytecode-instructions)。
1. Python的[内置类型](https://docs.python.org/3.8/library/stdtypes.html)。
## JavaScript编译器V8
V8是谷歌公司在2008年推出的一款JavaScript编译器主要由C++编写而成。V8主要应用于Chrome浏览器后来也被开源社区中诸如Node.js等项目所使用。其最为突出的特点就是“快”由于JavaScript是在浏览器下载完页面后马上编译并执行它对编译速度有更高的要求。因此V8采用了一系列技术手段优化编译和启动阶段运行速度。
在设计上V8结合了分阶段懒解析、空间换时间等设计思路突出了解析、启动阶段运行的时间开销。
- 对源代码的Parse进行了流Stream化处理也就是边下载边解析。
- 预解析PreParse处理也就是所谓懒解析最核心的设计思想每个JS函数只有被执行的时候才会解析函数体的内部逻辑。
另外V8的很多地方体现出了与Java编译器异曲同工之处。比如它将JavaScript源代码的编译分为了由Ignition字节码解释执行和TurboFan的JIT编译机器代码执行两部分组成类似于Java编译器的字节码解释执行和Graal优化编译后执行两阶段TurboFan编译器的IR也采用了Sea of Nodes这一点类似于Java的Graal编译器且也涉及到了内联优化和逃逸分析算法。
其运行方式分为两类:
- 常规情况下Ignition字节码解释执行
- 编译器判定热点代码TurboFan JIT编译成机器码执行并且TurboFan会依赖一些Ignition解释执行过程中的运行时数据来进行进一步优化使机器码尽可能高效。
因为JavaScript是动态类型语言因此对函数参数类型的推断以及针对性优化是一个V8的核心技术。V8涉及到的其他优化算法有
- 隐藏类Hidden Class。相同“形状”的JS对象会被以同一个隐藏类维护其数据结构。
- 内联缓存Inline Caching。针对隐藏类查找属性值时的指针偏移量进行内联缓存这属于结合隐藏类技术做进一步性能的优化。
<img src="https://static001.geekbang.org/resource/image/yy/05/yyf5a620aa7bbe31f710c3351f987d05.jpg" alt="">
参考资料:
1. V8项目的[官网](https://v8.dev/)以及V8的源代码-[官方文档](https://v8.dev/docs/build)。
1. 了解V8的解析器为什么速度非常快[Blazingly fast parsing, part 1: optimizing the scanner](https://v8.dev/blog/scanner)[Blazingly fast parsing, part 2: lazy parsing](https://v8.dev/blog/preparser)。
1. 了解Ignition的设计[Ignition Design Doc](https://docs.google.com/document/d/11T2CRex9hXxoJwbYqVQ32yIPMh0uouUZLdyrtmMoL44/mobilebasic)宫老师在Github上也放了一个[拷贝](https://github.com/RichardGong/CompilersInPractice/blob/master/v8/Ignition%20Design%20Doc.pdf)。
1. 了解Ignition的字节码[Understanding V8s bytecode](https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775)。
1. V8的指针压缩技术[Pointer Compression in V8](https://v8.dev/blog/pointer-compression)。
1. 介绍V8基于推理的优化机制[An Introduction to Speculative Optimization in V8](https://ponyfoo.com/articles/an-introduction-to-speculative-optimization-in-v8)。
1. 关于Ignition字节码做优化的论文[Register equivalence optimization](https://docs.google.com/document/d/1wW_VkkIwhAAgAxLYM0wvoTEkq8XykibDIikGpWH7l1I/edit?ts=570d7131#heading=h.6jz9dj3bnr8t)宫老师在Github上也放了一份[拷贝](https://github.com/RichardGong/CompilersInPractice/blob/master/v8/Ignition_%20Register%20Equivalence%20Optimization.pdf)。
## Julia的编译器
Julia语言最初发行于2012年其最初是为了满足高性能数值分析和计算科学的需要而设计的。Julia同时兼具了静态编译型和动态解释型语言的优点一方面它的性能很高可以跟Java和C语言媲美另一方面它又是动态类型的编写程序时不需要指定类型。
Julia编译器的特点是
- 作为动态类型语言却能拥有静态类型语言的执行速度最关键的原因是使用了LLVM作为编译器后端针对动态类型的变量在运行时由编译器JIT编译生成多个版本的目标代码保证程序的高性能
- 由C、C++、Lisp和Julia四种语言开发而成编译器前端主要采用Lisp实现
- 考虑到对函数式编程的支持有别于面向对象编程的“单一分派”Julia的编译器提供了“多重分派”的功能。
Julia编译器的编译过程主要涉及到了这样一些关键概念和核心算法
- Julia的编译器也是采用了递归下降算法来实现语法分析。
- 其内部IR采用了SSA格式主要作用是完成类型推断和内联优化。
- Julia的IR会被转化成LLVM的IR从而进一步利用LLVM的功能。在转换过程中会用Julia的内建函数这些内建函数代表了Julia语言中抽象度比较高的运算功能。
<img src="https://static001.geekbang.org/resource/image/06/e5/060397e2f5de051bf9474f2725f277e5.jpg" alt="">
参考资料:
1. LLVM的[官网](https://llvm.org/)以及LLVM的[源代码](https://github.com/llvm/llvm-project)。
1. Julia的开发者文档中有对如何使用LLVM的介绍[Working with LLVM](https://docs.julialang.org/en/v1/devdocs/llvm/)。
1. 对LLVM中的各种Pass的介绍[LLVMs Analysis and Transform Passes](https://llvm.org/docs/Passes.html)。
1. 《编译原理之美》的[第25讲](https://time.geekbang.org/column/article/153192)和[第26讲](https://time.geekbang.org/column/article/154438)宫老师对LLVM后端及其命令行工具做了介绍并且还手工调用LLVM的API示范了针对不同的语法结构比如if结构应该如何生成LLVM IR最后即时编译并运行。你可以参考一下。
## Go语言编译器gc
Go语言是Google开发的一种静态强类型、编译型、并发型并具有垃圾回收功能的编程语言又名Golang。Go广泛应用于Google的产品以及许多其他组织和开源项目其创建的初衷就是主要面向于部署于大量服务器之间的分布式程序也就是我们今天所说的“云”。因此Go的主要优势聚焦于服务端高并发场景。
Go语言编译器的特点是
- gc编译器除了少量标准库的内容是用汇编写的以外其绝大部分逻辑都是用Go语言本身写的因此实现了较为完整的自举Bootstraping从前端到后端的整个流程都使用Go语言实现在编程语言中是较为罕见的
- 教科书级别的设计,源代码完全自举、代码干净利索,因此非常适合作为学习参考。
Go语言编译器的编译过程主要涉及到了这样一些关键概念和核心算法
- 编译器前端gc编译器的词法分析和语法分析使用的都是非常经典、传统的算法如手写的递归下降算法、处理二元表达式时使用操作符优先级算法。
- 中间代码阶段SSA格式的IR基于CFG的IR利于“死代码”的发现与删除多遍Pass的优化框架。
- 机器码生成阶段线性扫描算法官方的gc编译器并没做指令重排这是基于编译过程中时间开销的考虑。
<img src="https://static001.geekbang.org/resource/image/b4/47/b4d6d2e094c9d2485303065945781047.jpg" alt="">
参考资料:
1. 介绍gc编译器的主要结构[Introduction to the Go compiler](https://github.com/golang/go/blob/release-branch.go1.14/src/cmd/compile/README.md)官方文档。
1. 介绍gc编译器的SSA[Introduction to the Go compilers SSA backend](https://github.com/golang/go/blob/release-branch.go1.14/src/cmd/compile/internal/ssa/README.md)官方文档。
1. Go compiler internals: adding a new statement to Go - [Part 1](https://eli.thegreenplace.net/2019/go-compiler-internals-adding-a-new-statement-to-go-part-1/)、[Part2](https://eli.thegreenplace.net/2019/go-compiler-internals-adding-a-new-statement-to-go-part-2/)。在这两篇博客里作者做了一个实验如果往Go里面增加一条新的语法规则需要做哪些事情。我们能够很好地、贯穿性地了解一个编译器的方法。
1. 介绍gc编译器的SSA优化规则描述语言的细节[Go compiler: SSA optimization rules description language](https://quasilyte.dev/blog/post/go_ssa_rules/)。
1. 介绍Go汇编的细节[A Primer on Go Assembly](https://github.com/teh-cmc/go-internals/blob/master/chapter1_assembly_primer/README.md)和[A Quick Guide to Gos Assembler](https://golang.org/doc/asm)。gc编译器采用的汇编语言是它自己的一种格式是“伪汇编”。
## MySQL的编译器
MySQL是一个开放源码的关系数据库管理系统原开发者为瑞典的MySQL AB公司后几经辗转目前归属于Oracle旗下产品。在过去MySQL性能高、成本低、可靠性好因此成为了最流行的开源数据库。SQL可以称得上是最成功的DSL特定领域语言之一MySQL中的SQL解析模块则是这门DSL的非常具有可参考性的一个实现。MySQL使用C++编写有少量几个代码文件是用C语言编写的。
MySQL的编译器的特点是
- SQL作为DSL中最具有代表性的一种语言学习它的编译器的实现可以为我们自己设计面向业务的DSL提供参考。
- 手写的词法分析、用工具bisonGNU的yacc生成的语法分析。
- 基于LALR处理二元表达式。
- 中后端优化方面MySQL是解释执行并没有做太多的机器相关优化在机器无关优化方面除了一些编译领域传统的优化技术之外还做了一些针对数据库特定场景的优化方式。
MySQL的编译器的编译过程主要涉及到了这样一些关键概念和核心算法
**词法分析和语法分析**
- 词法分析:手写的词法分析器。
- 语法分析由bison生成。bison是一种基于EBNF生成语法分析程序的工具可视为GNU版的yacc。
- bison支持的语法分析算法LALR算法。
**语义分析**
- MySQL中一些重要的数据结构THD线程对象、Table_ident对象。
- 上下文处理基于contextualize的上下文处理。
- 基于数据库“业务逻辑”的引用消解:库名、表名、列名、入口、查找和作用域(子查询)
**机器无关优化**
- 典型的优化:常量传播、死代码消除、常量折叠。
- 针对数据库场景的优化产生执行计划、生成JOIN对象。
**机器相关优化**
- MySQL实际上对表达式是解释执行所以并没有真正意义上做任何机器相关优化。
- 列举了PG另一种类似MySQL的DB有通过LLVM的JIT优化。
<img src="https://static001.geekbang.org/resource/image/bf/f0/bf7c421b3afa2a84dbdc8f3ae0c357f0.jpg" alt="">
参考资料:
1. 下载[MySQL的源代码](https://github.com/mysql/mysql-server)跟踪MySQL的执行过程要用Debug模式编译MySQL具体步骤可以参考这篇[开发者文档](https://dev.mysql.com/doc/internals/en/cmake.html)。
1. MySQL的内行手册[MySQL Internals Manual](https://dev.mysql.com/doc/internals/en/)。它能给我们提供一些重要的信息,但文档内容经常跟源代码的版本不同步,比如介绍源代码的目录结构的信息就过时了。需要注意一下。
1. bison的[手册](http://www.gnu.org/software/bison/manual/)。
1. 如果要加深对MySQL内部机制的了解宫老师推荐了两本书OReilly的《Understanding MySQL Internals》以及《Expert MySQL》。

View File

@@ -0,0 +1,8 @@
你好,我是宫文学。
到这里我们的课程就已经更新一半了今天我们来进行一场期中考试。我出了一套测试题共有5道判断题、5道多选题满分100核心考点都出自前面的“预备知识”模块和“真实编译器解析”模块。
我建议你来认真地做一下这套题目,检验一下自己的学习效果。答完题之后,你也可以回顾试卷的内容,对不太理解或答错的问题,进行深入思考和学习。如果有不明白的,欢迎随时在留言区提问,我会知无不言。还等什么?一起来做下这套题吧!
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=202&amp;exam_id=539)

View File

@@ -0,0 +1,94 @@
你好,我是编辑王惠。在处理这门课的留言时,我注意到易昊同学一直在跟随着宫老师的脚步,学习和实践编译原理的相关知识,留言的内容十分有见地、提出的问题也能看出是经过了他深入的思考。同时,咱们这门课也具有很强的互动性,所以我邀请他来和我们分享一下他的心得体会。
Hi我是易昊目前在武汉做Android开发已经工作12年了。很高兴能在这里跟你分享关于我学习编译原理的一些心得体会。
## 为什么我要再学编译原理?
首先我想给你解释一下我为什么会起“程序员不止有Bug和加班还有诗和远方”这样一个标题呢
这是因为作为一名应用开发者我经常会觉得自己只是在和源源不断的Bug以及项目进度作斗争日常工作好像已经无法给我带来技术上的成就感了。但我能肯定的是我对于技术的情怀并没有消失。我也认为我不应该只满足于完成日常的普通开发任务**而是应该去做点更有挑战性的事情,来满足自己精神上的追求**。
那么,我为什么会选择学习编译技术呢?
首要的原因,是这门课的内容不像编程语言、数据结构那样清晰直观。加上在大学时期,学校安排的课时较短,只有半个学期,自己又没有对它足够重视起来,导致这门课只学了个一知半解,从而造成自己对计算机底层工作原理没有掌握透彻,留下了遗憾。
《程序员的自我修养链接、装载与库》里有句话让我印象深刻“真正了不起的程序员对自己程序的每一个字节都了如指掌。”虽然说得有点夸张但一个优秀的程序员确实应该理解自己的程序为什么能在计算机上运行起来。而不是在写完代码后忐忑不安地看着IDE在进行编译等终于能运行起来时大喊一声“哇能编译运行了好神奇”。所以工作了几年之后我一直想找机会能够弥补一下大学时期的遗憾把编译知识学扎实。
另外,**编译技术的巨大挑战性,也是我想重拾学习的重要原因之一**。
你可能听过一个段子:程序员的三大浪漫,是自己实现编译器、操作系统和渲染引擎。
其实一开始,我并不理解为什么编译器会在这其中,我猜大概是因为特别困难吧。
我刚接触编程的时候觉得能学好C、Java这样的编程语言已经很不容易了更何况要自己去用编译器实现一门语言。
而且编译原理这门课中还有相当多比较深奥、晦涩的理论想要掌握起来非常困难它完全不像学习普通技术那样几行代码就能运行一个hello world。同时你光有理论又完全不够编译器中包含了很多巧妙的工程化处理。比如会用抽象语法树来表示整个代码的语法结构在函数调用时参数和返回值在栈帧中的内存地址顺序等。
所以,不得不说,编译器是一个非常复杂的工程,编译原理是一个非常不容易消化、掌握的基础技术,让人望而生畏。
但是一旦掌握了之后,可以说就打通了计算机技术的任督二脉,因为你已经掌握了计算机运行中相当底层部分的原理,那么再去看其他技术就不会有什么大的障碍了。
而且在工作中即使没有什么机会需要自己去创造语言或者写编译器编译原理对我也很有帮助。举个例子吧Android有很多动态化的技术像ReactNative、Weex都会使用JavaScript作为脚本语言来调用原生接口那么为了实现原生语言如Java、Objective-C和JavaScript的通信ReactNative和Weex都在框架中嵌入了JavaScriptCore这个组件因为JavaSciptCore包含了一个JavaScript的解释器可以在运行时执行JavaScript。
那么如果你也想在自己的项目中使用类似的动态化技术但又不愿意被ReactNative和Weex技术绑死就需要自己去剖析JavaScriptCore的工作原理甚至要去实现一个类似的脚本语言执行框架。
**而真的要自己去下载JavaScriptCore的源码来看你就会发现如果没有一定的编译原理知识作为基础是很难看懂的**。但是如果你具备了编译原理知识基础其实会发现这些源码里面也不外乎就是词法分析、语法分析、IR等这些都是编译原理中的常用概念和算法。
还有就是日常工作中会碰到的某些比较棘手的问题,**如果你不理解编译技术,可能就无法找到出现问题的根源**。
比如我早期的工作中在开发C++代码的时候经常会遇到链接时找不到符号的问题那个时候我对“符号是怎么产生的”并不理解所以遇到这类问题我就会在IDE或者代码里盲目地尝试修改。
后来重新学习了编译技术之后,我就理解了编译器在编译过程中会产生符号表,每个符号包含了符号的名称、类型、地址等信息。之所以出现这类问题,可能是依赖的静态库没有包含这些符号,或者是类型不正确,再或者是这些符号的地址无法被正确解析,所以我用相应的工具去检查一下静态库文件的符号表,一般就可以解决问题了。
你看,编译技术总能帮我解决一些,我之前挠破头都想不出解决方案的问题。到现在我工作了十几年以后,就会有一个越来越强烈的感悟:**最重要的还是底层知识**。以前我也曾经感慨过计算机技术发展之快各种技术层出不穷就拿Android技术来说各种什么插件化、组件化、动态化技术让人眼花缭乱要不就是今天谷歌在提倡用Kotlin明天又开始推Flutter了。
所以有一段时间我比较迷茫,“我究竟该学什么呢”,但我后来发现,虽然那些新技术层出不穷,但万变不离其宗,计算机核心的部分还是编译原理、操作系统那些底层技术。如果你对底层技术真正吃透了,再来看这些时髦的新技术,就不会感到那么神秘了,甚至是可以马上就弄明白它背后的技术原理。**原理都搞懂了,那掌握起来也就非常快了。**
## 我是怎么学习专栏的?
我开始意识到自己需要重新学习编译技术是在2015年当时为了能够在Android的原生代码里运行JavaScript脚本我依次尝试了WebView、JavaScriptCore、Rhino等方案觉得这种多语言混合开发的技术挺强大也许能够改变主流的应用开发模式于是就想继续研究这些框架是怎么工作的。但是我发现自己对编译原理知识掌握得不牢导致学习这些技术的时候有点无从下手。
那个时候还没有极客时间这样针对性较强的学习平台,我是自己买了些书,有权威的龙书和虎书,也有《两周自制脚本语言》这样的速成书籍,但总是不得要领。
像龙书、虎书这样的,主要花了大量的篇幅在讲理论。但面对工作和家庭的压力,又不允许我有那么大片的时间去学习理论,而且没有实践来练习的话,也很容易忘掉。
像速成书籍这样的,虽然实现了一个很简单的编译器,但它大量的篇幅是在讲代码的一些细节,原理又讲得太少,知其然而不知其所以然。后来我就没有继续深入地学下去了。
直到偶然地在极客时间上看到了《编译原理之美》这门课,我简单看了下目录之后,就立马买了下来,因为感觉非常实用,**既对理论部分的重点内容有深入浅出地讲解,也有与实际问题的紧密联系**。学完这门课后,我感觉有信心去尝试写一点东西了,正好临近春节,就计划着在春节期间**自己写些代码来验证学习的成果**。结果不成想遇到了疫情封城,因为没法复工,就索性在家自己照着龙书和课程中给出的思路,看看能否自己实现个编译器出来。
**结果我花了快两个月的时间真的写出来了一个简单的编译器能够把类似C语言风格的代码编译成汇编执行。**
回想那段时间,虽然疫情很让人焦虑,但当我全身心地投入到对编译技术的钻研时,可以暂时忘记疫情带来的困扰。通过写这个小项目,我算是对编译器的工作过程有了个切身的体会,还把多年未碰的汇编又重新拾起来投入使用,可以说是收获颇丰,疫情带给我的回忆也没有那么痛苦了,从另一个角度看,甚至还有一定的成就感。
这个简单的编译器项目完成了之后,就激发了我更大的兴趣。因为我毕竟只是实现了一个玩具型的编译器,那么工业界的编译器是如何工作的呢?
这个编译器我是用LL算法来实现的语法分析但龙书上还大篇幅地讲了LR、SLR、LALR那么实际中到底是使用LL算法还是LR算法呢
还有,我的编译器没有什么优化的功能,真实的编译器都有哪些优化措施呢?
这些问题吸引着我要去寻找答案。结果正巧,宫老师又推出了《编译原理实战课》,**深入浅出地讲解各大编译器的工作原理**,这可正好对我的胃口,于是又毫不犹豫地买下了。
上了几节课之后觉得收获很大特别是讲解javac和Graal编译器的部分。这两部分都给出了如何基于源码去剖析编译器的原理实际操作性很强**我跟着宫老师给出的步骤,下载和一步步地调试、跟踪源码**印象十分深刻。特别是宫老师介绍的javac在处理运算符优先级时引入了LR算法从而避免了教科书上介绍的当使用LL方式时为了消除左递归要写多级Tail函数的问题这些都让我对编译技术的实际应用理解得更加深刻了。在学习Graal时我也是花了不少的时间去配置Windows环境包括下载安装Kali Linux、OpenJDK等才终于把Graal跑起来。通过这样的实际操作体验到了“折腾”的乐趣对动手能力也是一种锻炼。
除此之外,**课程中还有丰富的流程图、类图和思维导图,因此我可以按图索骥,去研究自己感兴趣的知识点,不用再苦苦地从海量的源码中去大海捞针,学习效率得到了很大提升**。
## 如何更好地学习编译原理?
通过这段时间的学习后,我发现,编译原理其实并没有想象中的那么困难。我觉得计算机技术的一个特点就是像在搭积木,有时候只是知识点多、层次关系复杂而已。
编译技术尤其如此从前端到后端从词法分析到语法分析到语义分析、IR最后到优化。这些地方包含着各种知识点但这些知识也不是凭空变出来的而是环环相扣、层层叠加出来的。**因此你在学习编译技术时一定要静下心来,一点一点地去吃透里面的技术细节,并且还需要不断地总结和提炼,否则对知识的理解可能就不够透彻,浮于表面。**
**另外,你需要多去动手实践,特别是和算法相关的部分。**比如在编译器的语法分析阶段有个内容是计算First集合和Follow集合其实理解起来并不困难但它包含的细节不少容易算错所以需要在课后多去练习。
我就是在课下把宫老师布置的习题和龙书上相关的题目都算了一遍还写了计算First集合和Follow集合的程序。不过就算是做到了这些我感觉对这部分的理解还不够透彻但是我对编译技术的恐惧感已经消除了对后续进一步的深入挖掘也打下了基础。所以说静下心来学勤动手去练对学习编译原理这门课程来说非常重要。
我还有一个体会就是,**学习编译原理没有捷径可走**。因为编译技术是一个复杂而精密的工程它的知识是环环相扣的。比如说如果你对词法分析和语法分析的知识掌握得不够牢固不熟悉一些常用语法规则的推导过程那后面的IR、三地址代码、CFG等你就会学得一头雾水。
所以,我们只能够一步一个脚印地去学习。
当然了,如果你和我一样是一个有家有口的上班族,只利用碎片时间可能不好做到连续学习,那我建议你在学习课程的时候,可以稍微花点时间去**参考一下宫老师介绍的开源编译器代码**,比如[JavaCompiler的源码](https://hg.openjdk.java.net/jdk8/jdk8/langtools/)、[Graal的源码](https://github.com/oracle/graal)。这样就可以和课程中的内容结合起来。因为看代码和调试代码会更加直观,也更容易理解。
同时,**你也可以看看别人的代码设计**,有哪些地方是可以做成组件的,哪些函数是可以自己去实现来练手的。然后,你可以先给自己定一个小目标,就是利用业余时间去完成这些组件或者函数的开发,因为单个组件的工作量并不是太大,因此还是可以尝试完成的。这样在巩固理论知识的同时,还能锻炼自己的动手能力,我想热爱编程的你,应该可以从中得到不少快乐。
我相信,只要最终坚持下来,你和我,都可以掌握好编译这门“屠龙”技术。