- 原文地址:Creating and working with WebAssembly modules
- 原文做者:本文已获做者 Lin Clark 受权
- 译文出自:掘金翻译计划
- 译者: xilihuasi
- 校对者:Tina92、zhouzihanntu
这是 WebAssembly 系列文章的第四部分。若是你还没阅读过前面的文章,咱们建议你从头开始。javascript
WebAssembly 是一种不一样于 JavaScript 的在 web 页面上运行程序语言的方式。之前当你想在浏览器上运行代码来实现 web 页面不一样部分的交互时,你惟一的选择就是 JavaScript。前端
所以当人们谈论 WebAssembly 运行迅速时,合理的比较对象就是 JavaScript。但这并不意味着你必须在 WebAssembly 和 JavaScript 两者中选择一个使用。java
事实上咱们但愿开发者在同一应用中同时使用 WebAssembly 和 JavaScript。即便你不亲自写 WebAssembly 代码,你也可使用它。react
WebAssembly 组件定义的函数能够在 JavaScript 中使用。所以,就像如今你能够从 npm 上下载一个 lodash 这样的组件而且根据它的 API 调用方法同样,在将来你一样能够下载 WebAssembly 组件。android
那么让咱们看看如何建立 WebAssembly 组件,以及如何在 JavaScript 中使用这些组件吧。webpack
在上一篇关于汇编的文章里,我谈到过编译器怎么提取高级程序语言而且把它们翻译成机器码。ios
WebAssembly 对应这张图片的哪一个部分?git
你可能认为它只不过是又一个目标汇编语言。某种程度上是对的,不一样之处在于那些语言(x86,ARM)中每一个都对应一个特定的机器架构。github
当你经过 web 向用户的机器上发送要执行的代码时,你并不知道你的代码将要在哪一种目标架构上运行。web
因此 WebAssembly 和其余的汇编有些细微的差异。它是概念机的机器语言,而非真实的物理机。
正因如此,WebAssembly 指令有时也被称为虚拟指令。它们比 JavaScript 源码有更直接的机器码映射。它们表明一类能够在常见的流行硬件上高效执行的指令集合。可是它们并不直接映射某一具体硬件的特定机器码。
浏览器下载 WebAssembly 后,它就能从 WebAssembly 转成目标机器的汇编码。
LLVM 是当前对 WebAssembly 支持最好的编译工具链。不少先后端编译工具均可以嵌入 LLVM 中。
注:大部分 WebAssembly 组件开发者用 C 和 Rust 这样的语言编写代码,而后编译成 WebAssembly,但仍有其余的方法来建立 WebAssembly 组件。好比,有一个实验性的工具帮你使用 TypeScript 构建 WebAssembly 组件,或者你能够直接在 WebAssembly 的文本表示上编码。
好比说咱们想把 C 编译成 WebAssembly。咱们可使用 clang 编译器前端把 C 编译成 LLVM 中介码。一旦它处于 LLVM 的中间层,LLVM 编译它,LLVM 就能够展示一些性能优化。
要把 LLVM IR(中介码)编译成 WebAssembly,咱们须要一个后端支持。在 LLVM 项目中有一个这类后端正在开发中。这个后端项目已经接近完成而且应该很快就会定稿。然而,如今使用它还会有很多问题。
目前有一个稍微容易使用的工具叫 Emscripten。他有本身的后端,能够经过编译成其余对象(称为 asm.js)而后再转换成 WebAssembly 的方式来产生 WebAssembly。好像它底层仍旧使用 LLVM,所以你能够在 Emscripten 中切换这两种后端。
Emscripten 包含了许多附加工具和库来支持移植整个 C/C++ 代码库,所以它更像一个 SDK 而非编译器。举个例子,系统开发人员习惯于有一个文件系统用来读写,因此 Emscripten 可使用 IndexedDB 模拟一个文件系统。
忽略你已经使用的工具链,最后获得的结果就是一个后缀名为 .wasm 的文件。下面我将着重解释 .wasm 文件的结构。首先,咱们先看看怎样在JS中使用 .wasm 文件。
这个 .wasm 文件是一个 WebAssembly 组件,它能够在 JavaScript 中载入。在此情景下,载入过程稍微有些复杂。
functionfetchAndInstantiate(url, importObject) {
return fetch(url).then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(results =>
results.instance
);
}复制代码
你能够在咱们的文档中深刻了解这部份内容。
咱们致力于让这个过程变得更容易。咱们指望改进工具链,整合已存在的像 webpack 这样的模块打包工具以及相似 SystemJS 的动态加载器。咱们相信载入 WebAssembly 组件能够像载入 JavaScript 组件同样简单。
不过,WebAssembly 组件和 JS 组件有一个显著的区别。目前,WebAssembly 函数只能使用数字(整型或浮点型数字)做为参数和返回值。
对于更加复杂的数据类型,如字符串,你必须使用 WebAssembly 组件存储器。
像 C,C++,和 Rust 这些更高性能的语言倾向于手动管理内存。若是你大部分时间都在使用 JavaScript,也许对直接访问存储器的操做不熟悉。WebAssembly 组件存储器模拟了你在这些语言中会看到的堆。
为了实现这个功能,它使用了 JavaScript 中的类型化数组(ArrayBuffer)。类型化数组是存放字节的数组。数组的索引就是对应的存储器地址。
若是想要在 JavaScript 和 WebAssembly 中传递字符串,你须要把这些字符转换成他们的字符码常量。而后把这些写入存储器阵列。既然索引是整数,那么单个索引值就能够传入 WebAssembly 函数中。这样字符串中第一个字符的索引就能够被当成一个指针使用。
几乎全部想要开发供 web 开发者使用的 WebAssembly 组件的开发者,都会为组件建立一个包装器。这样以来,你做为组件的消费者并不须要了解内存管理。
若是想了解更多的话,查看咱们关于使用 WebAssembly 内存的文档。
若是你使用高级语言来编写代码而后把它编译成 WebAssembly,你没必要知道 WebAssembly 组件的结构。可是它能够帮助你理解其基本原理。
若是你以前没有了解这些基本原理,咱们建议你先阅读 汇编文章 (part 3 of the series)。
下面是一个 C 函数,咱们将把它转成 WebAssembly:
int add42(int num) {
return num + 42;
}复制代码
你可使用 WASM Explorer 来编译这个函数。
若是你打开 .wasm 文件(假设你的编辑器支持显示),你将看到相似这样的内容:
00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60
01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80
80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06
81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65
6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69
00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20
00 41 2A 6A 0B复制代码
这是组件的“二进制”表示法。我把二进制加上引号是由于它一般显示的是十六进制符号,但这很容易转换成二进制符号,或者人类可读的格式。
举个例子,下图是 num + 42
的几种表现形式。
若是你想知道的话,下图是执行的一些指令说明。
你可能注意到了 add
操做并无说明他的值应该从哪里来。这是由于 WebAssembly 是堆栈机的一个范例。这意味着一个操做所需的全部值在操做执行以前都在栈中排队。
例如 add
这类的操做指导它们须要多少值。若是 add
须要两个值,它将从栈顶取出两个值。这意味着 add
指令能够很短(单个字节),由于指令不须要指定源或者目的寄存器。这减小了 .wasm 文件的大小,也意味着下载的耗时更短。
即便 WebAssembly 就堆栈机而言是特定的,但那不是其在物理机上的工做方式。当浏览器把 WebAssembly 转化成其运行机器上对应的机器码时,将会用到寄存器。由于 WebAssembly 代码不指定寄存器,因此浏览器在当前机器上能更灵活的去使用最佳寄存器分配。
除了 add42
函数自身,.wasm 文件还有其余部分。那就是 sections。一些 sections 对任何组件都是必需的,而有一些是可选的。
必选项:
可选项:
更多关于 sections 的阐释,这有一篇深度好文解释这些 sections 如何运行。
如今你知道怎样使用 WebAssembly 组件了,让咱们看看为何 WebAssembly 这么快。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。