WebAssembly



Headlines
Installation
WebAssembly fundamentals

WebAssembly is a technology to write code that runs within the Web, both on the client side (browser) and the server side (e.g., Node.js) or more generally the cloud. On the client side, browser compatibility may be checked from

On the server side, WebAssembly is the highway to serverless computing based on the idea of serverless function within the cloud

Beyond browsers and Node.js as WebAssembly runtime supports, non-Web WebAssembly runtime supports emerge

As named, WebAssembly is a neutral (assembly) language whose ultimate end is to produce native code by means of a C compiler. In the meantime, source code may be written in any (high-level) programming language, say, AssemblyScript: introduction about WebAssembly with AssemblyScript

A step-by-step approach


Credit: J. Young
About Binaryen

Binaryen is the compiler (optimizer as well) to compile to WebAssembly. It is used by most “high-level” languages like AssemblyScript to produce .wat and .wasm code

macOS installation

brew upgrade binaryen
Creating a WebAssembly module from scratch

WebAssembly text format (.wat suffix) is the straightforward way of creating a WebAssembly function

Example What_year_.wat 

(module
    (func (export "what_year_") (result i32) ;; No parameter...
        i32.const 2025
        return
    )
)

The translation from WebAssembly text format (.wat suffix) to WebAssembly assembler (.wasm suffix) relies on an online converter, for instance,

Execution next relies on a WebAssembly runtime support like Wasmtime

Example What_year_.wasm  What_year_.wat 

wasmtime --invoke what_year_ What_year_.wasm
#Transpilation preceeds execution:
wasmtime --invoke what_year_ What_year_.wat

The one-to-one mapping between the text format and the assembler allows conversion from .wasm to .wat as well

WebAssembly text format sample

Example

;; '(;' <- is equivalent to '/*' while ';)' <- is equivalent to '*/'
 
(;
(func (param i32) (param f32) (local f64)
  local.get 0
  local.get 1
  local.get 2
…)
;)
 
;; '$' allows naming instead using numeric indexes: 0, 1, 2...
;; (func (param $first_parameter i32) (param $second_parameter f32) (local $local_variable f64)
;;   local.get $first_parameter
;;   local.get $second_parameter
;;   local.get $local_variable
;; …)

Other formats

Creating a WebAssembly module from AssemblyScript

AssemblyScript getting started … Once installed, AssemblyScript compiler version may be displayed

asc -v

Rule(s)

Example Barcode.ts Barcode.WebAssembly.zip 

// Entry file to generate WebAssembly module, i.e., 'Barcode.wat'/'Barcode.wasm'
export function Barcode_checksum(barcode: i64): i32 {
    // You can only export functions that call methods on an instance passed to them:
    const checksum = (new EAN(barcode.toString())).correct;
    // Host function from AssemblyScript standard library... Cannot work with 'Wasmtime' or 'WasmEdge':
    // console.info(checksum.toString());
    return checksum; // '0', '-1' or '-2'
}

// https://www.assemblyscript.org/concepts.html#special-imports
// Compilation: 'asc assembly/Barcode.ts --target release --use abort=assembly/Barcode/Abort'
function Abort(message: usize, fileName: usize, line: u32, column: u32): void {
    // Overriding 'abort' from 'env' (e.g., Node.js: 'process.exit(1);')
}

// AssemblyScript enumerated types can only be backed by 'i32':
enum Result {
    Correct /* = "Correct" */,
    Incorrect = -1 /* = "Incorrect" */,
    Invalid_format = -2 /* = "Invalid format" */
}

// 'Only variables, functions and enums become WebAssembly module exports.':
/* export default */
class EAN {
    // Regular expressions are not native in AssemblyScript:
    // static readonly Format: RegExp = new RegExp("^(?!000)(\\d{13}$)");
    static readonly Thirteen: i32 = 13;
    private readonly _data: Array<i32> = new Array;
    private _correct: Result = Result.Invalid_format; // Default value...
    get correct(): Result {
        return this._correct;
    }

    constructor(barcode: string) {
        if (barcode.length === EAN.Thirteen) {
            // barcode.split("").every(function (s: string) { // 'barcode' is divided into individual numbers...
            //     // JavaScript 'parseInt' returns 'f64' format that requires explicit casting in AssemblyScript:
            //     const element: i32 = parseInt(s) as i32; // Conversion for computations...
            //     if (isNaN(element)) return false; // 'every' stops with 'this._correct === Result.Invalid_format'...
            //     // Closures not yet implemented (https://blog.bitsrc.io/typescript-to-webassembly-the-what-the-how-and-the-why-3916a2561d37):
            //     this._data.push(element); // Bug...
            //     return true; // 'every' goes on...
            // });
// Substitute for closure:
            const elements = barcode.split("");
            for (let i = 0; i < elements.length; i++) {
                const element: i32 = parseInt(elements[i]) as i32;
                if (isNaN(element)) break;
                this._data.push(element);
            }
            this._correct = this._checksum() === this._data[EAN.Thirteen - 1] ? Result.Correct : Result.Incorrect;
        }
    }

