Difference between revisions of "WebAssembly/JS"

From Lazarus wiki
(Example)
m (Example)
Line 92: Line 92:
 
Once all imported entire as processed, the main module is finally linked and main function is executed.
 
Once all imported entire as processed, the main module is finally linked and main function is executed.
 
<source lang="javascript">
 
<source lang="javascript">
 +
// only "async" functions are allowed to perform actions "synchronously".
 +
// Just because "async" functions themselves are performed "on the thread"
 
async function runWasm()
 
async function runWasm()
 
{
 
{
   var r = await fetch('../out/main.wasm'); // Promise() object handling getting of the file
+
  // Reading the main.wasm file
   var bytes = await r.arrayBuffer();
+
   var r = await fetch('../out/main.wasm');  
   var mainmod = await WebAssembly.compile(bytes);
+
   var bytes = await r.arrayBuffer(); // converting the file to array of bytes
 +
 
 +
  // compilation is also time-consuming process thus "await" is needed
 +
  // the result of compile is WebAssembly.Module type
 +
   var mainmod = await WebAssembly.compile(bytes);  
 +
 
 
   // console.log(mainmod); // print the main module
 
   // console.log(mainmod); // print the main module
 
    
 
    
   var impobj = {};
+
   var impobj = {}; // the import object is supposed to resolve main unit dependencies
   var imports = WebAssembly.Module.imports(mainmod);
+
                    // originally we set to an empty object
 +
 
 +
  // asking the compiled module for the list of imported objects
 +
   var imports = WebAssembly.Module.imports(mainmod);  
 
   // console.log(imports); // print dependencies list
 
   // console.log(imports); // print dependencies list
   var loadedInst = [];
+
 
 +
  // keeping track of loaded dependencies instances
 +
  // just not to load the same module twice
 +
   var loadedInst = [];  
  
 
   // loading dependencies should be recursive!
 
   // loading dependencies should be recursive!
 +
  // for simplicity we assume that dependencies
 +
  // don't have dependencies themselves.
 
   for (var i = 0; i < imports.length; i++)
 
   for (var i = 0; i < imports.length; i++)
 
   {
 
   {
       var dep  = imports[i];
+
       var dep  = imports[i]; // dep contains fields "module","name" and "kind"
  
 
       var subinst = loadedInst[dep.module];
 
       var subinst = loadedInst[dep.module];
 
       if (!subinst)  
 
       if (!subinst)  
 
       {
 
       {
 +
        // the module has not been loaded yet, let's load it!
 
         var rr = await fetch('../out/'+dep.module+'.wasm');
 
         var rr = await fetch('../out/'+dep.module+'.wasm');
 
         var buf = await rr.arrayBuffer();
 
         var buf = await rr.arrayBuffer();
 +
        // try to link the module right away
 
         var submod = await WebAssembly.instantiate(buf);
 
         var submod = await WebAssembly.instantiate(buf);
 
         subinst = submod.instance;
 
         subinst = submod.instance;
 +
        // saving the instance for the later use, if it's used more than once
 
         loadedInst[dep.module] = subinst;
 
         loadedInst[dep.module] = subinst;
 
       }
 
       }
 
       //console.log(subinst); // print the sub module instance
 
       //console.log(subinst); // print the sub module instance
  
 +
      // adding the module to list of Import Objects (if not there yet)
 
       var impmod = impobj[dep.module];
 
       var impmod = impobj[dep.module];
 
       if (!impmod) {
 
       if (!impmod) {
Line 125: Line 144:
 
         impobj[dep.module] = impmod;
 
         impobj[dep.module] = impmod;
 
       }  
 
       }  
 +
      // extracting the function exported function
 
       impmod[dep.name] = await subinst.exports[dep.name];
 
       impmod[dep.name] = await subinst.exports[dep.name];
 
       impobj[dep.module] = impmod;
 
       impobj[dep.module] = impmod;
Line 130: Line 150:
 
   // console.log(impobj); // print of imported object object
 
   // console.log(impobj); // print of imported object object
  
 +
  // impobj - should now contain all the necessary functions for the main module
 +
  // in this example, it should only have
 +
  //  imbobj.test2.add
 
   var maininst = await WebAssembly.instantiate(mainmod, impobj);
 
   var maininst = await WebAssembly.instantiate(mainmod, impobj);
 
   // console.log(maininst); // print of the main instance object
 
   // console.log(maininst); // print of the main instance object

Revision as of 18:40, 5 September 2019

The primary target for WebAssembly is the browser.

WebAssembly is not a Javascript, it's only an addition to Javascript.

WebAssembly is not asm.js.

API

The primary API for dealing with WebAssembly (.wasm) files is WebAssembly JS standard

Using Wasm Functions

In order to use a .wasm file in Javascript the following steps needs to be taken

  • .wasm file needs to be loaded
  • as an external resource (a web server is required due to browsers security)
  • a binary file loaded thru a javascript (i.e. inlined).
  • loaded .wasm binary file needs to be compiled
  • the compiled file needs to be linked (aka instantiated). It's also common to do the compilation and instantiation in one steps, rather than two separate steps. (Two separate steps are typically needed, when "compiled" form would be used, either for caching OR for dependencies resolution stage)
  • when creating an instance (linking) all dependencies must be resolved.
  • after creating an instance, the exported functions of the instance can be called through Javascript, as a normal JS functions.

Here's a common JS example

// step #1 - loading the file and converting it to array of bytes, using fetch() function
fetch('../out/main.wasm').then(
  response => response.arrayBuffer()
).then(
  // step #2 and #3 - compiling and linking array of bytes (in one call) to WebAssembly.instantiate()
  bytes => WebAssembly.instantiate(bytes)).then(

    // step #4 using the linked instance object to call the exported function add()
    results => {
      instance = results.instance;
      document.getElementById("container").textContent = instance.exports.add(1,1);
    }
  ).catch(console.error);

Dependencies

As of 2019 there's no "out-of-the-box" way to resolve WebAssembly file import dependencies.

WebAssembly expects an import to be resolved by providing the import module name. The import module name doesn't have to be a WebAssembly module, it can be a plain JavaScript function as well. And the name of the object (i.e. function of memory) WebAssembly is trying to access.

Example

Here's an example of dependencies loading.

WARNING: you should NOT use this code in production. It's only used for educational purposes! Javascript and it's APIs are designed to do all the tasks asynchronously.

Most of the code is based on the callbacks (closures), that are triggered when the particular task is done. I.e. loading a resources from the web, would trigger an event once the loading is completed (or failed) - as shown in the example above.

The example below is intentionally performs each step synchronously (by using "await" syntax is used to explicitly wait for a completion of synchronous actions). Such intentional synchronization would impose slowness in a real-life application and should not be used.

The example consists of two WebAssembly files and a single Javascript file to load them.

main.wasm:

The module imports function named "add" from an external module named "test2"

(module 
  (import "test2" "add" (func $add (param i32)(param i32)(result i32) ))

  ;; the main() function calls the external add() with arguments 4 and 5 
  ;; and forwards the result of add as it's own result
  (func $main
    (result i32)
    i32.const	4
    i32.const	5
    call	$add
    return
  )
  ;; module exports the main function so it can be called outside (in javascript)
  (export "main" (func $main))
)
test2.wasm:

The module implements "add" function and exports it.

(module
  (func $add (param $lhs i32) (param $rhs i32) (result i32)
    get_local $lhs
    get_local $rhs
    i32.add)
  (export "add" (func $add))
)
javascript

The code below loads the "main.wasm".

Once loaded, it compiles the module. The compiled module is investigate, IF the needs any dependencies.

For each dependency found, the code tries to load the next module (assuming it's a .wasm unit).

The dependency module is loaded, compiled and linked. After it's linked, its exported functions are populated into Import object.

Import Object is used to satisfy the "main.wasm" unit dependencies.

Once all imported entire as processed, the main module is finally linked and main function is executed.

// only "async" functions are allowed to perform actions "synchronously". 
// Just because "async" functions themselves are performed "on the thread"
async function runWasm()
{
   // Reading the main.wasm file
   var r = await fetch('../out/main.wasm'); 
   var bytes = await r.arrayBuffer(); // converting the file to array of bytes

   // compilation is also time-consuming process thus "await" is needed
   // the result of compile is WebAssembly.Module type
   var mainmod = await WebAssembly.compile(bytes); 

   // console.log(mainmod); // print the main module
   
   var impobj = {}; // the import object is supposed to resolve main unit dependencies 
                    // originally we set to an empty object

   // asking the compiled module for the list of imported objects
   var imports = WebAssembly.Module.imports(mainmod); 
   // console.log(imports); // print dependencies list

   // keeping track of loaded dependencies instances
   // just not to load the same module twice
   var loadedInst = []; 

   // loading dependencies should be recursive!
   // for simplicity we assume that dependencies 
   // don't have dependencies themselves. 
   for (var i = 0; i < imports.length; i++)
   {
      var dep  = imports[i]; // dep contains fields "module","name" and "kind"

      var subinst = loadedInst[dep.module];
      if (!subinst) 
      {
        // the module has not been loaded yet, let's load it!
        var rr = await fetch('../out/'+dep.module+'.wasm');
        var buf = await rr.arrayBuffer();
        // try to link the module right away
        var submod = await WebAssembly.instantiate(buf);
        subinst = submod.instance;
        // saving the instance for the later use, if it's used more than once
        loadedInst[dep.module] = subinst;
      }
      //console.log(subinst); // print the sub module instance

      // adding the module to list of Import Objects (if not there yet)
      var impmod = impobj[dep.module];
      if (!impmod) {
        impmod = {};
        impobj[dep.module] = impmod;
      } 
      // extracting the function exported function
      impmod[dep.name] = await subinst.exports[dep.name];
      impobj[dep.module] = impmod;
   }
   // console.log(impobj); // print of imported object object

   // impobj - should now contain all the necessary functions for the main module
   // in this example, it should only have 
   //   imbobj.test2.add
   var maininst = await WebAssembly.instantiate(mainmod, impobj);
   // console.log(maininst); // print of the main instance object

   // calling the main() function of the main unit
   document.getElementById("container").textContent = maininst.exports.main();
}
runWasm();

See Also