MDN wants to learn about developers like you: https://qsurvey.mozilla.com/s3/MDN-dev-survey

使用WebAssembly JavaScript API

这篇翻译不完整。请帮忙从英语翻译这篇文章

如果您已经使用Emscripten等工具编译了另一种语言的模块,或者自己加载并运行代码,那么下一步是了解如何使用WebAssembly JavaScript API的其他功能。这篇文章告诉你你需要知道什么。

Note: 如果你不熟悉本文中提到到基础概念并且需要更多的解释,先阅读 WebAssembly concepts 然后再回来。

一个简单的例子

让我们通过一步一步的例子来了解如何在WebAssembly 中使用 Javascript API,和如何在网页中加载一个 wasm 模块。

Note: 你可以发现同样的代码在 webassembly-examples GitHub 仓库.

准备工作

  1. 首先需要一个 wasm 模块!下载 simple.wasm 文件到本机的一个新的目录下.
  2. 确保本机使用的是支持 webassembly 的浏览器。Firefox 52+ 和 Chrome 57+ 是默认支持 webassembly 的.
  3. 然后, 创建一个简单的HTML 文件命名为 index.html 和并且你的本机的 wasm 文件处于同一目录下 ( 如果你没有模板可以使用我们提供的 simple template ).
  4. 现在, 为了帮助我们理解发生了什么, 让我们来看看这个 wasm 模块的文本表示(也可以在 Converting WebAssembly format to wasm 见到):
    (module
      (func $i (import "imports" "imported_func") (param i32))
      (func (export "exported_func")
        i32.const 42
        call $i))
  5. 在第二行, 你将看到导入有一个两级命名空间 —— 内部函数 $i 是从 improts.imported_func 导入的. 编写要导入到wasm模块的对象时,我们需要在JavaScript中反映这个两级命名空间. 创建一个 <script></script> 节点在你的HTML 文件中, 并且添加下面的代码:
    var importObject = {
      imports: {
          imported_func: function(arg) {
            console.log(arg);
          }
        }
      };

如上所述,  我们在 imports.imported_func 中有我们导入的函数.

Note: 使用 ES6箭头函数 将会更加简洁:

var importObject = { imports: { imported_func: arg => console.log(arg) } };

具体哪种风格由你决定.

加载并使用 wasm 模块

当我们导入了对象后, 我们将获取 wasm 文件, 使其在 array buffer 可用, 然后就可以使用其导出的函数.

在第一个块下面添加以下代码到你的脚本中:

fetch('simple.wasm').then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, importObject)
).then(results => {
  results.instance.exports.exported_func();
});

Note: 我们已经非常详细地解释了这种语法如何工作通过 Loading and running WebAssembly code. 如果不确定,请回到那里进行复习。.

这样做的结果是执行我们导出的 WebAssembly 函数 exported_func,这样又调用了另一个我们导入的 JavaScript 函数 imported_func, 它将WebAssembly实例(42)中提供的值记录到控制台. 如果你保存实例代码并且在支持 WebAssembly 的浏览器中运行,你将看到此操作。

Note: WebAssembly 在 Firefox 52+ 和 Chrome 57+/latest Opera 是默认支持的(你也可以运行 wasm 代码 在 Firefox 47+ 通过将 about:config 中的 javascript.options.wasm flag 设置为 enabling , 或者在 Chrome (51+) 以及 Opera (38+) 通过访问 chrome://flags 并且将 Experimental WebAssembly flag 设置为 enabling.)

这是一个冗长的,令人费解的例子并且实现了很少的功能, 但它确实有助于说明这是可能的 —— 在 Web 应用中与 JavaScript 一起使用 WebAssembly 代码. 正如我们一直说的,  WebAssembly 并不旨在替代 JavaScript; 两者可以一起工作,借鉴对方的优势。

在 Developer tools 中查看 wasm 

在 Firefox 54+,  Developer Tool Debugger Panel 有用于公开网页中包含的任何 wasm 代码的文本表示的功能. 为了查看它们, 要查看它,您可以转到 Debugger Panel 然后单击 “xxx > wasm” .

从 Firfox 开始,除了将WebAssembly视为文本,开发者可以使用文本格式调试 (打断点, 检查调用堆栈, 单步调试等等.) WebAssembly 代码. 通过这个视频 WebAssembly debugging with Firefox DevTools 预览。

内存

在WebAssembly的低级内存模型中,内存被表示为称为 线性内存 的无类型字节的连续范围,通过模块中的加载和存储指令读取和写入。  在这个内存模型中, 任何加载或存储都可以访问整个线性存储器中的任何字节,这是忠实地表示C / C ++概念(如指针)所必需的。

然后,和原生 C/C++ 程序不同的是可用内存范围跨越整个进程,特定WebAssembly实例可访问的内存被限制在由WebAssembly Memory对象包含的一个特定的 —— 可能非常小的范围内。

在 JavaScript 中,内存实例可以被认为是可调整大小的ArrayBuffer,就像ArrayBuffers一样,一个Web应用程序可以创建许多独立的内存对象。 您可以使用 WebAssembly.Memory() 构造函数创建一个,它将参数作为初始大小和(可选)最大大小)。

