24 May 2020, 18:28

AssemblyScript: Passing Data to and From Your WebAssembly Program

AssemblyScript takes a strict subset of TypeScript and allows it to be compiled to WebAssembly. This is a very persuasive selling point for developers familiar with JavaScript and TypeScript as it immediately allows them to transfer their skills to be able to write WebAssembly programs. This is exciting as WebAssembly has proved useful from things like game engines such as Unity to design tools such as Figma.

In this short blog post, we will look at how you can pass data from JavaScript to WebAssembly programs created using AssemblyScript. This blog will assume you’ve followed the quick start guide and that you are familiar with npm and JavaScript.

As it currently stands the only types WebAssembly supports are integers and floats, which are stored in linear memory (a single contiguous address space of bytes) for the WebAssembly program. As a language, AssemblyScript works with us in abstracting away some of the complexity of managing more complex types like strings and arrays. A common use-case for AssemblyScript might be to write a WebAssembly module and then make use of that within a JavaScript runtime. At some point, it will probably be necessary to pass some data from the JavaScript code to the WebAssembly module and/or back again. Let’s take a look at how we go about passing various data types to AssemblyScript.

Numbers

Passing numbers to our program is straightforward and doesn’t require any special treatment. We can achieve this by just passing them number values directly to our WebAssembly module function. You can use i32 your AssemblyScript code for intergers and f32 for float types, like so:


    // AssemblyScript
    export function add(a: i32, b: i32): i32 {
      return a + b;
    }

    export function addFloats(a: f32, b: f32): f32 {
      return a + b;
    }


    // JavaScript
    {
        add,
        addFloats
    } = wasmModule.exports;

    const result = add(1, 2);
    // result will be 3

    const floatResult = addFloats(1.5, 2.5);
    // result will be 4

This is great, especially if we don’t need to deal with any other types. However if our program gets more complex we may need to start dealing with other types.

Introducing the loader

As mentioned previously, WebAssembly as it stands only deals with number types. So how can we go about passing JavaScript data types like strings and arrays to our WebAssembly programs? One solution is to to use the AssemblyScript loader which simplifies the process of loading more complex data types into the WebAssembly memory. The module provides a set of convenience functions to allow loading in types like strings and arrays into memory, returning their pointers. It also allows for the managing of their lifecycle via retaining and releasing them. To get started using the AssemblyScript loader lets install it into our project using npm:

npm install @assemblyscript/loader

Once we’ve compiled our AssemblyScript program to a wasm file, we will want to use this in our web application.

Let’s start with how we go about instantiating our program (here we will be in a Node environment):


    const loader = require("@assemblyscript/loader");
    const buf = fs.readFileSync('./build/optimized.wasm');
    const wasm = new WebAssembly.Module(new Uint8Array(buf));
    loader.instantiate(wasm, { 
      env: { 
        abort: (err) => {
          console.error(err)
        }
      }
    }).then((wasmModule) => {

      console.log(wasmModule.exports);
      // Code to use the instantiated wasm module

    });

Strings

For more complex types like strings, we can leverage the loader. Strings in AssemblyScript are immutable, and hence we can’t change a string once we’ve passed its pointer to the AssemblyScript function. We could, however, return a pointer to a newly constructed string value. In this case, we’ll replace ‘hello’ in a string with ‘hi’ in the string and return a new string pointer, and then read it with the __getString method:


    // AssemblyScript
    export function replaceHelloWithHi(a: string): string {
      return a.replace("hello", "hi");
    }


    // JavaScript
    {
        __retain, 
        __allocString,
        __release,
        replaceHelloWithHi
    } = wasmModule.exports;
    const originalStr = "hello world";
    const ptr = __retain(__allocString(originalStr));
    const newPtr = replaceHelloWithHi(ptr);
    const newStr = __getString(newPtr);
    __release(ptr);
    __release(newStr);
    console.log(newStr);
    // logs out 'hi world'

Arrays

