min_platform_host/
main.rs

1use anyhow::Result;
2
3#[cfg(not(target_os = "linux"))]
4fn main() -> Result<()> {
5    eprintln!("This example only runs on Linux right now");
6    Ok(())
7}
8
9#[cfg(target_os = "linux")]
10fn main() -> Result<()> {
11    use anyhow::{anyhow, Context};
12    use libloading::os::unix::{Library, Symbol, RTLD_GLOBAL, RTLD_NOW};
13    use object::{Object, ObjectSymbol};
14    use std::io::Write;
15    use wasmtime::{Config, Engine};
16
17    let mut args = std::env::args();
18    let _current_exe = args.next();
19    let triple = args
20        .next()
21        .ok_or_else(|| anyhow!("missing argument 1: triple"))?;
22    let embedding_so_path = args
23        .next()
24        .ok_or_else(|| anyhow!("missing argument 2: path to libembedding.so"))?;
25    let platform_so_path = args
26        .next()
27        .ok_or_else(|| anyhow!("missing argument 3: path to libwasmtime-platform.so"))?;
28
29    // Path to the artifact which is the build of the embedding.
30    //
31    // In this example this is a dynamic library intended to be run on Linux.
32    // Note that this is just an example of an artifact and custom build
33    // processes can produce different kinds of artifacts.
34    let binary = std::fs::read(&embedding_so_path)?;
35    let object = object::File::parse(&binary[..])?;
36
37    // Showcase verification that the dynamic library in question doesn't depend
38    // on much. Wasmtime build in a "minimal platform" mode is allowed to
39    // depend on some standard C symbols such as `memcpy` but any OS-related
40    // symbol must be prefixed by `wasmtime_*` and be documented in
41    // `crates/wasmtime/src/runtime/vm/sys/custom/capi.rs`.
42    //
43    // This is effectively a double-check of the above assertion and showing how
44    // running `libembedding.so` in this case requires only minimal
45    // dependencies.
46    for sym in object.symbols() {
47        if !sym.is_undefined() || sym.is_weak() {
48            continue;
49        }
50
51        match sym.name()? {
52            "memmove" | "memset" | "memcmp" | "memcpy" | "bcmp" | "__tls_get_addr" => {}
53            s if s.starts_with("wasmtime_") => {}
54            other => {
55                panic!("unexpected dependency on symbol `{other}`")
56            }
57        }
58    }
59
60    // Precompile modules for the embedding. Right now Wasmtime in no_std mode
61    // does not have support for Cranelift meaning that AOT mode must be used.
62    // Modules are compiled here and then given to the embedding via the `run`
63    // function below.
64    //
65    // Note that `Config::target` is used here to enable cross-compilation.
66    let mut config = Config::new();
67    config.target(&triple)?;
68
69    // If signals-based-traps are disabled then that additionally means that
70    // some configuration knobs need to be turned to match the expectations of
71    // the guest program being loaded.
72    if !cfg!(feature = "custom") {
73        config.memory_init_cow(false);
74        config.memory_reservation(0);
75        config.memory_guard_size(0);
76        config.memory_reservation_for_growth(0);
77        config.signals_based_traps(false);
78    }
79
80    let engine = Engine::new(&config)?;
81    let smoke = engine.precompile_module(b"(module)")?;
82    let simple_add = engine.precompile_module(
83        br#"
84            (module
85                (func (export "add") (param i32 i32) (result i32)
86                    (i32.add (local.get 0) (local.get 1)))
87            )
88        "#,
89    )?;
90    let simple_host_fn = engine.precompile_module(
91        br#"
92            (module
93                (import "host" "multiply" (func $multiply (param i32 i32) (result i32)))
94                (func (export "add_and_mul") (param i32 i32 i32) (result i32)
95                    (i32.add (call $multiply (local.get 0) (local.get 1)) (local.get 2)))
96            )
97        "#,
98    )?;
99
100    // Next is an example of running this embedding, which also serves as test
101    // that basic functionality actually works.
102    //
103    // Here the `wasmtime_*` symbols are implemented by
104    // `./embedding/wasmtime-platform.c` which is an example implementation
105    // against glibc on Linux. This library is compiled into
106    // `libwasmtime-platform.so` and is dynamically opened here to make it
107    // available for later symbol resolution. This is just an implementation
108    // detail of this exable to enably dynamically loading `libembedding.so`
109    // next.
110    //
111    // Next the `libembedding.so` library is opened and the `run` symbol is
112    // run. The dependencies of `libembedding.so` are either satisfied by our
113    // ambient libc (e.g. `memcpy` and friends) or `libwasmtime-platform.so`
114    // (e.g. `wasmtime_*` symbols).
115    //
116    // The embedding is then run to showcase an example and then an error, if
117    // any, is written to stderr.
118    unsafe {
119        let _platform_symbols = Library::open(Some(&platform_so_path), RTLD_NOW | RTLD_GLOBAL)
120            .with_context(|| {
121                format!(
122                    "failed to open {platform_so_path:?}; cwd = {:?}",
123                    std::env::current_dir()
124                )
125            })?;
126
127        let lib = Library::new(&embedding_so_path).context("failed to create new library")?;
128        let run: Symbol<
129            extern "C" fn(
130                *mut u8,
131                usize,
132                *const u8,
133                usize,
134                *const u8,
135                usize,
136                *const u8,
137                usize,
138            ) -> usize,
139        > = lib
140            .get(b"run")
141            .context("failed to find the `run` symbol in the library")?;
142
143        let mut error_buf = Vec::with_capacity(1024);
144        let len = run(
145            error_buf.as_mut_ptr(),
146            error_buf.capacity(),
147            smoke.as_ptr(),
148            smoke.len(),
149            simple_add.as_ptr(),
150            simple_add.len(),
151            simple_host_fn.as_ptr(),
152            simple_host_fn.len(),
153        );
154        error_buf.set_len(len);
155
156        std::io::stderr().write_all(&error_buf).unwrap();
157
158        #[cfg(feature = "wasi")]
159        {
160            let wasi_component_path = args
161                .next()
162                .ok_or_else(|| anyhow!("missing argument 4: path to wasi component"))?;
163            let wasi_component = std::fs::read(&wasi_component_path)?;
164            let wasi_component = engine.precompile_component(&wasi_component)?;
165
166            let run_wasi: Symbol<extern "C" fn(*mut u8, *mut usize, *const u8, usize) -> usize> =
167                lib.get(b"run_wasi")
168                    .context("failed to find the `run_wasi` symbol in the library")?;
169
170            const PRINT_CAPACITY: usize = 1024 * 1024;
171            let mut print_buf = Vec::with_capacity(PRINT_CAPACITY);
172            let mut print_len = PRINT_CAPACITY;
173            let status = run_wasi(
174                print_buf.as_mut_ptr(),
175                std::ptr::from_mut(&mut print_len),
176                wasi_component.as_ptr(),
177                wasi_component.len(),
178            );
179            print_buf.set_len(print_len);
180
181            if status > 0 {
182                std::io::stderr().write_all(&print_buf).unwrap();
183            } else {
184                std::io::stdout().write_all(&print_buf).unwrap();
185            }
186        }
187    }
188    Ok(())
189}