AssemblyScript - Typescript for WebAssembly

AssemblyScript - Typescript for WebAssembly

In my previous story I used C language and different npm modules available to build a webassembly module which is a small calculator to work on node and also on browser using same codebase with difference in loading according to runtime. If you haven't read it do sure it check it out C in browser — Experiments with WebAssembly or you can follow the series I am doing on webassembly Webassembly Experiments.

C is a good language but many people don't know it and if you want to make something popular these days it must be in something javascript world so there is AssemblyScript that uses a subset of Typescript with few updates to make it work with webassembly.

One of the main aim to build AssemblyScript is that you don't have to learn a new programming language to use webassembly and also produces glue-free WebAssembly modules that are very small with decent performance.

Thats lot of talk, lets code something. I will be replicating same C program, basic calculator and same HTML UI but this time with different wasm module built with Assemblyscript.

There as steps you have to follow to create a AssemblyScript project which is well document in there repo and website but I will repeat these here also so that you don't have to jump between tabs to get basic setup ready.

Setup

Create a new folder and initialise your node project using npm init or yarn init whichever you prefer, then install assemblyscript as dev-dependency in your project and asinit . using npx.

mkdir asmscript && cd asmscript
npm init -y
npm install --save-dev assemblyscript
npx asinit .

After this you will find assembly/ directory where your assemblyscript code lives and it will have a file index.ts with basic webassembly example.

Assemblyscript code for calculator

Update your index.ts with following:

export function add ( a: f64, b: f64 ): f64 {
  return a + b;
}

export function substract ( a: f64, b: f64 ): f64 {
  return a - b;
}

export function multiply ( a: f64, b: f64 ): f64 {
  return a * b;
}

export function divide ( a: f64, b: f64 ): f64 {
  return a / b;
}

export function mod ( a: f64, b: f64 ): f64 {
  return a % b;
}

export function square_root ( a: f64 ): f64 {
  return sqrt( a );
}

This index.ts whole file is a webassembly module. If you see my previous code which was in C using webassembly npm module and this code there are many similarities. We are exporting functions from webassembly but there are differences between C code I wrote, normal Typescript and AssemblyScript.

We used double in C code and we can use number in Typescript to define floating point number of numbers with decimal places. Where double is C data type which stores floating point number or number with decimal places. In Typescript number refers to both integer and floating point number. AssemblyScript has different data types depending on what you want to use which is aligned with webassembly datatypes.

AssemblyScript typeActual WebAssembly typeDescription
i32i3232-bit signed integer
u32i3232-bit unsigned integer
i64i6464-bit signed integer
u64i6464-bit unsigned integer
f32f3232-bit float
f64f6464-bit float
v128v128128-bit vector
i8i328-bit signed integer
u8i328-bit unsigned integer
i16i3216-bit signed integer
u16i3216-bit unsigned integer
booli321-bit unsigned integer
isizei32/i6432-bit signed integer for WASM32/ 64-bit signed integer for WASM64
usizei32/i6432-bit unsigned integer for WASM32/ 64-bit unsigned integer for WASM64
void-no return type
anyrefanyrefOpaque host reference

Datatype is must in Assemblyscript unlike Typescript. It will throw error during compile time if no datatype is present for a variable or a function parameter.

When we initialised Assemblyscript project using npx asinit . it added few npm script in our package.json - asbuild:untouched and asbuild:optimized which are self explanatory. When you run them it will create 3 files for each -

  • untouched.wasm - Wasm module
  • untouched.wat - Text representation of wasm
  • untouched.wasm.map - Source map
  • optimized.wasm - Optimized wasm module
  • optimized.wat - Text representation of optimized wasm module
  • optimzed.wasm.map - Source map of optimized wasm

Running in Node.js

Its easy to load Assemscript module in Node as they already provide a npm module for that @assemblyscript\loader. Here is the code for wasm_loader.js -

const fs = require( "fs" );
const loader = require( "@assemblyscript/loader" );
module.exports = loader.instantiateSync( fs.readFileSync( __dirname + "/build/optimized.wasm" ), { /* imports */ } )

Using wasm_loader.js -

const wasm = require( './wasm_loader' );

console.log( "1 + 2 = " + wasm.add( 1, 2 ) );
console.log( "1 - 2 = " + wasm.substract( 1, 2 ) );
console.log( "1 X 2 = " + wasm.multiply( 1, 2 ) );
console.log( "1 / 2 = " + wasm.divide( 1, 2 ) );
console.log( "1 % 2 = " + wasm.mod( 1, 2 ) );
console.log( "Square root of 9 = " + wasm.square_root( 9 ) );

On running

> node index.js
1 + 2 = 3
1 - 2 = -1
1 X 2 = 2
1 / 2 = 0.5
1 % 2 = 1
Square root of 9 = 3

Running in browser

Similar to what we did for C code using webassembly npm module we will be doing same here also and there is no to minimum change in main.js file which actually does all the work of getting events and displaying. There is change in wasm loader for browser wasmloader.js -

const loadWebAssembly = async ( wasmModuleUrl, importObject ) => {
  let response = undefined;

  if ( !importObject ) {
    importObject = {
      env: {
        abort: () => console.log( "Abort!" )
      }
    };
  }

  // Check if the browser supports streaming instantiation
  if ( WebAssembly.instantiateStreaming ) {
    // Fetch the module, and instantiate it as it is downloading
    response = await WebAssembly.instantiateStreaming(
      fetch( wasmModuleUrl ),
      importObject
    );
  } else {
    // Fallback to using fetch to download the entire module
    // And then instantiate the module
    const fetchAndInstantiateTask = async () => {
      const wasmArrayBuffer = await fetch( wasmModuleUrl ).then( response =>
        response.arrayBuffer()
      );
      return WebAssembly.instantiate( wasmArrayBuffer, importObject );
    };
    response = await fetchAndInstantiateTask();
  }

  console.log( response );

  return response.instance;
};

And following change in HTML:

<script type="text/javascript" src="./js/wasmloader.js"></script>
    <script type="text/javascript">
        // Basic check is browser supports webassembly
        if ( !( 'WebAssembly' in window ) ) {
            alert( 'You need Webassembly enabled browser' )
        }

        var cadd, csubs, cmultiply, cdivide, cmod, csquare_root;

        loadWebAssembly( './wasm/optimized.wasm' )
            .then( instance => {
                var exports = instance.exports;
                console.log( '1 + 2 = ' + exports.add( 1, 2 ) );
                console.log( '1 - 2 = ' + exports.substract( 1, 2 ) );
                console.log( '1 * 2 = ' + exports.multiply( 1, 2 ) );
                console.log( '1 / 2 = ' + exports.divide( 1, 2 ) );
                console.log( '1 % 2 = ' + exports.mod( 1, 2 ) );
                console.log( 'Square root of 9 = ' + exports.square_root( 9 ) );

                cadd = exports.add;
                csubs = exports.substract;
                cmultiply = exports.multiply;
                cdivide = exports.divide;
                cmod = exports.mod;
                csquare_root = exports.square_root;
            } );
        // console.log( wasm );
</script>
<script src="./js/main.js"></script>

Head over to github for full code:

Wasm experiment

Full wasm experiments repo

Sources

AssemblyScript

Happy Learning, Happy Coding!