Cette traduction est en cours.

Si vous avez déjà compile un module depuis un autre langage en utilisant des outils comme Emscripten, ou charger et éxecuter vous-même le code, l'étape suivant est d'en apprendre plus à propos des autres fonctionnalités de l'API JavaScript. Cet article vous enseigne ce que vous aurez besoin de connaître.

Note: Si vous n'est pas familier avec les concepts de base mentionné dans cet article et vous avez besoin de plus d'explication, lisez  en premier WebAssembly concepts , puis revenez.

Un exemple simple

Exécutons un exemple étape par étape qui explique comment utiliser l'API JavaScript de WebAssembly et comme l'utilisez pour charger un module wasm dans une page web.

Note: Vous pouvez trouver des exemples de code dans notre repo GitHub  webassembly-examples.

Préparons l'exemple

  1. Premièrement nous avons besoin d'un module wasm ! Récupérez notre fichier simple.wasm et sauvegarder un copie dans un nouveau document sur votre machine local.
  2. Ensuite, assurez-vous d'utiliser un navigateur supportant WebAssembly. Firefox 52+ et Chrome 57+ supportent WebAssembly par défaut.
  3. Pour poursuivre, créons un simple fichier nommé index.html dans le même dossier que votre fichier wasm ( vous pouvez utilisez notre simple template si vous if you haven’t got one easily available).
  4. Maintenant, pour nous aider à comprendre ce qui se passe ici, regardons la représenation texutel de notre module wasm  (que nous rencontrons aussi dans Converting WebAssembly format to wasm):
    (module
      (func $i (import "imports" "imported_func") (param i32))
      (func (export "exported_func")
        i32.const 42
        call $i))
  5. Dans le deuxième ligne, vous allez voir que l'import a un espace de noms à deux niveaux  — la fonction interne $i est importé depuis  imports.imported_func. Nous devons refléter cet espace de noms a deux niveaux en JavaScript lors de l'écriture l'objet à importer dans le module wasm. Créez un élément <script></script> dans votre fichier HTML, puis ajouter le code suivant:
    var importObject = {
      imports: {
          imported_func: function(arg) {
            console.log(arg);
          }
        }
      };

Comme expliqué précédemment, nous avons importé notre fonction disponible via imports.imported_func.

Note: Ceci peut être fait de manière plus concise en utilisant la syntaxe des fonctions fléchées d'ES6:

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

Vous êtes libre de choisir le style qui vous convient.

Changeons notre module wasm et utilisons-le

Avec l'object d'importation préparé, nous allons récupérer notre fichier wasm, le rendre disponible dans un array buffer, puis l'utilise sa fonction exporté.

Ajouter ce qui suit à votre script après le premier bloc:

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

Note: We've already explained in great detail how this syntax works in Loading and running WebAssembly code. Refer back to there for a refresher if you are not sure about it.

The net result of this is that we call our exported WebAssembly function exported_func, which in turn calls our imported JavaScript function imported_func, which logs the value provided inside the WebAssembly instance (42) to the console. If you save your example code now and load it a browser that supports WebAssembly, you’ll see this in action!

