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