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