Pre-Compiling Wasm - Wasmtime

## Keyboard shortcuts

Press `←` or `→` to navigate between chapters

Press `S` or `/` to search in the book

Press `?` to show this help

Press `Esc` to hide this help

# Pre-Compiling and Cross-Compiling WebAssembly Programs

Wasmtime can compile a WebAssembly program to native code on one machine, and then run it on a different machine. This has a number of benefits:

- Faster start up: Compilation is removed from the critical path. When a new HTTP request comes into your function-as-a-service platform, for example, you do not have to wait for the associated Wasm program to compile before it can start handling the request. Similarly, when a new update for your embedded device's Wasm application logic comes in, you do not need to compile the update on the under-powered device before it can begin running new updated logic.

- Less Memory Usage: Pre-compiled Wasm programs can be lazily `mmap`ed from disk, only paging their code into memory as those code paths are executed. If none of the code on a page is ever executed, the OS will never make the page resident. This means that running pre-compiled Wasm programs lowers overall memory usage in the system.

- Smaller Code Size for Embedded: Wasmtime can be built such that it can only run Wasm programs that were pre-compiled elsewhere. These builds will not include the executable code for Wasm compilation. This is done by disabling the `cranelift` and `winch` cargo features at build time. These builds are useful for embedded devices, where programs must be small and fit within the device's constrained environment.

- Smaller Attack Surfaces: Similarly, building Wasmtime without a compiler, and with only support for running pre-compiled Wasm programs, can be useful for security-minded embeddings to reduce the potential attack surface exposed to untrusted and potentially hostile Wasm guests. Compilation, triggered by the control plane, can happen inside a Wasmtime build that can compile but not run Wasm programs. Execution, in the data plane, can happen inside a Wasmtime build that can run but not compile new Wasm programs. Exposing a minimal attack surface to untrusted code is good security practice.

Note that these benefits are applicable regardless which Wasm execution strategy you've configured: Cranelift, Winch, or Pulley.

## Pre-Compile the Wasm on One Machine

This must be done with a Wasmtime build that has a Wasm execution strategy enabled, e.g. was built with the `cranelift` or `winch` cargo features. It does not require the ability to run Wasm programs, so the `runtime` cargo feature can be disabled at build time.

    //! Pre-compiling a Wasm program.

    use wasmtime::{Config, Engine, Result, Strategy};

    fn main() -> Result<()> {
        // Configure Wasmtime for compiling Wasm programs to x86_64 with the
        // Cranelift compiler.
        let mut config = Config::new();
        config.strategy(Strategy::Cranelift);
        if let Err(error) = config.target("x86_64") {
            eprintln!(
                "this Wasmtime was not built with the x86_64 Cranelift backend \
                 enabled: {error:?}",
            );
            return Ok(());
        }

        // Create an `Engine` with that configuration.
        let engine = match Engine::new(&config) {
            Ok(engine) => engine,
            Err(error) => {
                println!("Wasmtime build is incompatible with config: {error:?}");
                return Ok(());
            }
        };

        // Pre-compile a Wasm program.
        //
        // Note that passing the Wasm text format, like we are doing here, is only
        // supported when the `wat` cargo feature is enabled.
        let precompiled = engine.precompile_module(
            r#"
                (module
                  (func (export "add") (param i32 i32) (result i32)
                    (i32.add (local.get 0) (local.get 1))
                  )
                )
            "#
            .as_bytes(),
        )?;

        // Write the pre-compiled program to a file.
        //
        // Note that the `.cwasm` extension is conventional for these files, and is
        // what the Wasmtime CLI will use by default, for example.
        std::fs::write("add.cwasm", &precompiled)?;

        // And we are done -- now a different Wasmtime embedding can load and run
        // the pre-compiled Wasm program from that `add.cwasm` file!
        Ok(())
    }

## Run the Pre-Compiled Wasm on Another Machine

This must be done with a Wasmtime build that can run pre-compiled Wasm programs, that is a Wasmtime built with the `runtime` cargo feature. It does not need to compile new Wasm programs, so the `cranelift` and `winch` cargo features can be disabled.

    //! Running pre-compiled Wasm programs.

    use wasmtime::{Config, Engine, Instance, Module, Result, Store};

    fn main() -> Result<()> {
        // Create the default configuration for this host platform. Note that this
        // configuration must match the configuration used to pre-compile the Wasm
        // program. We cannot run Wasm programs pre-compiled for configurations that
        // do not match our own, therefore if you enabled or disabled any particular
        // Wasm proposals or tweaked memory knobs when pre-compiling, you should
        // make identical adjustments to this config.
        let config = Config::default();

        // Create an `Engine` with that configuration.
        let engine = Engine::new(&config)?;

        // Create a runtime `Module` from a Wasm program that was pre-compiled and
        // written to the `add.cwasm` file by `wasmtime/examples/pre_compile.rs`.
        //
        // **Warning:** Wasmtime does not (and in general cannot) fully validate
        // pre-compiled modules for safety -- only create `Module`s and `Component`s
        // from pre-compiled bytes you control and trust! Passing unknown or
        // untrusted bytes will lead to arbitrary code execution vulnerabilities in
        // your system!
        let module = match unsafe { Module::deserialize_file(&engine, "add.cwasm") } {
            Ok(module) => module,
            Err(error) => {
                println!("failed to deserialize pre-compiled module: {error:?}");
                return Ok(());
            }
        };

        // Instantiate the module and invoke its `add` function!
        let mut store = Store::new(&engine, ());
        let instance = Instance::new(&mut store, &module, &[])?;
        let add = instance.get_typed_func::<(i32, i32), i32>(&mut store, "add")?;
        let sum = add.call(&mut store, (3, 8))?;
        println!("the sum of 3 and 8 is {sum}");

        Ok(())
    }

## See Also

- Tuning Wasmtime for Fast Wasm Instantiation
- Tuning Wasmtime for Fast Wasm Execution
- Building a Minimal Wasmtime Embedding
- `wasmtime::Engine::precompile_module` and `wasmtime::Engine::precompile_component`
- `wasmtime::Module::deserialize`, `wasmtime::Module::deserialize_file`, `wasmtime::component::Component::deserialize`, and `wasmtime::component::Component::deserialize_file`