1 //! Evaluate an exported Wasm function using Wasmtime. 2 3 use crate::generators::{self, CompilerStrategy, DiffValue, DiffValueType, WasmtimeConfig}; 4 use crate::oracles::dummy; 5 use crate::oracles::engine::DiffInstance; 6 use crate::oracles::{StoreLimits, compile_module, engine::DiffEngine}; 7 use crate::single_module_fuzzer::KnownValid; 8 use arbitrary::Unstructured; 9 use wasmtime::{ 10 Error, Extern, FuncType, Instance, Module, Result, Store, Trap, Val, error::Context as _, 11 }; 12 13 /// A wrapper for using Wasmtime as a [`DiffEngine`]. 14 pub struct WasmtimeEngine { 15 config: generators::Config, 16 } 17 18 impl WasmtimeEngine { 19 /// Merely store the configuration; the engine is actually constructed 20 /// later. Ideally the store and engine could be built here but 21 /// `compile_module` takes a [`generators::Config`]; TODO re-factor this if 22 /// that ever changes. 23 pub fn new( 24 u: &mut Unstructured<'_>, 25 config: &mut generators::Config, 26 compiler_strategy: CompilerStrategy, 27 ) -> arbitrary::Result<Self> { 28 let mut new_config = u.arbitrary::<WasmtimeConfig>()?; 29 new_config.compiler_strategy = compiler_strategy; 30 new_config.update_module_config(&mut config.module_config, u)?; 31 new_config.make_compatible_with(&config.wasmtime); 32 33 let config = generators::Config { 34 wasmtime: new_config, 35 module_config: config.module_config.clone(), 36 }; 37 log::debug!("Created new Wasmtime differential engine with config: {config:?}"); 38 39 Ok(Self { config }) 40 } 41 } 42 43 impl DiffEngine for WasmtimeEngine { 44 fn name(&self) -> &'static str { 45 match self.config.wasmtime.compiler_strategy { 46 CompilerStrategy::CraneliftNative => "wasmtime", 47 CompilerStrategy::Winch => "winch", 48 CompilerStrategy::CraneliftPulley => "pulley", 49 } 50 } 51 52 fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> { 53 let store = self.config.to_store(); 54 let module = compile_module(store.engine(), wasm, KnownValid::Yes, &self.config).unwrap(); 55 let instance = WasmtimeInstance::new(store, module)?; 56 Ok(Box::new(instance)) 57 } 58 59 fn assert_error_match(&self, lhs: &Error, rhs: &Trap) { 60 let lhs = lhs 61 .downcast_ref::<Trap>() 62 .expect(&format!("not a trap: {lhs:?}")); 63 64 assert_eq!(lhs, rhs, "{lhs}\nis not equal to\n{rhs}"); 65 } 66 67 fn is_non_deterministic_error(&self, err: &Error) -> bool { 68 match err.downcast_ref::<Trap>() { 69 Some(trap) => super::wasmtime_trap_is_non_deterministic(trap), 70 None => false, 71 } 72 } 73 } 74 75 /// A wrapper around a Wasmtime instance. 76 /// 77 /// The Wasmtime engine constructs a new store and compiles an instance of a 78 /// Wasm module. 79 pub struct WasmtimeInstance { 80 store: Store<StoreLimits>, 81 instance: Instance, 82 } 83 84 impl WasmtimeInstance { 85 /// Instantiate a new Wasmtime instance. 86 pub fn new(mut store: Store<StoreLimits>, module: Module) -> Result<Self> { 87 let instance = dummy::dummy_linker(&mut store, &module) 88 .and_then(|l| l.instantiate(&mut store, &module)) 89 .context("unable to instantiate module in wasmtime")?; 90 Ok(Self { store, instance }) 91 } 92 93 /// Retrieve the names and types of all exported functions in the instance. 94 /// 95 /// This is useful for evaluating each exported function with different 96 /// values. The [`DiffInstance`] trait asks for the function name and we 97 /// need to know the function signature in order to pass in the right 98 /// arguments. 99 pub fn exported_functions(&mut self) -> Vec<(String, FuncType)> { 100 let exported_functions = self 101 .instance 102 .exports(&mut self.store) 103 .map(|e| (e.name().to_owned(), e.into_func())) 104 .filter_map(|(n, f)| f.map(|f| (n, f))) 105 .collect::<Vec<_>>(); 106 exported_functions 107 .into_iter() 108 .map(|(n, f)| (n, f.ty(&self.store))) 109 .collect() 110 } 111 112 /// Returns the list of globals and their types exported from this instance. 113 pub fn exported_globals(&mut self) -> Vec<(String, DiffValueType)> { 114 let globals = self 115 .instance 116 .exports(&mut self.store) 117 .filter_map(|e| { 118 let name = e.name(); 119 e.into_global().map(|g| (name.to_string(), g)) 120 }) 121 .collect::<Vec<_>>(); 122 123 globals 124 .into_iter() 125 .filter_map(|(name, global)| { 126 DiffValueType::try_from(global.ty(&self.store).content().clone()) 127 .map(|ty| (name, ty)) 128 .ok() 129 }) 130 .collect() 131 } 132 133 /// Returns the list of exported memories and whether or not it's a shared 134 /// memory. 135 pub fn exported_memories(&mut self) -> Vec<(String, bool)> { 136 self.instance 137 .exports(&mut self.store) 138 .filter_map(|e| { 139 let name = e.name(); 140 match e.into_extern() { 141 Extern::Memory(_) => Some((name.to_string(), false)), 142 Extern::SharedMemory(_) => Some((name.to_string(), true)), 143 _ => None, 144 } 145 }) 146 .collect() 147 } 148 149 /// Returns whether or not this instance has hit its OOM condition yet. 150 pub fn is_oom(&self) -> bool { 151 self.store.data().is_oom() 152 } 153 } 154 155 impl DiffInstance for WasmtimeInstance { 156 fn name(&self) -> &'static str { 157 "wasmtime" 158 } 159 160 fn evaluate( 161 &mut self, 162 function_name: &str, 163 arguments: &[DiffValue], 164 _results: &[DiffValueType], 165 ) -> Result<Option<Vec<DiffValue>>> { 166 let arguments: Vec<_> = arguments.iter().map(Val::from).collect(); 167 168 let function = self 169 .instance 170 .get_func(&mut self.store, function_name) 171 .expect("unable to access exported function"); 172 let ty = function.ty(&self.store); 173 let mut results = vec![Val::I32(0); ty.results().len()]; 174 function.call(&mut self.store, &arguments, &mut results)?; 175 176 let results = results.into_iter().map(Val::into).collect(); 177 Ok(Some(results)) 178 } 179 180 fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> { 181 Some( 182 self.instance 183 .get_global(&mut self.store, name) 184 .unwrap() 185 .get(&mut self.store) 186 .into(), 187 ) 188 } 189 190 fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> { 191 Some(if shared { 192 let memory = self 193 .instance 194 .get_shared_memory(&mut self.store, name) 195 .unwrap(); 196 memory.data().iter().map(|i| unsafe { *i.get() }).collect() 197 } else { 198 self.instance 199 .get_memory(&mut self.store, name) 200 .unwrap() 201 .data(&self.store) 202 .to_vec() 203 }) 204 } 205 } 206 207 impl From<&DiffValue> for Val { 208 fn from(v: &DiffValue) -> Self { 209 match *v { 210 DiffValue::I32(n) => Val::I32(n), 211 DiffValue::I64(n) => Val::I64(n), 212 DiffValue::F32(n) => Val::F32(n), 213 DiffValue::F64(n) => Val::F64(n), 214 DiffValue::V128(n) => Val::V128(n.into()), 215 DiffValue::FuncRef { null } => { 216 assert!(null); 217 Val::FuncRef(None) 218 } 219 DiffValue::ExternRef { null } => { 220 assert!(null); 221 Val::ExternRef(None) 222 } 223 DiffValue::AnyRef { null } => { 224 assert!(null); 225 Val::AnyRef(None) 226 } 227 DiffValue::ExnRef { null } => { 228 assert!(null); 229 Val::ExnRef(None) 230 } 231 DiffValue::ContRef { null } => { 232 assert!(null); 233 Val::ExnRef(None) 234 } 235 } 236 } 237 } 238 239 impl From<Val> for DiffValue { 240 fn from(val: Val) -> DiffValue { 241 match val { 242 Val::I32(n) => DiffValue::I32(n), 243 Val::I64(n) => DiffValue::I64(n), 244 Val::F32(n) => DiffValue::F32(n), 245 Val::F64(n) => DiffValue::F64(n), 246 Val::V128(n) => DiffValue::V128(n.into()), 247 Val::ExternRef(r) => DiffValue::ExternRef { null: r.is_none() }, 248 Val::FuncRef(r) => DiffValue::FuncRef { null: r.is_none() }, 249 Val::AnyRef(r) => DiffValue::AnyRef { null: r.is_none() }, 250 Val::ExnRef(e) => DiffValue::ExnRef { null: e.is_none() }, 251 Val::ContRef(c) => DiffValue::ContRef { null: c.is_none() }, 252 } 253 } 254 } 255 256 #[cfg(test)] 257 mod tests { 258 use super::*; 259 260 #[test] 261 fn smoke_cranelift_native() { 262 crate::oracles::engine::smoke_test_engine(|u, config| { 263 WasmtimeEngine::new(u, config, CompilerStrategy::CraneliftNative) 264 }) 265 } 266 267 #[test] 268 fn smoke_cranelift_pulley() { 269 crate::oracles::engine::smoke_test_engine(|u, config| { 270 WasmtimeEngine::new(u, config, CompilerStrategy::CraneliftPulley) 271 }) 272 } 273 274 #[test] 275 fn smoke_winch() { 276 if !cfg!(target_arch = "x86_64") { 277 return; 278 } 279 crate::oracles::engine::smoke_test_engine(|u, config| { 280 WasmtimeEngine::new(u, config, CompilerStrategy::Winch) 281 }) 282 } 283 } 284