阅读 123

WebAssembly进阶系列二:WebAssembly处于编译阶段哪个环节

导语:相信不少人听说过 WebAssembly,它是由 Google、Microsoft、Mozilla、Apple 等几家大公司合作发起的一个关于面向Web的通用二进制和文本格式的项目。现在就让我们一步步揭开WebAssembly的神秘面纱,并亲自动手将WebAssembly应用在实际业务中。

1. 引言

众所周知,无论是Chrome、Firefox、Safari、Edge还是其他浏览器,能够运行的语言就是Javascript。为了能够让其他语言的代码在浏览器中运行,WebAssembly被创造出来,详见《WebAssembly进阶系列二:WebAssembly是什么》。我们并不需要亲自编写WebAssembly的代码,唯一要做的就是把其他高级语言编译成WebAssembly即可,这样便能复用大量其他语言现有的代码。而且WebAssembly还拥有比JavaScript更好的性能,能够更快的加载和执行。

那为啥WebAssembly的性能就一定会比JavaScript好很多呢?具体原因还得看下它们到底是处于编译阶段哪个环节了。

2. 编译步骤

作为程序员的我们,每天都在用各种高级语言写源代码。但要让机器能读懂这些字符串代码,就得靠编译系统一步步把它们编译成目标代码。

(1) 预编译

预编译首先会处理源代码中那些以#开头(如#include、#define等)的预编译指令,在编译开始前就先对原始的代码文件进行调整。经过了预编译之后,你写的代码其实已经有了很大的变化。

(2) 词法分析

词法分析是计算机科学中将字符序列转换为单词(Token)序列的过程。进行词法分析的程序或者函数叫作词法分析器(lexical analyzer,简称lexer),也叫扫描器(Scanner),供语法分析器调用。

词法分析阶段是编译过程的第一个阶段,任务是从左到右以字符流的方式逐行扫描源代码,然后根据构词规则一一识别关键词、标志符、字面量、运算符等,并分割成一个个按顺序排列的标记Token。

(3) 语法分析

语法分析是根据某种给定的形式文法对由单词序列(如英语单词序列)构成的输入文本进行分析并确定其语法结构的一种过程。进行语法分析的程序或者函数叫作语法分析器(parser),供语义分析调用。

语法分析阶段是编译过程的一个逻辑阶段,任务是在词法分析分割出来的标记Token的基础上,将Token序列组合成各类语法短语如“程序”、“语句”、“表达式”等。

(4) 语义分析

语义分析是编译过程的一个最实质性的阶段,任务是对结构上正确的源代码进行上下文有关性质的审查,进行类型审查。

其实经过语法分析之后就能初步得到了抽象语法树,树上的每个节点都是一个表达式,但此时还不确定是否有意义。于是需要通过语义分析,遍历整个抽象语法树,把每个节点的表达式都标识类型,并且验证是否合法。

(5) 抽象语法树

抽象语法树(Abstract Syntax Tree,AST),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构,如下图所示:

从语法分析开始,就已经生成了初步的抽象语法树。经过了语义分析之后,抽象语法树又变得更加完善,自此最终版的抽象语法树AST已经建立完成。

(6) 中间码

经过前面几个阶段之后,我们已经有了最终版的AST。但是该AST并不能完美运行在各个硬件平台上,因为不同平台的汇编处理都是不一样的。

于是便在AST和多个平台的汇编代码中间,抽象出了一个中间码(Intermediate Representation),在中间码的设计里抹平了硬件平台造成的差异。中间码的强大之处在于跨平台,与语言无关。

(7) 目标代码

计算机是通过汇编指令来执行操作的,如移动到内存某个地址、位移多少个字节等。因此需要通过目标平台的汇编器,由AST转成的中间码生成目标平台的汇编代码,这些才是机器能读懂的目标代码。

3. 编译前端与后端

前面介绍了这么多编译步骤,其实我们可以把中间码当成一个分界线,中间码以前的环节叫做编译前端,中间码以后的环节叫做编译后端。

(1) 编译前端

编译前端包括预编译、词法分析、语法分析、语义分析、抽象语法树等,专门用来处理语言专属特性。虽然不同语言的词法关键字、语法规则、语义分析的函数类型校验可能都不一样,甚至某些语言都没有预编译这个环节,但每个语言都可以开发一套编译前端,按照标准生成的统一中间码就可以无缝对接给任意编译后端,这就是语言无关。

(2) 编译后端

编译后端这里只包含了目标代码生成部分,其实还应该包括将目标代码链接成为可执行文件的环节。编译后端专门负责处理各个平台的差异,根据不同语言生成的标准中间码,生成对应的目标代码,这就是平台无关。

4. 编译工具

(1) GCC

GCC(GNU Compiler Collection,GNU编译器套装),包含了编译前端与编译后端所有模块。其中,编译前端部分支持C、C++、Fortran、Pascal、Objective-C、Java等语言,编译后端支持x86、mips、Alpha、ARM、AVR、IA-64、SPARC、PowerPC等30多种平台。

GCC虽然被广泛的使用,但目前也面临了危机。后起之秀的Clang / LLVM,大有全面赶超GCC的势头。

(2) Clang / LLVM

Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C / C++ / Objective-C / Objective-C++ 的编译器。那为啥已经有了GCC后还要开发Clang呢?Clang相比于GCC有什么优势呢?因为Clang是一个高度模块化开发的轻量级编译器,它的编译速度快、占用内存小、非常方便进行二次开发。

而有了Clang这个编译器前端还不够,于是就跟LLVM这个编译器后端组合成一个完整的编译器套件,如下图所示:

5. WebAssembly在编译环节的位置

前面说了这么多,但还是不知道WebAssembly处于编译阶段哪个环节啊?心急吃不了热豆腐,凡事都要对基础知识有一定了解之后,才能茅塞顿开。

通过下图你便可以一目了然WebAssembly所处的位置,它能做到像Java字节码一样,一次编译到处运行,具有跨平台特性。以此同时,作为中间码的WebAssembly直接省略编译前端的步骤,而JavaScript需要实时编译,相比之下性能优势显著。

6. 写在最后

如果WebAssembly不出现,那么HTML、CSS、JavaScript将成为前端领域的事实汇编语言,浏览器最终会把它们作为“编译目标”。WebAssembly的出现则提供了一个更好的选择:接近原生的运算速度,不仅拥有开源、兼容性好、跨平台覆盖广等特性,还可以借此机会抛弃JavaScript的历史遗留问题,何乐而不为呢?

参考资料

  1. WebAssembly:解决 JavaScript 痼疾的银弹?
  2. WebAssembly的过去、现在和未来
  3. 扯淡:大白话聊聊编译那点事儿
  4. LLVM相比于JVM,有哪些技术优势?
  5. WebAssembly 系列(三)编译器如何生成汇编
  6. WebAssembly入门:将字节码带入Web世界
  7. JavaScript是如何工作的:与WebAssembly比较及其使用场景
  8. LLVM编译原理和使用

个人公众号:前端开发升值记


关注下面的标签,发现更多相似文章
评论