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