Skip to content

Instantly share code, notes, and snippets.

@jdmichaud
Last active August 21, 2018 16:56
Show Gist options
  • Save jdmichaud/cf0245e101e7ae54fa8117c720f48f8f to your computer and use it in GitHub Desktop.
Save jdmichaud/cf0245e101e7ae54fa8117c720f48f8f to your computer and use it in GitHub Desktop.
Minimalistic WebAssembly example

Tools

Install the emscripten toolchain. See instructions here

How does this works?

We will compile a simple c program using emscripten which will generate WebAssembly code. Then, in a simple web page, we will load the resulting wasm generated file and pass it to the WebAssembly facility now found in any decent browser. Simple as that!

Compile c to wasm

Create a simple c program:

extern void print(char *index, int length);

int main() {
  print("hello!", 5);
}

Then compile it using emscripten:

emcc hello.c -s WASM=1 -Os -o hello.js

WASM=1 tells emcc to compile to wasm instead of the native asm.js. -Os tells emcc to strip the runtime part (all the code that let us print, use math function, use SDL as WebGL, etc).

This will generate two files:

  • hello.js: which is the WebAssembly Javascript API, which we don't need in our simple example.
  • hello.wasm: the resulting assembly code.

Load the code in the browser

Create a simple index.html page.

In a script tag, use the fetch API to load the wasm file and pass the result to WebAssembly.instantiateStreaming:

  <script type="text/javascript">
    const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
    const buffer = new Uint8Array(memory.buffer);
    const imports = {
      env: {
        memory: memory,
        STACKTOP: 0,
        _print: (index, size) => {
          let s = "";
          for (let i = index; i < index + size; ++i)
            s += String.fromCharCode(buffer[i]);
          console.log(s);
        },
      },
    };

    WebAssembly.instantiateStreaming(fetch('hello.wasm'), imports)
      .then(obj => obj.instance.exports._main());
  </script>

Note the use of the imports object. This is an array passed to our compiled c code which it can callback.

Here we create a memory object which is just a buffer shared between the javascript instance and the c instance. When we call print, this is not the actual string which is passed but the pointer itself which happens to be the index of the string in the buffer. In the _print function, we just retrieve the data from the buffer and convert them to a string before printing it.

Server

To serve your file make sure that your web server knows the application/wasm MIME-type. python -m http.server do not know this type for example. You can customize it with your own type though.

// emcc hello.c -s WASM=1 -Os -o hello.js
extern void print(char *index, int size);
int main() {
print("hello!", 5);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Simple template</title>
</head>
<body>
<script type="text/javascript">
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
const buffer = new Uint8Array(memory.buffer);
const imports = {
env: {
memory: memory,
STACKTOP: 0,
_print: (index, size) => {
let s = "";
for (let i = index; i < index + size; ++i)
s += String.fromCharCode(buffer[i]);
console.log(s);
},
},
};
WebAssembly.instantiateStreaming(fetch('hello.wasm'), imports)
.then(obj => obj.instance.exports._main());
</script>
</body>
</html>
# -*- coding: utf-8 -*-
#test on python 3.4 ,python of lower version has different module organization.
import http.server
from http.server import HTTPServer, BaseHTTPRequestHandler
import socketserver
PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler
Handler.extensions_map = {
'.html': 'text/html',
'.js': 'application/x-javascript',
'.wasm': 'application/wasm',
'': 'application/octet-stream', # Default
}
httpd = socketserver.TCPServer(('', PORT), Handler)
print('serving at port', PORT)
httpd.serve_forever()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment