如何为prel语言加载模块_WebAssembly 系列(第四部分)- 创建和使用WebAssembly模块...

3029d5712813af3b8f1e8526db34dc7c.png

原文链接

这是 WebAssembly 系列的第四篇,如果你没读过第一篇,我建议你读下。

WebAssembly 是除了 JavaScript 外另一种在网页上面运行的编程语言。在过去,假如想要在浏览器上面运行,与网页的各部分交互,唯一的选择是 JavaScript。

因此当人们谈到 WebAssembly 执行的快,是相对于 JavaScript 而言的。但并不意味着你必须在 WebAssembly 与 JavaScript 选其中一个。

事实上,我们期望开发者在同一个应用中,同时使用 WebAssembly 和 JavaScript。即使你不自己编写 WebAssembly,你也能从中受益。

WebAssembly 模块定义的函数可以在 JavaScript 中被调用。就像你从 npm 上面下载了 lodash 的模块,然后通过调用它的 API 调用它导出的函数,在未来你可以下载 WebAssembly 的模块。

让我们看下如何创建一个 WebAssembly 模块,然后如何在 JavaScript 中调用它。

WebAssembly 适合哪里?

在讲汇编速成课这一章,我讲了编译器是如何将高级语言转换为机器语言的。

820f86170e294a85dbb28b43ef6173d1.png

WebAssembly 可以融入到图中的哪一部分?

你可能会认为它是另一个目标汇编语言,这是对的,除了 x86 和 ARM 都对应于特定的机器架构。

当将你的代码在用户的机器上面通过 web 传送去执行的时候,你不知道你的代码会在哪个机器架构上面运行。

因此 WebAssembly 和其他汇编语言有点儿不同。它是一个概念机器的机器语言,并不是真实的物理机。

因此,WebAssembly 的指令有时候也被称为虚拟指令。它们相对于 JavaScript 源代码对于机器语言有一个更直接的映射。它们代表了普遍流行的硬件上面可以高效完成的一种交叉。但它们并不直接映射特定硬件的特定机器语言。

e40a96980b8c31a710a3c748dc8023a6.png

浏览器下载 WebAssembly。然后把 WebAssembly 编译为特定机器的汇编。

编译为 .wasm

被称为 LLVM 的编译工具链目前提供了对 WebAssembly 的大部分支持。

假设从 C 编译到 WebAssembly。用前端(此前端非彼前端) clang 将 C 转换为 LLVM 的中间表示形式。LLVM 可以理解这种中间表示,然后对此做一些优化。

从 LLVM 的中间表示到 WebAssembly ,我们需要后端(此后端也非彼后端,这里的前端后端指的是从 LLVM 编译工具链的不同阶段)支持。在 LLVM 项目中也有一个在开发中的后端。大多数工作在后端,很快就能完成,不过目前还没有实现。

有另一个被称为 Emscription 的工具,目前相对来说是比较容易使用的。它有自己的后端,可以通过编译到另外一个目标(asm.js)生成 WebAssembly,然后将 asm.js 再转换为 Web Assembly。它底层使用的 LLVM,你可以在 Emscripten 中在两个后端之间切换。

f684d91f75e5990995d87cd7afbf29df.png

Emscripten 包含额外的工具和库允许移植整个 C/C++ 代码库,比编译器的软件开发包(SDK)更多。例如,系统开发人员习惯于拥有一个可以读写的文件系统,因此 Emscripten 可以使用 indexedDB 模拟一个文件系统。

无论你使用的什么工具链,最后的结果都是一个以 .wasm 为后缀的文件。下面我将介绍更多的 .wasm 文件结构。首先,让我们看下如何在 js 中使用它。

在 JavaScript 中加载一个 .wasm 模块

.wasm 文件是 WebAssembly 模块,可以在 JavaScript 中加载。加载的过程有点儿复杂。

