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::{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     // existed 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 }
224