If you have a regular untyped array in our JavaScript side, we’ll still need to allocate a typed array on the WebAssembly side, we can use the AssemblyScript i32 type for this. We can get its id using the idof function to get the ID. For Typed Arrays we can use the same approach use the appropriate Typed Array type, in this case, Int32Array. We use idof like so:


    // AssemblyScript
    export const i32ArrayId = idof<i32[]>()
    export const Int32ArrayId = idof<Int32Array>()

Now we have the IDs we can use them using the appropriate functions from the AssemblyScript loader. We will need to allocate an array in the module’s memory and retain it to make sure it doesn’t get collected prematurely. Let’s work through this for the example of summing an array:


    // AssemblyScript
    export function sumArray(arr: i32[]): i32 {
      let sum: i32 = 0;
      for (let i: i32 = 0; i < arr.length; i++) {
        sum = sum + arr[i];
      }
      return sum;
    }


    // JavaScript
    const { 
        __retain, 
        __allocArray,
        __release,
        i32ArrayId,
        Int32ArrayId,
        sumArray
    } = wasmModule.exports;


    // Untyped arrays

    const arrayPtr = __retain(__allocArray(i32ArrayId, [1, 2, 3]))
    const sum = sumArray(arrayPtr);

    __release(arrayPtr);

    // Now for TypedArrays

    const typedArrayPtr = __retain(__allocArray(Int32ArrayId, new Int32Array([1, 2, 3])))
    const typedSum = sumArray(typedArrayPtr);


    __release(typedArrayPtr);

Are there any other approaches?

The loader could is deliberately quite minimalist and not as abstracted as they potentially could be. If you are looking for something simpler, I would definitely recommend taking a look at Aaron Turner’s asbind library which steamlines the process. For example, we can reduce the string example to the following code:


    // JavaScript
    import { AsBind } from "as-bind";
    const wasm = fs.readFileSync("./build/optimized.wasm");

    (async () => {
      const asBindInstance = await AsBind.instantiate(wasm);

      const response = asBindInstance.exports.replaceHelloWithHi("Hello World!");
      console.log(response); // Hi World!
    })();

08 May 2020, 18:28

Writing Web Workers in TypeScript

TypeScript has taken the web development world by storm, and I too am a fan. Unfortuantely what I’m not a fan of is contention on the main thread, which has increased over time as we ship more and more JavaScript to our pages.

I’ve written in previous posts about Web Workers, but for those of you note familiar they allow the developer to move work off of the main thread and into a separate thread of execution. These work great for tasks that often block such as data crunching in audio, gaming and mapping applications. We can also leverage them for more generic work, and Surma has done a great job of explaining why that is an important consideration for web developers.

In this post, I want to show how you can write Workers in TypeScript and build them using the popular bundler Webpack. The first step we need to take is to install all the modules we need via npm as development dependencies. We can do this from our command line like so:


We also need to set up a TypeScript configuration, `tsconfig.json`, file in our root directory.  We can do a rudimentary implementation like this:

