Into WebAssembly: The easy way

- Shrawan Poudel

  • WebAssembly (Wasm)
  • Designed as a portable compilation target for programming languages
  • new type of code that can run on web browsers
  • Low level assembly like language
  • Provides languages such as C/C++ and Rust with compilation target so that they can run on web
  • Designed to run alongside JS
  • Provides way to run code written in multiple languages on web at near native speed
  • Can load Wasm modules into JS and share functionality

WebAssembly 1.0 has shipped in 4 major browser engines.

Firefox, Chrome, Safari, Edge

https://webassembly.org/
Compile C/C++ code into webAssembly

Install emscripten

(Emscripten SDK (emsdk))

https://emscripten.org/docs/getting_started/downloads.html

compile a C source file


// c file main.c
#include <stdio.h>

int main() {
     printf("Hello, World!\n");
     return 0;
}
                
compile it with
emcc main.c -o main.html

=> main.html, main.js, main.wasm

open main.html

Using the generated JS glue on Web


<html>
<script src="main.js"></script>
<html>
            

Adding some more functions


#include <stdio.h>

int add(int a, int b){
    return a+b;
}

int main() {
    printf("Hello, World!\n");
    int sum = add(1,2);
    printf("sum is %d\n",sum);
    return 0;
}
            

try compiling using previous command and open main.html

How do i call C functions from the JS

- with help of two functions provided by Emscripten "glue code"

  • ccall : calls the compiled C function with specified parameters
  • cwrap : wraps a compiled C function and returns a JS function

cwrap

Module.cwrap(FUNC_NAME,RETURN_TYPE,PARAMETERS)
  • FUNC_NAME = C function name
  • RETURN_TYPE = "number", "string" , "array" or null
  • PARAMETERS (Optional) = within square brackets i.e ["number","number"]

Note:

Emscripten will ignore all functions that seem unused

So, we tell Emscripten to keep that function "alive" using EMSCRIPTEN_KEEPALIVE

#include <stdio.h>
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int add(int a, int b){
    return a+b;
}

int main() {
    printf("Hello, World!\n");
    int sum = add(1,2);
    printf("sum is %d\n",sum);
    return 0;
}

Run using command


emcc main.c -o main.js -s EXPORTED_RUNTIME_METHODS='["cwrap"]'
            
<script src="main.js"></script>

<script>
    Module.onRuntimeInitialized = () => {
        let add_in_js = Module.cwrap("add",
                                    "number",
                                    ["number","number"]);
        let theSum = add_in_js(10,30);
        alert(theSum);
    }
</script> 

export functions without using EMSCRIPTEN_KEEPALIVE

when compiling


..... -s EXPORTED_FUNCTIONS='["_add"]'
            

Note: _ before a func name is important

Call JS functions from your C code

use `emscripten_run_script()`


#include <emscripten.h>

int add(int a, int b){
    emscripten_run_script("alert('add func called...')");
    return a+b;
}
  • by default emcc will emit JS and WASM file
  • JS loads WASM which contains compiled code
  • WASM depends on JS runtimes for features like checking date/time, printing to console ..
  • However, you can tell Emscripten to emit a standalone wasm (doesn't depend on EMSCRIPTEN JS RUNTIME)
  • Which Let's you write your own runtime
  • Or, you may be able to run it on outside the web
~ emcc main.c -o output.wasm

~ emcc main.c -s STANDALONE_WASM

(this emits JS as well, but wasm is standalone)
Example source for standalone wasm
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int add(int a, int b){
    return a+b;
}
emcc main.c -o add.wasm --no-entry

Note: --no-entry flag

--no-entry flag

If your C code doesn't have a main() function you will get compilation error

use, --no-entry flag to tell compiler you don't have an entry main function

Using the standalone .wasm on node

File: n.js


const binary = require('fs').readFileSync('add.wasm');

WebAssembly.instantiate(binary).then(({ instance }) => {
    console.log(instance.exports.add(100, 2));
});

run command: node n.js

Output: 102

Using the standalone .wasm on Web

<html>
<body>
Standalone run
<script>
    WebAssembly.instantiateStreaming(
        fetch("add.wasm")
    ).then((instance) => console.log(
            instance.instance.exports.add(100, 2)
        )
    );
</script>
</body>
</html>

Outside the Web

WASI

  • WebAssembly System Interface
  • API that provides access to OS like features including files, clocks, random numbers ..
  • Designed to be independent of browsers, so doesn't depend on WEB APIs or JS

The best part,

Esmcripten uses WASI APIs as much as possible, thus all/most programs like we just created can run on WASI supporting runtimes

Running in WASM runtimes

https://wasmer.io/
#for wasmer
~ wasmer run add.wasm --invoke add 1 2
https://wasmtime.dev/
#for wasmtime
~ wasmtime run add.wasm --invoke add 1 2

from https://wasmer.io

following previously generated add.wasm

Run .wasm on Python with wasmer

~ pip install wasmer wasmer_compiler_cranelift


from wasmer import engine, Store, Module, Instance
from wasmer_compiler_cranelift import Compiler

store = Store(engine.JIT(Compiler))

module = Module(store, open('add.wasm', 'rb').read())
instance = Instance(module)

result = instance.exports.add(5, 30)

print(result)
                
            

Wasmer Registry

Previously: WAPM / WebAssembly Package Manager

You can publish your package as well

~ wasmer run python/python -- -c "print('hello world')"

My favourite use case,

- Create a base library for your project/Business logic

- Use this base library on all your programming stacks :D

Something worth looking into..

Bartholomew MicroCMS

Bartholomew is a simple CMS-like (Content Management System) tool for managing a website

It is compiled to WebAssembly

https://bartholomew.fermyon.dev/
created using pyodide

In the cloud?

use it

or,

build it

Thank You !

hi@shrwn.com.np
LinkedIn/shrawanx