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