When dipping my toes into WebAssembly the first thing that struck me was how complicated the supposedly “simple” getting-started examples were. The C “Starting from scratch” example on the official WebAssembly website requires Emscripten, which produces a .js file with over 2000 lines and an equally large HTML page as well. Searching for other tutorials online didn’t lead anywhere either.

In this “Hello World!” tutorial we will:

  • Compile a C program to .wasm and run it on our browser
  • Not use Emscripten
  • Only use standard tools
  • Write less than 100 lines of code in total, all from scratch!

Code

You can find all the code on my Github here.

Requirements

You’ll need to make sure you have:

  • An up to date version of Clang (bundled with LLVM) which supports wasm32. You can check this by writing: llc -version and looking for wasm32
  • Python
  • A modern web browser

Getting started

Here I will run through how to get the program up and running.

C to wasm

Let’s first write our C file and save it as main.c, it will look as simple as this, where we are returning a pointer to memory containing our array of chars.

const char* getString() {
  return "Hello World!";
}

Next we want to compile our C program into a .wasm file. We use this Clang command to produce a main.wasm file.

clang --target=wasm32 -nostdlib -Wl,--no-entry -Wl,--export-all -o main.wasm main.c

Javascript and HTML “glue code”

Our HTML page will just load the Javascript script which actually fetches the .wasm file. We’ve also added an empty placeholder heading element. Save the following as index.html:

<!doctype html>

<html>
  <head>
    <meta charset="utf-8">
    <title>WebAssembly test!</title>
  </head>

  <body>
    <h1 id="heading"></h1>

    <script src="script.js"></script>
  </body>
</html>

Next we need to create our Javascript file, this is where things get a bit more complex. Due to us returning a memory pointer from our getString() function we need to actually go into the wasm program’s memory and fetch our char values. Then we need to convert it to char from the byte integer we store it as.

var importObject = { imports: { imported_func: arg => console.log(arg) } };

WebAssembly.instantiateStreaming(fetch('main.wasm'), importObject)
.then(obj => {
  var charArray = new Int8Array(
    obj.instance.exports.memory.buffer, // WASM's memory
    obj.instance.exports.getString(), // char's pointer
    12                                 // The string's length
  );

  let string = String.fromCharCode.apply(null, charArray) // Convert from ASCII code to char
  console.log(string);

  document.getElementById("heading").innerHTML = string // Set the value of the heading to our string
});

Running it

Now all we need to do is run the python server, save the code below as server.py:

import http.server
from http.server import HTTPServer, BaseHTTPRequestHandler

import socketserver

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

Handler.extensions_map['.wasm'] = 'application/wasm'

print("Server starting at port:", PORT)

httpd = socketserver.TCPServer(("", PORT), Handler)
httpd.serve_forever()

Then run:

python server.py

And navigate to localhost:8000 on your modern browser. You should see “Hello world!”