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