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