1 //! Evaluate an exported Wasm function using the wasmi interpreter. 2 3 use crate::generators::{Config, DiffValue, DiffValueType}; 4 use crate::oracles::engine::{DiffEngine, DiffInstance}; 5 use anyhow::{Context, Error, Result}; 6 use wasmtime::Trap; 7 8 /// A wrapper for `wasmi` as a [`DiffEngine`]. 9 pub struct WasmiEngine { 10 engine: wasmi::Engine, 11 } 12 13 impl WasmiEngine { 14 pub(crate) fn new(config: &mut Config) -> Self { 15 let config = &mut config.module_config.config; 16 // Force generated Wasm modules to never have features that Wasmi doesn't support. 17 config.simd_enabled = false; 18 config.relaxed_simd_enabled = false; 19 config.memory64_enabled = false; 20 config.threads_enabled = false; 21 config.exceptions_enabled = false; 22 config.gc_enabled = false; 23 config.wide_arithmetic_enabled = false; 24 config.max_memories = config.max_memories.min(1); 25 config.min_memories = config.min_memories.min(1); 26 27 let mut wasmi_config = wasmi::Config::default(); 28 wasmi_config 29 .consume_fuel(false) 30 .floats(true) 31 .wasm_mutable_global(true) 32 .wasm_sign_extension(config.sign_extension_ops_enabled) 33 .wasm_saturating_float_to_int(config.saturating_float_to_int_enabled) 34 .wasm_multi_value(config.multi_value_enabled) 35 .wasm_bulk_memory(config.bulk_memory_enabled) 36 .wasm_reference_types(config.reference_types_enabled) 37 .wasm_tail_call(config.tail_call_enabled) 38 .wasm_extended_const(true); 39 Self { 40 engine: wasmi::Engine::new(&wasmi_config), 41 } 42 } 43 } 44 45 impl DiffEngine for WasmiEngine { 46 fn name(&self) -> &'static str { 47 "wasmi" 48 } 49 50 fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> { 51 let module = 52 wasmi::Module::new(&self.engine, wasm).context("unable to validate Wasm module")?; 53 let mut store = wasmi::Store::new(&self.engine, ()); 54 let instance = wasmi::Linker::<()>::new(&self.engine) 55 .instantiate(&mut store, &module) 56 .and_then(|i| i.start(&mut store)) 57 .context("unable to instantiate module in wasmi")?; 58 Ok(Box::new(WasmiInstance { store, instance })) 59 } 60 61 fn assert_error_match(&self, trap: &Trap, err: &Error) { 62 // Acquire a `wasmi::Trap` from the wasmi error which we'll use to 63 // assert that it has the same kind of trap as the wasmtime-based trap. 64 let wasmi = match err.downcast_ref::<wasmi::Error>() { 65 Some(wasmi::Error::Trap(trap)) => trap, 66 67 // Out-of-bounds data segments turn into this category which 68 // Wasmtime reports as a `MemoryOutOfBounds`. 69 Some(wasmi::Error::Memory(msg)) => { 70 assert_eq!( 71 *trap, 72 Trap::MemoryOutOfBounds, 73 "wasmtime error did not match wasmi: {msg}" 74 ); 75 return; 76 } 77 78 // Ignore this for now, looks like "elements segment does not fit" 79 // falls into this category and to avoid doing string matching this 80 // is just ignored. 81 Some(wasmi::Error::Instantiation(msg)) => { 82 log::debug!("ignoring wasmi instantiation error: {msg}"); 83 return; 84 } 85 86 Some(other) => panic!("unexpected wasmi error: {other}"), 87 88 None => err 89 .downcast_ref::<wasmi::core::Trap>() 90 .expect(&format!("not a trap: {err:?}")), 91 }; 92 assert!(wasmi.trap_code().is_some()); 93 assert_eq!( 94 wasmi_to_wasmtime_trap_code(wasmi.trap_code().unwrap()), 95 *trap 96 ); 97 } 98 99 fn is_stack_overflow(&self, err: &Error) -> bool { 100 let trap = match err.downcast_ref::<wasmi::Error>() { 101 Some(wasmi::Error::Trap(trap)) => trap, 102 Some(_) => return false, 103 None => match err.downcast_ref::<wasmi::core::Trap>() { 104 Some(trap) => trap, 105 None => return false, 106 }, 107 }; 108 matches!(trap.trap_code(), Some(wasmi::core::TrapCode::StackOverflow)) 109 } 110 } 111 112 /// Converts `wasmi` trap code to `wasmtime` trap code. 113 fn wasmi_to_wasmtime_trap_code(trap: wasmi::core::TrapCode) -> Trap { 114 use wasmi::core::TrapCode; 115 match trap { 116 TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached, 117 TrapCode::MemoryOutOfBounds => Trap::MemoryOutOfBounds, 118 TrapCode::TableOutOfBounds => Trap::TableOutOfBounds, 119 TrapCode::IndirectCallToNull => Trap::IndirectCallToNull, 120 TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero, 121 TrapCode::IntegerOverflow => Trap::IntegerOverflow, 122 TrapCode::BadConversionToInteger => Trap::BadConversionToInteger, 123 TrapCode::StackOverflow => Trap::StackOverflow, 124 TrapCode::BadSignature => Trap::BadSignature, 125 TrapCode::OutOfFuel => unimplemented!("built-in fuel metering is unused"), 126 TrapCode::GrowthOperationLimited => unimplemented!("resource limiter is unused"), 127 } 128 } 129 130 /// A wrapper for `wasmi` Wasm instances. 131 struct WasmiInstance { 132 store: wasmi::Store<()>, 133 instance: wasmi::Instance, 134 } 135 136 impl DiffInstance for WasmiInstance { 137 fn name(&self) -> &'static str { 138 "wasmi" 139 } 140 141 fn evaluate( 142 &mut self, 143 function_name: &str, 144 arguments: &[DiffValue], 145 result_tys: &[DiffValueType], 146 ) -> Result<Option<Vec<DiffValue>>> { 147 let function = self 148 .instance 149 .get_export(&self.store, function_name) 150 .and_then(wasmi::Extern::into_func) 151 .unwrap(); 152 let arguments: Vec<_> = arguments.iter().map(|x| x.into()).collect(); 153 let mut results = vec![wasmi::Value::I32(0); result_tys.len()]; 154 function 155 .call(&mut self.store, &arguments, &mut results) 156 .context("wasmi function trap")?; 157 Ok(Some(results.into_iter().map(Into::into).collect())) 158 } 159 160 fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> { 161 Some( 162 self.instance 163 .get_export(&self.store, name) 164 .unwrap() 165 .into_global() 166 .unwrap() 167 .get(&self.store) 168 .into(), 169 ) 170 } 171 172 fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> { 173 assert!(!shared); 174 Some( 175 self.instance 176 .get_export(&self.store, name) 177 .unwrap() 178 .into_memory() 179 .unwrap() 180 .data(&self.store) 181 .to_vec(), 182 ) 183 } 184 } 185 186 impl From<&DiffValue> for wasmi::Value { 187 fn from(v: &DiffValue) -> Self { 188 use wasmi::Value as WasmiValue; 189 match *v { 190 DiffValue::I32(n) => WasmiValue::I32(n), 191 DiffValue::I64(n) => WasmiValue::I64(n), 192 DiffValue::F32(n) => WasmiValue::F32(wasmi::core::F32::from_bits(n)), 193 DiffValue::F64(n) => WasmiValue::F64(wasmi::core::F64::from_bits(n)), 194 DiffValue::V128(_) => unimplemented!(), 195 DiffValue::FuncRef { null } => { 196 assert!(null); 197 WasmiValue::FuncRef(wasmi::FuncRef::null()) 198 } 199 DiffValue::ExternRef { null } => { 200 assert!(null); 201 WasmiValue::ExternRef(wasmi::ExternRef::null()) 202 } 203 DiffValue::AnyRef { .. } => unimplemented!(), 204 } 205 } 206 } 207 208 impl From<wasmi::Value> for DiffValue { 209 fn from(value: wasmi::Value) -> Self { 210 use wasmi::Value as WasmiValue; 211 match value { 212 WasmiValue::I32(n) => DiffValue::I32(n), 213 WasmiValue::I64(n) => DiffValue::I64(n), 214 WasmiValue::F32(n) => DiffValue::F32(n.to_bits()), 215 WasmiValue::F64(n) => DiffValue::F64(n.to_bits()), 216 WasmiValue::FuncRef(f) => DiffValue::FuncRef { null: f.is_null() }, 217 WasmiValue::ExternRef(e) => DiffValue::ExternRef { null: e.is_null() }, 218 } 219 } 220 } 221 222 #[cfg(test)] 223 mod tests { 224 use super::*; 225 226 #[test] 227 fn smoke() { 228 crate::oracles::engine::smoke_test_engine(|_, config| Ok(WasmiEngine::new(config))) 229 } 230 } 231