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