图说 WebAssembly(四):快速入门

发布时间:2019-06-17 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了图说 WebAssembly(四):快速入门脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

本文是图说 WebAsSEMbly 系列文章的第四篇。如果您还未阅读之前的文章,建议您从第一篇入手。

WebAssembly 是一种使得除 JavaScript 以外的编程语言也能运行在网页上的技
在过去,当我们需要通过编程来控制网页内容时,我们的选择只有 JavaScript 。

所以当大家都说 WebAssembly 运行速度很快时,其实它的比较对象就是指 JavaScript 。
不过这并不意味着你只能使用 JavaScript 和 WebAssembly 中的一种。
反而,更推荐的做法是同时使用它们。即便是你不写 WebAssembly ,你也是可以从它身上获得好处的。

WebAssembly 模块定义了可以被 JavaScript 调用的函数。
就像我们现在可以直接从 npm 下载 lodash 模块并调用其接口一样,未来我们也可以下载 WebAssembly 模块并使用它。

所以,今天我们来看看如何创建 WebAssembly 模块,以及如何使用 JavaScript 调用它。

角色

上一篇文章中,我们介绍了编译器如何把高级语言编译为机器码。

@H_406_23@图说 WebAssembly(四):快速入门

在上图中,WebAssembly 对应哪个角色呢?

聪明的你可能已经想到,它只不过是另一种目标汇编语言而已。
从某种意义上来说,这种想法是对的,只不过图中的 x86、ARM 等其实对应的是一种特定的计算机架构。

对于开发者来说,他所开发的代码是希望能够运行在互联网上所有用户机器上的,但是他其实并不知道运行这些代码的机器属于哪种架构。

所以 WebAssembly 跟汇编相比还是有略微不同之处。
它面向的是一种概念上机器的机器语言,而不是一种真实存在的物理机器。

这也就导致了 WebAssembly 指令是一种虚拟指令
与 JavaScript 码相比,虚拟指令跟机器码的映射来得更为直接。
它们表示一种可以在普遍流行机器上高效使用的指令集合。但同时它们也不会直接映射到特定的机器码。

图说 WebAssembly(四):快速入门

浏览器会下载 WebAssembly,然后把它变成目标机器的汇编。

编译

目前对 WebAssembly 支持最多的编译器工具链称为 LLVM 。有很多不同的编译器前端和后端都在使用 LLVM 。

注意: 大多数的 WebAssembly 模块开发者都会使用 C 和 Rust 这样的语言,然后编译为 WebAssembly,但是也有其他方式创建 WebAssembly 模块。比如,有一个实验工具可以把 TyPEScript 编译为 WebAssembly 模块,更有甚者,
可以直接手写 WebAssembly 。

这里,假如我们想把 C 编译为 WebAssembly 。
我们可以使用 C 语言编译器前端把 C 代码编译为 LLVM 中间代码。一旦变成 LLVM 的中间代码,LLVM 就可以理解并分析代码,然后做一些优化。

为了把 LLVM 中间代码变成 WebAssembly,我们还需要一个编译器后端。刚好,LLVM 项目中确实有一个正在开发编译器后端,未来它应该是大部分人的共同选择,而且应该很快就要完成了。不过,现在用它的话还是相当棘手。

不过不用灰心,还有另一个工具称为 Emscripten,目前用起来会更加简单点。
它拥有自己编译器后端,可以把中间代码编译为 asm.js ,进而转化为 WebAssembly 。
不过它也支持 LLVM,因此我们也可以在 Emscripten 和其他后端之间相互切换。

图说 WebAssembly(四):快速入门

Emscripten 还包含了很多其他工具和库,允许开发者移植整个 C/C++ 代码,因此与其说它是编译器,其实它更像是软件开发套件(SDK)。

不管用什么工具链,最终的结果都是得到一个 .wasm 文件。后面我们会介绍 .wasm 文件的结构,不过首先让我们来看看如何在 JavaScript 中使用它。

加载

.wasm 文件就是 WebAssembly 模块,它可以直接使用 JavaScript 加载。
截止到目前,这种加载方式略微复杂。

function fetchAndInstantiate(url, importObject) {
    return fetch(url).then(res => res.arrayBuffer())
        .then(bytes => WebAssembly.instantiate(bytes, importObject))
        .then(results => results.instance);
}
想深入的话,可以参考这个MDN 文档

