Hello World In WebAssembly
Every now and then, I check on the progress of Web Assembly. I did it again around the time of this post and finally found enough tutorials, examples, and working software to get myself started in this area. In doing so, I made a video to demo some progress. (this article includes all the same information and more, so just read on if you don't have 15 minutes for YouTube).
Our goal:
- Use as much built-in tools on a Mac as possible. The web development toolchain scares me.
- Target the browser. That's where the value of WebAssembly is. (Node supports it as well. BUT, WHY THO?)
- Build from scratch. In the video I started from
mkdir
a folder. We should strive to understand details on every level whenever possible. Boilerplates and dependencies should come later.
Things you'll need:
- Safari 11+
- Xcode. More specifically, you should be able to run
clang
in a shell.
The Workflow
Having these things installed, get a copy of The WebAssembly Binary Toolkit (wabt). Build it. The README has detailed instructions. I just went into the folder and ran
make clang-release
This will generate a bunch of binary files in out/clang/Release
and you need
to make sure you can run them from wherever you want to work on WebAssembly
project (so either copy them into a folder included in your PATH
environment
variable or add the absolute path to out/clang/Release
to PATH
).
Among the binaries "wabt" builds, wat2wasm
takes a .wat
file and compiles it
to a WebAssembly binary. A .wat
is a source file in the text format for
WebAssembly, which is in the form of S-expressions. So
wat2wasm main.wat -o main.wasm
…will compile your WebAssembly module in main.wat
to generate main.wasm
, the
binary file. For now, main.wat
can be the simplest WebAssembly program:
(module)
Running the binary in a browser demands the bulk of the work. First, we'll need a web page. It doesn't need any content other than invoking some JavaScript code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!-- The only thing that matters is the following line,
although having a valid HTML5 page is nice. -->
<script src="play.js"></script>
</body>
</html>
First, the Javascript logic needs to fetch and instantiate the compiled WebAssembly module. Since this is not a JS or WebAssembly tutorial, I'll point you to the docmuntation for Fetch API, Promises, and the WebAssembly object for details:
fetch("main.wasm").then(reponse =>
reponse.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, {})
).then(result =>
result.instance
).then(main);
This snippet fetches main.wasm
(adjust this URL according to your choosing),
instantiate it, then pass it into a function named main
, we can put
a placeholder logic for it for now:
function main(wasm) {
console.log(wasm);
}
Before we move on, you'll find that simply opending your HTML file in browser
and looking at developer console won't work. Safari would complain about
cross-domain request error for fetch
. So we need to serve these resources
locally. I usually use the built in server module from Python standard library
for this kind of things:
# In your source folder, run
python -m SimpleHTTPServer
Now go to http://localhost:8000 and click on your HTML file. If everything went well, you should see a WebAssembly instance logged in the developer console.
Congratulations! You can start writing WebAssembly locally. Just remember to
re-compile main.wat
with wat2wasm
whenever you want to test things out in
browser.
An Actual "Hello, World!" Implementation
This is my implementation:
(module
;; Allocate a page of linear memory (64kb). Export it as "memory"
(memory (export "memory") 1)
;; Write the string at the start of the linear memory.
(data (i32.const 0) "Hello, world!") ;; write string at location 0
;; Export the position and length of the string.
(global (export "length") i32 (i32.const 12))
(global (export "position") i32 (i32.const 0)))
In other words, we expose information of the linear memory we manipulated to the
JavaScript environment. Things that has been export
ed will show up as
properties of exports
of the WebAssembly
instance. We can access them in the
main
JavaScript functions:
function main(wasm) {
const memory = wasm.exports.memory;
const length = wasm.exports.length;
const position = wasm.exports.position;
...
}
Then it's just plain-old Javascript (tho I had to steal it from tutorials).
memory.buffer
is of type ArrayBuffer. We need to convert it into a string
and log it to the console:
function main(wasm) {
const memory = wasm.exports.memory;
const length = wasm.exports.length;
const position = wasm.exports.position;
const bytes = new Uint8Array(memory.buffer, position, length);
const s = new TextDecoder('utf8').decode(bytes);
console.log(s);
}
Et, voilà! Hello, World!
hot off a Web Assembly module in your developer
console. To conclude, I personally like to use a Makefile
to streamline some
of the typing. Here's what I used for this demo:
compile:
wat2wasm main.wat -o main.wasm
serve:
python -m SimpleHTTPServer
Conclusion
No fancy schmancy Javascript build stack, no 3rd-party code dependency. Write code, compile, run on your (virtual, in browser) machine, repeat. That sounds like "assembly" to me!