;; learn-wasm.wast (module ;; In WebAssembly, everything is included in a module. Moreover, everything ;; can be expressed as an s-expression. Alternatively, there is the ;; "stack machine" syntax, but that is not compatible with Binaryen ;; intermediate representation (IR) syntax. ;; The Binaryen IR format is *mostly* compatible with WebAssembly text format. ;; There are some small differences: ;; local_set -> local.set ;; local_get -> local.get ;; We have to enclose code in functions ;; Data Types (func $data_types ;; WebAssembly has only four types: ;; i32 - 32 bit integer ;; i64 - 64 bit integer (not supported in JavaScript) ;; f32 - 32 bit floating point ;; f64 - 64 bit floating point ;; We can declare local variables with the "local" keyword ;; We have to declare all variables before we start doing anything ;; inside the function (local $int_32 i32) (local $int_64 i64) (local $float_32 f32) (local $float_64 f64) ;; These values remain uninitialized. ;; To set them to a value, we can use .const: (local.set $int_32 (i32.const 16)) (local.set $int_64 (i64.const 128)) (local.set $float_32 (f32.const 3.14)) (local.set $float_64 (f64.const 1.28)) ) ;; Basic operations (func $basic_operations ;; In WebAssembly, everything is an s-expression, including ;; doing math, or getting the value of some variable (local $add_result i32) (local $mult_result f64) (local.set $add_result (i32.add (i32.const 2) (i32.const 4))) ;; the value of add_result is now 6! ;; We have to use the right data type for each operation: ;; (local.set $mult_result (f32.mul (f32.const 2.0) (f32.const 4.0))) ;; WRONG! mult_result is f64! (local.set $mult_result (f64.mul (f64.const 2.0) (f64.const 4.0))) ;; WebAssembly has some builtin operations, like basic math and bitshifting. ;; Notably, it does not have built in trigonometric functions. ;; In order to get access to these functions, we have to either ;; - implement them ourselves (not recommended) ;; - import them from elsewhere (later on) ) ;; Functions ;; We specify arguments with the `param` keyword, and specify return values ;; with the `result` keyword ;; The current value on the stack is the return value of a function ;; We can call other functions we've defined with the `call` keyword (func $get_16 (result i32) (i32.const 16) ) (func $add (param $param0 i32) (param $param1 i32) (result i32) (i32.add (local.get $param0) (local.get $param1) ) ) (func $double_16 (result i32) (i32.mul (i32.const 2) (call $get_16)) ) ;; Up until now, we haven't be able to print anything out, nor do we have ;; access to higher level math functions (pow, exp, or trig functions). ;; Moreover, we haven't been able to use any of the WASM functions in Javascript! ;; The way we get those functions into WebAssembly ;; looks different whether we're in a Node.js or browser environment. ;; If we're in Node.js we have to do two steps. First we have to convert the ;; WASM text representation into actual webassembly. If we're using Binyaren, ;; we can do that with a command like the following: ;; wasm-as learn-wasm.wast -o learn-wasm.wasm ;; We can apply Binaryen optimizations to that file with a command like the ;; following: ;; wasm-opt learn-wasm.wasm -o learn-wasm.opt.wasm -O3 --rse ;; With our compiled WebAssembly, we can now load it into Node.js: ;; const fs = require('fs') ;; const instantiate = async function (inFilePath, _importObject) { ;; var importObject = { ;; console: { ;; log: (x) => console.log(x), ;; }, ;; math: { ;; cos: (x) => Math.cos(x), ;; } ;; } ;; importObject = Object.assign(importObject, _importObject) ;; ;; var buffer = fs.readFileSync(inFilePath) ;; var module = await WebAssembly.compile(buffer) ;; var instance = await WebAssembly.instantiate(module, importObject) ;; return instance.exports ;; } ;; ;; const main = function () { ;; var wasmExports = await instantiate('learn-wasm.wasm') ;; wasmExports.print_args(1, 0) ;; } ;; The following snippet gets the functions from the importObject we defined ;; in the JavaScript instantiate async function, and then exports a function ;; "print_args" that we can call from Node.js (import "console" "log" (func $print_i32 (param i32))) (import "math" "cos" (func $cos (param f64) (result f64))) (func $print_args (param $arg0 i32) (param $arg1 i32) (call $print_i32 (local.get $arg0)) (call $print_i32 (local.get $arg1)) ) (export "print_args" (func $print_args)) ;; Loading in data from WebAssembly memory. ;; Say that we want to apply the cosine function to a Javascript array. ;; We need to be able to access the allocated array, and iterate through it. ;; This example will modify the input array inplace. ;; f64.load and f64.store expect the location of a number in memory *in bytes*. ;; If we want to access the 3rd element of an array, we have to pass something ;; like (i32.mul (i32.const 8) (i32.const 2)) to the f64.store function. ;; In JavaScript, we would call `apply_cos64` as follows ;; (using the instantiate function from earlier): ;; ;; const main = function () { ;; var wasm = await instantiate('learn-wasm.wasm') ;; var n = 100 ;; const memory = new Float64Array(wasm.memory.buffer, 0, n) ;; for (var i=0; ia = a; (i32.store (get_local $sum_struct_ptr) (get_local $var$a) ) ;; c// sum_struct_ptr->b = b; (i32.store offset=4 (get_local $sum_struct_ptr) (get_local $var$b) ) ) (func $sum_local (result i32) (local $var$sum_struct$a i32) (local $var$sum_struct$b i32) (local $local_memstack_ptr i32) ;; reserve memstack space (i32.sub (get_global $memstack_ptr) (i32.const 8) ) tee_local $local_memstack_ptr ;; tee both stores and returns given value set_global $memstack_ptr ;; call the function, storing the result in the memstack (call $sum_struct_create ((;$sum_struct_ptr=;) get_local $local_memstack_ptr) ((;$var$a=;) i32.const 40) ((;$var$b=;) i32.const 2) ) ;; retrieve values from struct (set_local $var$sum_struct$a (i32.load offset=0 (get_local $local_memstack_ptr)) ) (set_local $var$sum_struct$b (i32.load offset=4 (get_local $local_memstack_ptr)) ) ;; unreserve memstack space (set_global $memstack_ptr (i32.add (get_local $local_memstack_ptr) (i32.const 8) ) ) (i32.add (get_local $var$sum_struct$a) (get_local $var$sum_struct$b) ) ) (export "sum_local" (func $sum_local)) )