Lance Pollard
Lance Pollard

Reputation: 79238

How to Directly Instantiate WebAssembly Module in JavaScript

The examples I've seen show essentially this:

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

But I would like to do it without making that extra HTTP request. Wondering if the only way is this (or some variation of it, which would be helpful to know):

var binary = '...mywasmbinary...'
var buffer = new ArrayBuffer(binary.length)
var view = new DataView(buffer)
for (var i = 0, n = binary.length; i < n; i++) {
  var x = binary[i]
  view.setInt8(i * 8, x)
}

Wondering if I have to worry about endianess or anything like that.

Or perhaps doing something with URL and blobs might be better, I'm not sure.

Upvotes: 8

Views: 3650

Answers (2)

Ravi Dev
Ravi Dev

Reputation: 61

Thank you ColinE. This solved my problem trying to upload a WASM module to NPM. For that, I had to split your answer into two file: 'export' and 'import.' Ignoring browser compatibility and focusing only on Node, my solution ended up like this:

const readFileSync = require('fs').readFileSync
const writeFile = require('fs').writeFileSync;

const wasmCode = readFileSync("./public/wasm/main.wasm");
const encoded = Buffer.from(wasmCode, 'binary').toString('base64');

function exportToJson(encoded){
    json = "\""+encoded+"\""
    writeFile("b64wasm.json", json, err => {
    if (err) {
      console.error(err);
    }
    // file written successfully
  });
}

exportToJson(encoded)

and

wasmBin = require("./b64wasm.json")

const crypto = require("crypto").webcrypto;
globalThis.crypto = crypto;
require('./public/wasm/wasm_exec.js');

function decode(encoded) {
    var binaryString =  Buffer.from(encoded, 'base64').toString('binary');
    var bytes = new Uint8Array(binaryString.length);
    for (var i = 0; i < binaryString.length; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes.buffer;
}

function loadWebAssembly() {
const go = new Go();
const importObject = go.importObject;
WebAssembly.instantiate(decode(wasmBin), importObject).then((results) => {
        const instance = results.instance
        go.run(instance);
        console.log("arrival One")
    });
}
loadWebAssembly()
setTimeout(()=>{
    let a = Math.floor(Math.random()*100)
    let b = Math.floor(Math.random()*100)
    const sum = addTwoNumbers(a,b);
    console.log("arrived two: ", sum)
},100)

Upvotes: 0

ColinE
ColinE

Reputation: 70132

Yes, you are correct, in order to inline wasm modules and avoid the HTTP request, you'll have to perform some sort of encoding. I'd recommend using Base64 encoded strings as they are the most compact form.

You can encode as follows:

const readFileSync = require('fs').readFileSync;

const wasmCode = readFileSync(id);
const encoded = Buffer.from(wasmCode, 'binary').toString('base64');

You can then load the module as follows:

    var encoded = "... contents of encoded from above ...";

    function asciiToBinary(str) {
      if (typeof atob === 'function') {
        // this works in the browser
        return atob(str)
      } else {
        // this works in node
        return new Buffer(str, 'base64').toString('binary');
      }
    }

    function decode(encoded) {
      var binaryString =  asciiToBinary(encoded);
      var bytes = new Uint8Array(binaryString.length);
      for (var i = 0; i < binaryString.length; i++) {
          bytes[i] = binaryString.charCodeAt(i);
      }
      return bytes.buffer;
    }

    var module = WebAssembly.instantiate(decode(encoded), {});

Upvotes: 12

Related Questions