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, TrapCode};
7 
8 /// A wrapper for `wasmi` as a [`DiffEngine`].
9 pub struct WasmiEngine;
10 
11 impl WasmiEngine {
12     pub(crate) fn new(config: &mut Config) -> Self {
13         let config = &mut config.module_config.config;
14         config.reference_types_enabled = false;
15         config.simd_enabled = false;
16         config.multi_value_enabled = false;
17         config.saturating_float_to_int_enabled = false;
18         config.sign_extension_enabled = false;
19         config.memory64_enabled = false;
20         config.bulk_memory_enabled = false;
21         config.threads_enabled = false;
22         config.max_memories = config.max_memories.min(1);
23         config.min_memories = config.min_memories.min(1);
24         config.max_tables = config.max_tables.min(1);
25         config.min_tables = config.min_tables.min(1);
26 
27         Self
28     }
29 }
30 
31 impl DiffEngine for WasmiEngine {
32     fn name(&self) -> &'static str {
33         "wasmi"
34     }
35 
36     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
37         let module = wasmi::Module::from_buffer(wasm).context("unable to validate Wasm module")?;
38         let instance = wasmi::ModuleInstance::new(&module, &wasmi::ImportsBuilder::default())
39             .context("unable to instantiate module in wasmi")?;
40         let instance = instance.run_start(&mut wasmi::NopExternals)?;
41         Ok(Box::new(WasmiInstance { module, instance }))
42     }
43 
44     fn assert_error_match(&self, trap: &Trap, err: &Error) {
45         // Acquire a `wasmi::Trap` from the wasmi error which we'll use to
46         // assert that it has the same kind of trap as the wasmtime-based trap.
47         let wasmi = match err.downcast_ref::<wasmi::Error>() {
48             Some(wasmi::Error::Trap(trap)) => trap,
49 
50             // Out-of-bounds data segments turn into this category which
51             // Wasmtime reports as a `MemoryOutOfBounds`.
52             Some(wasmi::Error::Memory(msg)) => {
53                 assert_eq!(
54                     trap.trap_code(),
55                     Some(TrapCode::MemoryOutOfBounds),
56                     "wasmtime error did not match wasmi: {msg}"
57                 );
58                 return;
59             }
60 
61             // Ignore this for now, looks like "elements segment does not fit"
62             // falls into this category and to avoid doing string matching this
63             // is just ignored.
64             Some(wasmi::Error::Instantiation(msg)) => {
65                 log::debug!("ignoring wasmi instantiation error: {msg}");
66                 return;
67             }
68 
69             Some(other) => panic!("unexpected wasmi error: {}", other),
70 
71             None => err
72                 .downcast_ref::<wasmi::Trap>()
73                 .expect(&format!("not a trap: {:?}", err)),
74         };
75         match wasmi.kind() {
76             wasmi::TrapKind::StackOverflow => {
77                 assert_eq!(trap.trap_code(), Some(TrapCode::StackOverflow))
78             }
79             wasmi::TrapKind::MemoryAccessOutOfBounds => {
80                 assert_eq!(trap.trap_code(), Some(TrapCode::MemoryOutOfBounds))
81             }
82             wasmi::TrapKind::Unreachable => {
83                 assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached))
84             }
85             wasmi::TrapKind::TableAccessOutOfBounds => {
86                 assert_eq!(trap.trap_code(), Some(TrapCode::TableOutOfBounds))
87             }
88             wasmi::TrapKind::ElemUninitialized => {
89                 assert_eq!(trap.trap_code(), Some(TrapCode::IndirectCallToNull))
90             }
91             wasmi::TrapKind::DivisionByZero => {
92                 assert_eq!(trap.trap_code(), Some(TrapCode::IntegerDivisionByZero))
93             }
94             wasmi::TrapKind::IntegerOverflow => {
95                 assert_eq!(trap.trap_code(), Some(TrapCode::IntegerOverflow))
96             }
97             wasmi::TrapKind::InvalidConversionToInt => {
98                 assert_eq!(trap.trap_code(), Some(TrapCode::BadConversionToInteger))
99             }
100             wasmi::TrapKind::UnexpectedSignature => {
101                 assert_eq!(trap.trap_code(), Some(TrapCode::BadSignature))
102             }
103             wasmi::TrapKind::Host(_) => unreachable!(),
104         }
105     }
106 
107     fn is_stack_overflow(&self, err: &Error) -> bool {
108         let trap = match err.downcast_ref::<wasmi::Error>() {
109             Some(wasmi::Error::Trap(trap)) => trap,
110             Some(_) => return false,
111             None => match err.downcast_ref::<wasmi::Trap>() {
112                 Some(trap) => trap,
113                 None => return false,
114             },
115         };
116         match trap.kind() {
117             wasmi::TrapKind::StackOverflow => true,
118             _ => false,
119         }
120     }
121 }
122 
123 /// A wrapper for `wasmi` Wasm instances.
124 struct WasmiInstance {
125     #[allow(dead_code)] // reason = "the module must live as long as its reference"
126     module: wasmi::Module,
127     instance: wasmi::ModuleRef,
128 }
129 
130 impl DiffInstance for WasmiInstance {
131     fn name(&self) -> &'static str {
132         "wasmi"
133     }
134 
135     fn evaluate(
136         &mut self,
137         function_name: &str,
138         arguments: &[DiffValue],
139         _results: &[DiffValueType],
140     ) -> Result<Option<Vec<DiffValue>>> {
141         let arguments: Vec<_> = arguments.iter().map(wasmi::RuntimeValue::from).collect();
142         let export = self
143             .instance
144             .export_by_name(function_name)
145             .context(format!(
146                 "unable to find function '{}' in wasmi instance",
147                 function_name
148             ))?;
149         let function = export.as_func().context("wasmi export is not a function")?;
150         let result = wasmi::FuncInstance::invoke(&function, &arguments, &mut wasmi::NopExternals)
151             .context("failed while invoking function in wasmi")?;
152         Ok(Some(if let Some(result) = result {
153             vec![result.into()]
154         } else {
155             vec![]
156         }))
157     }
158 
159     fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
160         match self.instance.export_by_name(name) {
161             Some(wasmi::ExternVal::Global(g)) => Some(g.get().into()),
162             _ => unreachable!(),
163         }
164     }
165 
166     fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
167         assert!(!shared);
168         match self.instance.export_by_name(name) {
169             Some(wasmi::ExternVal::Memory(m)) => {
170                 // `wasmi` memory may be stored non-contiguously; copy
171                 // it out to a contiguous chunk.
172                 let mut buffer: Vec<u8> = vec![0; m.current_size().0 * 65536];
173                 m.get_into(0, &mut buffer[..])
174                     .expect("can access wasmi memory");
175                 Some(buffer)
176             }
177             _ => unreachable!(),
178         }
179     }
180 }
181 
182 impl From<&DiffValue> for wasmi::RuntimeValue {
183     fn from(v: &DiffValue) -> Self {
184         use wasmi::RuntimeValue::*;
185         match *v {
186             DiffValue::I32(n) => I32(n),
187             DiffValue::I64(n) => I64(n),
188             DiffValue::F32(n) => F32(wasmi::nan_preserving_float::F32::from_bits(n)),
189             DiffValue::F64(n) => F64(wasmi::nan_preserving_float::F64::from_bits(n)),
190             DiffValue::V128(_) | DiffValue::FuncRef { .. } | DiffValue::ExternRef { .. } => {
191                 unimplemented!()
192             }
193         }
194     }
195 }
196 
197 impl Into<DiffValue> for wasmi::RuntimeValue {
198     fn into(self) -> DiffValue {
199         use wasmi::RuntimeValue::*;
200         match self {
201             I32(n) => DiffValue::I32(n),
202             I64(n) => DiffValue::I64(n),
203             F32(n) => DiffValue::F32(n.to_bits()),
204             F64(n) => DiffValue::F64(n.to_bits()),
205         }
206     }
207 }
208 
209 #[test]
210 fn smoke() {
211     crate::oracles::engine::smoke_test_engine(|_, config| Ok(WasmiEngine::new(config)))
212 }
213