#1 WebAssembly and C++: Baby steps
This post is part of a WebAssembly series focused on WASM and C++. The goal is to gain a thorough understanding of how WebAssembly works, how to use it as a compilation target for C++ code and hopefully have fun along the way. So, stick with me for this exciting journey.
Wherever mentioned, working WASM examples will be embedded directly on the page. If your browser supports it, you should be able to see them running.
Introduction
Let’s start by answering some basic questions regarding WebAssembly to understand what we’re dealing with.
What is WebAssembly?
The name is a bit misleading. WebAssembly (WASM) is a byte code that is executed by a WASM runtime. WASM runtime is effectively a virtual machine (just like JVM). There are many runtimes available, amongst most popular ones are:
Yes, I’ve mentioned a web browser and node.js. WASM can run within JavaScript engines and there’s a set of Web APIs created to support that which can be found here.
Why do we need the runtime?
It’s a byte code, not native code, as a result it requires a viable runtime environment to execute.
WebAssembly assembly?
If WebAssembly is the byte code, is there an assembly language for WebAssembly? Yes, there is. It’s called WebAssembly Text (WAT). You can read more about it here. It’s not my primary focus so, I’m not gonna go into details about it but in general, you don’t need Rust or C/C++ or any high level language to create WASM binaries. Theoretically, you can just write your code in WAT and compile that down to WASM. The complexity of this approach and all challenges that comes with it is probably a topic for a different story but it is possible nonetheless.
It’s good to familiarise yourself with WAT as it’s useful when debugging but, in general, not a strict requirement.
How would you compile WAT to WASM?
WebAssembly binary toolkit contains a complete set of tools comprising the most fundamental toolchain.
It’s useful to have it installed even when working with clang
or emscripten
or Rust technologies. Again, mainly for debugging purposes. If you’re using
Arch (like I am) or Ubuntu, then it’s even easier as it’s available in official
repositories, just:
pacman -S wabt
or
apt install wabt
Getting started
We need to start with most basic examples to understand the calling convention and get a general feel of how to work with this technology.
Prerequisites
Initially, I’m gonna use clang
which supports WASM targets:
|
|
Which one to use? Similarly as with native targets, the main difference
between the two is the size for POD types like int
and pointers, which are 32
bits in length for wasm32 and 64 for wasm64 respectively. It doesn’t really
matter at that point. I’m gonna use wasm32 as the runtime support is a bit
better.
Hello world… kind of
I’m gonna start with something bare bones simple:
|
|
This can be compiled into WASM binary the following way:
|
|
Some quick clarification on the above:
- I use
extern "C"
to avoid C++ name mangling --no-standard-libraries
means that the resulting binary is compiled in standalone mode, meaning it doesn’t require anything fromlibstdc++
,libc
or the OS.-Wl,--no-entry
just means that I’m not definingmain
and the linker shouldn’t complain about it
Okay, so now what?
foo.wasm
Let’s inspect the binary to learn what it contains. Here’s the disassembly:
|
|
As expected there are functions which I defined and one more:
__wasm_call_ctors
which is mainly used as an initialiser for static data (and global, initialised variables, if there are any).foo
first C function which just returnsbar
is slightly different, stack manipulation code is visible handling the argumentbaz
in which123
constant declaration is visible as a return value
Detailed content can be inspected by converting to WAT format:
|
|
Now, that’s a lot of code. All of that is simple stuff though. I just want to explain the basics. The general format of the binary is documented on the WebAssembly specification page. This document tells us, that the module is comprised of sections. In this case, there is:
You might be confused about the meaning of “(;0;)” within the WAT code. Well, that’s just a block comments, inserted by the tool to increase readability.
To summarise, we’ve got 3 function types declared at the very top, followed by 3 function definitions, then there’s memory module declaring two words (it’s not used though), seven global constants and a list of exports at the very end. Exports are the symbols which will be made public to the runtime environment.
Seeing how the code has been compiled allows us to understand what the calling convention will be for variety of function types. So, even though, I’m not gonna write WAT code manually it’s good to know what’s going on and be able to read it.
How to run it?
Let’s focus first on two most basic runtimes.
wasm-interp
WebAssembly binary toolkit provides us with the most fundamental runtime called
wasm-interp
. This can be used to run the binary directly:
|
|
Just like that, it executed all exported functions within the binary. Additionally, it can produce a useful trace allowing us to inspect what’s happening.
node.js
Node.js is a JavaScript engine. As such, it can’t run WASM directly. JavaScript APIs are needed to load and instantiate WASM module. To do that a simple wrapper is required:
|
|
After running it, the result is visible:
|
|
Congratulations! You should now understand the basics of what WebAssembly is, how it works, how to load & execute it! That’s a good progress. I’m gonna build on top of that to extend the knowledge about WebAssembly in the next post. See you there!