Before we get into the details we’re going to define some terminology.

Web Assembly was recently decided to be a finished MVP including

a JavaScript API and binary format accompanied by a reference interpreter.

Web Assembly is written in an s-expression syntax (wast) and then needs to be translated down to binary encoded wasm. Below is a gist of wast that imports the stdlib.print function taking a memory location offset and a length, as well as importing a block of memory from the controlling runtime. We then store the string "Hello World" at memory location 0. We then export a main function that calls the aforementioned print function with the memory location offset and length.

hello.wast

module
  (import "stdlib" "print" (func $print (param i32 i32)))
  (import "js" "mem" (memory 20))
  (data (i32.const 0) "Hello World")
  (func (export "main")
    i32.const 0  ;; pass offset 0 to log
    i32.const 20 ;; pass length 20 to log
    call $print))

If you know JavaScript the below will look rather mundane. The only interesting part is really the log function and creating the memory passed into the instantiated wasm. It’s 20 pages (1280KiB) which is pretty overkill for this application. Try playing with that value and watch at which point the wasm fails and how. You can see the exposed object is how we make functions from JavaScript available to the wasm underneath.

hello.js

document.addEventListener("DOMContentLoaded", main)

function log(offset, length) {
  const bytes = new Uint8Array(memory.buffer, offset, length)
  const string = new TextDecoder('utf8').decode(bytes)
  console.log(string)
}

var memory = new WebAssembly.Memory({ initial : 20 });

const exposed = {
  stdlib: { print: log },
  js: { mem: memory }
}

function main(event) {
  fetch('hello.wasm').then(response =>
    response.arrayBuffer()
  ).then(bytes =>
    WebAssembly.instantiate(bytes, exposed)
  ).then(result =>
    result.instance.exports.main()
  )
}

Now this is all fine and dandy aside from the fact that we can’t run the wast.

In order to run the wast we’ll need to turn into binary encoded wasm. Fortunately wabt exists. After you’ve headed over here and compiled the toolchain you should have a binary called wast2wasm among others.

Running this command should produce a runnable wasm binary.

wast2wasm hello.wast -o hello.wasm

Now that we have the binary encoded wasm, we need to run it somewhere. As of Node 7.9.0 it uses V8 version 5.5.372.43. Chrome 57 uses V8 5.7.492.71. The older version does not support the latest wasm spec meaning we can’t run our wasm in current stable Node (this will probably be wrong when you read this). Node version 8 nightly builds will try running the wasm however it errors out on my Macbook with Illegal Instruction: 4. To try running it in Node when this is solved you can call Node with node --expose-wasm hello.js.

Until then we’ll be running it in current Chrome. Here’s some HTML you can copy pasta, and if all goes well you should see "Hello World" in your dev tools console!

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="data:;base64,iVBORw0KGgo=">
    <title>Hello World WASM</title>
  </head>
  <body>
  <script src="hello.js"></script>
  </body>
</html>