我们正在努力把这个过程变得更加简单。我们也希望能够把工具链变得更加友好,希望能够直接集成到诸如 webpack 或者 SystemJS 等打包器中。相信未来 WebAssembly 模块可以跟加载 JavaScript 模块一样简单好用。

不过,WebAssembly 模块和 JavaScript 模块之间有一个主要的不同之处。
当前,WebAssembly 模块中的函数只能使用数字作为参数或者返回值。

图说 WebAssembly(四):快速入门

对于其他任何更复杂的数据类型,如字符串,我们必须直接操作 WebAssembly 模块的内存。

如果你大部分的时间都在使用 JavaScript,那么你可能对直接操作内容不太熟悉。
像 C、C++ 和 Rust 这些高性能的语言,它们都必须手动管理内存。
WebAssembly 模块的内存就模拟了这些语言的堆内存。

为了能够操作内存,我们需要使用 JavaScript 中的 ArrayBuffer
它是字节数组,所以它的索引当做内存地址来使用。

如果想要在 JavaScript 和 WebAssembly 之间传递字符串,那么必须先把字符串转为等效的字符码,然后写入 ArrayBuffer。由于数组索引是整数,所以索引可以传递给 WebAssembly 函数。这样,索引就变成了指向字符串首个字符的指针了。

图说 WebAssembly(四):快速入门

不过大部分情况下,WebAssembly 模块开发者都会把模块做友好地封装。此时,模块的使用者可能就没必要知道其内部是如何管理内存的了。

如果你对内存管理感兴趣,可以查看 MDN 文档

结构

如果你编程使用的是高级语言然后编译为 WebAssembly,那其实你没必要了解 WebAssembly 模块的结构,不过它可以帮你理解基础信息。

下面是一个 C 函数,我们将把它编译为 WebAssembly 。

int add42(int num) {
    return num + 42;
}

你可以使用 WasmExplorer来编译这个函数。

打开编译好的 .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 的模样:

图说 WebAssembly(四):快速入门

运行

你可能会对上图中的内容感到疑惑,下面我们把这些指令的作用标注出来。

图说 WebAssembly(四):快速入门

你可能已经注意到 add 操作并没有说要相加的两个数从哪里来。这是因为 WebAssembly 是一种称为堆栈机器。这意味着,操作码在操作之前,它所需的操作数已经在堆栈的队列当中了。

add 这样的操作码本身就知道它需要多少个操作数。因为 add 需要两个操作数,所以它会从堆栈的顶部取出两个值来作为操作数。
这样种设计中,add 指令可以变得很短,只占用一字节,因为它并不需要指定源和目标寄存器地址。这样就减小了 .wasm 文件的大小,从而更利于网络传输。

尽管 WebAssembly 是根据堆栈机器来设计的,但是这并不是它在真实物理机器上工作的方式。
当浏览器把 WebAssembly 编译为机器码时,它仍然会用到寄存器。不过,由于 WebAssembly 代码并不指定寄存器,所以浏览器能够更自由的为其指定最高效的寄存器。

组成

除了 add42 函数本身,.wasm 也还包含了其他内容。这些内容称为(Section)。有些段是任何模块都必须有的,有些则是可选的。

必选的有:

  • 类型:包含模块中函数和任何导入函数的函数签名。
  • 函数:给模块中的每个函数提供索引。
  • 代码:模块中每个函数的函数体。

可选的有:

  • 导出:使得函数、内存、表格和全局变量对其他模块和 JS 可访问。这可以使得模块可以单独编译,然后动态链接起来。
  • 导入:指定从其他模块或者 JS 中导入的函数、内存、表格和全局变量等。
  • 入口:模块加载时自动运行的函数。
  • 全局:模块中的全局变量声明
  • 内存:定义模块使用的内存。
  • 表格:用于映射不透明值,这些值不能在 WebAssembly 中表示或直接访问,例如 JS 的对象。
  • 数据:用于初始化导入的或本地的内存
  • 元素:用于初始化导入的或者本地的表格
更多的资料可参考 MDN 文档

结束

经过本文,相信你已经知道该如何使用 WebAssembly 模块了。下一篇文章我们将探索它为何如此快。

脚本宝典总结

以上是脚本宝典为你收集整理的图说 WebAssembly(四):快速入门全部内容,希望文章能够帮你解决图说 WebAssembly(四):快速入门所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。