diff options
| -rw-r--r-- | wasm.html.markdown | 227 | 
1 files changed, 227 insertions, 0 deletions
| diff --git a/wasm.html.markdown b/wasm.html.markdown new file mode 100644 index 00000000..a5c00d7b --- /dev/null +++ b/wasm.html.markdown @@ -0,0 +1,227 @@ +--- +language: WebAssembly +filename: learn-wasm.wast +contributors: +    - ["Dean Shaff", "http://dean-shaff.github.io"] +--- + +```webassembly +;; 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))) ;; WRONG! mult_result is f64! + +    ;; 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)) +) + +``` | 
