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