mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-23 17:41:41 +00:00
added my WebAssembly tutorial
This commit is contained in:
parent
84cb0e8899
commit
f62b48d928
227
wasm.html.markdown
Normal file
227
wasm.html.markdown
Normal file
@ -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))
|
||||
)
|
||||
|
||||
```
|
Loading…
Reference in New Issue
Block a user