#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.
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:
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.
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
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
pacman -S wabt
apt install wabt
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.
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
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-librariesmeans that the resulting binary is compiled in standalone mode, meaning it doesn’t require anything from
libcor the OS.
-Wl,--no-entryjust means that I’m not defining
mainand the linker shouldn’t complain about it
Okay, so now what?
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_ctorswhich is mainly used as an initialiser for static data (and global, initialised variables, if there are any).
foofirst C function which just returns
baris slightly different, stack manipulation code is visible handling the argument
123constant 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.
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.
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!