我们通过一个快速的例子来开始探索。

  1. 创建另一个简单的 HTML 页面 (复制我们的 simple template) 并且命名为 memory.html。添加一个 <script></script> 节点到页面中。

  2. 在脚本的顶部添加下面的一行代码来创建一个内存实例:

    var memory = new WebAssembly.Memory({initial:10, maximum:100});

    初始和最大的单位是 WebAssembly pages ——这些页面的大小固定为64KB。这意味着上述内存实例的初始大小为640KB,最大大小为6.4MB。

    WebAssembly内存通过简单地提供一个返回ArrayBuffer的缓冲区getter / setter来显示它的字节。例如,要直接将42写入线性内存的第一个单词,你可以这样做:

    new Uint32Array(memory.buffer)[0] = 42;

    你也可以得到刚才的值通过:

    new Uint32Array(memory.buffer)[0]
  3. 现在尝试这个演示 —— 保存目前为止添加的内容,将其加载到浏览器中,然后尝试在JavaScript控制台中输入上述两行。

增加内存

一个内存实例的大小可以通过 Memory.prototype.grow() 来增加,再次以 WebAssembly pages 为单位指定参数:

memory.grow(1);

如果在创建内存实例时提供了最大值,则尝试超过此最大值将抛出 WebAssembly.RangeError 异常。 引擎利用这个提供的上限来提前预留内存,这样可以使调整大小更有效率。

Note: 由于 ArrayBuffer 的byteLength是不可变的,所以在成功 Memory.prototype.grow() 操作之后,缓冲区getter将返回一个新的ArrayBuffer对象 新的byteLength)和任何先前的ArrayBuffer对象变成“分离”,或者与先前指向的底层内存断开连接。

和函数一样,线性内存可以在模块内部进行定义或者导入。类似地,模块还可以可选地导出其内存。这这意味着JavaScript可以通过创建new WebAssembly.Memory 并将其作为导入或通过接收内存导出传递给WebAssembly实例的内存来访问(通过 Instance.prototype.exports).

更多涉及 Memory 的例子

Let’s make the above assertions clearer by looking at a more involved memory example — a WebAssembly module that sums an array of integers. You can find this at memory.wasm.

  1. make a local copy of memory.wasm in the same directory as before.

    Note: You can see the module’s text representation at memory.wat.

  2. Go back to your memory.html sample file, and fetch, compile, and instantiate your wasm module as before — add the following to the bottom of your script:

    fetch('memory.wasm').then(response =>
      response.arrayBuffer()
    ).then(bytes =>
      WebAssembly.instantiate(bytes)
    ).then(results => {
      // add your code here
    });
  3. Since this module exports its memory, given an Instance of this module called instance we can use an exported function accumulate() to create and populate an input array directly in the module instance’s linear memory (mem). Add the following into your code, where indicated:

    var i32 = new Uint32Array(results.instance.exports.mem.buffer);
    for (var i = 0; i < 10; i++) {
      i32[i] = i;
    }
    
    var sum = results.instance.exports.accumulate(0, 10);
    console.log(sum);

Note how we create the Uint32Array view on the Memory object’s buffer (Memory.prototype.buffer), not on the Memory itself.