Note: WebAssembly is enabled by default in Firefox 52+ and Chrome 57+/latest Opera (you can also run wasm code in Firefox 47+ by enabling the javascript.options.wasm flag in about:config, or Chrome (51+) and Opera (38+) by going to chrome://flags and enabling the Experimental WebAssembly flag.)

This is a convoluted, longwinded example that achieves very little, but it does serve to illustrate what is possible — using WebAssembly code alongside JavaScript in your web applications. As we’ve said elsewhere, WebAssembly doesn’t aim to replace JavaScript; the two instead can work together, drawing on each other’s strengths.

Viewing wasm in developer tools

In Firefox 54+, the Developer Tool Debugger Panel has functionality to expose the text representation of any wasm code included in a web page. To view it, you can go to the Debugger Panel and click on the “xxx > wasm” entry.

Starting soon in Firefox, in addition to viewing WebAssembly as text, developers will be able to debug (place breakpoints, inspect the callstack, single-step, etc.) WebAssembly using the text format. See WebAssembly debugging with Firefox DevTools for a video preview.

Streaming WebAssembly modules

New in Firefox 58 is the ability to compile and instantiate WebAssembly modules directly from underlying sources. This is achieved using the WebAssembly.compileStreaming() and WebAssembly.instantiateStreaming() methods. These methods are easier than their non-streaming counterparts, because they can turn the byte code directly into Module/Instance instances, cutting out the need to separately put the Response into an ArrayBuffer.

The following example (see our compile-streaming.html demo on GitHub, and view it live also) directly streams a .wasm module from an underlying source then compiles it to a WebAssembly.Module object. Because the compileStreaming()  function accepts a promise for a Response object, you can directly pass it a WindowOrWorkerGlobalScope.fetch() call, and it will pass the response into the function when it fulfills.

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

WebAssembly.compileStreaming(fetch('simple.wasm'))
.then(module => WebAssembly.instantiate(module, importObject))
.then(instance => instance.exports.exported_func());

The resulting module instance is then instantiated using WebAssembly.instantiate(), and the exported function invoked.

This example (see our instantiate-streaming.html demo on GitHub, and view it live also) does the same thing, but using instantiateStreaming() to cut out the need for a separate instantiate step:

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

WebAssembly.instantiateStreaming(fetch('simple.wasm'), importObject)
.then(obj => obj.instance.exports.exported_func());

Memory

In the low-level memory model of WebAssembly, memory is represented as a contiguous range of untyped bytes called Linear Memory that are read and written by load and store instructions inside the module.  In this memory model, any load or store can access any byte in the entire linear memory, which is necessary to faithfully represent C/C++ concepts like pointers.

Unlike a native C/C++ program, however, where the available memory range spans the entire process, the memory accessible by a particular WebAssembly Instance is confined to one specific — potentially very small — range contained by a WebAssembly Memory object.  This allows a single web app to use multiple independent libraries — each of which are using WebAssembly internally — to have separate memories that are fully isolated from each other.

In JavaScript, a Memory instance can be thought of as a resizable ArrayBuffer and, just as with ArrayBuffers, a single web app can create many independent Memory objects.  You can create one using the WebAssembly.Memory() constructor, which takes as arguments an initial size and (optionally) a maximum size.

Let’s start exploring this by looking at a quick example.

  1. Create another new simple HTML page (copy our simple template) and call it memory.html. Add a <script></script> element to the page.

  2. Now add the following line to the top of your script, to create a memory instance:

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

    The unit of initial and maximum is WebAssembly pages — these are fixed to 64KB in size. This means that the above memory instance has an initial size of 640KB, and a maximum size of 6.4MB.

    WebAssembly memory exposes its bytes by simply providing a buffer getter/setter that returns an ArrayBuffer. For example, to write 42 directly into the first word of linear memory, you can do this:

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

    You can then return the same value using:

    new Uint32Array(memory.buffer)[0]
  3. Try this now in your demo — save what you’ve added so far, load it in your browser, then try entering the above two lines in your JavaScript console.

Growing memory

A memory instance can be grown by calls to Memory.prototype.grow(), where again the argument is specified in units of WebAssembly pages:

memory.grow(1);

If a maximum value was supplied upon creation of the memory instance, attempts to grow past this maximum will throw a WebAssembly.RangeError exception. The engine takes advantage of this supplied upper-bounds to reserve memory ahead of time, which can make resizing more efficient.

Note: Since an ArrayBuffer’s byteLength is immutable, after a successful Memory.prototype.grow() operation the buffer getter will return a new ArrayBuffer object (with the new byteLength) and any previous ArrayBuffer objects become “detached”, or disconnected from the underlying memory they previously pointed to.

Just like functions, linear memories can be defined inside a module or imported. Similarly, a module may also optionally export its memory. This means that JavaScript can get access to the memory of a WebAssembly instance either by creating a new WebAssembly.Memory and passing it in as an import or by receiving a Memory export (via Instance.prototype.exports).

More involved memory example

Let’s make the above assertions clearer by looking at a more involved memory example — a WebAssembly module that imports the memory instance we defined earlier, populates it with an array of integers, then sums them. 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, { js: { mem: memory } })
    ).then(results => {
      // add 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(memory.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

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : dattaz
 Dernière mise à jour par : dattaz,