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