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

引言

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

图0:WebAssembly 到底处于编译阶段的哪个环节?

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

编译步骤

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

图1:WebAssembly 到底处于编译阶段的哪个环节?

预编译

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

词法分析

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

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

语法分析

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

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

语义分析

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

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

抽象语法树

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

图2:WebAssembly 到底处于编译阶段的哪个环节?

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

中间码

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

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

目标代码

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

图3:WebAssembly 到底处于编译阶段的哪个环节?

编译前端与后端

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

编译前端

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

编译后端

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

图4:WebAssembly 到底处于编译阶段的哪个环节?

编译工具

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 的势头。

Clang / LLVM

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

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

图5:WebAssembly 到底处于编译阶段的哪个环节?

WebAssembly 在编译环节的位置

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

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

图6:WebAssembly 到底处于编译阶段的哪个环节?

余下全文(1/3)

本文最初发表在www.infoq.cn,文章内容属作者个人观点,不代表本站立场。

分享这篇文章:

请关注我们:

发表评论

电子邮件地址不会被公开。 必填项已用*标注