    private _checksum(): i32 {
        // Méthode : https://fr.wikipedia.org/wiki/EAN_13#Calcul_de_la_cl%C3%A9_de_contr%C3%B4le_EAN_13
        // '471-9-5120-0288-x' with 'x' as checksum (i.e., 'x === 9')
        // '7', '9', '1', '0', '2', '8'
        const remainder = (this._data.filter((element: i32, index: i32) => index % 2 !== 0)
                .reduce((result, element) => result + 3 * element, 0) +
            // '4', '1', '5', '2', '0', '8'
            this._data.filter((element: i32, index: i32) => index % 2 === 0 && index !== EAN.Thirteen - 1)
                .reduce((result, element) => result + element, 0)) % 10;
        return remainder === 0 ? 0 : 10 - remainder;
    }
}

Rule(s)

Execution

wasmtime --invoke Barcode_checksum ./build/Barcode.wasm 4719512002889

Host function: the case of JavaScript

Rule(s)

Example

export function Barcode_checksum(barcode: i64): i32 {
    // You can only export functions that call methods on an instance passed to them:
    const checksum = (new EAN(barcode.toString())).correct;
    // Host function from AssemblyScript standard library... Cannot work with 'Wasmtime' or 'WasmEdge':
    console.info(checksum.toString());
    return checksum; // '0', '-1' or '-2'
}

Barcode.wat file

(import "env" "console.info" (func $~lib/bindings/dom/console.info (param i32)))

Exercise

Creating a WebAssembly module from Go

Creation of a WebAssembly module may rely on varied programming languages . Ultimately, .wat and .wasm files provide computational matter in a programming language-agnostic matter.

Go and TinyGo installation on Windows

# Scoop install. first...
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser; Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
#WASI access at compilation time requires 'binaryen':
choco install go; scoop install tinygo; scoop install binaryen

TinyGo installation on macOS

brew install go
brew tap tinygo-org/tools
# Binaryen also installed:
brew install tinygo

Once installed, Go and TinyGo compiler version may be displayed

go version
#Forthcoming 'build' fails on Windows if 'Temp' directory does not exist...
tinygo version; tinygo --help; mkdir C:\Temp
# See '-wit-package string' option
# See '-wit-world string' option
tinygo build --help

Example Barcode.goBarcode.WebAssembly.zip 

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {}

type Result int

const (
	Correct Result = iota
	Incorrect
	Invalid_format
)

//export Barcode_checksum
func Barcode_checksum(bar_code int64) Result {
	_bar_code := strconv.FormatInt(bar_code, 10)
	// values := []byte(_bar_code) // Get ASCII values...
	elements := strings.Split(_bar_code, "")
	if len(elements) == 13 {
		_data := [13]int{}
		for index, character := range elements {
			element, error := strconv.Atoi(character)
			if error != nil {
				return Invalid_format
			}
			_data[index] = element
		}
		if _Checksum(_data) == _data[len(_data)-1] {
			fmt.Println("Barcode: " + _bar_code + " is correct...") // WASI support generated by '-target=wasi' at build time...
			return Correct
		}
		return Incorrect
	}
	return Invalid_format
}

func _Checksum(_data [13]int) int {
	remainder := 0
	for index, datum := range _data {
		if index%2 != 0 {
			remainder += 3 * datum
		}
	}
	for index, datum := range _data {
		if index%2 == 0 && index != len(_data)-1 {
			remainder += datum
		}
	}
	if remainder%10 == 0 {
		return 0
	}
	return 10 - remainder%10
}

Build (WASI-enabled)

tinygo build -o ./TinyGo/Barcode.wasm -target=wasi ./TinyGo/Barcode.go

Execution

wasmtime --invoke Barcode_checksum ./TinyGo/Barcode.wasm 4719512002889

Exercise

TinyGo support for WebAssembly

calling wasm within Go /
Using a WebAssembly module

Rule(s)

Client side (browser)

AssemblyScript compiler automatically generates JavaScript stuff (e.g., Barcode.js) to run a WebAssembly module on the client side (browser) using the WebAssembly JavaScript API

Example Barcode.WebAssembly.zip 

<!-- Use of default JavaScript file is generated by AssemblyScript -->
<script type="module">
    import {Barcode_checksum} from "./build/Barcode.js";
    window.document.body.innerText = Barcode_checksum(4719512002888n);