function fetchAndInstantiate(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 中的函数只能使用数值类型参数(整数或者浮点型)或者 数值类型返回值。

ead6afd8182f41f34d0fd6a269043d6f.png

对于其他一些数据类型比较复杂,比如字符串,不得不使用 WebAssembly 模块的内存。

如果你使用 JavaScript 比较多,可能对直接访问内存不太熟悉。性能更高的语言,比如 C,C++,Rust,倾向于手动管理内存。WebAssembly 模块内存模拟在其他语言中的堆。

为了做到这些,使用了 javascript 中的 ArrayBuffer。buffer 数组是字节码的数组。所以数组的索引作为内存地址。如果你想在 JavaScript 和 WebAssembly 之间传递字符串,你将字符转为等价的字符码。然后将它写入内存数组里。因为索引是整型的,所以索引可以传入到 WebAssembly 函数中。因此字符串的第一个字符所以可以作为指针。

66221c36c5c74d506f01342e62acc0e6.png

好像任何一个开发被 web 开发人员使用的 WebAssembly 模块的开发人员要创建一个模块包裹器(wrapper) ,你作为一个模块使用者不需要知道内存管理。

如果你想要学的更多,看下关于使用 WebAssembly 内存文档。

.wasm 文件的结构

如果你编写高级语言的代码,然后编译为 WebAssembly,你不需要知道 WebAssembly 模块结构是什么。但有助于理解基础。

如果你还没有准备好,我们建议读一读关于汇编那一篇(系列第三篇)。

这里有一个 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 .

b59b9a96459c01d8c56f12c187a0d601.png

代码是如何工作的:堆栈机

如果你想知道,下面是那些指令做的。

2a977e8be1f9305dffa66c33af7936fc.png

你可能注意到 add 这个操作并没有表明值来自哪里。这是因为 WebAssembly 是一个堆栈机的例子。意味在操作执行之前,所需要的值需要在栈上排列。

像 add 操作知道它们需要多少值。add 需要两个,它需要从栈顶获取两个值。这意味着 add 指令可以更少(一个字节),因为指令不需要指定源或者目的寄存器。这减少 .wasm 文件的大小,意味着花费更少的下载时间。

尽管 WebAssembly 定义涉及到堆栈机,并不是讲如何在物理机上工作的。当浏览器将 WebAssembly 转换为浏览器运行的机器的机器码,它使用寄存器。尽管 WebAssembly 代码没有指定 寄存器,它给了浏览器使用最多机器寄存器回收更多的灵活性。

模块的部分(sections)

除了 add42 函数本身,.wasm 文件中还有其他的。这些被称为部分(sections)。它们中的一些是任何一个模块必须的,另一些是可选的。

必须的:

  1. Type. 包含这个模块中定义的函数的函数签名
  2. Function. 模块中每个函数的索引
  3. Code. 这个模块中每个函数真实的函数体。

可选的:

  1. Export. 将函数,内存,表 和 全局变量导出到其他的 WebAssembly 模块和 JavaScript。这允许分别编译的模块动态链接到一起。This is WebAssembly’s version of a .dll.
  2. Import. 指定函数,内存,表和从另一个 WebAssembly 模块或者 JavaScript 导入的全局变量。
  3. Start. 一个在 WebAssembly 模块加载完自动执行的函数(有点儿像主函数)。
  4. Global. 为模块声明全局变量
  5. Memory. 定义模块将会用到的内存
  6. Table. 将值映射到外部 WebAssembly 的模块成为可能,例如,javascript 对象。这对于间接函数调用尤其有用。
  7. Data. 初始化导入或者本地内存。
  8. Element. 初始化导入和本地表

关于 sections 更多的知识,可以进入 sections 是如何工作的 深入了解。

接下来

现在你知道如何处理 WebAssembly 模块了,让我们知道为什么 WebAssembly 执行这么快。

译者注:一个可以在线体验 webAssembly 网站

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值