1 //! Evaluate an exported Wasm function using Wasmtime.
2 
3 use crate::generators::{self, CompilerStrategy, DiffValue, DiffValueType, WasmtimeConfig};
4 use crate::oracles::dummy;
5 use crate::oracles::engine::DiffInstance;
6 use crate::oracles::{StoreLimits, compile_module, engine::DiffEngine};
7 use crate::single_module_fuzzer::KnownValid;
8 use anyhow::{Context, Error, Result};
9 use arbitrary::Unstructured;
10 use wasmtime::{Extern, FuncType, Instance, Module, Store, Trap, Val};
11 
12 /// A wrapper for using Wasmtime as a [`DiffEngine`].
13 pub struct WasmtimeEngine {
14     config: generators::Config,
15 }
16 
17 impl WasmtimeEngine {
18     /// Merely store the configuration; the engine is actually constructed
19     /// later. Ideally the store and engine could be built here but
20     /// `compile_module` takes a [`generators::Config`]; TODO re-factor this if
21     /// that ever changes.
22     pub fn new(
23         u: &mut Unstructured<'_>,
24         config: &mut generators::Config,
25         compiler_strategy: CompilerStrategy,
26     ) -> arbitrary::Result<Self> {
27         let mut new_config = u.arbitrary::<WasmtimeConfig>()?;
28         new_config.compiler_strategy = compiler_strategy;
29         new_config.update_module_config(&mut config.module_config, u)?;
30         new_config.make_compatible_with(&config.wasmtime);
31 
32         let config = generators::Config {
33             wasmtime: new_config,
34             module_config: config.module_config.clone(),
35         };
36         log::debug!("Created new Wasmtime differential engine with config: {config:?}");
37 
38         Ok(Self { config })
39     }
40 }
41 
42 impl DiffEngine for WasmtimeEngine {
43     fn name(&self) -> &'static str {
44         match self.config.wasmtime.compiler_strategy {
45             CompilerStrategy::CraneliftNative => "wasmtime",
46             CompilerStrategy::Winch => "winch",
47             CompilerStrategy::CraneliftPulley => "pulley",
48         }
49     }
50 
51     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
52         let store = self.config.to_store();
53         let module = compile_module(store.engine(), wasm, KnownValid::Yes, &self.config).unwrap();
54         let instance = WasmtimeInstance::new(store, module)?;
55         Ok(Box::new(instance))
56     }
57 
58     fn assert_error_match(&self, lhs: &Error, rhs: &Trap) {
59         let lhs = lhs
60             .downcast_ref::<Trap>()
61             .expect(&format!("not a trap: {lhs:?}"));
62 
63         assert_eq!(lhs, rhs, "{lhs}\nis not equal to\n{rhs}");
64     }
65 
66     fn is_non_deterministic_error(&self, err: &Error) -> bool {
67         match err.downcast_ref::<Trap>() {
68             Some(trap) => super::wasmtime_trap_is_non_deterministic(trap),
69             None => false,
70         }
71     }
72 }
73 
74 /// A wrapper around a Wasmtime instance.
75 ///
76 /// The Wasmtime engine constructs a new store and compiles an instance of a
77 /// Wasm module.
78 pub struct WasmtimeInstance {
79     store: Store<StoreLimits>,
80     instance: Instance,
81 }
82 
83 impl WasmtimeInstance {
84     /// Instantiate a new Wasmtime instance.
85     pub fn new(mut store: Store<StoreLimits>, module: Module) -> Result<Self> {
86         let instance = dummy::dummy_linker(&mut store, &module)
87             .and_then(|l| l.instantiate(&mut store, &module))
88             .context("unable to instantiate module in wasmtime")?;
89         Ok(Self { store, instance })
90     }
91 
92     /// Retrieve the names and types of all exported functions in the instance.
93     ///
94     /// This is useful for evaluating each exported function with different
95     /// values. The [`DiffInstance`] trait asks for the function name and we
96     /// need to know the function signature in order to pass in the right
97     /// arguments.
98     pub fn exported_functions(&mut self) -> Vec<(String, FuncType)> {
99         let exported_functions = self
100             .instance
101             .exports(&mut self.store)
102             .map(|e| (e.name().to_owned(), e.into_func()))
103             .filter_map(|(n, f)| f.map(|f| (n, f)))
104             .collect::<Vec<_>>();
105         exported_functions
106             .into_iter()
107             .map(|(n, f)| (n, f.ty(&self.store)))
108             .collect()
109     }
110 
111     /// Returns the list of globals and their types exported from this instance.
112     pub fn exported_globals(&mut self) -> Vec<(String, DiffValueType)> {
113         let globals = self
114             .instance
115             .exports(&mut self.store)
116             .filter_map(|e| {
117                 let name = e.name();
118                 e.into_global().map(|g| (name.to_string(), g))
119             })
120             .collect::<Vec<_>>();
121 
122         globals
123             .into_iter()
124             .filter_map(|(name, global)| {
125                 DiffValueType::try_from(global.ty(&self.store).content().clone())
126                     .map(|ty| (name, ty))
127                     .ok()
128             })
129             .collect()
130     }
131 
132     /// Returns the list of exported memories and whether or not it's a shared
133     /// memory.
134     pub fn exported_memories(&mut self) -> Vec<(String, bool)> {
135         self.instance
136             .exports(&mut self.store)
137             .filter_map(|e| {
138                 let name = e.name();
139                 match e.into_extern() {
140                     Extern::Memory(_) => Some((name.to_string(), false)),
141                     Extern::SharedMemory(_) => Some((name.to_string(), true)),
142                     _ => None,
143                 }
144             })
145             .collect()
146     }
147 
148     /// Returns whether or not this instance has hit its OOM condition yet.
149     pub fn is_oom(&self) -> bool {
150         self.store.data().is_oom()
151     }
152 }
153 
154 impl DiffInstance for WasmtimeInstance {
155     fn name(&self) -> &'static str {
156         "wasmtime"
157     }
158 
159     fn evaluate(
160         &mut self,
161         function_name: &str,
162         arguments: &[DiffValue],
163         _results: &[DiffValueType],
164     ) -> Result<Option<Vec<DiffValue>>> {
165         let arguments: Vec<_> = arguments.iter().map(Val::from).collect();
166 
167         let function = self
168             .instance
169             .get_func(&mut self.store, function_name)
170             .expect("unable to access exported function");
171         let ty = function.ty(&self.store);
172         let mut results = vec![Val::I32(0); ty.results().len()];
173         function.call(&mut self.store, &arguments, &mut results)?;
174 
175         let results = results.into_iter().map(Val::into).collect();
176         Ok(Some(results))
177     }
178 
179     fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
180         Some(
181             self.instance
182                 .get_global(&mut self.store, name)
183                 .unwrap()
184                 .get(&mut self.store)
185                 .into(),
186         )
187     }
188 
189     fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
190         Some(if shared {
191             let memory = self
192                 .instance
193                 .get_shared_memory(&mut self.store, name)
194                 .unwrap();
195             memory.data().iter().map(|i| unsafe { *i.get() }).collect()
196         } else {
197             self.instance
198                 .get_memory(&mut self.store, name)
199                 .unwrap()
200                 .data(&self.store)
201                 .to_vec()
202         })
203     }
204 }
205 
206 impl From<&DiffValue> for Val {
207     fn from(v: &DiffValue) -> Self {
208         match *v {
209             DiffValue::I32(n) => Val::I32(n),
210             DiffValue::I64(n) => Val::I64(n),
211             DiffValue::F32(n) => Val::F32(n),
212             DiffValue::F64(n) => Val::F64(n),
213             DiffValue::V128(n) => Val::V128(n.into()),
214             DiffValue::FuncRef { null } => {
215                 assert!(null);
216                 Val::FuncRef(None)
217             }
218             DiffValue::ExternRef { null } => {
219                 assert!(null);
220                 Val::ExternRef(None)
221             }
222             DiffValue::AnyRef { null } => {
223                 assert!(null);
224                 Val::AnyRef(None)
225             }
226             DiffValue::ExnRef { null } => {
227                 assert!(null);
228                 Val::ExnRef(None)
229             }
230             DiffValue::ContRef { null } => {
231                 assert!(null);
232                 Val::ExnRef(None)
233             }
234         }
235     }
236 }
237 
238 impl From<Val> for DiffValue {
239     fn from(val: Val) -> DiffValue {
240         match val {
241             Val::I32(n) => DiffValue::I32(n),
242             Val::I64(n) => DiffValue::I64(n),
243             Val::F32(n) => DiffValue::F32(n),
244             Val::F64(n) => DiffValue::F64(n),
245             Val::V128(n) => DiffValue::V128(n.into()),
246             Val::ExternRef(r) => DiffValue::ExternRef { null: r.is_none() },
247             Val::FuncRef(r) => DiffValue::FuncRef { null: r.is_none() },
248             Val::AnyRef(r) => DiffValue::AnyRef { null: r.is_none() },
249             Val::ExnRef(e) => DiffValue::ExnRef { null: e.is_none() },
250             Val::ContRef(c) => DiffValue::ContRef { null: c.is_none() },
251         }
252     }
253 }
254 
255 #[cfg(test)]
256 mod tests {
257     use super::*;
258 
259     #[test]
260     fn smoke_cranelift_native() {
261         crate::oracles::engine::smoke_test_engine(|u, config| {
262             WasmtimeEngine::new(u, config, CompilerStrategy::CraneliftNative)
263         })
264     }
265 
266     #[test]
267     fn smoke_cranelift_pulley() {
268         crate::oracles::engine::smoke_test_engine(|u, config| {
269             WasmtimeEngine::new(u, config, CompilerStrategy::CraneliftPulley)
270         })
271     }
272 
273     #[test]
274     fn smoke_winch() {
275         if !cfg!(target_arch = "x86_64") {
276             return;
277         }
278         crate::oracles::engine::smoke_test_engine(|u, config| {
279             WasmtimeEngine::new(u, config, CompilerStrategy::Winch)
280         })
281     }
282 }
283