</script>

Instead, one may customize the way the WebAssembly JavaScript API is used

Example (alternative: use of WebAssembly JavaScript API )

window.fetch("./build/Barcode.wasm").then(bytes => bytes.arrayBuffer()).then(async buffer => {
    window.console.assert(buffer instanceof ArrayBuffer);
    const module = await WebAssembly.compile(buffer);
    window.console.assert(module instanceof WebAssembly.Module);
    // Etc.
});

Example (alternative: use of WebAssembly JavaScript API )

/** Compilation is such that 'abort' is bypassed:
     * 'asc assembly/Barcode.ts --target release --use abort=assembly/Barcode/Abort'
     * As a result, AssemblyScript compiler no longer imports 'abort'... (https://www.assemblyscript.org/concepts.html#special-imports),
     * but 'console.info(checksum.toString());' requires binding:
     */
    const import_ = {env: {"console.info": data => console.info(`'console.info': ${data}`)}};

    // 'import_' is *REQUIRED* in 'WebAssembly.instantiateStreaming' only if the instantiated module actually imports something.
    // https://stackoverflow.com/questions/48039547/webassembly-typeerror-webassembly-instantiation-imports-argument-must-be-pres
    WebAssembly.instantiateStreaming(window.fetch("./build/Barcode.wasm"), import_).then(web_assembly => {
        window.console.assert(web_assembly.module instanceof window.WebAssembly.Module);
        window.console.assert(web_assembly.instance instanceof window.WebAssembly.Instance);

        // Memory provided by WebAssembly module (https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Memory):
        const memory = web_assembly.instance.exports.memory;
        window.console.assert(memory instanceof WebAssembly.Memory && memory.buffer instanceof ArrayBuffer);
        // Alter 'memory' ('ArrayBuffer' objects cannot be directly handled):
        // const data = new DataView(memory.buffer);

        const imports = WebAssembly.Module.imports(web_assembly.module); // Array of declared imports...
        for (const import_ of imports)
            window.console.log(JSON.stringify(import_)); // 'imports[0]': '{"module":"env","name":"console.info","kind":"function"}'

        window.console.assert("Barcode_checksum" in web_assembly.instance.exports);
        window.document.body.innerText = web_assembly.instance.exports.Barcode_checksum(4719512002889n); // Display '0' meaning "OK"...
    }, error => {
        window.console.error(error); // window.alert(error);
    });

Server side (e.g., Node.js )

AssemblyScript compiler automatically generates JavaScript stuff (e.g., Barcode.js) to run a WebAssembly module on the server side using the WebAssembly JavaScript API

Example Using_Barcode.wasm_server_side.mjs Barcode.WebAssembly.zip 

import path from 'path';
import {fileURLToPath} from 'url';

import {Barcode_checksum} from "../build/Barcode.js"; // JavaScript file is generated by AssemblyScript!

// Display the fact that executable file extension is '.mjs' so that Node.js uses ES module technology
// Note: 'ReferenceError: __dirname is not defined in ES module scope'
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
console.info("\t" + __filename + " WITHIN " + __dirname);
console.assert(typeof Barcode_checksum === 'function');
console.info("\t" + 'Barcode_checksum(4719512002889n): ' + Barcode_checksum(4719512002889n)); // '0' means "OK"...
Inner workings of WebAssembly

WebAssembly great deal is memory! There are several good tutorials on this and

Imported memory from host, e.g., JavaScript on the client side (browser)

Example Memory.wat Memory.wat.zip 

(module
    (; '0' is the number of pages in the default memory (1 page = 65536 bytes) ;)
    (; Name of memory within JavaScript: 'memory_from_JavaScript.memory_within_module' ;)
    (import "memory_from_JavaScript" "memory_within_module" (memory 0))
    (data (i32.const 0) "Franck Barbier") ;; String is stored in the 'memory_within_module' object from position '0'...
    (func (export "get_length") (result i32)
        i32.const 14 ;; Data length
        return
    )
)

Allocate memory to WebAssembly module

Example Memory.js Memory.wat.zip 

// JavaScript creates a WebAssembly-based memory object:
const memory_from_JavaScript = new WebAssembly.Memory({initial: 1}); // 1 page - 65536 bytes
// Translation using WABT: 'wat2wasm Memory.wat -o Memory.wasm'
WebAssembly.compileStreaming(window.fetch("./Memory.wasm"))
    // "importObject" is *REQUIRED* when the instantiated module actually imports something:
    .then((module) => WebAssembly.instantiate(module, {
        memory_from_JavaScript: {memory_within_module: memory_from_JavaScript}
    }))
    .then((executable) => {
        window.console.assert(executable instanceof WebAssembly.Instance);
        const length = executable.exports.get_length(); // '(func (export "get_length")…'
        const bytes = new Uint8Array(memory_from_JavaScript.buffer, 0, length);
        window.alert(new TextDecoder('UTF8').decode(bytes));  // "Franck Barbier"
    });

