-
Notifications
You must be signed in to change notification settings - Fork 34
Using the C API with WebAssembly
The Cartesi machine emulator library can be compiled for WASM and used in the frontend of web applications. Here we will guide step-by-step on how to do this.
First make sure you have:
- Git, wget, and other build utilities.
- Emscripten toolchain installed and ready for use
You can install them on Ubuntu with:
apt-get install git wget build-essential emscripten
If you are not downloading uarch/uarch-pristine-hash.c
and uarch/uarch-pristine-ram.c
files for your Emulator release, you will need Docker to build them.
git clone --recurse-submodules https://github.com/cartesi/machine-emulator.git
cd machine-emulator
make downloads bundle-boost
make CC=emcc CXX=em++ AR="emar rcs" slirp=no release=yes libcartesi.a
make STRIP=emstrip EMU_TO_LIB_A=src/libcartesi.a DESTDIR=wasm-pkg install-headers install-static-libs
Now you should have a directory with all required files in wasm-pkg
for building C applications targeting WASM with the Emscripten toolchain:
$ tree wasm-pkg
wasm-pkg/
└── usr
├── include
│ └── cartesi-machine
│ ├── htif-defines.h
│ ├── jsonrpc-machine-c-api.h
│ ├── machine-c-api.h
│ ├── machine-c-defines.h
│ ├── machine-c-version.h
│ ├── pma-defines.h
│ ├── rtc-defines.h
│ └── uarch-defines.h
└── lib
└── libcartesi.a
You should use libcartesi.a
to link to your WASM application, and the cartesi-machine/machine-c-api.h
header for using the cartesi machine C API.
Create a test.c
file with the following contents:
#include <stdio.h>
#include <cartesi-machine/machine-c-api.h>
int main() {
// Initialize a new machine config from defaults
const cm_machine_config* def_config = cm_new_default_machine_config();
if (!def_config) {
printf("failed to get default machine config\n");
return -1;
}
cm_machine_config config = *def_config;
// Adjust initial machine config
config.ram.image_filename = "linux.bin";
config.ram.length = 128 * 1024 * 1024;
config.dtb.bootargs = "no4lvl quiet earlycon=sbi console=hvc0 rootfstype=ext2 root=/dev/pmem0 rw init=/usr/sbin/cartesi-init";
config.dtb.entrypoint = "uname -a; echo Hello from Cartesi Machine!";
// Adjust flash drive configs
cm_memory_range_config flash_drives[1] = {0};
flash_drives[0].image_filename = "rootfs.ext2";
flash_drives[0].start = 0x80000000000000;
flash_drives[0].length = UINT64_MAX;
config.flash_drive.entry = flash_drives;
config.flash_drive.count = 1;
// Set machine runtime config
cm_machine_runtime_config runtime_config = {0};
// Create the machine
cm_machine *machine = NULL;
char *err_msg = NULL;
int err = cm_create_machine(&config, &runtime_config, &machine, &err_msg);
if (err != 0) {
printf("failed to create machine: %s\n", err_msg);
cm_delete_cstring(err_msg);
cm_delete_machine_config(def_config);
return -1;
}
// Run the machine until it halts
CM_BREAK_REASON break_reason;
err = cm_machine_run(machine, UINT64_MAX, &break_reason, &err_msg);
if (err != 0) {
printf("failed to run machine: %s\n", err_msg);
cm_delete_cstring(err_msg);
cm_delete_machine(machine);
cm_delete_machine_config(def_config);
return -1;
}
// Cleanup
cm_delete_machine(machine);
cm_delete_machine_config(def_config);
return 0;
}
Before compiling, make sure you have linux.bin
and rootfs.ext2
files available, these files will be used to boot the machine.
You can download them with:
wget -O rootfs.ext2 https://github.com/cartesi/machine-emulator-tools/releases/download/v0.14.1/rootfs-tools-v0.14.1.ext2
wget -O linux.bin https://github.com/cartesi/image-kernel/releases/download/v0.19.1/linux-6.5.9-ctsi-1-v0.19.1.bin
Now you can compile with:
emcc test.c -o test.html \
-O3 \
-I./wasm-pkg/usr/include -L./wasm-pkg/usr/lib -lcartesi \
--embed-file linux.bin@linux.bin \
--embed-file rootfs.ext2@rootfs.ext2 \
-sSTACK_SIZE=4MB \
-sTOTAL_MEMORY=512MB \
-sNO_DISABLE_EXCEPTION_CATCHING
The files test.html
, test.js
and test.wasm
will be compiled, and be ready for testing in a web application:
ls -la test.*
-rwxr-xr-x 1 user user 100M Feb 2 12:56 test.wasm
-rw-r--r-- 1 user user 84K Feb 2 12:56 test.js
-rw-r--r-- 1 user user 20K Feb 2 12:56 test.html
-rw-r--r-- 1 user user 1.8K Feb 2 12:56 test.c
You can quickly test in your browser with emrun
tool, which is a very simple HTTP server:
emrun test.html
If everything works, a page will open in your web browser, and after a few seconds the machine will boot with a message like:
Linux localhost.localdomain 6.5.9-ctsi-1 #1 Tue, 05 Dec 2023 15:20:56 +0000 riscv64 riscv64 riscv64 GNU/Linux
Hello from Cartesi Machine!
Here is a screenshot:
When compiling with emcc
a few options were used, here is the reason for each one:
-
-O3
optimizes the build. It's recommended to always generate optimized builds for WASM, otherwise the WASM file will be too big and slow, some browsers may even fail to load. -
-I./wasm-pkg/usr/include -L./wasm-pkg/usr/lib -lcartesi
exposes the libcartesi headers and library for linking. -
--embed-file
options will bundle kernel and rootfs files used by the machine. -
-sSTACK_SIZE=4MB
is mandatory, otherwise the "stack size" will be too small for running the emulator. -
-sTOTAL_MEMORY=512MB
is mandatory, otherwise WASM memory will be too small for running the machine, this can be adjusted though depending on the size of kernel, rootfs and machine memory. -
-sNO_DISABLE_EXCEPTION_CATCHING
helps on giving tracebacks for any error in case of any.
All this is only useful if you can interact with environment inside the machine in the HTML5 page, but how can you do with?
You can pass messages between the C code and anything running inside the emulator with the yield and rollup devices. By yielding from inside the machine cm_machine_run
will break, and you can read/write to rollup buffers to exchange messages, check the official Cartesi Machine documentation on how to do this.
Once your messages are in the C code, you can propagate to javascript and HTML5 web page, please check Emscripten documentation on how to call between C and JavaScript code.