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