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