```javascript
{
    "compilerOptions": {
      "outDir": "./dist/",
      "noImplicitAny": true,
      "module": "es6",
      "target": "es5",
      "allowJs": true,
      "sourceMap": true
    }
}

You can adjust this to your required tastes but this is a barebones starter to get going. Next lets setup the webpack.config.js file again in our root directory to configure Webpack and allow us to build our application and worker:

const path = require('path');

module.exports = {
    mode: 'development',
    entry: './src/index.ts',
    devtool: 'inline-source-map',
    module: {
        rules: [
            // Handle TypeScript
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: [/node_modules/]
            },
            // Handle our workers
            {
                test: /\.worker\.js$/,
                use: { loader: 'worker-loader' }
            }
        ]
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    output: {
        // This is required so workers are known where to be loaded from
        publicPath: '/dist/',
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist/')
    }
};

This covers the build step side of things, now we can look at our code itself. Let’s assume we have a src folder for our source code, and a dist folder for a compiled code. The first thing we’ll want to do is setup types for the Workers so that TypeScript doesn’t complain:

// types.d.ts
declare module "worker-loader!*" {
    class WebpackWorker extends Worker {
      constructor();
    }
  
    export default WebpackWorker;
}

Now, let’s write a Worker. As an example of a large workload, this Worker will generate primes using the Sieve of Erastosthenes and return them back to the main thread:

// worker.js

// We alias self to ctx and give it our newly created type
const ctx: Worker = self as any;

class SieveOfEratosthenes {
    
    // This is the logic for giving us back the primes up to a given number
    calculate(limit: number) {

      const sieve = [];
      const primes: number[] = [];
      let k;
      let l;

      sieve[1] = false;
      for (k = 2; k <= limit; k += 1) {
        sieve[k] = true;
      }

      for (k = 2; k * k <= limit; k += 1) {
        if (sieve[k] !== true) {
          continue;
        }
        for (l = k * k; l <= limit; l += k) {
          sieve[l] = false;
        }
      }

      sieve.forEach(function (value, key) {
        if (value) {
          this.push(key);
        }
      }, primes);

      return primes;

    }

}

// Setup a new prime sieve once on instancation 
const sieve = new SieveOfEratosthenes();

// We send a message back to the main thread
ctx.addEventListener("message", (event) => {

    // Get the limit from the event data
    const limit = event.data.limit;

    // Calculate the primes 
    const primes = sieve.calculate(limit);

    // Send the primes back to the main thread
    ctx.postMessage({ primes });
});

And then back to our main thread we instantiate the worker and send a message asking the first 1000 primes:

// index.ts

// Not the worker-loader! syntax to keep Webpack happy
import PrimeWorker from "worker-loader!./worker";

const worker = new PrimeWorker();

worker.postMessage({ limit: 1000 });
worker.onmessage = (event) => {
    document.getElementById("primes").innerHTML = event.data.primes;
};

Now if we can build the file. Here we assign a build script "build": "webpack" in our package.json which will build the file for us into a dist directory as bundle.js. This can then be referenced inside your webpage of choice.

If you want to see the full working example I’ve posted it to this GitHub repository for you to experiment with.

01 Oct 2018, 20:37

A Quick Look at WorkerDOM

In the browser, many operations occur on a single main thread. Due to the number of things we need to handle as web developers such as styling, DOM updates, fetches, data transforms, timers and so forth we can end up with a lot going on that thread. Unfortunately, this thread is also responsible for handling user inputs and rendering to the screen, which are user critical requirements and have large impacts on their experience. Here if we have tasks that run for long periods this can block responding to user input and rendering new frames.

In the browser we have access to threading via Web Workers. Web Workers allow us to run JavaScript in a separate thread whilst also being able to message back to the main thread. The limitations of Web Workers are that they have transfer times for data between the main and worker thread, limited transfer types (i.e. Functions and Errors aren’t allowed) and also perhaps most importantly no DOM access (i.e. document.getElementByID for example). I have experimented and written about transfer times for workers, showing that for small loads the fear is probably over played. However a core aspect of frontend development is, maybe somewhat obviously, trying to update what is happening on the screen (render new content). This makes the DOM limitation pretty painful for those using Web Workers, as it mostly limits it to computational logic which we then proxy back to the main thread.

On mobile devices, where performance perhaps matters the most, we’re now seeing low-end devices with multiple cores (for example a Nokia 1 and the Micromax Bharat Go both have quadcore processors). Arguably this is where multiple threaded code would probably have the largest benefit. However, we still have a particularly single-threaded approach to writing browser code. This is why it’s interesting to see the emergence of the recently announced WorkerDOM library from the AMP team, which aims to let developers leverage workers more easily, by removing the aforementioned limitation of lack of DOM access (or at least perceived lack of DOM access).

What is WorkerDOM?

WorkerDOM is a library which you can include in your applications and pages. It provides much of the regular DOM API and in turn proxied access to the real DOM on the main thread, passing mutations to the DOM over to the main-thread to be handled. This encourages a lot of logic that was previously happening on the main thread to be handled by the Web Worker, leaving the main thread to handle user input and DOM changes. The library itself is written in TypeScript and is compiled down to both global variable and module formats.

Setting up WorkerDOM

You can include WorkerDOM by installing it via NPM like so:

npm install @ampproject/worker-dom

or if you use Yarn then:

yarn add @ampproject/worker-dom

Alternatively you could use a script tag directly, using a CDN, in the following fashion:

<script src="https://unpkg.com/@ampproject/worker-dom@0.1.2/dist/index.js"></script>

We can actually take this a step further as WorkerDOM is distributed as both a global (aliased as MainThread) and an ES Module, allowing for the import syntax. This means you can do the following;

<script src="https://unpkg.com/@ampproject/worker-dom@0.1.2/dist/index.mjs" type="module"></script>
<script src="https://unpkg.com/@ampproject/worker-dom@0.1.2/dist/index.js" nomodule defer></script>

Here only browsers that understand modules (all modern browsers, except Samsung Internet now support modules) will receive the module script.

First Steps

WorkerDOM allows us to expose a specific part of the DOM to be upgraded, we can do this by doing something we would probably not normally do; we set the src attribute on the containing element we want to upgraded to work with WorkerDOM. The src attribute is updated to be the name of the script we’re interested in running as our worker script. For the purposes of this blog, we are going to generate some prime numbers and render them into the DOM. For our index.html file, we might start with something like this in our case:

<body>
    <div src="primes.js" id="primes">
    </div>
</body>

So now we have an element that we can upgrade in our body. We need to declare explicitly that we want to upgrade the element using the upgradeElement method. We can do that like so:


<script type="module">
    import {upgradeElement} from '/dist/index.mjs';
    upgradeElement(document.getElementById('primes'), '/dist/primes.mjs');
</script>

<script nomodule async=false defer>
    document.addEventListener('DOMContentLoaded', function() {
        MainThread.upgradeElement(document.getElementById('primes'), '/dist/primes.js');
    }, false);
</script>

You can see we take both the module and none module code paths allowing us to handle both scenarios. This means we can now begin looking at our actual worker logic!

The Worker File

The coolest part about WorkerDOM is that it allows us to behave as if we have DOM access in the worker thread. That means we have access to properties like document.createElement for example. Continuing on with the concept that we want to generate primes and add them to the DOM, we could do something like this:


const startNumber = 1;

function generatePrimes() {

    while (document.body.firstChild) {
        document.body.removeChild(document.body.firstChild);
    }

    const numDivs = 1000;
    const limit = startNumber + numDivs;
    const primes = sieveOfEratosthenes(limit); // An algorithm for generating primes up to 'limit'

    const div = document.createElement('div');
    div.className = 'parent';

    for (let i = startNumber; i < limit; i++) {
        const numberDiv = document.createElement('div');
        numberDiv.className = "number";
        const numberText = document.createTextNode(i);
        if (primes.has(i)) {
        numberDiv.style.fontWeight = 'bold';
        numberDiv.style.color = '#240098';
        }
        numberDiv.appendChild(numberText);
        div.appendChild(numberDiv);
    }

    document.body.appendChild(div);
    startNumber += numDivs;

}

setTimeout(generatePrimes, 0); // Not sure why we need this in a timeout?
document.body.addEventListener('click', generatePrimes);

This code will create divs with numbers in them, with prime numbers being highlighted and put in bold. The numbers will update on click on the document. Notice how we can behave as if this worker code is on the main thread, with access to DOM APIs. It’s worth pointing out the main difference here is that the "primes" div from the index.html is considered our document.body here.

As it stands, WorkerDOM is currently in alpha, and as such is still being worked on. Hopefully, this post has given you a reasonable overview of the library, and if you are interested in learning more about WorkerDOM I would recommend these resources: