Introduction
In this article, We're going to talk about WebAssembly, Wasmer and how to embed Wasmer in a C application.
WebAssembly
WebAssembly is a portable binary instruction format for virtual stack machines.
It aims to execute at native or near-native speed by assuming a few characteristics about the execution environment.
WebAssembly is designed to be executed in a fully sandboxed environment with linear memory. The binary is completely blind to the host environment by default because of that design. That being said, we can set up some kind of communication by reading memory or importing functions which we'll talk about it later.
Wasmer
We talked about WebAssembly and learned what is all about but this article will be focusing on how to use WebAssembly in action.
Between all of these compilers, interpreter, and runtimes, We'll be covering a runtime named Wasmer which supports multiple backends including LLVM, Cranelift, and Single pass (dynasm).
Backends
- Single Pass: super fast compilation, non-optimized execution
- Cranelift: normal compilation, normal execution (default)
- LLVM: slow compilation, fast execution
Wasmer Basics
Wasmer is crazy easy to install and use so don't worry about its learning curve because It is very simple despite being very flexible and powerful.
Standalone Runtime
Wasmer provides a CLI in order to easily execute wasm files in the command line.
On Linux you can install it like this:
curl https://get.wasmer.io -sSfL | sh
On windows, you can go the Github Releases Page, and there you can find .exe installers.
To check if everything is working fine you can download the qjs.wasm file at the wapm website.
Now you can run Wasmer to get a QuickJS prompt which you can use to run javascript!
wasmer qjs.wasm
WAPM - Package Manager for WebAssembly
WAPM is a package manager for WebAssembly modules which comes bundled with the Wasmer itself.
In the previous section, we downloaded QuickJS manually but using wapm we can install the QuickJS package much easier.
We can use wapm to install QuickJS globally.
wapm install -g quickjs
And now you can use QuickJS everywhere.
# Run a file
qjs --dir=. examples/hello_module.js
# Run the REPL
qjs
The good thing about WAPM is that it doesn't force you to use Wasmer.
Wasmer Integration in C
Wasmer provides official integration tools for several languages including C/C++, Go, Rust, Javascript, PHP, and C#. Sky's the limit when C headers are provided :)
Embedding Wasmer in C using the already provided header file is pretty straight forward and by following these steps, You should be able to get how things work.
Setup Environment
To embed Wasmer in your C application you'll need a C compiler and the Wasmer C API SDK which you can download from the Wasmer releases page.
- Linux:
wasmer-c-api-linux-amd64.tar.gz
- macOS:
wasmer-c-api-darwin-amd64.tar.gz
- Windows:
wasmer-c-api-windows.tar.gz
After downloading the SDK you need to extract the contents and set the WASMER_C_API
env variable to the path which you extracted the SDK in.
# Extract the contents to a dir
mkdir wasmer-c-api
tar -C wasmer-c-api -zxvf wasmer-c-api*.tar.gz
export WASMER_C_API=`pwd`/wasmer-c-api
# Update LD_LIBRARY_PATH to link against the libwasmer.so in the examples
export LD_LIBRARY_PATH=`pwd`/wasmer-c-api/lib/:$LD_LIBRARY_PATH
You can also build the SDK youself. Click here for more information.
Embedding Wasmer
Before writing any code, You need to have a .wasm file ready. In this example, you can use add.wasm which exports a function named add_one
. This function takes an int
and returns the number plus 1!
You can find the source here.
Now let's get to coding! The first thing you should do is including the header file.
#include <stdio.h>
#include "wasmer.h" // Wasmer C API
Make a main()
function and read the add.wasm
into the memory.
FILE *file = fopen("add.wasm", "r");
assert(file != NULL);
fseek(file, 0, SEEK_END);
long len = ftell(file);
uint8_t *bytes = malloc(len);
fseek(file, 0, SEEK_SET);
fread(bytes, 1, len, file);
fclose(file);
By calling wasmer_instantiate
, You can instantiate WebAssembly Instance from wasm bytes. This is where you can import host functions or memory but importing won't be covered in this article so We just create an empty imports array. (Maybe a part 2 of this article covering advanced topics like importing?)
wasmer_import_t imports[] = {};
wasmer_instance_t *instance = NULL;
wasmer_result_t compile_result = wasmer_instantiate(
&instance, // Our reference to our Wasm instance
bytes, // The bytes of the WebAssembly modules
len, // The length of the bytes of the WebAssembly module
imports, // The Imports array the will be used as our importObject
0 // The number of imports in the imports array
);
wasmer_result_t
is an enum that represents a success or a faliure.
-
WASMER_OK
: Represents a success. -
WASMER_ERROR
: Represents a failure.
Check the compile_result
for its value and exit the program if there's an error.
if (compile_result != WASMER_OK)
{
return 1;
}
At this point, our wasm file is compiled and we can call the exported functions.
WebAssembly provides 4 value types.
-
i32
: 32-bit integer. -
i64
: 64-bit integer. -
f32
: 32-bit floating point. -
f64
: 64-bit floating point.
Yes, there are all numbers but by being such a low-level standard there is no way around it.
But don't worry, Strings and even dynamic strings can be easily implanted. Simplest implantation is a C-style string which basically is an array of ASCII bytes with a null byte at the end (hence the name null-terminated string).
You can use wasmer_value_t
to define a WebAssembly value. We use it to define 2 values, one for the function parameter and one for the result.
We put them in arrays before calling the add_one
function.
wasmer_value_t param_one = { .tag = WASM_I32, .value.I32 = 24 };
wasmer_value_t params[] = { param_one };
wasmer_value_t result_one = { 0 };
wasmer_value_t results[] = {result_one};
wasmer_value_t
has two fields.
-
wasmer_value_tag
: WebAssembly type. (WASM_I32
,WASM_I64
,WASM_F32
,WASM_F64
) -
wasmer_value
: Value.
It's time to call the add_one
function. This can be done by calling wasmer_instance_call
within our C program.
wasmer_result_t call_result = wasmer_instance_call(
instance, // Our Wasm Instance
"add_one", // the name of the exported function we want to call on the guest Wasm module
params, // Our array of parameters
1, // The number of parameters
results, // Our array of results
1 // The number of results
);
Like before we check the call_result
to see if the call was successful or not.
if (call_result != WASMER_OK)
{
return 1;
}
Now we can read the result value which we know it's a 32-bit integer.
int response_value = results[0].value.I32;
Print the result to show the glory :)
printf("Result value: %d\n", response_value);
We're done here. You should call wasmer_instance_destroy
to destroy the instance and return a success exit status code.
wasmer_instance_destroy(instance);
return 0;
This is our final code:
#include <stdio.h>
#include "wasmer.h" // Wasmer C API
int main()
{
FILE *file = fopen("add.wasm", "r");
assert(file != NULL);
fseek(file, 0, SEEK_END);
long len = ftell(file);
uint8_t *bytes = malloc(len);
fseek(file, 0, SEEK_SET);
fread(bytes, 1, len, file);
fclose(file);
wasmer_import_t imports[] = {};
wasmer_instance_t *instance = NULL;
wasmer_result_t compile_result = wasmer_instantiate(
&instance, // Our reference to our Wasm instance
bytes, // The bytes of the WebAssembly modules
len, // The length of the bytes of the WebAssembly module
imports, // The Imports array the will be used as our importObject
0 // The number of imports in the imports array
);
if (compile_result != WASMER_OK)
{
return 1;
}
wasmer_value_t param_one = {.tag = WASM_I32, .value.I32 = 24};
wasmer_value_t params[] = {param_one};
wasmer_value_t result_one = {0};
wasmer_value_t results[] = {result_one};
wasmer_result_t call_result = wasmer_instance_call(
instance, // Our Wasm Instance
"add_one", // the name of the exported function we want to call on the guest Wasm module
params, // Our array of parameters
1, // The number of parameters
results, // Our array of results
1 // The number of results
);
if (call_result != WASMER_OK)
{
return 1;
}
int response_value = results[0].value.I32;
printf("Result value: %d\n", response_value);
wasmer_instance_destroy(instance);
return 0;
}
For more example, you can use a few wonderful examples provided by Wasmer which you can find here.
Conclusion
In this article we learned about:
- WebAssembly basics
- Wasmer standalone runtime and package manager
- WebAssembly value types
- Embedding Wasmer in our C application
WebAssembly still is a new concept. There's been lots of cool and yet useful stuff built using WebAssembly. And the interesting thing about WebAssembly is that there is application for it on both client and server sides.
I'm interested to write a part 2 discussing advanced topics like importing functions and reading shared memory and implanting strings and streams but I don't have a plan for it yet.
I hope you enjoyed this article and It didn't waste too much of your time.
Top comments (3)
Kinda curious. Can Wasmer be used for creating a FaaS platform?
Yes it can! In fact, I'd be surprised if it hasn't been already used in some FaaS platform or in smart contracts.
Is there any documentation or tutorials that you know of?