Why WebAssembly on Embedded Systems
There is always a cost of porting software to a new platform. The same is true for WebAssembly. A key benefit of WebAssembly is that it provides software isolation and sandboxing at a reduced cost when compared to virtual machines or containers. It also has the ability of providing a common compilation target. These are things that the embedded world has never had before. This explains why WebAssembly, despite originating in the Web provides particular advantages to embedded software developers.
An Introduction to WebAssembly
WebAssembly is a universal bytecode format that was originally designed to reduce the cost of porting software written in different programming languages to web browsers. Browsers, at the time, could only execute javascript. This ment that if you wanted to port your existing, say, C, or C++ based application to the web you would basically need to rewrite it in JavaScript. A very expensive effort.
The advent of LLVM and the ability to add different compiler outputs changed this. Initial experiments showed that it was possible to compile C or C++ and instead of producing a binary executable, you could produce JavaScript which the browser could execute. This worked, but was a bit slow. WebAssembly is essentially an extension to this. Instead of producing JavaScript, a WebAssembly compiler produces a binary format which can can be interpreted, JIT (Just in Time) or AoT (Ahead of Time) compiled and executed. It is fast. Not quite as fast as native, but pretty quick.
A side effect of executing inside a JavaScript environment is making sure that the executing WebAssembly didn’t do anything nasty to the rest of the JavaScript code on the page. To prevent this considerable effort was put into designing a sandbox. The compiler does a lot of work to translate memory accesses from literal addresses to an offset from a known base. This allows the resulting bytecode to be relocated, but importantly it allows the memory accesses to be verified and sandboxed.
Because WebAssembly was designed to support porting other applications to the browser it supports functions being imported from the Javascript host world, and exported from the compiled WebAssembly to the host. This makes it easy to integrate software between the JavaScript world and the WebAssembly bytecode format.
Escaping the Browser
Javascript escaped the browser with node.js, and likewise WebAssembly has escaped the browser with WebAssembly interpreters designed for a range of use cases from the cloud with the WasmTime runtime, to embedded systems like the WAMR runtime.
Lightweight sandboxing
The sandboxing that WebAssembly provides is significantly lighter than either a virtual machine or a container. A virtual machine would typically require a hypervisor and hardware support something not available on an embedded system. Likewise using a container would typically also require the use of Linux, this usually requires more than 8 megs of memory, which can be a luxury on an embedded system.
Using WebAssembly in the Embedded World
The embedded world is still pretty fragmented across both the hardware and software basis. We have different instructions sets, from ARM, Xtensa (ESP32), RISC-V, some 68000 and PowerPC solutions are still present. On top of that we have different operating systems from proprietary RTOSs (Real Time Operating System) through to different open source versions, NuttX, FreeRTOS, and ZephyrOS.
This wide variation is due to the wide applications embedded systems cover. Some applications need to be extremely power efficient, particularly when battery powered, other applications require low heat for ruggedised, passively cooled systems, as dust has a nasty habit of blocking fans. Still others may require systems where high precision timing is important, like manufacturing, or car control systems. There isn’t a one size fits all solution.
All of the scenarios detailed above this entail some customised code specific to their application. However, as these devices become increasingly interconnected there is a need to add additional functionality, things like, a simple web servers for device configuration and setup through to network protocol adapters, like MQTT. All of these additional bits of functionality are available as open source projects, and many of these need to be compiled and tweaked for the combinations of hardware and RTOS being used.
Common Compilation Target
RTOSs traditionally provide a POSIX like interface, because this makes it easier to port software. However not all RTOSs are the same and they frequently behave differently, even if they provide functions with identical signatures.
WASI 0.1 / Preview 1 / Original WASI - the first version of WASI provides a common base against which software can be compiled. This, via WAMR, will work across a number of embedded targets. For the first time we have a common compilation target across multiple combinations of hardware and RTOS.
This means that the embedded market could have catalogues of reusable binary software for the first time.
Sandboxing
Embedded software has traditionally been structured without an internal security barriers. The executable code is typically stored in a ROM image and executed at start up, and the device has a single task to perform. But embedded software is changing. With the addition of network interfaces, HTTP, and network protocols like MQTT, as mentioned above, the devices now have a need for a security barrier. Anything accessing the network interface could impact the rest of the embedded device. So, it becomes important to place a barrier between the two.
This is where WebAssembly can offer embedded users two amazing advantages, a common compilation target, with easy to re-use software, and sandboxing of this software.
Filling in the blanks…
There is still a little way to go. Right now the leading platform for WebAssembly on embedded systems is WAMR, and it provides threads, sockets and file system support. But the threading is due to change due to updates in the Core WebAssembly specification. The runtime supports exception handling, but only when it executes in the interpreter code (not the JIT / AoT modes), this is due to be updated. You would be forgiven for thinking that Exception Handling is only needed for C++ exceptions, but it is also needed to provide support setjmp
and longjump
functionality. The runtime doesn’t have support for signal handling another prerequisite for a lot of open source software. The WASIX / Wasmer runtime has support for this, so it suggests that it could be added.
So it’s not perfect, but it is getting close. What is missing is a common POSIX like target with these features across a number of runtimes.