├── README.md └── lessons ├── 00 └── README.md ├── 01 └── README.md ├── 02 └── README.md ├── 03 └── README.md ├── 04 └── README.md ├── 05 └── README.md ├── 06 └── README.md ├── 07 └── README.md └── 08 └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # `learnhandwasm` - Learn to Handcraft WebAssembly! 2 | 3 | ## [Go to lesson 0 to get started](https://github.com/emilbayes/learnhandwasm/tree/master/lessons/00) 4 | 5 | ## Extra links 6 | 7 | S-expression cheatsheet https://github.com/WebAssembly/spec/tree/master/interpreter#s-expression-syntax 8 | Core WAST tests (good for looking up syntax): https://github.com/WebAssembly/spec/tree/master/test/core 9 | Memory Alignment: https://gist.github.com/emilbayes/0b011404cf1719b651280cb77a209566 10 | -------------------------------------------------------------------------------- /lessons/00/README.md: -------------------------------------------------------------------------------- 1 | # 00. Welcome! 2 | 3 | Welcome to the handcrafting WebAssembly workshop. WebAssembly is a byte-code for 4 | the web, making it possible for compilers to target at common instruction format. 5 | 6 | Despite the name it is note quite assembly and not quite web, since it does not 7 | natively allow you to access any Web APIs (yet!) and is more high-level than 8 | assembly, as you don't directly interact with the CPUs instruction set, but 9 | an idealised instruction set, that the runtime can very simply and efficiently 10 | translate to machine code. 11 | 12 | As we will see when we look at the instruction format, WebAssembly is an 13 | extremely secure sandbox, where access to anything outside the VM is granted 14 | explicitly. 15 | 16 | To get started you need a recent version of Node (8+), which you can check with: 17 | 18 | ```sh 19 | node -v 20 | ``` 21 | 22 | You also need a simple compiler that can translate the text-format to the binary 23 | format. For the beginning of this workshop we will also use an additional tool 24 | that will wrap you WASM binary module in a Javascript module for easy usage. 25 | 26 | Install the following tools: 27 | 28 | ```sh 29 | npm install --global wat2wasm wat2js 30 | ``` 31 | 32 | You can verify the tools with: 33 | 34 | ```sh 35 | wat2wasm --help 36 | wat2js --help 37 | ``` 38 | 39 | `wat2wasm` is the compiler and `wat2js` depends on this compiler, and in 40 | addition will produce a very simple Javascript module from your WAT code. 41 | 42 | **Remark**: The above tools are userland tools, the first made by me and the 43 | second made by @mafintosh. There is however an official toolchain called WABT, 44 | WebAssembly Binary Toolkit, but it can be tricky to compile this C++ based 45 | toolchain, so for this workshop we will work with the simplified versions. 46 | In practise the `wat2wasm` from npm is the native WABT `wat2wasm` compiled to 47 | WebAssembly. So you will be compiling WebAssembly with WebAssembly. Meta! 48 | [Official WABT repository](https://github.com/WebAssembly/wabt) 49 | 50 | [**Exercise 01**](../01) 51 | -------------------------------------------------------------------------------- /lessons/01/README.md: -------------------------------------------------------------------------------- 1 | # 01. Your first WASM Module 2 | 3 | Without further ado, here is the first WASM module we will be looking at: 4 | 5 | ```webassembly 6 | (module $arithmetic 7 | (func $i32.add (export "i32.add") 8 | (param $a i32) 9 | (param $b i32) 10 | (result i32) 11 | (i32.add 12 | (get_local $a) 13 | (get_local $b)))) 14 | ``` 15 | 16 | In this workshop we will be working with the S-expression based WebAssembly 17 | Text-Format (WAT), which at first will seem foreign to Javascript developers 18 | but quickly will become familiar. 19 | 20 | The above module is the "hello world" of WebAssembly. Before we start writing 21 | WAT ourselves we need to learn a bit about the syntax and grammar of WAT. You 22 | should paste the module into a file so we can play around with it. I will be 23 | calling mine for `arithmetic.wat`. 24 | 25 | The first thing we notice is the `(module $arithmetic)` instruction. 26 | `$arithmetic` is a label that the compiler will be turning into a simple 27 | integer that will count up. In WASM you can (at present) have a single module 28 | per file, and the name is only for debugging, as it will be replaced with said 29 | integer. However `wat2wasm` can extract labels and place them into a special 30 | debugging section, like source maps from Javascript, so they can be consumed 31 | by disassemblers and debuggers. 32 | 33 | Try running the following command: 34 | 35 | ``` 36 | echo '(module $arithmetic)' | wat2wasm --dump-module - 37 | ``` 38 | 39 | What it does is that it compiles the module from `stdin` and prints the 40 | annotated byte-code. As you can see it's is not very interesting, simply 41 | printing a `\0` byte, the ASCII string `asm` and then a version identifier. 42 | 43 | Now try with the following command: 44 | 45 | ``` 46 | echo '(module $arithmetic)' | wat2wasm --dump-module --debug-names - 47 | ``` 48 | 49 | You can see a lot more information has been added to the binary module. 50 | In particular notice line `0000012` with our name `arithmetic` encoded. 51 | During the workshop it is recommended to always append the `--debug-names` 52 | flag, when compiling your modules. However, for production it is recommended 53 | to not add these symbols, as they can incur a significant increase in module 54 | size, just like you wouldn't ship source maps to production. 55 | 56 | A bit of a nitty gritty detour, but head on to the next exercise where we will 57 | look at `$i32.add` function: 58 | 59 | [**Exercise 02**](../02) 60 | -------------------------------------------------------------------------------- /lessons/02/README.md: -------------------------------------------------------------------------------- 1 | # 02. Arithmetic ... WAT? 2 | 3 | Recall the module from the last exercise: 4 | 5 | ```webassembly 6 | ;; arithmetic.wat 7 | (module $arithmetic 8 | (func $i32.add (export "i32.add") 9 | (param $a i32) 10 | (param $b i32) 11 | (result i32) 12 | (i32.add 13 | (get_local $a) 14 | (get_local $b)))) 15 | ``` 16 | 17 | As you may already have guessed this function simply adds two numbers together, 18 | however there are a number of details in the function. 19 | 20 | ## S-Expressions 21 | 22 | As of yet I haven't even explained how the syntax works. This syntax 23 | is called an "S-Expression", and the format is `(operator operands*)`, where 24 | operator is the instruction to be called, and `operands` can be zero or more 25 | "arguments" to be passed. A simple, familiar example would be `2 + 2 * 4`, which 26 | as an S-Expression would be `(+ 2 (* 2 4))`. As you can see precedence is 27 | extremely clear, the syntax is very easy to parse for computers and humans (with 28 | a bit of practise) and there is no ambiguity. As a mental model for executing 29 | this code in your head, you can start at the innermost expression, execute it 30 | and replace the result, while you move outwards. See the following: 31 | 32 | ``` 33 | 2 * 2 + 4 * 8 / 2 34 | (+ (* 2 2) (* 4 (/ 8 2))) ;; evaluating the division 35 | (+ (* 2 2) (* 4 4)) ;; evaluating both multiplications 36 | (+ 4 16) ;; evaluating the addition 37 | 20 38 | ``` 39 | 40 | Also note that whitespace is ignored, so you can use it to organise your code. 41 | Single line comments are made with `;;` while multiline comments are `(; ... ;)` 42 | making it easy to remove entire instructions. 43 | 44 | ## The `$i32.add` function 45 | 46 | The first instruction you encounter in the module is `(func $i32.add ...)`. 47 | The `$i32.add` is a label just like `$arithmetic` was, and will be replaced with 48 | a counting integer when compiled, for efficiency reasons. The name is not 49 | arbitrarily chosen. In WASM there is the convention of naming instructions 50 | `type.operator`, and I tend to follow the same convention, to make my 51 | modules more readable. 52 | 53 | Next we declare `(export "i32.add")`, which is to say that the function should 54 | be publicly accessible outside the module. This means that in a minute, we will 55 | be able to call the function from Javascript or other WASM modules, under that 56 | name. 57 | 58 | In WASM, part of the sandboxing model is that modules should be able to be 59 | "validated" at compile time (which happens in Node or the Browser) for soundness. 60 | This implicates that everything in WASM is strongly typed. There are very few 61 | builtin types, namely `i32`, `i64`, `f32` and `f64`. The first two are integers 62 | of different bit lengths. We have 32-bit integers in Javascript (yes it's true!) 63 | which we will see later. We do have 64-bit integers through BigInt, but this 64 | type is not yet accessible from Javascript. Both of these integer types are 65 | sign-agnostic, meaning that whether a number is positive or negative depends on 66 | the operators it is used with. We will have a look at this later. 67 | 68 | Lastly we have single-precision and double-precision floating point numbers, 69 | where double-precision is exactly the `Number` you know from Javascript. A new 70 | type that is coming to WASM is the `v128` vectorised type. This will allow 71 | writing even faster algorithms by utilising SIMD (Single Instruction, Multiple 72 | Data), however this has not completely laded yet. You can see all proposals here 73 | https://github.com/WebAssembly/proposals. 74 | 75 | You may ask now where booleans, strings, arrays and objects are in all of this, 76 | and the short answer is that they don't exist! Or said in another way, we will 77 | need to write our own primitives as we go along. 78 | 79 | Back to typing. As you can see the strict typing means we need to explicitly 80 | define the "signature" of our function, under the restrictions of the provided 81 | types. This will be familiar to anyone who has worked with a strictly typed 82 | language, such as TypeScript, Java or Go. AS you can see we define to parameters 83 | `$a` and `$b`, both being `i32` and a result of `i32` also. Like all other 84 | labels, these will be replaced by a counting integer when compiled. We may as 85 | well have written: 86 | 87 | ```webassembly 88 | ;; arithmetic.wat 89 | (module 90 | (func (;0;) (export "i32.add") 91 | (param (;0;) i32) 92 | (param (;1;) i32) 93 | (result i32) 94 | (i32.add 95 | (get_local 0) 96 | (get_local 1)))) 97 | ``` 98 | 99 | We finally arrive at the body of our function, with the `i32.add` instruction 100 | (which the function is named after). A surprising detail is that variable need 101 | to be explicitly accessed with the `(get_local $label)` instruction. Remember 102 | the mental model from earlier and you should be able to execute the code. 103 | 104 | You may question why there is no return statement, despite we having said the 105 | result of the function is `i32`. We could have written `(result (i32.add ...))` 106 | to make it explicit, but this just works. A hand-wavy explanation for now is 107 | that the last value returned, will become the return value of the function. 108 | 109 | ## Compiling! 110 | 111 | Now try compiling the WASM module: 112 | 113 | ``` 114 | wat2wasm arithmetic.wat 115 | ``` 116 | 117 | Also have a look at the dumped module, with and without the debug names. 118 | How many instructions is the actual function body? What is the type section? 119 | What happened to the function signature? 120 | 121 | When you have answered these questions, try changing the function. 122 | 123 | * Try changing one of the parameters to `f64` 124 | * Try doing two additions in the body next to each other. What does the compiler 125 | say? 126 | 127 | When done, continue to the next exercise: 128 | 129 | [**Exercise 03**](../03) 130 | -------------------------------------------------------------------------------- /lessons/03/README.md: -------------------------------------------------------------------------------- 1 | # 03. WASM <3 Javascript 2 | 3 | So far we have only looked at WASM code at rest and compiled from WAT, but we 4 | are yet to see how to interact with WASM from Javascript, which is what we are 5 | all so excited about! 6 | 7 | At this point you should have `wat2js` installed globally. Otherwise go ahead 8 | and fetch it from npm. We can now compile and wrap our `arithmetic.wat` in a 9 | CommonJS module: 10 | 11 | ```sh 12 | wat2js arithmetic.wat -o=arithmetic.js 13 | ``` 14 | 15 | If you want to pass options to `wat2wasm`, it can be done as this: 16 | 17 | ```sh 18 | wat2js arithmetic.wat -o=arithmetic.js -- --debug-names 19 | ``` 20 | 21 | `--` is a convention to split args. This can also be used with eg. npm scripts 22 | to pass args to the underlying command, eg `npm start -- --port=8080`. Anyway! 23 | 24 | You will now have you `arithmetic.js` module, which is a light-weight wrapper 25 | around our WASM module, making it a bit easier to use. There are other tools 26 | doing this like `wasm-bindgen`, transforms for Webpack and Browserify etc. 27 | However in this workshop we will only use this tool and later look at how to 28 | instantiate modules manually. 29 | 30 | To use our module you can require it from Javascript: 31 | 32 | ```js 33 | // require the compiled module as you would any other js module 34 | var wasm = require('./arithmetic') 35 | 36 | // instantiate the wasm 37 | var mod = wasm() 38 | 39 | // and call an exported wasm function from js 40 | console.log(mod.exports['i32.add'](10, 10)) 41 | ``` 42 | 43 | A couple of things to note: 44 | 45 | * The wrapper module exports a single function. This functions takes some 46 | arguments that we will look at later, eg. an object of imports and whether the 47 | module should load sync or async 48 | * When initialised, the wrapper returns an object with a couple of convenience 49 | properties: 50 | * `mod.buffer` is the binary WASM module as a `Uint8Array` 51 | * `mod.memory` will be the exported linear memory as a `Uint8Array` if 52 | present 53 | * `mod.exports` contains all the exported "things" from the module under 54 | their given name. There is no namespacing as `.` may have suggested when 55 | reading the source. There are other elements than functions that can be 56 | exported from a WASM module 57 | * Exported functions are directly callable from Javascript 58 | 59 | Try executing the following commands in context of the script above: 60 | 61 | ```js 62 | console.log(4294967295 + 3735928559) 63 | console.log(mod.exports['i32.add'](4294967295, 3735928559)) 64 | console.log(4294967295 + 3735928559 >>> 0) 65 | 66 | console.log(7.09 + 5.381) 67 | console.log(mod.exports['i32.add'](7.09, 5.381)) 68 | console.log(7.09 + 5.381 >>> 0) 69 | ``` 70 | 71 | * What results did you get? 72 | * What happened? Why? 73 | * What is `>>>`? 74 | 75 | [**Exercise 04**](../04) 76 | -------------------------------------------------------------------------------- /lessons/04/README.md: -------------------------------------------------------------------------------- 1 | # 04. Multiplication 2 | 3 | Now it is time to write you own WAT module. We will start very simple, as to 4 | get you used to the syntax. A popular topic for conversation is the weather, 5 | however when talking to my American friends I can never understand what they 6 | mean because they use this scale called Fahrenheit. So let's make conversation 7 | easier by building a converter! 8 | 9 | The conversion formula is `c = (f - 32) * 5 / 9` for Fahrenheit to Celsius and 10 | `f = (c * 9 / 5) + 32` the other way around. I have put in a couple of extra 11 | parenthesis to make the task a little less bug-free. The type signature for your 12 | functions should use `f64`, and you may use the `f64.div: [f64, f64] -> [f64]`, 13 | `f64.sub: [f64, f64] -> [f64]`, `f64.const: [num|hex] -> [f64]` and 14 | `f64.mul: [f64, f64] -> [f64]` instructions. Remember `(get_local $name)` from 15 | the previous module. 16 | 17 | The typing notation here is not standard, but looks like the one you may 18 | encounter in the spec, and eg. the one for `f64.const` means you can give it a 19 | number or a hex number like `0xff`. Sometimes you may encounter instructions 20 | that take one type and return another like `f64.eq: [f64, f64] -> [i32]`. 21 | 22 | One surprising detail is that you cannot just write numbers inline in WASM, like 23 | `(i32.add 2 2)` but have to explicitly define these numbers as constants: 24 | `(i32.add (i32.const 2) (i32.const 2))` 25 | 26 | 27 | The exercise is to write the functions `f64.f2c` for Fahrenheit to Celsius and 28 | `f64.c2f` for the other way around. You can use the following test cases: 29 | 30 | ```js 31 | var wasm = require('./temperature') 32 | 33 | var mod = wasm() 34 | 35 | console.log(mod.exports['f64.f2c'](0) === -17.77777777777778) 36 | console.log(mod.exports['f64.c2f'](-17.77777777777778) === 0) 37 | console.log(mod.exports['f64.f2c'](100) === 37.77777777777778) 38 | console.log(mod.exports['f64.c2f'](37.77777777777778) === 100) 39 | 40 | console.log(mod.exports['f64.f2c'](-459.66999999999996) === -273.15) 41 | console.log(mod.exports['f64.c2f'](-273.15) === -459.66999999999996) 42 | ``` 43 | 44 |
45 | Hint 46 | 47 | ```webassembly 48 | ;; temperature.wat 49 | (module $temperature 50 | (func $f64.f2c (export "f64.f2c") 51 | (param $f f64) 52 | (result f64) 53 | (f64.mul 54 | (f64.div (f64.const 5) 55 | (f64.const 9)) 56 | (f64.sub (get_local $f) 57 | (f64.const 32)))) 58 | (func $f64.c2f (export "f64.c2f") 59 | (param $c f64) 60 | (result f64) 61 | (; It's not supposed to be that easy :) ;))) 62 | ``` 63 | 64 | ```sh 65 | wat2js temperature.wat -o=temperature.js -- --debug-names 66 | ``` 67 |
68 | 69 | When ready head on to next exercise: 70 | 71 | [**Exercise 05**](../05) 72 | -------------------------------------------------------------------------------- /lessons/05/README.md: -------------------------------------------------------------------------------- 1 | # 05. Writing Arrays 2 | 3 | So now we can convert between the two scales, but only one temperature at a 4 | time. The message spreads far and wide that we have made this new high 5 | performance solution for converting between Fahrenheit and Celsius, that we are 6 | now asked if we can work on large scale datasets. However, our function only 7 | take a single data point at a time. We know from Javascript that we would just 8 | take an array of data points and convert them in one big batch, so let's see 9 | how this is done in WASM. This will introduce us to linear memory and blocks. 10 | 11 | ## Linear memory 12 | 13 | So in Javascript all memory is automatically managed for you, but in WebAssembly 14 | we have to manage this by ourselves. We are even more low-level here than in C, 15 | as there is no memory allocator, but simply one big piece of continuous memory. 16 | You can think of this like a big `Buffer` from Node or an 17 | `ArrayBuffer`/`Uint8Array` like in Browsers. 18 | 19 | From a module we have to explicitly define that we want to use memory, 20 | beyond the simple parameters we had available in our previous functions. We 21 | also need to explicitly say that we will make this memory available outside the 22 | module, so eg. Javascript or other WASM modules can access it. 23 | This is done like this: 24 | 25 | ```webassembly 26 | (module $memory 27 | (memory (export "memory") 1) 28 | (; functions here ;)) 29 | ``` 30 | 31 | The signature is `(memory $name? (export|import)? initialPages maxPages?)`. 32 | You can currently only have one "memory" in a WASM module, and it is allocated 33 | in "pages", which are 64kB. As the signature implies, you can also import 34 | external memory! Fancy! However we will not look at this for now. You can also 35 | define a maximum number of pages that you want the memory to be able to 36 | allocate. There is an implied maximum of 4GB of RAM as we will see in just a 37 | moment. 38 | 39 | Also note it is important in what order you define various top-level constructs 40 | such as functions and memory. Simply define memory as the first thing and you 41 | should be good :) 42 | 43 | ### Storing data 44 | 45 | To store data we need to learn a bit about how memory works. Let's create a 46 | function that can store Numbers for us. The memory addressed at a byte level, 47 | which is why you can think of it as a bit byte array. Addressing is simply a 48 | `i32` index into the memory, often also called a pointer. For our function 49 | below we want to pretend the memory is a big array of Numbers, and as `f64` 50 | hints, this is 64-bits wide. `64 bits = 8 bytes` which means to go from an 51 | index to a pointer, we need to multiply by 8. 52 | 53 | Here is a table showing what the memory would look like with the f64 `1` at 54 | address 0 and the f64 `10e-100` at address 8: 55 | 56 | | Address | +0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 | 57 | |:--------|:-----------|:-----------|:-----------|:-----------|:-----------|:-----------|:-----------|:-----------| 58 | | 0 | `00000000` | `00000000` | `00000000` | `00000000` | `00000000` | `00000000` | `11110000` | `00111111` | 59 | | 8 | `00111110` | `11000011` | `11011000` | `01001110` | `01111101` | `01111111` | `01100001` | `00101011` | 60 | | 16 | `00000000` | `00000000` | `00000000` | `00000000` | `00000000` | `00000000` | `00000000` | ... | 61 | 62 | 63 | As you can see the representations are quite funky. This is due to the floating 64 | point standard used by Javascript called IEEE 754. 65 | 66 | We could write a simple helper that lets us treat our memory as a large array 67 | of `f64`s. 68 | 69 | ```webassembly 70 | ;; memory.wat 71 | (module $memory 72 | (memory (export "memory") 1) 73 | 74 | (func $f64.set (export "f64.set") 75 | (param $index i32) 76 | (param $value f64) 77 | 78 | (local $ptr i32) 79 | 80 | (set_local $ptr (i32.mul (i32.const 8) 81 | (get_local $index))) 82 | 83 | (f64.store (get_local $ptr) 84 | (get_local $value)))) 85 | ``` 86 | 87 | There are a couple of new instructions above. I introduced a `local`, which is 88 | like a variable, to hold our pointer. As mentioned, we need to multiply our 89 | index by 8 to convert to an `f64` address. Locals must be defined at the 90 | beginning of a function in WASM, just like `params`. We then store the value 91 | into memory at the pointer. 92 | 93 | Compiling the above: 94 | 95 | ```sh 96 | wat2js memory.wat -o=memory.js -- --debug-names 97 | ``` 98 | 99 | And running in Javascript: 100 | 101 | ```js 102 | var wasm = require('./memory') 103 | 104 | var mod = wasm() 105 | 106 | mod.exports['f64.set'](0, 1) // set 1 at address 0 107 | mod.exports['f64.set'](1, 10e-100) // set 10e-100 at address 8 108 | 109 | console.log(mod.memory) // You should be able to see the table above 110 | ``` 111 | 112 | Check the above and see what the resulting memory looks like. 113 | 114 | ### Exercise 115 | 116 | Write the function `$f64.get: [i32] -> [f64]` that reads a Number from a index 117 | by multiplying up to the right address. You can use the instruction of the name 118 | `f64.load: [i32] -> [f64]` which is almost equivalent to `f64.store`. 119 | 120 | Test case: 121 | 122 | ```js 123 | var wasm = require('./memory') 124 | 125 | var mod = wasm() 126 | 127 | mod.exports['f64.set'](0, 1) 128 | mod.exports['f64.set'](1, 10e-100) 129 | 130 | console.log(mod.exports['f64.get'](0) === 1) 131 | console.log(mod.exports['f64.get'](1) === 10e-100) 132 | ``` 133 | 134 | [**Exercise 06**](../06) 135 | -------------------------------------------------------------------------------- /lessons/06/README.md: -------------------------------------------------------------------------------- 1 | # Writing to memory from Javascript 2 | 3 | Now we have seen how to manipulate memory from inside WASM, but this is not much 4 | better than just calling our conversion functions directly. So now we will see 5 | how we can manipulate memory from outside. 6 | 7 | As mentioned earlier, WASM has no concept of data structures, so we will need to 8 | make our own. In Javascript an `Array` is quite a number of things at the same 9 | time. It's resizeable, it supports queue operations like shift/unshift, it also 10 | works like a stack with push/pop and we can have holes in the middle of the 11 | array, we cannot go out of bounds easily (this will cause an error instead of 12 | giving us garbage data), it can have heterogeneous data (numbers, strings, 13 | objects, etc. in the same array) and we do not have to think about the 14 | underlying memory. 15 | 16 | We have seen that we can pretend the memory in WASM is an array if we fix up 17 | the indexes and multiply them up to the data length. From Javascript we can 18 | also manipulate the memory as if it was an Array of `f64`, namely with the 19 | [TypedArray `Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array). 20 | 21 | This can be done like this: 22 | 23 | ```js 24 | var wasm = require('./memory') 25 | 26 | var mod = wasm() 27 | 28 | // Change the view of the wasm memory to Float64 29 | var f64Array = new Float64Array(mod.memory.buffer) 30 | 31 | f64Array[0] = 1 32 | f64Array[1] = 10e-100 33 | 34 | console.log(mod.exports['f64.get'](0) === 1) 35 | console.log(mod.exports['f64.get'](1) === 10e-100) 36 | ``` 37 | 38 | ## Exercise 39 | 40 | Write javascript that sends a `f64`array of size 3 to your WASM program, which 41 | takes these three numbers, adds them, and returns the result back to the 42 | javascript program. The javascript program should print out the result. 43 | 44 | [**Exercise 07**](../07) 45 | -------------------------------------------------------------------------------- /lessons/07/README.md: -------------------------------------------------------------------------------- 1 | # 07. A detour on debugging 2 | 3 | So far we have trusted our ability to write correct code, but soon we will go 4 | into more looping, which is notorious for causing bugs, so I want to do a detour 5 | on how I do debugging in WASM. 6 | 7 | ## Disassembly 8 | 9 | So far we have only covered the S-expression format, but there is actually 10 | another format, which is the one that your WAT is compiled into, called the 11 | linear format. It carries with it a short description of how WASM works. 12 | In practise WASM is based around a stack machine. This means our initial 13 | example of `$i32.add` will compile to be: 14 | 15 | S-Expression: 16 | 17 | ```webassembly 18 | (module $arithmetic 19 | (func $i32.add (export "i32.add") 20 | (param $a i32) 21 | (param $b i32) 22 | (result i32) 23 | 24 | (i32.add 25 | (get_local $a) 26 | (get_local $b)))) 27 | ``` 28 | 29 | Linear format: 30 | 31 | ```webassembly 32 | (module $arithmetic 33 | (func $i32.add (export "i32.add") 34 | (param $a i32) 35 | (param $b i32) 36 | (result i32) 37 | 38 | get_local $a 39 | get_local $b 40 | i32.add)) 41 | ``` 42 | 43 | The idea is that an instruction can pop it's arguments off a virtual stack, 44 | and push a value back onto the stack for the next instruction. In the above 45 | example `get_local $a` pushes an `i32` onto the stack, `get_local $b` pushes an 46 | `i32` onto the stack, making it two `i32`s on the stack, and then `i32.add` pops 47 | two `i32`s and pushes an `i32` onto the stack. This matches the `result` of the 48 | function. This is why having two `add`s in Exercise 02 was compiler error. 49 | 50 | Current Firefox and Chrome will disassemble WASM into the linear format in their 51 | DevTools if you go to the "Sources" tab, but in slightly different ways. 52 | They will also allow you to inspect the stack of values when hitting a 53 | breakpoint and step though the instructions, though this can be flaky in Chrome, 54 | in my experience. 55 | 56 | Chrome 69 Debugger: 57 | 58 | ```webassembly 59 | func $i32.add (param i32 i32) (result i32) 60 | get_local 0 61 | get_local 1 62 | i32.add 63 | end 64 | ``` 65 | 66 | Firefox 63 Debugger: 67 | 68 | ```webassembly 69 | (module 70 | (type $type0 (func (param i32 i32) (result i32))) 71 | (export "i32.add" (func $i32.add)) 72 | (func $i32.add (;0;) (param $var0 i32) (param $var1 i32) (result i32) 73 | get_local $var0 74 | get_local $var1 75 | i32.add 76 | ) 77 | ) 78 | ``` 79 | 80 | ## Logging 81 | 82 | However sometimes you just want to log a value, whether as a separate 83 | instruction or inline in a call to see a value at a particular point in time. 84 | 85 | For this I always define `$t.log: [t] -> []` and `$t.log_tee: [t] -> [t]`. 86 | The former will log any one value eg. 87 | 88 | ```webassembly 89 | (call $i32.log (get_local $counter)) 90 | ``` 91 | 92 | or log inline by inspecting the value and putting it back on the stack: 93 | 94 | ```webassembly 95 | (set_local $counter 96 | (call $i32.log_tee 97 | (i32.add (i32.const 8) 98 | (call $i32.log_tee 99 | (get_local $counter)) 100 | ``` 101 | 102 | Notice how this will first log the initial value of `$counter` before 103 | multiplication and the product after multiplication, before updating `$counter`, 104 | by simply injecting `(call $i64.log expr)`. This will always work since WASM 105 | functions (as of current) can only push a single value on the stack at a time. 106 | 107 | ### How it's done 108 | 109 | To achieve logging we need to call out to Javascript. For this we need to import 110 | a function. 111 | 112 | This can be done like this: 113 | 114 | ```webassembly 115 | (module $debug_helpers 116 | (func $i32.log (import "debug" "log") (param i32)) 117 | (func $i32.log_tee (import "debug" "log_tee") (param i32) (result i32))) 118 | ``` 119 | 120 | Notice how you need two strings when importing and you need to define the 121 | function signature when importing. 122 | 123 | From Javascript with `wat2js` you can then do: 124 | 125 | ```js 126 | var wasm = require('./debug-module') 127 | 128 | var mod = wasm({ 129 | import: { 130 | debug: { 131 | log (...args) { 132 | console.stack(...args) 133 | }, 134 | log_tee (arg) { 135 | console.stack(arg) 136 | return arg 137 | } 138 | } 139 | } 140 | }) 141 | ``` 142 | 143 | This will make the two functions available for WASM to call. 144 | 145 | The full example for all WASM types is given here: 146 | 147 | ```webassembly 148 | (module $debug_helpers 149 | (func $i32.log (import "debug" "log") (param i32)) 150 | (func $i32.log_tee (import "debug" "log_tee") (param i32) (result i32)) 151 | ;; No i64 interop with JS yet - but maybe coming with WebAssembly BigInt 152 | ;; So we can instead fake this by splitting the i64 into two i32 limbs, 153 | ;; however these are WASM functions using i32x2.log: 154 | (func $i32x2.log (import "debug" "log") (param i32) (param i32)) 155 | (func $f32.log (import "debug" "log") (param f32)) 156 | (func $f32.log_tee (import "debug" "log_tee") (param f32) (result f32)) 157 | (func $f64.log (import "debug" "log") (param f64)) 158 | (func $f64.log_tee (import "debug" "log_tee") (param f64) (result f64)) 159 | 160 | ;; i64 logging by splitting into two i32 limbs 161 | (func $i64.log 162 | (param $0 i64) 163 | (call $i32x2.log 164 | ;; Upper limb 165 | (i32.wrap/i64 166 | (i64.shl (get_local $0) 167 | (i64.const 32))) 168 | ;; Lower limb 169 | (i32.wrap/i64 (get_local $0)))) 170 | 171 | (func $i64.log_tee 172 | (param $0 i64) 173 | (result i64) 174 | (call $i64.log (get_local $0)) 175 | (return (get_local $0)))) 176 | ``` 177 | 178 | ```js 179 | var wasmModule = require('./debug_helpers') 180 | 181 | wasmModule({ 182 | import: { 183 | debug: { 184 | log (...args) { 185 | console.stack(...args) 186 | }, 187 | log_tee (arg) { 188 | console.stack(arg) 189 | return arg 190 | } 191 | } 192 | } 193 | }) 194 | ``` 195 | 196 | To use this, paste the WASM part into `debug_helpers.wasm`, then copy the JS 197 | above into each JS file. 198 | 199 | ## Asserting 200 | 201 | Another good technique to catch bugs is being very defensive when coding, which 202 | can be done by making assertions of certain facts in a program. In Node.js we 203 | have the `assert` module providing a suite of utilities for asserting various 204 | facts. In WASM there's a `unreachable` instructions which will cause an 205 | unconditional trap, meaning the execution of the program stops, just like throw 206 | in Javascript. The trap will be propagated to the Javascript engine and 207 | converted to an `RuntimeError`. 208 | 209 | For assertions I use the following snippet: 210 | 211 | ``` 212 | (func $assert (param i32) (if (i32.eqz (get_local 0)) (unreachable))) 213 | ``` 214 | 215 | Now I can make assertions in my code: 216 | 217 | ```webassembly 218 | (module $arithmetic 219 | (func $assert (param i32) (if (i32.eqz (get_local 0)) (unreachable))) 220 | 221 | (func $i32.add (export "i32.add") 222 | (param $a i32) 223 | (param $b i32) 224 | (result i32) 225 | 226 | ;; assert that $a is less than 10 227 | (call $assert (i32.le_u (get_local $a) (i32.const 10))) 228 | 229 | (i32.add 230 | (get_local $a) 231 | (get_local $b)))) 232 | ``` 233 | 234 | When the trap is converted to a Javascript error, the error message will have 235 | `assert` in the stack trace, letting me know that the error was caused by an 236 | assertions. 237 | 238 | Important to note, just as in Javascript, is that any mutations done before an 239 | error will persist after, but this exacerbated by the fact that everything is 240 | written to global memory. 241 | 242 | [**Exercise 08**](../08) 243 | -------------------------------------------------------------------------------- /lessons/08/README.md: -------------------------------------------------------------------------------- 1 | ## 07. Loops 2 | 3 | One important construct that we are missing to process a large data set is a way 4 | to iterate over memory. We already know that we need to read 8 bytes at a 5 | time but have yet to see how to do loops. 6 | 7 | ## Branching 8 | 9 | One of the security features of WASM, in contrast to real assembler, is that you 10 | cannot jump arbitrarily around the code (eg `goto` and `longjmp`). However there 11 | is a model for branching/looping: 12 | 13 | ```webassembly 14 | (func $looping 15 | (param $input.ptr i32) 16 | (param $input.len i32) 17 | 18 | (local $i i32) 19 | (local $input.end i32) 20 | 21 | (set_local $i (i32.const 0)) 22 | (set_local $input.end (i32.mul (get_local $input.len) (i32.const 8))) 23 | 24 | (loop $continue 25 | ;; do something at iteration with a f64. Here we drop the value, but may 26 | ;; sum it or similar 27 | (drop (i64.load (i32.add (get_local $input.ptr) 28 | (get_local $i)))) 29 | 30 | (br_if $continue 31 | ;; tee_local lets sets the value and also returns it in a single 32 | ;; instruction, so we can do `length < i++` 33 | (i32.lt_u (tee_local $i (i32.add (get_local $i) (i32.const 8))) 34 | (get_local $input.end)))) 35 | ``` 36 | 37 | So the `loop` instruction is a label that you can move to with the `(br $label`) 38 | unconditionally, and move to based on a condition with `(br_if $label i32)` as 39 | in the above example. Note how the loop above increments the counter by 8 every 40 | iteration, since that's the byte width of our type. 41 | 42 | ## Exercise 43 | 44 | The exercise now is to set a list of temperatures into memory, convert them 45 | from Fahrenheit to Celsius in-place with load/store from the previous exercises 46 | and read out the contents. 47 | --------------------------------------------------------------------------------