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            // Hooks used by the embedding's `CustomCodeMemory`, also provided by
53            // `wasmtime-platform.c`.
54            "embedding_publish_executable" | "embedding_unpublish_executable" => {}
55            other => {
56                panic!("unexpected dependency on symbol `{other}`")
57            }
58        }
59    }
60
61    // Precompile modules for the embedding. Right now Wasmtime in no_std mode
62    // does not have support for Cranelift meaning that AOT mode must be used.
63    // Modules are compiled here and then given to the embedding via the `run`
64    // function below.
65    //
66    // Note that `Config::target` is used here to enable cross-compilation.
67    let mut config = Config::new();
68    config.target(&triple)?;
69
70    // If signals-based-traps are disabled then that additionally means that
71    // some configuration knobs need to be turned to match the expectations of
72    // the guest program being loaded.
73    if !cfg!(feature = "custom") {
74        config.memory_init_cow(false);
75        config.memory_reservation(0);
76        config.memory_guard_size(0);
77        config.memory_reservation_for_growth(0);
78        config.signals_based_traps(false);
79    }
80
81    // For x86_64 targets be sure to enable relevant CPU features to avoid
82    // float-related libcalls which is required for the `x86_64-unknown-none`
83    // target.
84    //
85    // Note that the embedding will need to check that these features are
86    // actually available at runtime. CPU support for these features has
87    // existed since 2013 (Haswell) on Intel chips and 2012 (Piledriver) on
88    // AMD chips.
89    if cfg!(target_arch = "x86_64") {
90        unsafe {
91            config.cranelift_flag_enable("has_sse3");
92            config.cranelift_flag_enable("has_ssse3");
93            config.cranelift_flag_enable("has_sse41");
94            config.cranelift_flag_enable("has_sse42");
95            config.cranelift_flag_enable("has_fma");
96        }
97    }
98
99    let engine = Engine::new(&config)?;
100    let smoke = engine.precompile_module(b"(module)")?;
101    let simple_add = engine.precompile_module(
102        br#"
103            (module
104                (func (export "add") (param i32 i32) (result i32)
105                    (i32.add (local.get 0) (local.get 1)))
106            )
107        "#,
108    )?;
109    let simple_host_fn = engine.precompile_module(
110        br#"
111            (module
112                (import "host" "multiply" (func $multiply (param i32 i32) (result i32)))
113                (func (export "add_and_mul") (param i32 i32 i32) (result i32)
114                    (i32.add (call $multiply (local.get 0) (local.get 1)) (local.get 2)))
115            )
116        "#,
117    )?;
118    let simple_floats = engine.precompile_module(
119        br#"
120            (module
121                (func (export "frob") (param f32 f32) (result f32)
122                    (f32.ceil (local.get 0))
123                    (f32.floor (local.get 1))
124                    f32.add)
125            )
126        "#,
127    )?;
128
129    // Next is an example of running this embedding, which also serves as test
130    // that basic functionality actually works.
131    //
132    // Here the `wasmtime_*` symbols are implemented by
133    // `./embedding/wasmtime-platform.c` which is an example implementation
134    // against glibc on Linux. This library is compiled into
135    // `libwasmtime-platform.so` and is dynamically opened here to make it
136    // available for later symbol resolution. This is just an implementation
137    // detail of this exable to enably dynamically loading `libembedding.so`
138    // next.
139    //
140    // Next the `libembedding.so` library is opened and the `run` symbol is
141    // run. The dependencies of `libembedding.so` are either satisfied by our
142    // ambient libc (e.g. `memcpy` and friends) or `libwasmtime-platform.so`
143    // (e.g. `wasmtime_*` symbols).
144    //
145    // The embedding is then run to showcase an example and then an error, if
146    // any, is written to stderr.
147    unsafe {
148        let _platform_symbols = Library::open(Some(&platform_so_path), RTLD_NOW | RTLD_GLOBAL)
149            .with_context(|| {
150                format!(
151                    "failed to open {platform_so_path:?}; cwd = {:?}",
152                    std::env::current_dir()
153                )
154            })?;
155
156        let lib = Library::new(&embedding_so_path).context("failed to create new library")?;
157        let run: Symbol<
158            extern "C" fn(
159                *mut u8,
160                usize,
161                *const u8,
162                usize,
163                *const u8,
164                usize,
165                *const u8,
166                usize,
167                *const u8,
168                usize,
169            ) -> usize,
170        > = lib
171            .get(b"run")
172            .context("failed to find the `run` symbol in the library")?;
173
174        let mut error_buf = Vec::with_capacity(1024);
175        let len = run(
176            error_buf.as_mut_ptr(),
177            error_buf.capacity(),
178            smoke.as_ptr(),
179            smoke.len(),
180            simple_add.as_ptr(),
181            simple_add.len(),
182            simple_host_fn.as_ptr(),
183            simple_host_fn.len(),
184            simple_floats.as_ptr(),
185            simple_floats.len(),
186        );
187        error_buf.set_len(len);
188
189        if len > 0 {
190            bail!("{}", String::from_utf8_lossy(&error_buf));
191        }
192
193        // Without signals-based traps Wasmtime should not touch any
194        // process-global synchronized state when loading, running, and
195        // unloading modules (and trapping). The embedding is built without
196        // custom sync primitives, meaning that Wasmtime's internal locks panic
197        // on contention, so this tests that guarantee by running separate
198        // engines in separate threads concurrently.
199        if !cfg!(feature = "custom") {
200            let concurrent = engine.precompile_module(
201                br#"
202                    (module
203                        (memory 1 1)
204                        (func (export "add") (param i32 i32) (result i32)
205                            (i32.add (local.get 0) (local.get 1)))
206                        (func (export "unreachable")
207                            unreachable)
208                        (func (export "load") (param i32) (result i32)
209                            (i32.load (local.get 0)))
210                    )
211                "#,
212            )?;
213
214            let run_concurrent_module_load_test: Symbol<
215                extern "C" fn(*mut u8, usize, *const u8, usize, usize) -> usize,
216            > = lib.get(b"run_concurrent_module_load_test").context(
217                "failed to find the `run_concurrent_module_load_test` symbol in the library",
218            )?;
219
220            const THREADS: usize = 2;
221            const ITERATIONS: usize = 2000;
222            let barrier = std::sync::Barrier::new(THREADS);
223            std::thread::scope(|s| {
224                let threads = (0..THREADS)
225                    .map(|_| {
226                        s.spawn(|| {
227                            barrier.wait();
228                            let mut error_buf = Vec::with_capacity(1024);
229                            let len = run_concurrent_module_load_test(
230                                error_buf.as_mut_ptr(),
231                                error_buf.capacity(),
232                                concurrent.as_ptr(),
233                                concurrent.len(),
234                                ITERATIONS,
235                            );
236                            error_buf.set_len(len);
237                            if len > 0 {
238                                bail!("{}", String::from_utf8_lossy(&error_buf));
239                            }
240                            Ok(())
241                        })
242                    })
243                    .collect::<Vec<_>>();
244                threads.into_iter().try_for_each(|t| t.join().unwrap())
245            })?;
246        }
247
248        #[cfg(feature = "wasi")]
249        {
250            let wasi_component_path = args
251                .next()
252                .ok_or_else(|| format_err!("missing argument 4: path to wasi component"))?;
253            let wasi_component = std::fs::read(&wasi_component_path)?;
254            let wasi_component = engine.precompile_component(&wasi_component)?;
255
256            let run_wasi: Symbol<extern "C" fn(*mut u8, *mut usize, *const u8, usize) -> usize> =
257                lib.get(b"run_wasi")
258                    .context("failed to find the `run_wasi` symbol in the library")?;
259
260            const PRINT_CAPACITY: usize = 1024 * 1024;
261            let mut print_buf = Vec::with_capacity(PRINT_CAPACITY);
262            let mut print_len = PRINT_CAPACITY;
263            let status = run_wasi(
264                print_buf.as_mut_ptr(),
265                std::ptr::from_mut(&mut print_len),
266                wasi_component.as_ptr(),
267                wasi_component.len(),
268            );
269            print_buf.set_len(print_len);
270            let print_buf = String::from_utf8_lossy(&print_buf);
271
272            if status > 0 {
273                bail!("{print_buf}");
274            } else {
275                println!("{print_buf}");
276            }
277        }
278    }
279    Ok(())
280}