wasmtime_fuzzing/oracles/
diff_wasmtime.rs

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