WebAssembly aka wasm is an efficient low-level bytecode format for compilation to the web browsers, initial aim for wasm is compilation C/C++ code, but also other languages are supported such as Rust. Wasm allows run code faster and efficient, thanks to stack machine. Wasm can be interpreted as the game changer for the web, which compiles C++/C into byte code and it can be run with near-native performance, it’s amazing!

There exist a popular asm.js which allow the almost the same as wasm except for a few important features for wasm:

  • Wasm compiled into byte-code, when asm.js is modified high-perfomance js language. It’s means asm.js can be parsed slower than wasm can be decoded, some sources says it’s faster than in 20x times. That allow decrease waiting time for your users.
  • Constant-time type checking, and well-formedness cheching.
  • Rational designed
    • Stack machine allow smaller binary encoding.
    • Dense Encoding and effecient decoding, compilation, and interpratation - thanks to stack machine.

If you have a project written in asm.js, you can easily compile it into wasm.

Use cases

First of all, wasm can be run on the web platform and non-web platforms - like Node.js. In web platform, wasm has a wide range of usage, for me as AR developer to which is important every millisecond, wasm can be lifebuoy to run AR algorithms in the web.

Wasm use-cases:

  • Working with wide range of library which wrote on C++
  • Image/video editing.
  • Audio Industry - thanks to Timur Doumler for this great video about performance in the audio industry.
  • Music streaming.
  • Image recognition.
  • Video augmentation(like snapchat lenses put’s some assets on your face)
  • VR and AR
  • Scientific simulation and visualization.
  • Developing tooling(IDE’s, compilers, debugger, etc.) - Microsoft currently running visual studion in the browser.
  • Platform simulation, virtual machines

A full list of wasm use cases you can find here.

Demo’s

Wasm

On the main wasm website, you can find Unity game ‘Tanks’ which ported to wasm. Tanks Also, I found EpicZenGarder. I recommend to you run it on latest Firefox version. EpicZenGarden

Asm.js

Asm.js not poor for demos unlike wasm, it has a lot of demo’s, you can find full list here. I really enjoy Quake port ☺️ Quakejs

Classic MacOS MacOS

SQLite ported to js! SQLite.js

Instalation

For compilation from LLVM which can be generated by C++/C to wasm we will use awesome project emscripten. If you use Windows please skip this section and use this link to install.

cd ~/libs
wget https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz
tar -xzf emsdk-portable.tar.gz && rm emsdk-portable.tar.gz
cd emsdk-portable
./emsdk update
./emsdk install latest
./emsdk activate latest

## Set system path
source ./emsdk_env.sh

Emscripten

Emscripten is a compiler, which takes LLVM code as input and generates .js code, like asm.js code. Also, it can be used to compile into web assembly format. Emscripten used inside binaryen toolchain to compile into .wasm format. It takes c++ llvm bitcode, compile it into .s file, uses s2wasm command from binaryen project to generate wasm bitcode. Emscipten compilation diagramm

Compile simple C++{11} code

#include <algorithm>
#include <iostream>
#include <array>
#include <iterator>

