In most of the WebAssembly examples I’ve posted so far I’ve written simple, but complete applications, complete with a main function. But WebAssembly really comes into its own when you use it to embed third party code inside your application.

Exporting Functions

With the WAMR VM (in fairness all Wasm engines) you can load a WebAssembly module and invoke functions on that module. It’s a bit like loading a dll or a shared object (.so) but the shared object / dll is sandboxed. To do this when we compile our WebAssembly application we need to tell the compiler which functions we want to export. This can be done with this clang intrinsic:

__attribute__((export_name("name of function")))

So, for instance if I wanted to export a function which added two numbers together I could do it like this:

__attribute__((export_name("add"))) int add(int a, int b) {
    return a + b;
}

Now, one of the really cool things about the export name is it can be any valid string so we could export the function like this:

__attribute__((export_name("the answer to life the universe and everything"))) int add(int a, int b) {
    return a + b;
}

Typing __attribute__((export_name("blah"))) in front of every function is a pain in the behind. So instead we can use a macro to make life a bit easier :

#define WASM_EXPORT(A) __attribute__((export_name(A)))

Now our add function becomes:

WASM_EXPORT("add") int add(int a, int b) {
    return a + b;
}

Now, that looks a lot better. Our entire shared object now looks a little like this (add.c) :

#define WASM_EXPORT(A) __attribute__((export_name(A)))

WASM_EXPORT("add") int add(int a, int b) {
    return a + b;
}

Compiling our Wasm

Now let’s assume we want to compile our Wasm version of a shared object, when we do we need to tell our compiler that it’s not a full application. On a normal Linux system we’d indicate that the code was shared. For WebAssembly it’s a little different, we compile and tell the compiler to not expect a main function. This can be done with this argument -Wl,--no-entry. So we compile it like this:

/opt/wasi-sdk/bin/clang  -Wl,--no-entry -o add.wasm ./add.c

This is going to produce a add.wasm file which is about 9.8k in size. That’s huge for such a simple function, and that’s because it is not optimized, and it’s also including the c standard library (printf , malloc etc.). We don’t need that library, so we can even remove that with the following -nostdlib and we can shrink the wasm file to 193 bytes. Note, of course if you don’t want to include some of the standard library functionality, like a printf you will not be able to use --nostdlib .

We can check to see what this actually produces by running it through the wasm2wat tool:

wasm2wat -o add.wat add.wasm

If you peak into the add.wat you’ll see something which pretty much is exactly what we wrote - a wasm module which exports a function called add, which takes two 32 bit integers adds them together and returns the result. It will look like this:

(module
  (type (;0;) (func (param i32 i32) (result i32)))
  (func $add (type 0) (param i32 i32) (result i32)
    local.get 1
    local.get 0
    i32.add)
  (table (;0;) 1 1 funcref)
  (memory (;0;) 2)
  (global $__stack_pointer (mut i32) (i32.const 66560))
  (export "memory" (memory 0))
  (export "add" (func $add)))

Now we just need to invoke that WebAssembly application from our host environment

Invoking a Wasm Function from the Host

Setting up the host environment can be rather lengthy, to save the time the code to do this is available in the wasm_example project I created on github, but the interesting bit is the following function:

bool invoke_add(wasm_module_inst_t module_instance, wasm_exec_env_t exec_env, int a, int b, int* answer) {
	const char* WASM_FUNCTION = "add";
	printf("look up a function [%s]\n", WASM_FUNCTION);
	wasm_function_inst_t func = wasm_runtime_lookup_function(module_instance, WASM_FUNCTION);
	if (!func) {
		printf("couldn't find function [%s]\n", WASM_FUNCTION);
		return false;
	}
	printf(">function found!\n");	
	wasm_val_t parameters[] = {
		{.kind = WASM_I32
		,.of.i32 = a},
		{.kind = WASM_I32
		,.of.i32 = b}
	};

	wasm_val_t result[] = {
		{.kind = WASM_I32
		,.of.i32 = 0}		
	};

	printf(">calling function with parameters(%p)\n", parameters[0].of.ref);
	bool executed_ok = wasm_runtime_call_wasm_a(exec_env, func, 1, &result[0], 2, &parameters[0]);
	if (!executed_ok) {
		printf("call failed!\n");
		return false;
	}

	(*answer) = result[0].of.i32;

	return true;	
}

In this function you can see that the name of function is being looked up, then the parameters are packaged up in a wasm_val_t array of structures, a second array of structures has also been created, and it took has space for a response value, these are then passed to the function, and the function is executed.

bool executed_ok = wasm_runtime_call_wasm_a(exec_env, func, 1, &result[0], 2, &parameters[0]);

This translates to, call the function pointer func it will return 1 return value (note that wasm functions could return more than one return value) this should be stored in the &result[0] address, the function requires 2 arguments and these are located in the array pointed to by &parameters[0]. The function will return a boolean value indicating success. If false then the function failed to execute.