Skip to content

Compiling Rust To WebAssembly

This chapter will walk you through compiling a Rust crate to a WebAssembly (Wasm) module in binary (i.e. .wasm) form. The Miden compiler has a frontend which can take such modules and compile them on to Miden Assembly, which will be covered in the next chapter.

Setup

First, let’s set up a simple Rust project that contains an implementation of the Fibonacci function (I know, it’s overdone, but we’re trying to keep things as simple as possible to make it easier to show the results at each step, so bear with me):

Start by creating a new library crate:

cargo new --lib wasm-fib && cd wasm-fib

To compile to WebAssembly, you must have the appropriate Rust toolchain installed, so let’s add a toolchain file to our project root so that rustup and cargo will know what we need, and use them by default:

cat <<EOF > rust-toolchain.toml
[toolchain]
channel = "stable"
targets = ["wasm32-wasip1"]
EOF

Next, edit the Cargo.toml file as follows:

[package]
name = "wasm-fib"
version = "0.1.0"
edition = "2021"

[lib]
# Build this crate as a self-contained, C-style dynamic library
# This is required to emit the proper Wasm module type
crate-type = ["cdylib"]

[dependencies]
# Use a tiny allocator in place of the default one, if we want
# to make use of types in the `alloc` crate, e.g. String. We
# don't need that now, but it's good information to have in hand.
#miden-sdk-alloc = "0.0.5"

# When we build for Wasm, we'll use the release profile
[profile.release]
# Explicitly disable panic infrastructure on Wasm, as
# there is no proper support for them anyway, and it
# ensures that panics do not pull in a bunch of standard
# library code unintentionally
panic = "abort"
# Enable debug information so that we get useful debugging output
debug = true
# Optimize the output for size
opt-level = "z"

Most of these things are done to keep the generated code size as small as possible. Miden is a target where the conventional wisdom about performance should be treated very carefully: we’re almost always going to benefit from less code, even if conventionally that code would be less efficient, simply due to the difference in proving time accumulated due to extra instructions. That said, there are no hard and fast rules, but these defaults are good ones to start with.

Tip

We reference a simple bump allocator provided by miden-sdk-alloc above, but any simple allocator will do. The trade offs made by these small allocators are not generally suitable for long-running, or allocation-heavy applications, as they “leak” memory (generally because they make little to no attempt to recover freed allocations), however they are very useful for one-shot programs that do minimal allocation, which is going to be the typical case for Miden programs.

Next, edit src/lib.rs as shown below:

// Do not link against libstd (i.e. anything defined in `std::`)
#![no_std]

// However, we could still use some standard library types while
// remaining no-std compatible, if we uncommented the following lines:
//
// extern crate alloc;
// use alloc::{string::String, vec::Vec};

// If we wanted to use the types mentioned above, it would also be
// a good idea to use the allocator we pulled in as a dependency
// in Cargo.toml, like so:
//#[global_allocator]
//static ALLOC: miden_sdk_alloc::BumpAlloc = miden_sdk_alloc::BumpAlloc::new();

// Required for no-std crates
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    // Compiles to a trap instruction in WebAssembly
    core::arch::wasm32::unreachable()
}

// Marking the function no_mangle ensures that it is exported
// from the compiled binary as `fib`, otherwise it would have
// a mangled name that has no stable form.
//
// You can specify a different name from the library than the
// name in the source code using the `#[export_name = "foo"]`
// attribute, which will make the function callable as `foo`
// externally (in this example)
#[no_mangle]
pub fn fib(n: u32) -> u32 {
    let mut a = 0;
    let mut b = 1;
    for _ in 0..n {
        let c = a + b;
        a = b;
        b = c;
    }
    a
}

This exports our fib function from the library, making it callable from within a larger Miden program.

All that remains is to compile to WebAssembly:

cargo build --release --target=wasm32-wasip1

This places a wasm_fib.wasm file under the target/wasm32-wasip1/release/ directory, which we can then examine with wasm2wat to set the code we generated:

wasm2wat target/wasm32-wasip1/release/wasm_fib.wasm

Which dumps the following output (may differ slightly on your machine, depending on the specific compiler version):

(module $wasm_fib.wasm
  (type (;0;) (func (param i32) (result i32)))
  (func $fib (type 0) (param i32) (result i32)
    (local i32 i32 i32)
    i32.const 0
    local.set 1
    i32.const 1
    local.set 2
    loop (result i32)  ;; label = @1
      local.get 2
      local.set 3
      block  ;; label = @2
        local.get 0
        br_if 0 (;@2;)
        local.get 1
        return
      end
      local.get 0
      i32.const -1
      i32.add
      local.set 0
      local.get 1
      local.get 3
      i32.add
      local.set 2
      local.get 3
      local.set 1
      br 0 (;@1;)
    end)
  (memory (;0;) 16)
  (global $__stack_pointer (mut i32) (i32.const 1048576))
  (export "memory" (memory 0))
  (export "fib" (func $fib)))

Success!

Next steps

In Compiling WebAssembly to Miden Assembly, we walk through how to take the WebAssembly module we just compiled, and lower it to Miden Assembly using midenc!