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.relaxed_simd_enabled = false; 18 config.threads_enabled = false; 19 config.exceptions_enabled = false; 20 config.gc_enabled = false; 21 22 // FIXME: once the active fuzz bug for wasmi's simd differential fuzzing 23 // has been fixed and we've updated then this should be re-enabled. 24 config.simd_enabled = false; 25 // FIXME: requires updating to a wasmi that contains 26 // wasmi-labs/wasmi#1531. 27 config.memory64_enabled = false; 28 // FIXME: until https://github.com/wasmi-labs/wasmi/issues/1544 is fixed. 29 config.wide_arithmetic_enabled = false; 30 31 let mut wasmi_config = wasmi::Config::default(); 32 wasmi_config 33 .consume_fuel(false) 34 .floats(true) 35 .wasm_mutable_global(true) 36 .wasm_sign_extension(config.sign_extension_ops_enabled) 37 .wasm_saturating_float_to_int(config.saturating_float_to_int_enabled) 38 .wasm_multi_value(config.multi_value_enabled) 39 .wasm_bulk_memory(config.bulk_memory_enabled) 40 .wasm_reference_types(config.reference_types_enabled) 41 .wasm_tail_call(config.tail_call_enabled) 42 .wasm_multi_memory(config.max_memories > 1) 43 .wasm_extended_const(config.extended_const_enabled) 44 .wasm_custom_page_sizes(config.custom_page_sizes_enabled) 45 .wasm_memory64(config.memory64_enabled) 46 .wasm_simd(config.simd_enabled) 47 .wasm_wide_arithmetic(config.wide_arithmetic_enabled); 48 Self { 49 engine: wasmi::Engine::new(&wasmi_config), 50 } 51 } 52 53 fn trap_code(&self, err: &Error) -> Option<wasmi::core::TrapCode> { 54 let err = err.downcast_ref::<wasmi::Error>()?; 55 if let Some(code) = err.as_trap_code() { 56 return Some(code); 57 } 58 59 match err.kind() { 60 wasmi::errors::ErrorKind::Instantiation( 61 wasmi::errors::InstantiationError::ElementSegmentDoesNotFit { .. }, 62 ) => Some(wasmi::core::TrapCode::TableOutOfBounds), 63 wasmi::errors::ErrorKind::Memory(wasmi::errors::MemoryError::OutOfBoundsAccess) => { 64 Some(wasmi::core::TrapCode::MemoryOutOfBounds) 65 } 66 _ => { 67 log::trace!("unknown wasmi error: {:?}", err.kind()); 68 None 69 } 70 } 71 } 72 } 73 74 impl DiffEngine for WasmiEngine { 75 fn name(&self) -> &'static str { 76 "wasmi" 77 } 78 79 fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> { 80 let module = 81 wasmi::Module::new(&self.engine, wasm).context("unable to validate Wasm module")?; 82 let mut store = wasmi::Store::new(&self.engine, ()); 83 let instance = wasmi::Linker::<()>::new(&self.engine) 84 .instantiate(&mut store, &module) 85 .and_then(|i| i.start(&mut store)) 86 .context("unable to instantiate module in wasmi")?; 87 Ok(Box::new(WasmiInstance { store, instance })) 88 } 89 90 fn assert_error_match(&self, lhs: &Error, rhs: &Trap) { 91 match self.trap_code(lhs) { 92 Some(code) => assert_eq!(wasmi_to_wasmtime_trap_code(code), *rhs), 93 None => panic!("unexpected wasmi error {lhs:?}"), 94 } 95 } 96 97 fn is_non_deterministic_error(&self, err: &Error) -> bool { 98 matches!( 99 self.trap_code(err), 100 Some(wasmi::core::TrapCode::StackOverflow) 101 ) 102 } 103 } 104 105 /// Converts `wasmi` trap code to `wasmtime` trap code. 106 fn wasmi_to_wasmtime_trap_code(trap: wasmi::core::TrapCode) -> Trap { 107 use wasmi::core::TrapCode; 108 match trap { 109 TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached, 110 TrapCode::MemoryOutOfBounds => Trap::MemoryOutOfBounds, 111 TrapCode::TableOutOfBounds => Trap::TableOutOfBounds, 112 TrapCode::IndirectCallToNull => Trap::IndirectCallToNull, 113 TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero, 114 TrapCode::IntegerOverflow => Trap::IntegerOverflow, 115 TrapCode::BadConversionToInteger => Trap::BadConversionToInteger, 116 TrapCode::StackOverflow => Trap::StackOverflow, 117 TrapCode::BadSignature => Trap::BadSignature, 118 TrapCode::OutOfFuel => unimplemented!("built-in fuel metering is unused"), 119 TrapCode::GrowthOperationLimited => unimplemented!("resource limiter is unused"), 120 } 121 } 122 123 /// A wrapper for `wasmi` Wasm instances. 124 struct WasmiInstance { 125 store: wasmi::Store<()>, 126 instance: wasmi::Instance, 127 } 128 129 impl DiffInstance for WasmiInstance { 130 fn name(&self) -> &'static str { 131 "wasmi" 132 } 133 134 fn evaluate( 135 &mut self, 136 function_name: &str, 137 arguments: &[DiffValue], 138 result_tys: &[DiffValueType], 139 ) -> Result<Option<Vec<DiffValue>>> { 140 let function = self 141 .instance 142 .get_export(&self.store, function_name) 143 .and_then(wasmi::Extern::into_func) 144 .unwrap(); 145 let arguments: Vec<_> = arguments.iter().map(|x| x.into()).collect(); 146 let mut results = vec![wasmi::Val::I32(0); result_tys.len()]; 147 function 148 .call(&mut self.store, &arguments, &mut results) 149 .context("wasmi function trap")?; 150 Ok(Some(results.into_iter().map(Into::into).collect())) 151 } 152 153 fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> { 154 Some( 155 self.instance 156 .get_export(&self.store, name) 157 .unwrap() 158 .into_global() 159 .unwrap() 160 .get(&self.store) 161 .into(), 162 ) 163 } 164 165 fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> { 166 assert!(!shared); 167 Some( 168 self.instance 169 .get_export(&self.store, name) 170 .unwrap() 171 .into_memory() 172 .unwrap() 173 .data(&self.store) 174 .to_vec(), 175 ) 176 } 177 } 178 179 impl From<&DiffValue> for wasmi::Val { 180 fn from(v: &DiffValue) -> Self { 181 use wasmi::Val as WasmiValue; 182 match *v { 183 DiffValue::I32(n) => WasmiValue::I32(n), 184 DiffValue::I64(n) => WasmiValue::I64(n), 185 DiffValue::F32(n) => WasmiValue::F32(wasmi::core::F32::from_bits(n)), 186 DiffValue::F64(n) => WasmiValue::F64(wasmi::core::F64::from_bits(n)), 187 DiffValue::V128(n) => WasmiValue::V128(wasmi::core::V128::from(n)), 188 DiffValue::FuncRef { null } => { 189 assert!(null); 190 WasmiValue::FuncRef(wasmi::FuncRef::null()) 191 } 192 DiffValue::ExternRef { null } => { 193 assert!(null); 194 WasmiValue::ExternRef(wasmi::ExternRef::null()) 195 } 196 DiffValue::AnyRef { .. } => unimplemented!(), 197 DiffValue::ExnRef { .. } => unimplemented!(), 198 DiffValue::ContRef { .. } => unimplemented!(), 199 } 200 } 201 } 202 203 impl From<wasmi::Val> for DiffValue { 204 fn from(value: wasmi::Val) -> Self { 205 use wasmi::Val as WasmiValue; 206 match value { 207 WasmiValue::I32(n) => DiffValue::I32(n), 208 WasmiValue::I64(n) => DiffValue::I64(n), 209 WasmiValue::F32(n) => DiffValue::F32(n.to_bits()), 210 WasmiValue::F64(n) => DiffValue::F64(n.to_bits()), 211 WasmiValue::V128(n) => DiffValue::V128(n.as_u128()), 212 WasmiValue::FuncRef(f) => DiffValue::FuncRef { null: f.is_null() }, 213 WasmiValue::ExternRef(e) => DiffValue::ExternRef { null: e.is_null() }, 214 } 215 } 216 } 217 218 #[cfg(test)] 219 mod tests { 220 use super::*; 221 222 #[test] 223 fn smoke() { 224 crate::oracles::engine::smoke_test_engine(|_, config| Ok(WasmiEngine::new(config))) 225 } 226 } 227