wasmtime_fuzzing/oracles/
diff_wasmtime.rs1use 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
12pub struct WasmtimeEngine {
14 config: generators::Config,
15}
16
17impl WasmtimeEngine {
18 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
74pub struct WasmtimeInstance {
79 store: Store<StoreLimits>,
80 instance: Instance,
81}
82
83impl WasmtimeInstance {
84 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 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 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 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 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}