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