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