小前端学编译原理

2,429 阅读5分钟

以前听尤大说懂编译原理就可以为所欲为,于是简单地研究了一点(nai)点(nai)的编译原理,感觉自己可以为所欲为了。。。

一般的编译流程

经典的《编译原理》一书当中,开篇大概讲解了关于编译原理的一些基本概念和编译器的基本结构:

基本概念

编译器: 一个编译器就是一个程序,它可以阅读某一种语言编写的程序,并把该程序翻译成为一个等价的、用另一种语言(目标语言)编写的程序。 解释器,另一种常见的语言处理器,它并不通过翻译的方式生成目标程序。从用户的角度看,解释器直接利用用户提供的输入执行源程序中指定的操作。

编译器基本结构

  1. 词法分析(lexical analysis) 词法分析器读入源程序中的字符序列,将他们组织为具有词法含义的词素,生成并输出代表这些词素的 词法单元(token) 序列。

  2. 语法分析(syntax analysis) 语法分析或称解析(parsing)。语法分析器由词法分析器生成的各个词法单元创建树形的中间表示。该中间表示给出了词法分析产生的词法单元流的语法结构。一个常用的表示法是语法树 (syntax tree) , 树中的每个内部结点表示一个运算,而该结点的子结点表示该运算的分量。

  3. 语义分析 (semantic anlaysis) 语义分析器使用语法树和符号表中的信息来检查源程序是否和语言定义的语义一致。 语义分析完成语法检查以及相关的自动类型转换。

  4. 中间代码生成

  5. 代码优化 机器无关的代码优化步骤试图改进中间代码,以便生成更好的目标代码。

  6. 代码生成 以源代码的中间表示形式为输入,并把他映射到目标语言。

流程图

虽说,一般把上面的流程看作基本的编译原理,但是,大部分编程语言的编译器都和这个流程有些不同。

JavaScript 引擎

这里参考了另外一本书 -- 《WebKit 技术内幕》。

从 C/C++语言说起。编译器会将我们编写的代码编译为本地代码(适合当前计算机运行的指令集)。

C++代码的编译过程

当需要执行程序时, CPU 可以直接执行这些本地代码,无需其他处理。

再来看看脚本语言 Python。一般地,当需要执行 Python 程序时,由解释器直接解释执行开发者写的代码。

解释器解释过程

接下来看看 Java。Java 首先是和 C++一样的编译阶段,不同的是编译生成了字节码。字节码的特点是与平台无关,可以运行在不同的操作系统上。 Java 的运行环境中,使用 Java 虚拟机加载字节码,然后:

  • 由解释器对字节码加以解释执行。
  • 现代 Java 虚拟机都引入了 JIT(just in time) 技术(即时编译器),可以把字节码转为本地代码然后执行。

java 代码的编译和执行过程

这里说了一个 JIT 的概念,百度了一下,大致特点如下:

  • 一般是给解释器用的一种优化策略;
  • 原理是把字节码转为本地代码,然后再执行本地代码;
  • 单单执行一段代码来看的话,JIT 并不比解释器快,解释器边解释边执行,而 JIT 还要生成代码;
  • JIT 之所以称为优化手段,是因为 JIT 可以存生成的本地代码,以便下次执行;
  • 解释器 和 JIT 搭配,效果更佳。

最后看看前端的主角 JavaScript。JavaScript 本是一个解释型脚本语言。因为早期是由解释器解释执行,即将源代码转为抽象语法树,然后在抽象语法树上解释执行。 在 JavaScript 引擎的发展中,为了提高性能,引入了 Java 虚拟机和 C++编译器中的众多技术。现在 JavaScript 引擎的执行过程大致是:

JavaScript 代码编译和执行过程

V8 引擎

关于 V8 的身世和众多的黑科技我们不去追究,反正一提到 V8 引擎,知道很牛逼就是了。 相对上面提到的典型的 JavaScript 引擎,V8 的编译采用了比较激进的方式,将抽象语法树通过 JIT 技术直接转换成本地代码。 这么做可以减少抽象语法树到字节码的转换时间,同时也带了一些问题:

  • 某些场景下,使用解释器更为合适,因为没有必要生成本地代码;
  • 放弃了在字节码阶段可以进行的一些性能优化。

对于这些问题,V8 也在解决:5.9 版本开始新增了一个 Ignition 字节码解释器。关于这个话题,下面引用一段话,更多细节可以看这里

Ignition + TurboFan 的组合,就是字节码解释器 + JIT 编译器的黄金组合。这一黄金组合在很多 JS 引擎中都有所使用,例如微软的 Chakra,它首先解释执行字节码,然后观察执行情况,如果发现热点代码,那么后台的 JIT 就把字节码编译成高效代码,之后便只执行高效代码而不再解释执行字节码。苹果公司的 SquirrelFish Extreme 也引入了 JIT。SpiderMonkey 更是如此,所有 JS 代码最初都是被解释器解释执行的,解释器同时收集执行信息,当它发现代码变热了之后,JaegerMonkey、IonMonkey 等 JIT 便登场,来编译生成高效的机器码。

参考

《编译原理》 很经典,很好的一本书,翻译很精准。比如,词法分析的结果 token,书中翻译为词法单元,但是国内很多的博客会都翻译为令牌,这就很令人费解。

《WebKit 技术内幕》 仅仅看了 JavaScript 引擎一段,感觉作者不太用心。有错别字,比如把整型写成了整形。最尴尬的是很多的语句不通。感觉是直接怼的谷歌翻译的结果。最骚的是,这是国人原著而非翻译书籍。。。 后来百度了一下,据说这本书的内容是根据作者的个人博客整理的。