(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
;; Data Types (func $data_types ;; WebAssembly 只有四种类型: ;; i32 - 32 bit integer ;; i64 - 64 bit integer (not supported in JavaScript) ;; f32 - 32 bit floating point ;; f64 - 64 bit floating point
;; 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) )
;; 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.
;; 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
;; 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; i<n; i++) { ;; memory[i] = i; ;; } ;; wasm.apply_cos64(n) ;; } ;; ;; This function will not work if we allocate a Float32Array on the JavaScript ;; side.
(memory (export"memory") 100)
(func $apply_cos64 (param $array_length i32) ;; declare the loop counter (local $idx i32) ;; declare the counter that will allow us to access memory (local $idx_bytes i32) ;; constant expressing the number of bytes in a f64 number. (local $bytes_per_double i32)
;; declare a variable for storing the value loaded from memory (local $temp_f64 f64)
(block (loop ;; this sets idx_bytes to bytes offset of the value we're interested in. (local.set $idx_bytes (i32.mul (local.get $idx) (local.get $bytes_per_double)))
;; get the value of the array from memory: (local.set $temp_f64 (f64.load (local.get $idx_bytes)))
;; now apply the cosine function: (local.set $temp_64 (call $cos (local.get $temp_64)))
;; now store the result at the same location in memory: (f64.store (local.get $idx_bytes) (local.get $temp_64))
;; do it all in one step instead (f64.store (local.get $idx_bytes) (call $cos (f64.load (local.get $idx_bytes))))
;; stop the loop if the loop counter is equal the array length (br_if1 (i32.eq (local.get $idx) (local.get $array_length))) (br0) ) ) ) (export"apply_cos64" (func $apply_cos64))
;; Wasm is a stack-based language, but for returning values more complicated ;; than an int/float, a separate memory stack has to be manually managed. One ;; approach is to use a mutable global to store the stack_ptr. We give ;; ourselves 1MiB of memstack and grow it downwards. ;; ;; Below is a demonstration of how this C code **might** be written by hand ;; ;; typedef struct { ;; int a; ;; int b; ;; } sum_struct_t; ;; ;; sum_struct_t sum_struct_create(int a, int b) { ;; return (sum_struct_t){a, b}; ;; } ;; ;; int sum_local() { ;; sum_struct_t s = sum_struct_create(40, 2); ;; return s.a + s.b; ;; }
;; Unlike C, we must manage our own memory stack. We reserve 1MiB (global $memstack_ptr (mut i32) (i32.const 65536))
;; Structs can only be returned by reference (func $sum_struct_create (param $sum_struct_ptr i32) (param $var$a i32) (param $var$b i32) ;; c// sum_struct_ptr->a = a; (i32.store (get_local $sum_struct_ptr) (get_local $var$a) )
;; 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) )