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
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
codemacOS installation
brew upgrade binaryen
WebAssembly text format (
.wat
suffix) ☛ is the straightforward way of creating a WebAssembly functionExample 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
.wast
☛ is a superset of.wat
. Use of named parameters above, e.g.,local.get $first_parameter
is rather part of.wast
whilelocal.get 0
is rather part of.wat
.cwasm
is a kind of.wasm
in the sense that it refers toAhead-Of-Time
-AOT- compilation (opposite toJust-In-Time
-JIT- compilation)
AssemblyScript getting started ☛… Once installed, AssemblyScript compiler version may be displayed
asc -v
Rule(s)
- AssemblyScript has, compared to TypeScript, its own types ☛ like, for instance,
i32
oru32
(unsigned 32-bit integer). In constrast, TypeScript core types, namelyany
andunknown
, are not part of AssemblyScript- Although AssemblyScript is inspired by TypeScript, there are significant differences ☛
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)
- The AssemblyScript compiler generates two files from
Barcode.ts
:Barcode.wat
(WebAssembly text format based on symbolic expressions found in programming languages like List Processing -LisP-) andBarcode.wasm
(assembler)Execution
wasmtime --invoke Barcode_checksum ./build/Barcode.wasm 4719512002889
Host function: the case of JavaScript
Rule(s)
- Simply speaking, exported (provided) functions in WebAssembly modules (e.g.,
Barcode_checksum
) compute output data that aim at being pushed as input data in other functions. The need for Input/Output support then relies on WebAssembly itself (WebAssembly System Interface -WASI-) or homemade support- To that extent, AssemblyScript provides a standard library including JavaScript “commonalities”, say,
console
,process
… with local functions, say,info
forconsole
,exit
forprocess
… These are (facilitation) host functions.- By construction, host functions are imported in WebAssembly modules (
Barcode.wat
andBarcode.wasm
). If no host bindings are provided then no executions are possible within WebAssembly runtime supportsExample
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 ☛
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.go
Barcode.WebAssembly.zippackage 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 /
Rule(s)
- Common JavaScript programs may run WebAssembly modules by using the WebAssembly JavaScript API ☛
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.zipimport 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"...
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)
- WebAssembly non-numerical types, (i.e., types different from integers like
i64
, reals likef64
…), strings typically, must be handled through memory pages (1 page: 65536 bytes). To that extent,WebAssembly.Memory
type allows the handling of WebAssembly memory within the WebAssembly JavaScript API ☛- WebAssembly modules may own memory pages and export them. The WebAssembly JavaScript API ☛ may then access them through, if exported,
instance.exports.my_memory_name
under the following condition:window.console.assert(instance instanceof WebAssembly.Instance)
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.zipWebAssembly.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 exportedPopulate
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
withinExercise.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' });
Rule(s)
- The lack of resource access and consumption facilities in WebAssembly modules leads to WebAssembly System Interface -WASI-: original paper ☛ (March 2019)
- WASI is promoted by the Bytecode Alliance ☛, which includes Amazon, Google, Microsoft, Intel…
- The key issue behind WebAssembly is the fact that, by nature, a module has no access (and therefore consumption possibility) of “host” resources. For example, access to the file system in Node.js (as “host”) is based on the
fs
object. A WebAssembly module cannot deal with files in general throughfs
in Node.js- Discussion about the need of WASI on the server side is still open ☛
AssemblyScript
as-wasi
☛ is the WASI support in AssemblyScript. However, it seems to be no longer maintained ☛as-wasi
☛ exposes resources and possible usages (e.g.,WASI.Console.log
below) in a standardized way (i.e., WASI) avoiding the use of convenient AssemblyScript host functions specific to JavaScript (e.g.,console.log
)- WASI aims at creating total neutrality within AssemblyScript code. WebAssembly modules written in AssemblyScript or competitor programming languages like Rust, then have agnostic mechanisms in
.wat
and.wasm
files in the way, the host execution context is interacted withExample
Barcode_WASI.ts
Barcode.WebAssembly.zipimport * 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 requiredasc 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 onBarcode_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 istrue
) 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 withasconfig.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
- Node.js provides a builtin API for WASI* ☛
- Principle: Node.js “pushes” its execution context to WASI, namely
env
,process
… The executed WebAssembly module having Node.js as host transparently accesses and consumes resources from this execution context using a Portable Operating System Interface X -POSIX-, which is a compliant entry point namedwasi_snapshot_preview1
☛_start
Example
Using_Barcode.wasm_server_side_WASI.mjs
as Node.js host program Barcode.WebAssembly.zipimport {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"
Rule(s)
- Wasmtime is the more direct way of operating WASI and therefore WebAssembly. As standalone runtime support, Wasmtime is a non-Web (i.e., outside browsers and Node.js) WebAssembly runtime support
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 nextAhead-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 nextJust-In-Time
-JIT- compilation followed by executionwasmtime 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
Note: instead of Wasmtime, WasmEdge does not directly deal with the
.wat
formatInstallation ☛
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 namedwasmedge_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'dfunction 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 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};
Installation ☛
macOS
brew tap fermyon/tap; brew install fermyon/tap/spin
Version and help
spin -V; spin -h
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