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 
25         let mut wasmi_config = wasmi::Config::default();
26         wasmi_config
27             .consume_fuel(false)
28             .floats(true)
29             .wasm_mutable_global(true)
30             .wasm_sign_extension(config.sign_extension_ops_enabled)
31             .wasm_saturating_float_to_int(config.saturating_float_to_int_enabled)
32             .wasm_multi_value(config.multi_value_enabled)
33             .wasm_bulk_memory(config.bulk_memory_enabled)
34             .wasm_reference_types(config.reference_types_enabled)
35             .wasm_tail_call(config.tail_call_enabled)
36             .wasm_multi_memory(config.max_memories > 1)
37             .wasm_extended_const(true);
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             _ => {
57                 log::trace!("unknown wasmi error: {:?}", err.kind());
58                 None
59             }
60         }
61     }
62 }
63 
64 impl DiffEngine for WasmiEngine {
65     fn name(&self) -> &'static str {
66         "wasmi"
67     }
68 
69     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
70         let module =
71             wasmi::Module::new(&self.engine, wasm).context("unable to validate Wasm module")?;
72         let mut store = wasmi::Store::new(&self.engine, ());
73         let instance = wasmi::Linker::<()>::new(&self.engine)
74             .instantiate(&mut store, &module)
75             .and_then(|i| i.start(&mut store))
76             .context("unable to instantiate module in wasmi")?;
77         Ok(Box::new(WasmiInstance { store, instance }))
78     }
79 
80     fn assert_error_match(&self, trap: &Trap, err: &Error) {
81         match self.trap_code(err) {
82             Some(code) => assert_eq!(wasmi_to_wasmtime_trap_code(code), *trap),
83             None => panic!("unexpected wasmi error {err:?}"),
84         }
85     }
86 
87     fn is_stack_overflow(&self, err: &Error) -> bool {
88         matches!(
89             self.trap_code(err),
90             Some(wasmi::core::TrapCode::StackOverflow)
91         )
92     }
93 }
94 
95 /// Converts `wasmi` trap code to `wasmtime` trap code.
96 fn wasmi_to_wasmtime_trap_code(trap: wasmi::core::TrapCode) -> Trap {
97     use wasmi::core::TrapCode;
98     match trap {
99         TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,
100         TrapCode::MemoryOutOfBounds => Trap::MemoryOutOfBounds,
101         TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,
102         TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,
103         TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,
104         TrapCode::IntegerOverflow => Trap::IntegerOverflow,
105         TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,
106         TrapCode::StackOverflow => Trap::StackOverflow,
107         TrapCode::BadSignature => Trap::BadSignature,
108         TrapCode::OutOfFuel => unimplemented!("built-in fuel metering is unused"),
109         TrapCode::GrowthOperationLimited => unimplemented!("resource limiter is unused"),
110     }
111 }
112 
113 /// A wrapper for `wasmi` Wasm instances.
114 struct WasmiInstance {
115     store: wasmi::Store<()>,
116     instance: wasmi::Instance,
117 }
118 
119 impl DiffInstance for WasmiInstance {
120     fn name(&self) -> &'static str {
121         "wasmi"
122     }
123 
124     fn evaluate(
125         &mut self,
126         function_name: &str,
127         arguments: &[DiffValue],
128         result_tys: &[DiffValueType],
129     ) -> Result<Option<Vec<DiffValue>>> {
130         let function = self
131             .instance
132             .get_export(&self.store, function_name)
133             .and_then(wasmi::Extern::into_func)
134             .unwrap();
135         let arguments: Vec<_> = arguments.iter().map(|x| x.into()).collect();
136         let mut results = vec![wasmi::Val::I32(0); result_tys.len()];
137         function
138             .call(&mut self.store, &arguments, &mut results)
139             .context("wasmi function trap")?;
140         Ok(Some(results.into_iter().map(Into::into).collect()))
141     }
142 
143     fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
144         Some(
145             self.instance
146                 .get_export(&self.store, name)
147                 .unwrap()
148                 .into_global()
149                 .unwrap()
150                 .get(&self.store)
151                 .into(),
152         )
153     }
154 
155     fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
156         assert!(!shared);
157         Some(
158             self.instance
159                 .get_export(&self.store, name)
160                 .unwrap()
161                 .into_memory()
162                 .unwrap()
163                 .data(&self.store)
164                 .to_vec(),
165         )
166     }
167 }
168 
169 impl From<&DiffValue> for wasmi::Val {
170     fn from(v: &DiffValue) -> Self {
171         use wasmi::Val as WasmiValue;
172         match *v {
173             DiffValue::I32(n) => WasmiValue::I32(n),
174             DiffValue::I64(n) => WasmiValue::I64(n),
175             DiffValue::F32(n) => WasmiValue::F32(wasmi::core::F32::from_bits(n)),
176             DiffValue::F64(n) => WasmiValue::F64(wasmi::core::F64::from_bits(n)),
177             DiffValue::V128(_) => unimplemented!(),
178             DiffValue::FuncRef { null } => {
179                 assert!(null);
180                 WasmiValue::FuncRef(wasmi::FuncRef::null())
181             }
182             DiffValue::ExternRef { null } => {
183                 assert!(null);
184                 WasmiValue::ExternRef(wasmi::ExternRef::null())
185             }
186             DiffValue::AnyRef { .. } => unimplemented!(),
187         }
188     }
189 }
190 
191 impl From<wasmi::Val> for DiffValue {
192     fn from(value: wasmi::Val) -> Self {
193         use wasmi::Val as WasmiValue;
194         match value {
195             WasmiValue::I32(n) => DiffValue::I32(n),
196             WasmiValue::I64(n) => DiffValue::I64(n),
197             WasmiValue::F32(n) => DiffValue::F32(n.to_bits()),
198             WasmiValue::F64(n) => DiffValue::F64(n.to_bits()),
199             WasmiValue::FuncRef(f) => DiffValue::FuncRef { null: f.is_null() },
200             WasmiValue::ExternRef(e) => DiffValue::ExternRef { null: e.is_null() },
201         }
202     }
203 }
204 
205 #[cfg(test)]
206 mod tests {
207     use super::*;
208 
209     #[test]
210     fn smoke() {
211         crate::oracles::engine::smoke_test_engine(|_, config| Ok(WasmiEngine::new(config)))
212     }
213 }
214