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