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     /// Returns whether or not this instance has hit its OOM condition yet.
135     pub fn is_oom(&self) -> bool {
136         self.store.data().is_oom()
137     }
138 }
139 
140 impl DiffInstance for WasmtimeInstance {
141     fn name(&self) -> &'static str {
142         "wasmtime"
143     }
144 
145     fn evaluate(
146         &mut self,
147         function_name: &str,
148         arguments: &[DiffValue],
149         _results: &[DiffValueType],
150     ) -> Result<Option<Vec<DiffValue>>> {
151         let arguments: Vec<_> = arguments.iter().map(Val::from).collect();
152 
153         let function = self
154             .instance
155             .get_func(&mut self.store, function_name)
156             .expect("unable to access exported function");
157         let ty = function.ty(&self.store);
158         let mut results = vec![Val::I32(0); ty.results().len()];
159         function.call(&mut self.store, &arguments, &mut results)?;
160 
161         let results = results.into_iter().map(Val::into).collect();
162         Ok(Some(results))
163     }
164 
165     fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
166         Some(
167             self.instance
168                 .get_global(&mut self.store, name)
169                 .unwrap()
170                 .get(&mut self.store)
171                 .into(),
172         )
173     }
174 
175     fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {
176         Some(if shared {
177             let memory = self
178                 .instance
179                 .get_shared_memory(&mut self.store, name)
180                 .unwrap();
181             memory.data().iter().map(|i| unsafe { *i.get() }).collect()
182         } else {
183             self.instance
184                 .get_memory(&mut self.store, name)
185                 .unwrap()
186                 .data(&self.store)
187                 .to_vec()
188         })
189     }
190 }
191 
192 impl From<&DiffValue> for Val {
193     fn from(v: &DiffValue) -> Self {
194         match *v {
195             DiffValue::I32(n) => Val::I32(n),
196             DiffValue::I64(n) => Val::I64(n),
197             DiffValue::F32(n) => Val::F32(n),
198             DiffValue::F64(n) => Val::F64(n),
199             DiffValue::V128(n) => Val::V128(n),
200             DiffValue::FuncRef { null } => {
201                 assert!(null);
202                 Val::FuncRef(None)
203             }
204             DiffValue::ExternRef { null } => {
205                 assert!(null);
206                 Val::ExternRef(None)
207             }
208         }
209     }
210 }
211 
212 impl Into<DiffValue> for Val {
213     fn into(self) -> DiffValue {
214         match self {
215             Val::I32(n) => DiffValue::I32(n),
216             Val::I64(n) => DiffValue::I64(n),
217             Val::F32(n) => DiffValue::F32(n),
218             Val::F64(n) => DiffValue::F64(n),
219             Val::V128(n) => DiffValue::V128(n),
220             Val::FuncRef(f) => DiffValue::FuncRef { null: f.is_none() },
221             Val::ExternRef(e) => DiffValue::ExternRef { null: e.is_none() },
222         }
223     }
224 }
225 
226 #[test]
227 fn smoke() {
228     crate::oracles::engine::smoke_test_engine(|u, config| WasmtimeEngine::new(u, config))
229 }
230