This is the code, with as many comments as I can add:
(module (export "iterations" (func $iterations)) (; the function to export: $iterations ;) (type $iterations_fn (func (param i32 i32 i32 i32 i32) (result i32))) (; the function signature ;) (func $iterations (; function declaration including all the parameters ;) (type $iterations_fn) (param $px i32) (param $py i32) (param $width i32) (param $height i32) (param $max_iterations i32) (result i32) (; result type, the number of iterations ;) (local $x0 f32) (; local variables needed for the computation ;) (local $y0 f32) (local $x f32) (local $y f32) (local $tmp f32) (local $iteration i32) (local.set $x0 (; $x0 = $px * 2.47 / $width - 2 ;) (f32.sub (f32.div (f32.mul (f32.convert_i32_u (local.get $px)) (f32.const 2.47)) (f32.convert_i32_u (local.get $width))) (f32.const 2))) (local.set $y0 (; $y0 = $py * 2.24 / $height / 1.12 ;) (f32.sub (f32.div (f32.mul (f32.convert_i32_u (local.get $py)) (f32.const 2.24)) (f32.convert_i32_u (local.get $height))) (f32.const 1.12))) (local.set $x (f32.const 0)) (; $x = 0 ;) (local.set $y (f32.const 0)) (; $y = 0 ;) (local.set $iteration (i32.const 0)) (; $iteration = 0 ;) (block (; this section calculates the iterations number ;) (loop (; while loop but with the finishing conditions reversed (vs wikipedia) ;) (br_if 1 (; terminate the loop if $iteration >= $max_iterations ;) (i32.ge_u (local.get $iteration) (local.get $max_iterations))) (br_if 1 (; terminate the loop if ($x * $x + $y * $y) > 4 ;) (f32.gt (f32.add (f32.mul (local.get $x) (local.get $x)) (f32.mul (local.get $y (local.get $y)))) (f32.const 4))) (local.set $tmp (; $tmp = ($x * $x) - ($y * $y) + $x0 ;) (f32.add (f32.sub (f32.mul (local.get $x) (local.get $x)) (f32.mul (local.get $y) (local.get $y))) (local.get $x0))) (local.set $y (; $y = 2 * $x * $y + $y0 ;) (f32.add (f32.mul (f32.const 2) (f32.mul (local.get $x) (local.get $y))) (local.get $y0))) (local.set $x (; $x = $tmp ;) (local.get $tmp)) (local.set $iteration (; $iteration = iteration + 1 ;) (i32.add (local.get $iteration (i32.const 1)))) (br 0) (; continue loop ;) ) ) (local.get $iteration) (; return $iteration ;) ) )
Naturally, since WAT is very simple, the code for doing small tasks is very verbose, for example, incrementing the iteration variable takes 4 instructions:
i32.const. Nothing surprising here, since the instructions supported are very limited and there is no
INC like in x86 Assembly.
The next step now is to build this code and convert it into a binary wasm file with the following instruction:
$ wat2wasm mandelbrot.wat -o mandelbrot.wasm
Up to this point, nothing else needs to be done to the binary, except, I want to embed it in HTML and not having to host this file someplace. For this effect, I'll convert it to base64.
$ base64 -i mandelbrot.wasm AGFzbQEAAAABCgFgBX9/f39/AX8DAgEABw4BCml0ZXJhdGlvbnMAAAqUAQGRAQIFfQF/IACzQ3sUHkCUIAKzlUMAAABAkyEFIAGzQylcD0CUIAOzlUMpXI8/kyEGQwAAAAAhB0MAAAAAIQhBACEKAkADQCAKIARPDQEgByAHlCAIIAiUkkMAAIBAXg0BIAcgB5QgCCAIlJMgBZIhCUMAAABAIAcgCJSUIAaSIQggCSEHQQEgCmohCgwACwsgCgs=
I found the way to embed it, in this very useful stackoverflow answer.
First the canvas where to draw the image:
<canvas id="fractalCanvas" width="200" height="200"></canvas>
iterationFn calc function and converting the result into a gray scale color pixel:
Almost there, the final step is loading the wasm binary and executing the
drawMandelbrot function passing the
The code is using the base64 representation of the wasm binary, converts it into a Uint8Array and calls WebAssembly.instantiate to load the code and make it usable. The usual way to do this is simply loading the wasm using fetch and call WebAssembly.instantiateStreaming but I wanted to have it all in a single file.
And that's it, the result can be seen below: