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 type | Actual WebAssembly type | Description |
i32 | i32 | 32-bit signed integer |
u32 | i32 | 32-bit unsigned integer |
i64 | i64 | 64-bit signed integer |
u64 | i64 | 64-bit unsigned integer |
f32 | f32 | 32-bit float |
f64 | f64 | 64-bit float |
v128 | v128 | 128-bit vector |
i8 | i32 | 8-bit signed integer |
u8 | i32 | 8-bit unsigned integer |
i16 | i32 | 16-bit signed integer |
u16 | i32 | 16-bit unsigned integer |
bool | i32 | 1-bit unsigned integer |
isize | i32/i64 | 32-bit signed integer for WASM32/ 64-bit signed integer for WASM64 |
usize | i32/i64 | 32-bit unsigned integer for WASM32/ 64-bit unsigned integer for WASM64 |
void | - | no return type |
anyref | anyref | Opaque 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 moduleuntouched.wat
- Text representation of wasmuntouched.wasm.map
- Source mapoptimized.wasm
- Optimized wasm moduleoptimized.wat
- Text representation of optimized wasm moduleoptimzed.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:
Sources
Happy Learning, Happy Coding!