1 use anyhow::Result;
2 
3 #[cfg(not(target_os = "linux"))]
4 fn main() -> Result<()> {
5     eprintln!("This example only runs on Linux right now");
6     Ok(())
7 }
8 
9 #[cfg(target_os = "linux")]
10 fn 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     Ok(())
159 }
160