diff options
Diffstat (limited to 'wasm.html.markdown')
| -rw-r--r-- | wasm.html.markdown | 312 | 
1 files changed, 312 insertions, 0 deletions
| diff --git a/wasm.html.markdown b/wasm.html.markdown new file mode 100644 index 00000000..81344abe --- /dev/null +++ b/wasm.html.markdown @@ -0,0 +1,312 @@ +--- +language: WebAssembly +filename: learn-wasm.wast +contributors: +    - ["Dean Shaff", "http://dean-shaff.github.io"] +--- + +``` +;; 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 <type>.const: + +    (local.set $int_32 (i32.const 16)) +    (local.set $int_32 (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; 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) + +    (local.set $idx (i32.const 0)) +    (local.set $idx_bytes (i32.const 0)) ;; not entirely necessary +    (local.set $bytes_per_double (i32.const 8)) + +    (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)))) + +        ;; increment the loop counter +        (local.set $idx (i32.add (local.get $idx) (i32.const 1))) + +        ;; stop the loop if the loop counter is equal the array length +        (br_if 1 (i32.eq (local.get $idx) (local.get $array_length))) +        (br 0) +      ) +    ) +  ) +  (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) +    ) + +    ;; 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)) +) + +``` | 
