Skip to main content

min_platform_host/
main.rs

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