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