General approach

(import "a" "f" (func ...))
(import "a" "g" (func ...))
(import "b" "m" (memory 0))
const import_ = {
    a: {f: function (...) {...}, g: function (...) {...}},
    b: {m: new WebAssembly.Memory(...)}
};
WebAssembly.instantiate(module, import_).then(executable => {
    window.console.assert(executable instanceof WebAssembly.Instance);
    // Etc.

Imported function from host, e.g., JavaScript on the client side (browser)

Example Memory_size.wat Memory.wat.zip 

(module
    (; Client-side (browser) JavaScript function is imported: ;)
    (import "window" "alert" (func $window_alert (param i32)))
    (memory 10) ;; 655360 bytes
    (func (export "Memory_size")
        memory.size ;; Put the "memory size" on the stack...
        call $window_alert ;; Call through local name, i.e., '$window_alert'
    )
)

Example Memory_size.js Memory.wat.zip 

// Browser only!
WebAssembly.instantiateStreaming(window.fetch("./Memory_size.wasm"),
        {window: {alert: window.alert}})
    .then(web_assembly => {
        window.console.assert("Memory_size" in web_assembly.instance.exports);
        web_assembly.instance.exports.Memory_size(); // 10 pages...
    });

Exported memory

Rule(s)

Example Exported_memory.wat Memory.wat.zip 

(; Multiple memories do not work! ;)
;; https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format#multiple_memories
(module
    (import "env" "log" (func $display_character (param i32)))
    ;; Only one memory block is allowed:
    (memory $my_page 1) ;; [0-65535]
    ;; "RuntimeError: WebAssembly.instantiate(): data segment 0 is out of bounds" ->
    ;; (memory $my_page 0) ;; No space is allowed!
    (export "My_page" (memory $my_page))

    (data (i32.const 0) "'$my_page' is ") ;; Add "'$my_page' is " to default (0-index) memory...
    (data (memory 0) (i32.const 14) "writte")

    (func $main
        i32.const 20 ;; Position...
        i32.const 110 ;; 'n' character...
        i32.store8 ;; 'n' character is stored...
        ;; (i32.store8 (i32.const 20) (i32.const 110)) ;; 'n' character is stored at position '20'...
        (i32.load8_s (i32.const 19)) ;; Get character at 'position '19' for display...
        call $display_character ;; 101 => 'e'
    )
    (start $main)
)

Example Exported_memory.js Memory.wat.zip 

WebAssembly.instantiateStreaming(fetch("./Exported_memory.wasm"),
    {env: {log: console.log}}) // Bind actual host function to "virtual" imported function within module...
    .then(({instance}) => { // '{instance}' means "get 'instance' field"...
        const my_page = instance.exports.My_page; // Exported memory...
        const bytes = new Uint8Array(my_page.buffer, 0, 21); // < 65536
        console.info(new TextDecoder('UTF8').decode(bytes));  // '$my_page' is written
    });

Exercise

Create Exercise.wat file, which records ($Index global variable) the first available position in memory. This has to occur with regard to an exported Populate function that stores bytes from $Index and next increments $Index

Exercise.wat

(module
    (memory 1) ;; [0-65535]
    (global $Index (mut i32) (i32.const 0)) ;; Index of first available position...
    (func $Get (export "Get") (result i32)
        global.get $Index
    )
    (func $Set (param i32)
        local.get 0 ;; Get parameter...
        global.set $Index
    )
    ;; 'Populate' exported function...
)
(module
    (memory 1) ;; [0-65535]
    (global $Index (mut i32) (i32.const 0)) ;; Index of first available position...
    (func $Get (export "Get") (result i32)
        global.get $Index
    )
    (func $Set (param i32)
        local.get 0 ;; Get parameter...
        global.set $Index
    )
    (func (export "Populate") (param $data i64)
        i32.const 8 ;; 8 bytes have to be stored...
        call $Get ;; Index of first available position <= 65535
        i32.add
        i32.const 65535
        i32.gt_u ;; 'i32.add' is greater than '65535'
        (if
            (then unreachable)
            (else
                call $Get ;; Index of first available position <= 65535
                local.get $data ;; Data (8 bytes) to be stored...
                i64.store
                i32.const 8 ;; 8 bytes have just been stored...
                call $Get
                i32.add
                call $Set ;; Increment...
            )
        )
    )
)

Test: call multiple times Populate within Exercise.js file

Exercise.js

WebAssembly.instantiateStreaming(fetch("./Exercise.wasm"))
    .then(web_assembly => {
        window.console.assert("Get" in web_assembly.instance.exports);
        window.console.assert("Populate" in web_assembly.instance.exports);
        console.info("$Index: " + web_assembly.instance.exports.Get());  // '0'
        web_assembly.instance.exports.Populate(2025n); // '2025' as BigInt, i.e., using 'n'
        window.alert("$Index: " + web_assembly.instance.exports.Get());  // '8'
    });
WebAssembly System Interface -WASI-

Rule(s)

AssemblyScript

Example Barcode_WASI.ts Barcode.WebAssembly.zip 

import * as WASI from "as-wasi/assembly";

import {Barcode_checksum} from "./Barcode";

// Access and consume command line arguments from host:
const barcode: string = WASI.CommandLine.all[0].includes("node")
    ? WASI.CommandLine.all[2]
    : WASI.CommandLine.all[1]; // Wasmtime
WASI.Console.log("\t\t(WebAssembly module hosted) Barcode: " + barcode);
let checksum: i32 = Barcode_checksum(I64.parseInt(barcode));
WASI.Console.log("\t\t(WebAssembly module hosted) Checksum: " + checksum.toString());
checksum = WASI.CommandLine.all[0].includes("node")
    ? checksum
    : -checksum; // Wasmtime requires [0..126) interval...
WASI.Process.exit(checksum); // Node.js: '0', '-1' or '-2' versus Wasmtime: '0', '1' or '2'

Note: wasi_abort is not available within "as-wasi": "^0.6.0". Version "as-wasi": "^0.5.1" is required

asc assembly/Barcode_WASI.ts --exportStart _start --outFile ./build/Barcode_WASI.wasm --textFile ./build/Barcode_WASI.wat --target release --use abort=wasi_abort

Node.js execution involves the --experimental-wasi-unstable-preview1 option. Execution relies on Barcode_WASI.js JavaScript file generated by AssemblyScript: it does not work

node --experimental-wasi-unstable-preview1 ./build/Barcode_WASI.js 4719512002889"

Instead, execution based on Wasmtime works fine…

wasmtime --invoke _start ./build/Barcode_WASI.wasm 4719512002889"

AssemblyScript exportRuntime compiler option (default is true) provides C-like primitives (e.g., __new) to deal with pointers within JavaScript. As expected, host memory is the first resource a WebAssembly module aims at dealing with
asconfig.json file

"options": {
    "bindings": "esm",
    "exportRuntime": true,
    // "exportStart": "_start" // Command line instead...
},
…

WASI, the case of Node.js

Rule(s)

*Node.js WASI API imposes that the “start” function must be explicitly named _start

Example Using_Barcode.wasm_server_side_WASI.mjs as Node.js host program Barcode.WebAssembly.zip 

import {readFile} from 'node:fs/promises';
import {env} from 'node:process';
import {WASI} from 'node:wasi';

console.info("\t(Node.js host) Version: " + process.version);
console.info("\t(Node.js host) Current directory: " + process.cwd());
console.info("\t(Node.js host) Barcode: " + process.argv[2]);
const module = await WebAssembly.compile(await readFile(new URL('../build/Barcode_WASI.wasm', import.meta.url)));

// https://nodejs.org/api/wasi.html#new-wasioptions
const wasi = new WASI({
    args: process.argv, // Node.js command line arguments are passed to the WebAssembly module
    env, // Caution here: 'process.env' is *ACTUALLY* passed to 'wasi'...
    preopens: {
        '/sandbox': process.cwd() // Access to resource(s) in current directory...
    },
    returnOnExit: true, // 'wasi.start()' returns the exit code to Node.js...
    version: 'preview1' // Mandatory!
});
// Imported object is required, i.e., the API itself to access resource(s):
const instance = await WebAssembly.instantiate(module, wasi.getImportObject());
// Node.js WASI API imposes that the start method must be explicitly named '_start':
const checksum = wasi.start(instance); // '0', '-1' or '-2'
console.info("\t(Node.js host) Checksum: " + checksum);
process.exit(checksum); // Node.js returns the exit code to Operating System -OS- (Windows: 'Write-Output $?' -> 'True' or 'False')...

Execution

node --experimental-wasi-unstable-preview1 ./js/Using_Barcode.wasm_server_side_WASI.mjs 4719512002889"
Wasmtime from Bytecode Alliance

Rule(s)

Once installed, Wasmtime comes with a Command Line Interface -CLI-, version may be displayed

wasmtime -V

Execution encompasses native code (e.g., Windows) production from compilation, and chained sandboxed execution

Example Global_variable.wat Memory.wat.zip 

(module ;; Global_variable.wat
  (global $YEAR (mut i32) (i32.const 2024)) ;; Mutable global var.
  (func $Happy_new_year (param $value i32) (result i32)
    local.get $value ;; Push '$value' arg. on the stack
    global.set $YEAR ;; Set '$YEAR' to current value on the stack
    global.get $YEAR ;; Push '$YEAR' on the stack for return...
    ;; return ;; May be omitted...
  )
  (func (export "_start") ;; '_start' is the default expected name avoiding '--invoke' option using Wasmtime...
        (result i32)
    i32.const 2025 ;; Push '2025' as argument for 'Happy_new_year'
    call $Happy_new_year
    ;; return ;; May be omitted...
  )
)

Transpilation .wat.wasm and next Ahead-Of-Time -AOT- compilation followed by execution (.cwasm format)

wasmtime compile Global_variable.wat; wasmtime run Global_variable.cwasm

Execution (run may be omitted): transpilation .wat.wasm and next Just-In-Time -JIT- compilation followed by execution

wasmtime run Global_variable.wat

Example Multiple_memories.wat Memory.wat.zip 

(module ;; Multiple_memories.wat
  ;; Check 'multi memory' from https://webassembly.github.io/wabt/demo/wat2wasm/
  (memory $my_own_memory_0 10) ;; 10 pages...
  (memory $my_own_memory_1 20)
  (data (memory 0) (i32.const 0) "Franck") ;; String is stored in the default (0-index) memory from position '0'...
  (data (memory 1) (i32.const 0) "Bar") ;; String is stored in the (1-index) memory from position '0'...
  (data (memory $my_own_memory_1) (i32.const 3) "bier") ;; String is stored in the (1-index) memory from position '3'...
  (func (export "_start") (result i32)
    call $_ranges
    i32.sub ;; Size of string is returned
  )
  (func $_ranges (result i32 i32)
    i32.const 5 ;; Final storing position
    i32.const 0 ;; Initial storing position
  )
  (func (export "SIZE") (result i32) (memory.size)) ;; '10'
  ;; Does not work, but translation to .wasm succeeds:
  ;; (func (export "SIZE_") (result i32) (memory.size (memory $my_own_memory_1)))
  (func (export "GET") (param i32) (result i32) ;; '0' -> 70 ('F'), '1' -> 114 ('r')...
    (i32.load8_s (local.get 0))
  )
  ;; Does not work, but translation to .wasm succeeds:
  (func (export "GET_") (param i32) (result i32) ;; '0' -> 65 ('B')...
      (i32.load8_s (memory $my_own_memory_1) (local.get 0))
  )
)
# Display -> 5
wasmtime Multiple_memories.wat
# Display -> 10
wasmtime --invoke SIZE Multiple_memories.wat
# Display -> 70 -> 'F'
wasmtime --invoke GET Multiple_memories.wat 0

WASI, the case of Wasmtime

asc assembly/Barcode_WASI.ts --exportStart _start --outFile ./build/Barcode_WASI.wasm --textFile ./build/Barcode_WASI.wat --target release --use abort=wasi_abort
wasmtime --invoke _start ./build/Barcode_WASI.wasm 4719512002889

Get result at Operating System -OS- level (Windows PowerShell)

Write-Output $? // 'True' ('0') or 'False' ('1' or '2')

Alternative (Windows PowerShell)

$LASTEXITCODE
WasmEdge from Cloud Native Computing Foundation -CNCF-

Note: instead of Wasmtime, WasmEdge does not directly deal with the .wat format

Installation

macOS including WebAssembly System Interface -WASI-

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- --plugins wasi_nn-ggml

Or…

curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install_v2.sh | bash

Windows PowerShell including WebAssembly System Interface -WASI-

winget upgrade wasmedge

Once installed, version and help may be displayed

wasmedge -v; wasmedge -h

Running WebAssembly code

Example What_year_.wasm 

#JIT compilation
wasmedge What_year_.wasm what_year_

Compiling WebAssembly code

Example What_year_.wasm 

# AOT compilation
wasmedge compile --enable-time-measuring What_year_.wasm What_year_.cwasm

macOS

# "In the reactor mode, wasmedge runs a specified function exported by the WebAssembly program."
time wasmedge --reactor What_year_.wasm what_year_
time wasmedge --reactor What_year_.cwasm what_year_

Windows PowerShell

Measure-Command {wasmedge --reactor What_year_.wasm what_year_}
Measure-Command {wasmedge --reactor What_year_.cwasm what_year_}

Running third-party code, the case JavaScript

Code is developed through C++, Go, JavaScript, Python or Rust . As forJavaScript , QuickJS JavaScript engine is embedded as a .wasm module named wasmedge_quickjs.wasm

git clone https://github.com/second-state/wasmedge-quickjs
cd wasmedge-quickjs
# AOT compilation for better performance:
curl -OL https://github.com/second-state/wasmedge-quickjs/releases/download/v0.5.0-alpha/wasmedge_quickjs.wasm wasmedge compile wasmedge_quickjs.wasm wasmedge_quickjs.wasm

Example What_year_.js 

const d = new Date(Date.now());
console.log(d.getFullYear());

Doc.: “--dir Binding directories into WASI virtual filesystem. Each directory can be specified as --dir `host_path`. You can also map a guest directory to a host directory by --dir `guest_path:host_path`, where `guest_path` specifies the path that will correspond to `host_path` for calls like `fopen` in the guest. The default permission is `readwrite`, however, you can use --dir `guest_path:host_path:readonly` to make the mapping directory become a read only mode.”

wasmedge --dir .:. wasmedge_quickjs.wasm What_year_.js

Example Barcode_checksum.js 

// https://bellard.org/quickjs/quickjs.html#Global-objects
import * as process from 'process';
// The standard library is included by default in the command line interpreter.
// It contains the two modules std and os and a few global objects. 

// print("Command line is... ", ...args); // 'print' <=> 'console.log'

const Thirteen = 13;
if (args.length !== 2)
    exit(-1);
if (args[1].constructor.name !== 'String' || args[1].length !== Thirteen) // EAN13 only...
    exit(-2);

const barcode = new Array;
args[1].split("").forEach(s => { // Each number within the input string...
    const element = parseInt(s); // Conversion...
    if (isNaN(element))
        exit(-3);
    else
        barcode.push(element);
});

// print(...barcode);

function Barcode_checksum() {
    // Méthode : https://fr.wikipedia.org/wiki/EAN_13#Calcul_de_la_cl%C3%A9_de_contr%C3%B4le_EAN_13
    // '471-9-5120-0288-x' with 'x' as check sum (i.e., 'x === 9')
    // '7', '9', '1', '0', '2', '8':
    const remainder = (barcode.filter((element, index) => index % 2 !== 0)
            .reduce((result, element) => result + 3 * element, 0) +
            // '4', '1', '5', '2', '0', '8':
            barcode.filter((element, index) => index % 2 === 0 && index !== Thirteen - 1)
            .reduce((result, element) => result + element, 0)) % 10;
    return remainder === 0 ? 0 : 10 - remainder;
}
print(Barcode_checksum() === barcode[Thirteen - 1] ? "CORRECT" : "INCORRECT");
// For test only:
for (const k in process.env)
    print(k, '=', process.env[k]);

macOS

wasmedge --dir .:. wasmedge_quickjs.wasm Barcode_checksum.js 3200000003774; echo $?

Windows PowerShell

wasmedge --dir .:. wasmedge_quickjs.wasm Barcode_checksum.js 3200000003774; echo $LastExitCode

Exercise Barcode_country.js

const ISO_3166_international_code = Object.freeze({
    Austria: 36,
    France: 250,
    Monaco: 492,
    Portugal: 620,
    Japan: 392,
    Russia: 643,
    Taiwan: 158,
    Incorrect: -1,
    Invalid_format: -2
});

const Countries = new Map;
Countries.set(ISO_3166_international_code.Austria, [900, 919]);
Countries.set(ISO_3166_international_code.France, [300, 379]);
Countries.set(ISO_3166_international_code.Monaco, [300, 379]);
Countries.set(ISO_3166_international_code.Portugal, [560, 560]);
Countries.set(ISO_3166_international_code.Japan, [450, 459]);
Countries.set(ISO_3166_international_code.Russia, [460, 469]);
Countries.set(ISO_3166_international_code.Taiwan, [471, 471]);

const Thirteen = 13;
if (args.length !== 2)
    exit(ISO_3166_international_code.Invalid_format);
if (args[1].constructor.name !== 'String' || args[1].length !== Thirteen) // EAN-13 only...
    exit(ISO_3166_international_code.Invalid_format);

// 'Barcode_country' function here...

Barcode_country.js cont'd

function Barcode_country() {
    const country_code = parseInt(args[1].substr(0, 3));
    if (isNaN(country_code))
        return ISO_3166_international_code.Invalid_format;
    const result = new Array;
    Countries.forEach((value, key) => {
        if (country_code >= value[0] && country_code <= value[1])
            result.push(key);
    });
    // 'result[0]' raises problem between, for instance, 'France' and 'Monaco':
    return result.length === 0 ? ISO_3166_international_code.Incorrect : result[0];
}

macOS

# '643' is displayed
wasmedge --dir .:. wasmedge_quickjs.wasm Barcode_country.js 4606453849072; echo $?

Windows PowerShell

# '643' is displayed
wasmedge --dir .:. wasmedge_quickjs.wasm Barcode_country.js 4606453849072; echo $LastExitCode

Host function

Installation of WasmEdge Go SDK

#Unavailable:
go install github.com/second-state/WasmEdge-go/wasmedge@v0.14.1
# List of all packages:
go list ...
wasmCloud from Cloud Native Computing Foundation -CNCF-

wasmCloud has Wasmtime as underlying WebAssembly middleware

Installation

macOS

brew install wasmcloud/wasmcloud/wash

Windows PowerShell

choco install wash

Version and help

wash -V; wash --help

TypeScript

Note: tutorial does not work within Windows PowerShell

Pre-built wasmCloud TypeScript project

Example bar-code-wasm-cloud.zip 

TypeScript project creation

wash new component Bar_code_wasmCloud --template-name hello-world-typescript

jco supports the componentization from JavaScript code to WebAssembly

#File 'wasmcloud.toml' expresses the 'build' command
# 'npm run build' as alternative...
wash build
#See 'Hosts' within user interface...
wash up
#Dashboard at http://localhost:3030
wash ui
#File 'local.wadm.yaml' for deployment configuration
wash app deploy local.wadm.yaml
wash app list

Result at http://localhost:1963?bar_code=3200000003774

Administration console

Implementation

import {
    IncomingRequest,
    ResponseOutparam,
    OutgoingBody,
    OutgoingResponse,
    Fields
} from 'wasi:http/types@0.2.0';

/* Implementation of 'wasi-http-0.2.0' 'incoming-handler' interface */
/* Types involved are described here: wit/deps/wasi-http-0.2.0/packages.wit */

/* interface incoming-handler {
    use types.{incoming-request, response-outparam};
    /// This function is invoked with an incoming HTTP Request, and a resource
    /// `response-outparam` which provides the capability to reply with an HTTP
    /// Response. The response is sent by calling the `response-outparam.set`
    /// method, which allows execution to continue after the response has been
    /// sent. This enables both streaming to the response body, and performing other work.
    ///
    /// The implementor of this function must write a response to
    /// `response-outparam` before returning, or else the caller will respond
    /// with an error on its behalf.
    handle: func(request: incoming-request, response-out: response-outparam);
} */
function handle(request: IncomingRequest, response: ResponseOutparam) {
    console.assert(request.method().tag === "get");
    // Start building an outgoing response:
    const outgoingResponse = new OutgoingResponse(new Fields());
    // Access the outgoing response body:
    const outgoingBody = outgoingResponse.body();
    //const _bar_code = request.pathWithQuery()!.substring(request.pathWithQuery()!.indexOf('?') + 1, request.pathWithQuery()!.length);
    let _bar_code = "";
    const parameters = request.pathWithQuery()!.split("=");
    if (parameters.length === 2)
        _bar_code= parameters[1];
    // Create a stream for the response body:
    const outputStream = outgoingBody.write();
    // Write checksum correctness to the response stream:
    try {
        const bar_code = new Bar_code(_bar_code);
        outputStream.blockingWriteAndFlush(new Uint8Array(new TextEncoder().encode(_bar_code + ": " + bar_code.correct)));
    } catch (error: unknown) {
        outputStream.blockingWriteAndFlush(new Uint8Array(new TextEncoder().encode(_bar_code + ": " + error)));
    }
    // @ts-ignore: this is required in order to dispose the stream before return:
    outputStream[Symbol.dispose]();
    // Set the status code for the response:
    outgoingResponse.setStatusCode(200);
    // Finish the response body:
    OutgoingBody.finish(outgoingBody, undefined);
    // Set the created response:
    ResponseOutparam.set(response, {tag: 'ok', val: outgoingResponse});
}

/** Map 'handle ' to 'wasi:http/incoming-handler@0.2.0'
 * 'incomingHandler' get 'handle' field with 'handle' function body as value
 */
export const incomingHandler = {handle};
Spin

Installation

macOS

brew tap fermyon/tap; brew install fermyon/tap/spin

Version and help

spin -V; spin -h
Wasm Interface Type -WIT-

The WebAssembly Component model is exposed while WASI is exposed

Modules versus components

https://wasmcloud.com/docs/reference/glossary#module

https://component-model.bytecodealliance.org/language-support/javascript.html https://github.com/second-state/witc https://github.com/WasmEdge/WasmEdge/issues/1892 PYTHON: https://wasmcloud.com/blog/bring-your-own-wasm-components