Memory imports work just like function imports, only Memory objects are passed as values instead of JavaScript functions. Memory imports are useful for two reasons:

  • They allow JavaScript to fetch and create the initial contents of memory before or concurrently with module compilation.
  • They allow a single Memory object to be imported by multiple module instances, which is a critical building block for implementing dynamic linking in WebAssembly.

Note: You can find our complete demo at memory.html (see it live also) — this version uses the fetchAndInstantiate() function.

Tables

A WebAssembly Table is a resizable typed array of references that can be accessed by both JavaScript and WebAssembly code.  While Memory provides a resizable typed array of raw bytes, it is unsafe for references to be stored in a Memory since a reference is an engine-trusted value whose bytes must not be read or written directly by content for safety, portability, and stability reasons.

Tables have an element type, which limits the types of reference that can be stored in the table.  In the current iteration of WebAssembly, there is only one type of reference needed by WebAssembly code — functions — and thus only one valid element type.  In future iterations, more element types will be added.

Function references are necessary to compile languages like C/C++ that have function pointers.  In a native implementation of C/C++, a function pointer is represented by the raw address of the function’s code in the process’s virtual address space and so, for the safety reasons mentioned above, cannot be stored directly in linear memory.  Instead, function references are stored in a table and their indexes, which are integers and can be stored in linear memory, are passed around instead.

When the time comes to call a function pointer, the WebAssembly caller supplies the index, which can then be safety bounds checked against the table before indexing and calling the indexed function reference.  Thus, tables are currently a rather low-level primitive used to compile low-level programming language features safely and portably.

Tables can be mutated via Table.prototype.set(), which updates one of the values in a table, and Table.prototype.grow(), which increases the number of values that can be stored in a table.  This allows the indirectly-callable set of functions to change over time, which is necessary for dynamic linking techniques.  The mutations are immediately accessible via Table.prototype.get() in JavaScript, and to wasm modules.

A table example

Let’s looking at an simple table example — a WebAssembly module that creates and exports a table with two elements: element 0 returns 13 and element 1 returns 42. You can find this at table.wasm.

  1. Make a local copy of table.wasm in a new directory.

    Note: You can see the module’s text representation at table.wat.

  2. Create a new copy of our HTML template in the same directory and call it table.html.

  3. As before, fetch, compile, and instantiate your wasm module — add the following into a <script> element at the bottom of your HTML body:

    fetch('table.wasm').then(response =>
      response.arrayBuffer()
    ).then(bytes =>
      WebAssembly.instantiate(bytes)
    ).then(results => {
      // add your code here
    });
  4. Now let’s access the data in the tables — add the following lines to your code in the indicated place:

    var tbl = results.instance.exports.tbl;
    console.log(tbl.get(0)());  // 13
    console.log(tbl.get(1)());  // 42

This code accesses each function reference stored in the table in turn, and instantiates them to print the values they hold to the console — note how each function reference is retrieved with a Table.prototype.get() call, then we add an extra set of parentheses on the end to actually invoke the function.

Note: You can find our complete demo at table.html (see it live also) — this version uses the fetchAndInstantiate() function.

Multiplicity

Now we’ve demonstrated usage of the main key WebAssembly building blocks, this is a good place to mention the concept of multiplicity. This provides WebAssembly with a multitude of advances in terms of architectural efficiency:

  • One module can have N Instances, in the same way that one function literal can produce N closure values.
  • One module instance can use 0–1 memory instances, which provide the "address space" of the instance. Future versions of WebAssembly may allow 0–N memory instances per module instance (see Multiple Tables and Memories).
  • One module instance can use 0–1 table instances — this is the "function address space" of the instance, used to implement C function pointers. Future versions of WebAssembly may allow 0–N table instances per module instance in the future.
  • One memory or table instance can be used by 0–N module instances — these instances all share the same address space, allowing dynamic linking.

You can see multiplicity in action in our Understanding text format article — see the Mutating tables and dynamic linking section (TBD).

Summary

This article has taken you through the basics of using the WebAssembly JavaScript API to include a WebAssembly module in a JavaScript context and make use of its functions, and how to use WebAssembly memory and tables in JavaScript. We also touched on the concept of multiplicity.

See also

文档标签和贡献者

 此页面的贡献者: skyfore, xgqfrms-GitHub
 最后编辑者: skyfore,