Skip to content

Instantly share code, notes, and snippets.

@jedisct1
Last active September 19, 2023 22:09
Show Gist options
  • Save jedisct1/9b9bb6d26a5372a8ba65af73595c22cd to your computer and use it in GitHub Desktop.
Save jedisct1/9b9bb6d26a5372a8ba65af73595c22cd to your computer and use it in GitHub Desktop.
Compiling C code to WebAssembly and Rust

How to embed C/C++ code in a Rust project targeting WebAssembly

When targeting WebAssembly, C/C++ code can be compiled as a library, and then get statically linked to a Rust project.

Step 1

Install the Zig toolchain in order to compile C and C++ code to WebAssembly.

zig cc is available for many platforms including Windows, and makes it easy to switch back and forth between native and wasm targets. WebAssembly is a Tier-1 target, and it was successfully used to port libraries such as ffmpeg, zlib, openssl, boringssl and libsodium.

Step 2

Compile individual files with zig cc -target wasm32-freestanding or zig cc -target wasm32-wasi instead of cc :

zig cc -target wasm32-wasi -Os -c example.c

Here's an example C file:

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

The wasm32-freestanding target works everywhere out of the box, including web browsers, whereas wasm32-wasi requires an additional JavaScript helper, or a runtime that supports WASI-Core.

Runtime-specific optimizations can be enabled with the -mcpu command-line flag, for example -mcpu=generic+simd128. Optimizations apply both to application/libraries being compiled and to the C library.

Step 3

Link the object files into a static library, by using the zig ar command instead of the ar command:

zig ar r libexample.a *.o

Congratulations! The WebAssembly library has been created.

An alternative to steps 2 and 3 is to build the project using the Zig build system, by creating a build.zig file, and then compile with zig build -Dtarget=wasm32-wasi -Doptimize=ReleaseSmall. Examples: libhydrogen, libaegis.

Step 4

Create a new Rust project, as well as the following files in its root directory:

  • rust-toolchain with the following content:
[toolchain]
targets = ["wasm32-wasi"]
  • build.rs with the following content:
use std::env;

pub fn main() {
    let src_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
    println!("cargo:rustc-link-lib=static=example"); // name of the wasm static library to link
    println!("cargo:rustc-link-search=native={}/wasm-libs", src_dir); // directory of the wasm static library
}

Step 5

Create a wasm-libs directory, and copy the libexample.a file into that directory, so that the Rust project can find it.

Step 6

Use C functions using the Rust FFI interface:

use core::ffi::*;

extern "C" {
    pub fn add(a: c_int, b: c_int) -> c_int;
}

fn main() {
    println!("{}", unsafe { add(1, 2) });
}

Step 7

Compile the Rust code:

cargo build --target=wasm32-wasi

Or install the cargo-wasix Cargo subcommand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment