- WebAssembly原理与核心技术
- 张秀宏
- 2367字
- 2021-04-05 03:42:58
1.2 Wasm简介
我们都知道,在计算机发展的早期阶段,程序是直接用机器语言编写的。虽然机器语言的执行效率是最高的,但是开发效率却极低,所以没过多久就诞生了基于符号的汇编语言,然后顺理成章地诞生了FORTRAN、C、C++等高级语言,后来Java、JavaScript、Python等更高级的语言陆续出现。但随着语言不断进化,开发效率越来越高,运行效率却越来越低。多亏了摩尔定律,运行效率下降在大多数情况下也不是什么大问题。现在,除了少数特殊情况,已经很少需要程序员直接编写汇编代码了。
当然,不管编程语言怎么演化,计算机唯一能够执行的仍然是机器语言。所以现代的高级编程语言要么是由编译器编译成机器码(Machine Code)然后执行,要么就是由解释器直接解释执行。前者可以理解为预先编译(Ahead-of-Time compilation,AOT),后者可以在执行时将部分“热”代码即时编译成机器码并执行,以提升性能,这就是所谓的即时编译(Just-in-Time Compilation,JIT)。
为了降低代码实现难度、提高可扩展性,现代编译器一般都会按模块化的方式设计和实现。典型的做法是把编译器分成前端(Front End)、中端(Middle End)和后端(Back End)。前端主要负责预处理、词法分析、语法分析、语义分析,生成便于后续处理的中间表示(Intermediate Representation,IR)。中端对IR进行分析和各种优化,例如常量折叠、死代码消除、函数内联。后端生成目标代码,把IR转化成平台相关的汇编代码,最终由汇编器编译为机器码。采用这种设计的编译器很好扩展:如果要开发新的语言,只需要新写一个编译器前端;如果要支持新的目标平台,只需要添加一个新的编译器后端。图1-1所示是简化后的现代编译器工作原理。
按字面意思理解,WebAssembly就是Web汇编,是为Web浏览器定制的汇编语言。既然号称汇编语言,那就得有点汇编语言的样子。
第一,层次必须低,尽量接近机器语言,这样浏览器才更容易进行AOT/JIT编译,以趋近原生应用的速度运行Wasm程序。第二,要适合作为目标代码,由其他高级语言编译器生成。
图1-1 现代编译器工作原理示意图
而要在浏览器运行,Wasm又必须满足其他一些要求。首先代码必须安全可控,不能像真正的汇编代码那样可以执行任意操作。然后,代码必须是平台无关的,这样才可以跨浏览器执行。Wasm还有很多特点,这里先不罗列了,后文会详细介绍。作为一种编译器目标语言,我们把Wasm也画进图1-1里,如图1-2所示。
图1-2 作为编译器目标的Wasm
如上所述,从高级语言编译器的角度来看,Wasm是目标代码。但从浏览器的角度来看,Wasm却更像IR,最终会被AOT/JIT编译器编译成平台相关的机器码。基于以上介绍的这些因素,Wasm最终采用了虚拟机/字节码技术,并且定义了紧凑的二进制格式。下面是Wasm技术的一些特点。
1.规范
Wasm技术目前有3份规范。其中《核心规范》描述了Wasm模块的结构和语义,这些完全是平台(比如浏览器)无关的,任何Wasm实现都必须满足这些语义。《JavaScript API规范》和《Web API规范》则是平台相关的,是专门为Web和浏览器定制的API规范。本书主要讨论Wasm核心语义,如无特别说明,后文出现的“Wasm规范”均指《核心规范》(1.1版)。
2.模块
模块是Wasm程序编译、传输和加载的单位。Wasm规范定义了两种模块格式:二进制格式和文本格式。如果和传统汇编语言做类比,那么Wasm模块的二进制格式相当于目标文件或可执行文件格式,文本格式则相当于汇编语言。使用汇编器可以把文本格式编译为二进制格式,使用反汇编器可以把二进制格式反编译成文本格式。本书第二部分将详细介绍Wasm模块的具体内容以及这两种格式。
(1)二进制格式
二进制格式是Wasm模块的主要编码格式,存储为文件时一般以.wasm为后缀。和Java类文件一样,Wasm二进制格式设计得非常紧凑。Wasm规范第5章对二进制格式进行了描述,本书将在第2章和第3章详细介绍Wasm二进制格式,并讨论如何实现二进制模块解码器。
(2)文本格式
文本格式主要是为了方便开发者理解Wasm模块,或者编写一些小型的测试代码(本书就使用了很多这样的代码)。Wasm文本格式可以简写为WAT(WebAssembly Text),存储为文件时一般以.wat为后缀。Wasm规范第6章对文本格式进行了描述,本书将在第4章详细介绍Wasm文本格式。
3.指令集
和Java虚拟机(Java Virtual Machine,JVM)一样,Wasm也采用了栈式虚拟机和字节码。Wasm规范第4章描述了指令的执行语义,本书将在第3章和第4章简要介绍Wasm指令集,然后在第5~9章详细介绍每一条指令并讨论如何实现Wasm解释器。在第13章,我们还会讨论AOT和JIT编译器。
4.验证
Wasm模块必须是安全可靠的,绝不允许有任何恶意行为。为了保证这一点,Wasm模块包含了大量类型信息,这样绝大多数问题就可以通过静态分析在代码执行前被发现,只有少数问题需要推迟到运行时进行检查。Wasm规范第3章描述了模块验证规则,附录7.3给出了代码验证算法和伪代码。本书将在第11章详细介绍Wasm模块验证逻辑,并讨论如何实现验证器。
前面介绍了Wasm模块的两种格式:二进制格式和文本格式。二进制格式主要由高级语言编译器生成,但也可以通过文本格式编译。文本格式可以由开发者直接编写,也可以由二进制格式反编译生成。其实除了规范定义的这两种格式,Wasm模块还有第三种格式:内存(in-memory)格式。Wasm实现(如解释器)通常会把二进制模块解码为内部形式(即内存格式,比如C/C++/Go结构体),然后再进行后续处理。模块的二进制、文本、内存这3种表现形式之间的关系如图1-3所示。
图1-3 Wasm模块的3种表现形式
从语义上讲,一个Wasm模块从二进制格式到最终被执行可以分为3个阶段:解码、验证、执行。解码阶段将二进制模块解码为内存格式;验证阶段对模块进行静态分析,确保模块的结构满足规范要求,且函数的字节码没有不良行为(比如调用不存在的函数等,详见第11章);执行阶段可以进一步分为实例化和函数调用两个阶段。Wasm模块的解码、验证、实例化阶段如图1-4所示。
图1-4 Wasm模块语义阶段
本书第二部分的第2章、第3章将详细介绍Wasm解码阶段。第三部分(第5~11章)主要从解释器的角度介绍模块的执行,其中第5~9章将详细介绍函数调用阶段,第10章将详细介绍Wasm实例化阶段,第11章将详细介绍Wasm验证阶段。第四部分的第13章将介绍AOT和JIT编译技术。