I started to look into webassembly with the goal of implementing a version of Conway’s Game of Life which was able to use an engine written in C to calculate the next state of the game.
In this short article I want to illustrate the steps I made to compile a simple C function into a webassembly module callable by Javascript. To compile the C code I used emscripten, an open source LLVM (Low Level Virtual Machine) to Javascript compiler.
Installing emscripten
Installing the emscripten toolchain is pretty straightforward, the official documentation is available here. The installation process could take quite a lot of time anyway (depending on your network and machine speed), since the project is quite big.
Note: I use macOS therefore the syntax used in the article may be slightly different on Windows or Linux :)
Once emscripten is installed you have to set the system path to the active version of it (from the emsdk directory):
source ./emsdk_env.sh
To test if emscripten is correctly configured just run:
emcc -v
Writing and compiling a C function
The C function that I am going to compile implements the logic to define if a cell of the grid will be dead or alive in the next iteration of the game. Let’s create a file called getCellStatus.c
and paste this code in it.
int dead = 0;
int alive = 1;
int getCellStatus(int status, int neighboursCount) {
switch(neighboursCount) {
case 3:
status = alive;
break;
case 2:
break;
default:
status = dead;
}
return status;
}
The logic of getCellStatus is pretty simple. It accepts two integer parameters: the status and the number of neighbours, and returns the new status of the cell as an integer.
Now run:
emcc getCellStatus.c -o getCellStatus.js -s EXPORTED_FUNCTIONS=”[‘_getCellStatus’]”
This command will generate a javascript file (getCellStatus.js
) wrapping our C code in a module.
For the moment we need to manually export the function that we want to call from Javascript by adding the EXPORTED_FUNCTIONS
options to the command.
Now there are two ways to call a C compiled function from Javascript:
- Wrapping the function using
cwrap()
(this returns Javascript function callable multiple times) - Directly called the function using
ccall()
(this returns directly the return value of the C function)
We can now call getCellStatus
by adding some code to the end of our Javascript module.
Wrapping a function with cwrap
cwrap
accepts three parameters and returns a Javascript function:
- The name of the function to be wrapped
- The return type of the function
- An array with the types of the parameters of the function (which can be omitted if the function has no parameter)
In our case a call to cwrap
would be as follows:
const getCellStatus = Module.cwrap('getCellStatus ', 'number', ['number', 'number'])
console.log(getCellStatus(1, 3))
console.log(getCellStatus(0, 3))
console.log(getCellStatus(0, 2))
console.log(getCellStatus(1, 1))
// Prints 1 1 0 0
Calling a function with ccall
ccall
is similar to cwrap
. It returns the value returned by the C function and requires an additional parameter defining the actual arguments to pass to the function call.
const getCellStatus = Module.ccall('getCellStatus ', 'number', ['number', 'number'], [1, 3])
// Prints 1
Using EMSCRIPTEN_KEEPALIVE to export functions
When we compiled our C file to Javascript we had to manually declare in the command which functions we wanted to export.
Emscripten provide us with an useful annotation to avoid this: EMSCRIPTEN_KEEPALIVE
. So let’s rewrite our C file:
#include <emscripten.h>
int dead = 0;
int alive = 1;
EMSCRIPTEN_KEEPALIVE
int getCellStatus(int status, int neighboursCount) {
switch(neighboursCount) {
case 3:
status = alive;
break;
case 2:
break;
default:
status = dead;
}
return status;
}
Compile the function (no need to declare exported functions now)
emcc getCellStatus.c -o getCellStatus.js
Then we can just create an index.js file to test our function
const em_module = require('./getCellStatus.js')
console.log(em_module.ccall('getCellStatus', [0, 3])) // using ccall
console.log(em_module._getCellStatus(1, 2)) // vdirect call
And run it with
node index_getCellStatus.js
// Prints 0 1
Conclusion
As you can see using emscripten to generate a Javascript module from a C file and to call compiled C function with it is pretty easy.
To get further details about how you can use emscripten to interact with C functions read the official documentation here.
Anyway this is a really basic usage of this tool, more advanced access to the memory or to wasm files requires a little more effort. I will try to explain how to do that in the second part of this article.
Thanks for reading 😎