From f62b48d9283fd04738a1fd664c4b92b6891b4e04 Mon Sep 17 00:00:00 2001 From: Dean Shaff Date: Tue, 16 Jul 2019 11:51:18 +1000 Subject: added my WebAssembly tutorial --- wasm.html.markdown | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 wasm.html.markdown (limited to 'wasm.html.markdown') 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 .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 Date: Sun, 4 Aug 2019 14:18:55 +0530 Subject: Update wasm.html.markdown Fix lexer issue --- wasm.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wasm.html.markdown') diff --git a/wasm.html.markdown b/wasm.html.markdown index a5c00d7b..98bfc000 100644 --- a/wasm.html.markdown +++ b/wasm.html.markdown @@ -5,7 +5,7 @@ contributors: - ["Dean Shaff", "http://dean-shaff.github.io"] --- -```webassembly +``` ;; learn-wasm.wast (module -- cgit v1.2.3 From b27d3a088db2fac2324df2a05330801afee2e92b Mon Sep 17 00:00:00 2001 From: Rett Berg Date: Sat, 16 Nov 2019 12:38:14 -0700 Subject: add example of how stack might be managed --- wasm.html.markdown | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) (limited to 'wasm.html.markdown') diff --git a/wasm.html.markdown b/wasm.html.markdown index 98bfc000..92831e69 100644 --- a/wasm.html.markdown +++ b/wasm.html.markdown @@ -222,6 +222,92 @@ contributors: ) ) (export "apply_cos64" (func $apply_cos64)) + + ;; 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; + ;; } + ;; + ;; Wasm is a stack-based language, but for returning values more complicated + ;; than an int/float, a 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 mem-stack and grow it downwards. + ;; + ;; Note: we are differentiating from the memstack (stack stored in memory) + ;; and the "stack", which wasm implicitly uses to to pass and return values. + (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 stack 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 stack + (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 stack 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)) ) ``` -- cgit v1.2.3 From 4727925b1aef7f030adb8ae76bf7562a6280b54e Mon Sep 17 00:00:00 2001 From: Rett Berg Date: Sat, 16 Nov 2019 12:44:47 -0700 Subject: update comments a bit --- wasm.html.markdown | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'wasm.html.markdown') diff --git a/wasm.html.markdown b/wasm.html.markdown index 92831e69..d62539b8 100644 --- a/wasm.html.markdown +++ b/wasm.html.markdown @@ -223,7 +223,12 @@ contributors: ) (export "apply_cos64" (func $apply_cos64)) - ;; Demonstration of how this C code might be written by hand + ;; Wasm is a stack-based language, but for returning values more complicated + ;; than an int/float, a 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 mem-stack and grow it downwards. + ;; + ;; Below is a demonstration of how this C code **might** be written by hand ;; ;; typedef struct { ;; int a; @@ -238,17 +243,11 @@ contributors: ;; sum_struct_t s = sum_struct_create(40, 2); ;; return s.a + s.b; ;; } - ;; - ;; Wasm is a stack-based language, but for returning values more complicated - ;; than an int/float, a 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 mem-stack and grow it downwards. - ;; - ;; Note: we are differentiating from the memstack (stack stored in memory) - ;; and the "stack", which wasm implicitly uses to to pass and return values. + + ;; Unlike C, we must manage our own memory stack (global $memstack_ptr (mut i32) (i32.const 65536)) - ;; structs can only be returned by reference + ;; Structs can only be returned by reference (func $sum_struct_create (param $sum_struct_ptr i32) (param $var$a i32) -- cgit v1.2.3 From 0427cb31681114e3d4c2bf664c37d17aa82a7f59 Mon Sep 17 00:00:00 2001 From: Rett Berg Date: Sat, 16 Nov 2019 12:47:32 -0700 Subject: more comment updates --- wasm.html.markdown | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'wasm.html.markdown') diff --git a/wasm.html.markdown b/wasm.html.markdown index d62539b8..aba2084f 100644 --- a/wasm.html.markdown +++ b/wasm.html.markdown @@ -224,9 +224,9 @@ contributors: (export "apply_cos64" (func $apply_cos64)) ;; Wasm is a stack-based language, but for returning values more complicated - ;; than an int/float, a memory stack has to be manually managed. One + ;; 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 mem-stack and grow it downwards. + ;; ourselves 1MiB of memstack and grow it downwards. ;; ;; Below is a demonstration of how this C code **might** be written by hand ;; @@ -244,7 +244,7 @@ contributors: ;; return s.a + s.b; ;; } - ;; Unlike C, we must manage our own memory stack + ;; 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 @@ -270,7 +270,7 @@ contributors: (local $var$sum_struct$b i32) (local $local_memstack_ptr i32) - ;; reserve stack space + ;; reserve memstack space (i32.sub (get_global $memstack_ptr) (i32.const 8) @@ -278,7 +278,7 @@ contributors: tee_local $local_memstack_ptr ;; tee both stores and returns given value set_global $memstack_ptr - ;; call the function, storing the result in the stack + ;; 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) @@ -293,7 +293,7 @@ contributors: (i32.load offset=4 (get_local $local_memstack_ptr)) ) - ;; unreserve stack space + ;; unreserve memstack space (set_global $memstack_ptr (i32.add (get_local $local_memstack_ptr) -- cgit v1.2.3 From 3406234ef19735ed033ab171185b9b6e24fccfdb Mon Sep 17 00:00:00 2001 From: Aleksei Trifonov Date: Sun, 5 Apr 2020 01:12:31 +0300 Subject: Remove wrong comment The second comment probably got to the actual expression by mistake. Commented expression is wrong as it should be, but the other one is fine --- wasm.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wasm.html.markdown') diff --git a/wasm.html.markdown b/wasm.html.markdown index aba2084f..81344abe 100644 --- a/wasm.html.markdown +++ b/wasm.html.markdown @@ -61,7 +61,7 @@ contributors: ;; 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! + (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. -- cgit v1.2.3