int main(){
    std::array<int, 20> array;
    for(int i=0;i<array.size();i++){
        array[i] = i;
    }
    std::random_shuffle(array.begin(), array.end());

    std::cout<<"Shuffled array"<<std::endl;
    std::copy(array.begin(), array.end(),std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;
	
	//Sorting
    std::sort(array.begin(),array.end());

    std::cout<<"Sorted array"<<std::endl;
    std::copy(array.begin(), array.end(),std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;

    return 0;
}

simple.cpp

Here we create a simple code, which fills array of length 20, shuffle it, and sort it. As you can see we use c++11 std::array, and standard library algorithms.

Compile C++ into wasm

Emscripten can generate asm.js code and wasm code, and they can generate HTML for testing JS/wasm with a console which displays result of evaluation.

emcc simple.cpp -o simple.html -s "BINARYEN_METHOD='native-wasm'" -s WASM=1 -O1
  • -o specify output for generated file.
    • -o simple.js, generates javascript code, which you can invoke for example by using nodejs node simple.js
    • -o simple.html, generates HTML for testing
  • -s javascript code generation option.
    • BINARYEN_METHOD=’interpret-binary’ - force usage of wasm interpreter, read-more, if your platform doesn’t support webassembly(this method much slower than native support).
    • BINARYEN_METHOD=’native-wasm,asmjs’ - will try native support, and if fail will use asm.js.
    • WASM=1 or BINARYEN=1 generate *.wasm code.
  • -O1 enable LLVM optimization, by default all optimization disabled.
  • -Os enabling super optimization.

Pleas take a look at this page with full list of arguments for emcc tool. simple.cpp

Compile into js file

emcc simple.cpp -o simple.js -s "BINARYEN_METHOD='native-wasm'" -s WASM=1 -Os
echo "<script src='./simple.js'></script>" > index.html

And after opening file index.html in the browser I have: index.html

But when I trying run it in the node.js I have an error :

node simple.js
trying binaryen method: native-wasm
no native wasm support detected

It seems nodejs doesn’t have and wasm interpreter, we need compile to asm.js -s "BINARYEN_METHOD='native-wasm,asm.js'" or with force using interpreter -s -BINARYEN_METHOD='interpret-binary'. I prefer asm.js because of performance(force using interpret much slower than asm.js).

> emcc simple.cpp -o simple.js -s "BINARYEN_METHOD='native-wasm,asmjs'" -s WASM=1 -Os
> time node simple.js 
trying binaryen method: native-wasm
no native wasm support detected
trying binaryen method: asmjs
binaryen method succeeded.
Shuffled array
14 13 7 4 0 16 9 12 2 19 10 18 5 3 8 1 17 6 11 15 
Sorted array
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
node simple.js  0.17s user 0.03s system 97% cpu 0.197 total

How webassembly works?

As I said above wasm using stack machine model on the hood, that helps run code faster and efficient.

Stack machine

A stack machine is a type of computer, but wasm just imitate stack machine. This type of ‘computer’ has only two operations: push, pop and some computation with poped values like operation add(+). As you might have guessed stack machine using [stack aka LIFO](https://en.wikipedia.org/wiki/Stack_(abstract_data_type) inside.

Stack Stack ilustration

A stack machine is easy to understand and it has a lot of advantages:

  • Each operation pop’s and push’s from the same place.
  • Uniform compiler scheme
  • Very compact code
  • Simple compiler, because of uniform compiler scheme
  • Simple interpreter
  • Fast operation access

Stack machine - compilation

Let’s imagine we need compile this expression (a + b) * c - d. Where S-expression is (- (* (+ a b) c) d). Syntax tree will look like:

syntax tree

After compilation, generated code will look like :

PUSH(a)
PUSH(b)
PUSH(POP() + POP())
PUSH(c)
PUSH(POP() * POP())
PUSH(d)
PUSH(POP() - POP())

Now evaluate this code so easily, like play chess without any chessman :) Evaluation:

[a]
[a,b]
[(a+b)]
[(a+b),c]
[(a+b)*c]
[(a+b)*c,d]
[(a+b)*c - d]

Here I just illustrate how our stack will look like in every step of evaluation code.

Instructions

Each function contains list of instructions, which can be:

  • Control instruction - loops, if/else conditions, return statement, blocks, branches.
  • Simple instruction - is simple instruction, like add, subtract, multiply, etc.
#CONTROL INSTRUCTIONS
for i in range 1..5
	...
end
#SIMPLE INSTRUCTION
1 + 5

Most computation in wasm use stack of values, but loops, conditions expressed in structured constructs. You can read more here.

Storing data

WASM has 4 types of data:

i32 - integer 32 bit
i64 - integer 64 bit
f32 - float 32 bit
f64 - float 64 bit

Variables can be global or local.
Local variables stored inside the function. Each function has pre-declared number of variable, and each of varialbe has type and initialized by default by +0. for float, 0 for integer, except input fuction argument variables. Also wasm has function for interaction with variables, like:

  • get_local - return local variable
  • set_local - set local variable
  • tee_local - set variable and return set value

Global can be mutable or immutable, also you can import them or define inside the module, and ofc you can access global variables them by using methods:

  • get_global - get global value
  • set_global - set global value

Generation result value In any high-level language it means generate constant variable const int i = 5, this code will generate the constant variable type of integer. In wasm, you can generate variable by using %TYPE%.const keyword, where %TYPE is i64, i32, f64, f32, for example: i64.const 15 - create a constant variable with type of 64 bit integer and assign value 15 to this const. Function calls Each function contains: sequence of return types, and sequence of input argument types. A function doesn’t support varargs. To call function use call keyword, where argument is function index in function index space.

WASM binary code example

C++ Binary Text
int factorial(int n) {
  if (n == 0)
    return 1;
  else
    return n * factorial(n-1);
}
20 00
42 00
51
04 7e
42 01
05
20 00
20 00
42 01
7d
10 00
7e
0b
get_local 0
i64.const 0
i64.eq
if i64
    i64.const 1
else
    get_local 0
    get_local 0
    i64.const 1
    i64.sub
    call 0
    i64.mul
end

Binary code can be easily translated into text format

Here was I try to show stack in each line of code:

1.  get_local 0     # [n]
2.  i64.const 0	   # [n,0]
3.  i64.eq          # [n == 0]
4.  if i64          # n == 0 ?
5.     i64.const 1 # [1]
6.  else
7.     get_local 0 # [n]
8.     get_local 0 # [n, n]
9.     i64.const 1 # [n, n, 1]
10.    i64.sub     # [n, n - 1]
11.    call 0      # [n, fibonaci(n-1)]
12.    i64.mul     # [n * fibonaci(n - 1)]
13. end             
# returs 1 or n * fibonaci(n - 1), depends on if-else statement

On the first line get_local 0 get the local variable by index 0, i64.const 0 - create constant variable with value 0, i64.eq - checking last two values in the stack( the result of evaluation get_local 0 and i64.const 0), and put the result in a stack. Condition line if: if i64 - popping a value from the stack and checking value, if a condition is true pushing in the stack value 1, otherwise going to line 7. Pushing values into the stack on line 7-9, making subtraction, call 0 calls function by index 0 and put the result of the evaluation in the stack. Line 12 multiply call 0 and n. And at end of the function, we return the result.