Version information
- Azure Functions SDK: 3.0.3
- Wasmtime: 0.19.0-preview1
The WebAssembly Universe: a Code Execution Platform
WebAssembly has been introduced into major browsers some years ago (beginning at the end of 2017). With it, you can take more or less arbitrary source code and languages and compile it into a binary bytecode, usually ending in a .wasm file. People soon realized that it would be a great idea to not interpret WebAssembly as WebBrowswerAssembly :-). The notion of a common, very lightweight runtime that can uniformly run any code on any platform, is tempting. So, under the lead of Mozilla, the WebAssembly experts started to work on a specification that enables a uniform hosting, execution, and usage of WebAssembly also outside of the browser.
WASI
The WebAssembly system interface (WASI) was born. The perfect intro to WASI can be found in the GitHub repo of the Wasmtime project:
It is an API […] that provides access to several operating-system-like features, including files and filesystems, Berkeley sockets, clocks, and random numbers, that we will be proposing for standardization. It is designed to be independent of browsers, so it does not depend on Web APIs or JS, and is not limited by the need to be compatible with JS. And it has integrated capability-based security, so it extends WebAssembly’s characteristic sandboxing to include I/O.
That really sounds good and very promising to me. With WASI we can have a uniform interface for the browser and any other execution environments to run WebAssembly code. This indeed sounds a little like a potential base for having very lightweight sandboxes safely executing code – something that may be an interesting alternative to Docker, someday.
Wasmtime
The Wasmtime project from the Bytecode Alliance is an effort to create a WASI-compliant stand-alone runtime for WebAssembly: https://wasmtime.dev/. This means that we can run Wasm modules that adhere to WASI everywhere, either via a CLI or via embedding it into other applications.
Currently, Wasmtime supports
- Rust
- C
- Python
- .NET
- Go
- Bash
There we have it: .NET (Core)!
A Simple Sample: Fibonacci, All the Things!
In order to get a clear picture of what is possible and also of what is needed to run WebAssembly code in a .NET Core application, let us create a sample application. Suppose, we have some really sophisticated C-based algorithms. They are proven, they are mature, they are blazingly fast. Indeed, we are dealing with this situation in customer projects from time to time.
However, I cannot expose those intellectual property assets in an online article. Therefore, let us use a C implementation of finding the n-th Fibonacci number for demonstration purposes.
Please have a look at the following quite simplistic implementation:
int fib(int n) {
if(n <= 0){
return 0;
}
int i, t, a = 0, b = 1;
for (i = 1; i < n; i++) {
t = a + b;
a = b;
b = t;
}
return b;
}
The beginning of the Fibonacci sequence is: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, … If we pass a value of 4 as n into the function, the result will be 2. You get it 🙂
Compiling C/C++ Code to WebAssembly With Emscripten
There are a number of compiler tools able to compile C or C++ code to WebAssembly. One of the most commonly used is Emscripten. Our C code above is simple enough for explaining the WebAssembly use case, and the only thing we need to do is decorate the function with the EMSCRIPTEN_KEEPALIVE
macro.
#include
EMSCRIPTEN_KEEPALIVE
int fib(int n) {
if(n <= 0){
return 0;
}
int i, t, a = 0, b = 1;
for (i = 1; i < n; i++) {
t = a + b;
a = b;
b = t;
}
return b;
}
This macro takes care that the function will not be inlined and will be exported for external usage.
If you want to avoid to repetitively install the latest Emscripten toolchain, you can save some time by using the official Docker image.
The following command runs emcc in Docker, passes the wasm/fibonacci.c file as an input parameter and generates the WebAssembly file in wasm/fibonacci.wasm:
docker run \
--rm \
-v `pwd`:`pwd` \
-w `pwd` \
-u $(id -u):$(id -g) \
emscripten/emsdk \
emcc native/fibonacci.c -o wasm/fibonacci.wasm --no-entry
The important detail here is to explicitly specify a .wasm file as the output and the –no-entry flag. With both in place, emcc generates a WASI-compliant interface and does not use Emscripten’s proprietary ABI and system interface.
We can put this command into a shell script to re-use it in the build process. Let us use this now to create the needed .wasm file and host it in a .NET Core application – specifically in an Azure Function with C#.
Using WebAssembly Modules in .NET Core
One of the currently simplest and most straightforward ways of running Wasm code in .NET is to use the wasmtime-dotnet .NET embedding of Wasmtime.
We can add it to our project simply by adding the Nuget package: dotnet add package --version 0.19.0-preview1 wasmtime
(preview version at the time of writing)
With this in place, we can write a simple Azure function.
Creating an Azure Function to Execute WebAssembly Code
Our Azure Functions project from the demo repository looks like this:
The essential files are:
- the source C file in the native folder
- the compile/build shell script containing the Docker emcc command
- the resulting .wasm file in the wasm folder
All that is left is to use wasmtime-dotnet’s API in the Azure Function to load the Wasm module and invoke the desired C function via an HTTP trigger – fib(number) in our case:
public static class RunWasm
{
[FunctionName("FibonacciWasm")]
public static async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous,
"GET", Route = "fibonacci/{number}")]
HttpRequest request,
int number,
ILogger log)
{
using var engine = new Engine();
using var module = Module.FromFile(engine, "wasm/fibonacci.wasm");
using var host = new Host(engine);
using dynamic instance = host.Instantiate(module);
var result = instance.fib(number);
return new OkObjectResult(result);
}
}
wasmtime-dotnet uses dynamics which makes the API very readable.
If we finally call the Azure Function with a number parameter of, for instance, 12 (e.g. by simply calling the URL via a browser), it returns 144 as the result from invoking the WebAssembly code (remember: which was a C algorithm in the first place).
Conclusion - Running WebAssembly Beyond the Browser
Having the ability to use WebAssembly as common byte code and WASI as a common interface to run WebAssembly code literally everywhere – and beyond the web browser – is very tempting. In this article, you have seen how to take WASI-compliant WebAssembly code, like existing C/C++ code and run it in your modern .NET Core application. A number of inspiring use cases are opening up – at least in the heads of some customers.
The entire sample application can be found on Github here.