Writing FizzBuzz in WebAssembly

FizzBuzz is a programming challenge that requires a user to take in a positive number loop from 1 to a chosen number and print out the results based on the following criteria:

  • If the number is divisible by 3, then print Fizz
  • If the number is divisible by 5, then print Buzz
  • If the number is divisible by 15, then print FizzBuzz

Let's go ahead and kick this off by getting our JavaScript environment ready. The following code should look familiar, except for our new logging function:

const memory = new WebAssembly.Memory({initial : 1});
const storeByte = new Int32Array(memory.buffer, 0, 1);
function consoleLogString(offset, length) {
const bytes = new Uint8Array(memory.buffer, offset, length);
const string = new TextDecoder('utf8').decode(bytes);
console.log(string);
}
const importObject = { console: {log: consoleLogString}, js: {mem: memory}};
WebAssembly.instantiateStreaming(fetch('fizzbuzz.wasm'), importObject).then(obj => {
//obj.instance.exports.fizzbuzz(10);
});

This function takes in the offset of the memory and the length of the data and prints it out. As we mentioned previously, we need to know where the data is, as well as its length, to be able to read it from the heap. Now, we can get into the heart of the program. Follow these steps to do so:

  1. Create a new file called fizzbuzz.wat.
  2. We know that we will need to import both our memory and the console function, just like we have been importing other functions. We also know that we will be creating a function called fizzbuzz and that we will be exporting this so that our JavaScript context can utilize it:
(module
(import "console" "log" (func $log (param i32 i32)))
(import "js" "mem" (memory 1))
(global $g (mut i32) (i32.const 0))
(func $fizzbuzz (param $p i32)
;; content of the function
)

(export "fizzbuzz" (func $fizzbuzz))
)

The only interesting piece of the preceding code is the global section. This is a global variable that can be thought of as the stack of our context. It isn't on the heap, so the JavaScript context doesn't have access to it. We can also see the mut keyword in front of the declaration. This tells us that we are going to be changing the global variable from the various parts of our WebAsembly code. We are going to utilize this so that it holds the length of our print out.

  1. We will need to check for both conditions of FizzBuzz:
(func $checkFizz (param $p1 i32))
(func $checkBuzz (param $p1 i32))

Both of our functions will take a number. For the checkFizz function, we will test to see if it is divisible by 3. If it is, we will store the word Fizz in the memory heap where the global variable is and then update that global variable to the location after the word Fizz. For Buzz, we will do the exact same thing, except we will test to see if the number is divisible by 5. If this is true, we will put Buzz in the global pointer location and update it.

The following is the checkFizz function:

local.get $p1
i32.const 3
i32.rem_s
(if (i32.eq (i32.const 0))
(then
(i32.store8 (global.get $g) (i32.const 70))
(i32.store8 (i32.add (global.get $g) (i32.const 1))
(i32.const 105))
(i32.store8 (i32.add (global.get $g) (i32.const 2))
(i32.const 122))
(i32.store8 (i32.add (global.get $g) (i32.const 3))
(i32.const 122))
(global.set $g (i32.add (global.get $g) (i32.const 4)))
)
)

Here, we grab the number that was passed in. Then, we put 3 on the stack and run the remainder function. If the result is equal to 0, then we put the word Fizz into memory. Now, what's being put into memory may not look like the word Fizz, but if we look at the UTF8 decimal numbers for each of the letters, we will see that that is what we are putting into memory.

If we head back to our JavaScript code, we will see that we are utilizing a TextDecoder. This allows us to read these byte values and translate them into their string equivalent. Since WebAssembly only understands the concept of integers and floating-point numbers, this is how we have to deal with it for now.

Next is the checkBuzz function. It should look similar to the preceding code, except for the divisible, which is 5:

(func $checkBuzz (param $p1 i32)
local.get $p1
i32.const 5
i32.rem_s
(if (i32.eq (i32.const 0))
(then
(i32.store8 (global.get $g) (i32.const 66))
(i32.store8 (i32.add (global.get $g) (i32.const 1))
(i32.const 117))
(i32.store8 (i32.add (global.get $g) (i32.const 2))
(i32.const 122))
(i32.store8 (i32.add (global.get $g) (i32.const 3))
(i32.const 122))
(global.set $g (i32.add (global.get $g) (i32.const 4)))
)
)
)
  1. Now, we can write fizzbuzz. We will take in the integer and then loop from 1 to that value running our checkFizz and checkBuzz functions:
(func $fizzbuzz (param $p i32)
(local $start i32)
(local.set $start (i32.const 1))
(block
(loop
(call $checkFizz (local.get $start))
(call $checkBuzz (local.get $start))
(br_if 1 (i32.eq (local.get $start) (local.get $p)))
(local.set $start (i32.add (local.get $start)
(i32.const 1)))
(br 0)
)
)
i32.const 0
global.get $g
call $log
)

The loop is fairly simple. br_if tests to see whether our start variable equals what we put in. If it does, it will equal 1 and it will break out of the loop. Otherwise, it will increment the start variable by one. (br 0) is what keeps the loop going.

Once we have finished the loop, we will get our global variable, wherever it finished up, and call the log function. Let's compile this and run the following test:

obj.instance.exports.fizzbuzz(10);

By doing this, we should get the following output:

FizzBuzzFizzFizzBuzz

We have just written a nontrivial program in pure WebAssembly! By now, you should have realized why most people don't write in pure WebAssembly since what should have been a simple program took us quite a bit of coding.

In the next section, we'll learn how to use a higher-level language, C, to write programs for the web.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.30.19