(转)WASM(WebAssember)快速了解第四篇——创建和使用WebAssembly模块

这是有关WebAssembly的系列文章的第四部分,如果您还没有阅读其他文章,我们建议从头开始

WebAssembly是一种在网页上运行JavaScript以外的编程语言的方法。过去,当您想在浏览器中运行代码以与网页的不同部分进行交互时,唯一的选择就是JavaScript。

因此,当人们谈论WebAssembly的速度很快时,Apple与Apple的比较就是JavaScript。但这并不意味着这是两种情况,要么您正在使用WebAssembly,要么您正在使用JavaScript。

实际上,我们期望开发人员将在同一应用程序中同时使用WebAssembly和JavaScript。即使您自己不编写WebAssembly,也可以利用它。

WebAssembly模块定义可从JavaScript使用的功能。因此,就像您今天从npm下载lodash之类的模块并调用其API中的函数一样,将来您将能够下载WebAssembly模块。

因此,让我们看看如何创建WebAssembly模块,然后如何从JavaScript中使用它们。

WebAssembly适合哪里?

在有关汇编的文章中,我谈到了编译器如何采用高级编程语言并将其转换为机器代码。

WebAssembly在哪里适合这张图片?

您可能会认为这只是目标汇编语言中的另一种。这是真的,只是每种语言(x86,ARM)对应于特定的计算机体系结构。

当您通过网络交付要在用户计算机上执行的代码时,您不知道代码将在您的目标体系结构上运行。

因此,WebAssembly与其他类型的程序集有所不同。它是概念性机器的机器语言,而不是实际的物理机器。

因此,WebAssembly指令有时称为虚拟指令。与JavaScript源代码相比,它们对机器代码的映射要直接得多。它们代表了可以在常见的流行硬件上有效完成的工作的一种交集。但是它们并不是直接映射到一种特定硬件的特定机器代码。

浏览器下载WebAssembly。然后,它可以使从WebAssembly到目标计算机的汇编代码的距离很短。

编译为.wasm

当前对WebAssembly的支持最多的编译器工具链称为LLVM。LLVM可以插入许多不同的前端和后端。

注意:大多数WebAssembly模块开发人员将使用C和Rust等语言进行编码,然后编译为WebAssembly,但是还有其他方法可以创建WebAssembly模块。例如,有一个实验性工具可以帮助您使用TypeScript构建WebAssembly模块,或者您可以直接在WebAssembly的文本表示形式中进行编码

假设我们想从C到WebAssembly。我们可以使用clang前端从C到LLVM中间表示。一旦进入LLVM的IR,LLVM就会理解它,因此LLVM可以执行一些优化。

为了从LLVM的IR(中间表示)过渡到WebAssembly,我们需要一个后端。LLVM项目中正在进行中的一项。后端是其中的大部分方式,应尽快完成。但是,要使其今天开始工作可能会很棘手。

还有另一个名为Emscripten的工具,此工具现在更易于使用。它有自己的后端,可以通过编译到另一个目标(称为asm.js)并将其转换为WebAssembly来生成WebAssembly。但是,它在后台使用LLVM,因此您可以在Emscripten的两个后端之间切换。

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中的函数只能将数字(整数或浮点数)用作参数或返回值。

对于任何更复杂的数据类型(例如字符串),您必须使用WebAssembly模块的内存。

如果您主要使用JavaScript,那么直接访问内存就不那么熟悉了。C,C ++和Rust等性能更高的语言往往具有手动内存管理功能。WebAssembly模块的内存模拟了您会在这些语言中找到的堆。

为此,它使用JavaScript中的ArrayBuffer。数组缓冲区是字节数组。因此,数组的索引用作内存地址。

如果要在JavaScript和WebAssembly之间传递字符串,请将字符转换为等效的字符代码。然后将其写入内存阵列。由于索引是整数,因此可以将索引传递给WebAssembly函数。因此,字符串的第一个字符的索引可以用作指针。

可能正在开发要由Web开发人员使用的WebAssembly模块的任何人都将围绕该模块创建包装器。这样,您作为模块的使用者就无需了解内存管理。

如果您想了解更多信息,请查阅有关使用WebAssembly的内存的文档

.wasm文件的结构

如果您要使用高级语言编写代码,然后将其编译为WebAssembly,则无需了解WebAssembly模块的结构。但这有助于了解基本知识。

如果您还没有的话,我们建议您阅读有关汇编文章(该系列的第3部分)。

这是一个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文件中还有其他部分。这些称为段。对于任何模块,某些段都是必需的,而某些则是可选的。

需要:

  1. Input,包含此模块中定义的功能和所有导入功能的功能签名。
  2. Function,为该模块中定义的每个函数提供索引。
  3. Code,该模块中每个功能的实际功能主体。

可选的:

  1. Export,使功能,内存,表和全局变量可用于其他WebAssembly模块和JavaScript。这允许将单独编译的模块动态链接在一起。这是WebAssembly的.dll版本。
  2. Import,指定要从其他WebAssembly模块或JavaScript导入的函数,内存,表和全局变量。
  3. Start,加载WebAssembly模块时将自动运行的功能(基本上像主要功能一样)。
  4. Global,声明模块的全局变量。
  5. Memory。定义此模块将使用的内存。
  6. Table,可以映射到WebAssembly模块外部的值,例如JavaScript对象。这对于允许间接函数调用特别有用。
  7. Data,初始化导入的或本地的内存。
  8. Element,初始化导入的表或本地表。

有关各节的更多信息,这是这些节的工作原理的深入解释

接下来的是

既然您知道如何使用WebAssembly模块,那么让我们看看为什么WebAssembly很快

转自:https://hacks.mozilla.org/2017/02/creating-and-working-with-webassembly-modules/

posted @ 2019-10-14 21:26  重设代码的天空  阅读(2234)  评论(0编辑  收藏  举报