1 //! Oracles.
2 //!
3 //! Oracles take a test case and determine whether we have a bug. For example,
4 //! one of the simplest oracles is to take a Wasm binary as our input test case,
5 //! validate and instantiate it, and (implicitly) check that no assertions
6 //! failed or segfaults happened. A more complicated oracle might compare the
7 //! result of executing a Wasm file with and without optimizations enabled, and
8 //! make sure that the two executions are observably identical.
9 //!
10 //! When an oracle finds a bug, it should report it to the fuzzing engine by
11 //! panicking.
12 
13 pub mod dummy;
14 
15 use arbitrary::Arbitrary;
16 use log::debug;
17 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
18 use std::sync::{Arc, Condvar, Mutex};
19 use std::time::{Duration, Instant};
20 use wasmtime::*;
21 use wasmtime_wast::WastContext;
22 
23 static CNT: AtomicUsize = AtomicUsize::new(0);
24 
25 fn log_wasm(wasm: &[u8]) {
26     if !log::log_enabled!(log::Level::Debug) {
27         return;
28     }
29 
30     let i = CNT.fetch_add(1, SeqCst);
31     let name = format!("testcase{}.wasm", i);
32     std::fs::write(&name, wasm).expect("failed to write wasm file");
33     log::debug!("wrote wasm file to `{}`", name);
34     let wat = format!("testcase{}.wat", i);
35     match wasmprinter::print_bytes(wasm) {
36         Ok(s) => std::fs::write(&wat, s).expect("failed to write wat file"),
37         // If wasmprinter failed remove a `*.wat` file, if any, to avoid
38         // confusing a preexisting one with this wasm which failed to get
39         // printed.
40         Err(_) => drop(std::fs::remove_file(&wat)),
41     }
42 }
43 
44 fn create_store(engine: &Engine) -> Store<StoreLimits> {
45     let mut store = Store::new(
46         &engine,
47         StoreLimits {
48             // Limits tables/memories within a store to at most 1gb for now to
49             // exercise some larger address but not overflow various limits.
50             remaining_memory: 1 << 30,
51             oom: false,
52         },
53     );
54     store.limiter(|s| s as &mut dyn ResourceLimiter);
55     return store;
56 }
57 
58 struct StoreLimits {
59     /// Remaining memory, in bytes, left to allocate
60     remaining_memory: usize,
61     /// Whether or not an allocation request has been denied
62     oom: bool,
63 }
64 
65 impl StoreLimits {
66     fn alloc(&mut self, amt: usize) -> bool {
67         match self.remaining_memory.checked_sub(amt) {
68             Some(mem) => {
69                 self.remaining_memory = mem;
70                 true
71             }
72             None => {
73                 self.oom = true;
74                 false
75             }
76         }
77     }
78 }
79 
80 impl ResourceLimiter for StoreLimits {
81     fn memory_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
82         // Units provided are in wasm pages, so adjust them to bytes to see if
83         // we are ok to allocate this much.
84         self.alloc((desired - current) as usize * 16 * 1024)
85     }
86 
87     fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
88         // Units provided are in table elements, and for now we allocate one
89         // pointer per table element, so use that size for an adjustment into
90         // bytes.
91         let delta = (desired - current) as usize * std::mem::size_of::<usize>();
92         self.alloc(delta)
93     }
94 }
95 
96 /// Methods of timing out execution of a WebAssembly module
97 #[derive(Debug)]
98 pub enum Timeout {
99     /// No timeout is used, it should be guaranteed via some other means that
100     /// the input does not infinite loop.
101     None,
102     /// A time-based timeout is used with a sleeping thread sending a signal
103     /// after the specified duration.
104     Time(Duration),
105     /// Fuel-based timeouts are used where the specified fuel is all that the
106     /// provided wasm module is allowed to consume.
107     Fuel(u64),
108 }
109 
110 /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
111 /// panic or segfault or anything else that can be detected "passively".
112 ///
113 /// Performs initial validation, and returns early if the Wasm is invalid.
114 ///
115 /// You can control which compiler is used via passing a `Strategy`.
116 pub fn instantiate(wasm: &[u8], known_valid: bool, strategy: Strategy) {
117     // Explicitly disable module linking for now since it's a breaking change to
118     // pre-module-linking modules due to imports
119     let mut cfg = crate::fuzz_default_config(strategy).unwrap();
120     cfg.wasm_module_linking(false);
121     instantiate_with_config(wasm, known_valid, cfg, Timeout::None);
122 }
123 
124 /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
125 /// panic or segfault or anything else that can be detected "passively".
126 ///
127 /// The engine will be configured using provided config.
128 ///
129 /// See also `instantiate` functions.
130 pub fn instantiate_with_config(
131     wasm: &[u8],
132     known_valid: bool,
133     mut config: Config,
134     timeout: Timeout,
135 ) {
136     crate::init_fuzzing();
137 
138     config.interruptable(match &timeout {
139         Timeout::Time(_) => true,
140         _ => false,
141     });
142     config.consume_fuel(match &timeout {
143         Timeout::Fuel(_) => true,
144         _ => false,
145     });
146     let engine = Engine::new(&config).unwrap();
147     let mut store = create_store(&engine);
148 
149     let mut timeout_state = SignalOnDrop::default();
150     match timeout {
151         Timeout::Fuel(fuel) => store.add_fuel(fuel).unwrap(),
152         // If a timeout is requested then we spawn a helper thread to wait for
153         // the requested time and then send us a signal to get interrupted. We
154         // also arrange for the thread's sleep to get interrupted if we return
155         // early (or the wasm returns within the time limit), which allows the
156         // thread to get torn down.
157         //
158         // This prevents us from creating a huge number of sleeping threads if
159         // this function is executed in a loop, like it does on nightly fuzzing
160         // infrastructure.
161         Timeout::Time(timeout) => {
162             let handle = store.interrupt_handle().unwrap();
163             timeout_state.spawn_timeout(timeout, move || handle.interrupt());
164         }
165         Timeout::None => {}
166     }
167 
168     log_wasm(wasm);
169     let module = match Module::new(&engine, wasm) {
170         Ok(module) => module,
171         Err(_) if !known_valid => return,
172         Err(e) => panic!("failed to compile module: {:?}", e),
173     };
174 
175     instantiate_with_dummy(&mut store, &module);
176 }
177 
178 fn instantiate_with_dummy(store: &mut Store<StoreLimits>, module: &Module) -> Option<Instance> {
179     // Creation of imports can fail due to resource limit constraints, and then
180     // instantiation can naturally fail for a number of reasons as well. Bundle
181     // the two steps together to match on the error below.
182     let instance =
183         dummy::dummy_linker(store, module).and_then(|l| l.instantiate(&mut *store, module));
184 
185     let e = match instance {
186         Ok(i) => return Some(i),
187         Err(e) => e,
188     };
189 
190     // If the instantiation hit OOM for some reason then that's ok, it's
191     // expected that fuzz-generated programs try to allocate lots of
192     // stuff.
193     if store.data().oom {
194         return None;
195     }
196 
197     // Allow traps which can happen normally with `unreachable` or a
198     // timeout or such
199     if e.downcast_ref::<Trap>().is_some() {
200         return None;
201     }
202 
203     let string = e.to_string();
204     // Also allow errors related to fuel consumption
205     if string.contains("all fuel consumed")
206         // Currently we instantiate with a `Linker` which can't instantiate
207         // every single module under the sun due to using name-based resolution
208         // rather than positional-based resolution
209         || string.contains("incompatible import type")
210     {
211         return None;
212     }
213 
214     // Everything else should be a bug in the fuzzer or a bug in wasmtime
215     panic!("failed to instantiate {:?}", e);
216 }
217 
218 /// Compile the Wasm buffer, and implicitly fail if we have an unexpected
219 /// panic or segfault or anything else that can be detected "passively".
220 ///
221 /// Performs initial validation, and returns early if the Wasm is invalid.
222 ///
223 /// You can control which compiler is used via passing a `Strategy`.
224 pub fn compile(wasm: &[u8], strategy: Strategy) {
225     crate::init_fuzzing();
226 
227     let engine = Engine::new(&crate::fuzz_default_config(strategy).unwrap()).unwrap();
228     log_wasm(wasm);
229     let _ = Module::new(&engine, wasm);
230 }
231 
232 /// Instantiate the given Wasm module with each `Config` and call all of its
233 /// exports. Modulo OOM, non-canonical NaNs, and usage of Wasm features that are
234 /// or aren't enabled for different configs, we should get the same results when
235 /// we call the exported functions for all of our different configs.
236 pub fn differential_execution(
237     module: &crate::generators::GeneratedModule,
238     configs: &[crate::generators::DifferentialConfig],
239 ) {
240     use std::collections::{HashMap, HashSet};
241 
242     crate::init_fuzzing();
243 
244     // We need at least two configs.
245     if configs.len() < 2
246         // And all the configs should be unique.
247         || configs.iter().collect::<HashSet<_>>().len() != configs.len()
248     {
249         return;
250     }
251 
252     let configs: Vec<_> = match configs.iter().map(|c| c.to_wasmtime_config()).collect() {
253         Ok(cs) => cs,
254         // If the config is trying to use something that was turned off at
255         // compile time, eg lightbeam, just continue to the next fuzz input.
256         Err(_) => return,
257     };
258 
259     let mut export_func_results: HashMap<String, Result<Box<[Val]>, Trap>> = Default::default();
260     let wasm = module.to_bytes();
261     log_wasm(&wasm);
262 
263     for mut config in configs {
264         // Disable module linking since it isn't enabled by default for
265         // `GeneratedModule` but is enabled by default for our fuzz config.
266         // Since module linking is currently a breaking change this is required
267         // to accept modules that would otherwise be broken by module linking.
268         config.wasm_module_linking(false);
269 
270         let engine = Engine::new(&config).unwrap();
271         let mut store = create_store(&engine);
272 
273         let module = Module::new(&engine, &wasm).unwrap();
274 
275         // TODO: we should implement tracing versions of these dummy imports
276         // that record a trace of the order that imported functions were called
277         // in and with what values. Like the results of exported functions,
278         // calls to imports should also yield the same values for each
279         // configuration, and we should assert that.
280         let instance = match instantiate_with_dummy(&mut store, &module) {
281             Some(instance) => instance,
282             None => continue,
283         };
284 
285         let exports = instance
286             .exports(&mut store)
287             .filter_map(|e| {
288                 let name = e.name().to_string();
289                 e.into_func().map(|f| (name, f))
290             })
291             .collect::<Vec<_>>();
292         for (name, f) in exports {
293             // Always call the hang limit initializer first, so that we don't
294             // infinite loop when calling another export.
295             init_hang_limit(&mut store, instance);
296 
297             let ty = f.ty(&store);
298             let params = dummy::dummy_values(ty.params());
299             let this_result = f
300                 .call(&mut store, &params)
301                 .map_err(|e| e.downcast::<Trap>().unwrap());
302 
303             let existing_result = export_func_results
304                 .entry(name.to_string())
305                 .or_insert_with(|| this_result.clone());
306             assert_same_export_func_result(&existing_result, &this_result, &name);
307         }
308     }
309 
310     fn init_hang_limit<T>(store: &mut Store<T>, instance: Instance) {
311         match instance.get_export(&mut *store, "hangLimitInitializer") {
312             None => return,
313             Some(Extern::Func(f)) => {
314                 f.call(store, &[])
315                     .expect("initializing the hang limit should not fail");
316             }
317             Some(_) => panic!("unexpected hangLimitInitializer export"),
318         }
319     }
320 
321     fn assert_same_export_func_result(
322         lhs: &Result<Box<[Val]>, Trap>,
323         rhs: &Result<Box<[Val]>, Trap>,
324         func_name: &str,
325     ) {
326         let fail = || {
327             panic!(
328                 "differential fuzzing failed: exported func {} returned two \
329                  different results: {:?} != {:?}",
330                 func_name, lhs, rhs
331             )
332         };
333 
334         match (lhs, rhs) {
335             (Err(_), Err(_)) => {}
336             (Ok(lhs), Ok(rhs)) => {
337                 if lhs.len() != rhs.len() {
338                     fail();
339                 }
340                 for (lhs, rhs) in lhs.iter().zip(rhs.iter()) {
341                     match (lhs, rhs) {
342                         (Val::I32(lhs), Val::I32(rhs)) if lhs == rhs => continue,
343                         (Val::I64(lhs), Val::I64(rhs)) if lhs == rhs => continue,
344                         (Val::V128(lhs), Val::V128(rhs)) if lhs == rhs => continue,
345                         (Val::F32(lhs), Val::F32(rhs)) if f32_equal(*lhs, *rhs) => continue,
346                         (Val::F64(lhs), Val::F64(rhs)) if f64_equal(*lhs, *rhs) => continue,
347                         (Val::ExternRef(_), Val::ExternRef(_))
348                         | (Val::FuncRef(_), Val::FuncRef(_)) => continue,
349                         _ => fail(),
350                     }
351                 }
352             }
353             _ => fail(),
354         }
355     }
356 }
357 
358 fn f32_equal(a: u32, b: u32) -> bool {
359     let a = f32::from_bits(a);
360     let b = f32::from_bits(b);
361     a == b || (a.is_nan() && b.is_nan())
362 }
363 
364 fn f64_equal(a: u64, b: u64) -> bool {
365     let a = f64::from_bits(a);
366     let b = f64::from_bits(b);
367     a == b || (a.is_nan() && b.is_nan())
368 }
369 
370 /// Invoke the given API calls.
371 pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
372     use crate::generators::api::ApiCall;
373     use std::collections::HashMap;
374 
375     crate::init_fuzzing();
376 
377     let mut config: Option<Config> = None;
378     let mut engine: Option<Engine> = None;
379     let mut store: Option<Store<StoreLimits>> = None;
380     let mut modules: HashMap<usize, Module> = Default::default();
381     let mut instances: HashMap<usize, Instance> = Default::default();
382 
383     for call in api.calls {
384         match call {
385             ApiCall::ConfigNew => {
386                 log::trace!("creating config");
387                 assert!(config.is_none());
388                 config = Some(crate::fuzz_default_config(wasmtime::Strategy::Cranelift).unwrap());
389             }
390 
391             ApiCall::ConfigDebugInfo(b) => {
392                 log::trace!("enabling debuginfo");
393                 config.as_mut().unwrap().debug_info(b);
394             }
395 
396             ApiCall::ConfigInterruptable(b) => {
397                 log::trace!("enabling interruption");
398                 config.as_mut().unwrap().interruptable(b);
399             }
400 
401             ApiCall::EngineNew => {
402                 log::trace!("creating engine");
403                 assert!(engine.is_none());
404                 engine = Some(Engine::new(config.as_ref().unwrap()).unwrap());
405             }
406 
407             ApiCall::StoreNew => {
408                 log::trace!("creating store");
409                 assert!(store.is_none());
410                 store = Some(create_store(engine.as_ref().unwrap()));
411             }
412 
413             ApiCall::ModuleNew { id, wasm } => {
414                 log::debug!("creating module: {}", id);
415                 let wasm = wasm.to_bytes();
416                 log_wasm(&wasm);
417                 let module = match Module::new(engine.as_ref().unwrap(), &wasm) {
418                     Ok(m) => m,
419                     Err(_) => continue,
420                 };
421                 let old = modules.insert(id, module);
422                 assert!(old.is_none());
423             }
424 
425             ApiCall::ModuleDrop { id } => {
426                 log::trace!("dropping module: {}", id);
427                 drop(modules.remove(&id));
428             }
429 
430             ApiCall::InstanceNew { id, module } => {
431                 log::trace!("instantiating module {} as {}", module, id);
432                 let module = match modules.get(&module) {
433                     Some(m) => m,
434                     None => continue,
435                 };
436 
437                 let store = store.as_mut().unwrap();
438                 if let Some(instance) = instantiate_with_dummy(store, module) {
439                     instances.insert(id, instance);
440                 }
441             }
442 
443             ApiCall::InstanceDrop { id } => {
444                 log::trace!("dropping instance {}", id);
445                 drop(instances.remove(&id));
446             }
447 
448             ApiCall::CallExportedFunc { instance, nth } => {
449                 log::trace!("calling instance export {} / {}", instance, nth);
450                 let instance = match instances.get(&instance) {
451                     Some(i) => i,
452                     None => {
453                         // Note that we aren't guaranteed to instantiate valid
454                         // modules, see comments in `InstanceNew` for details on
455                         // that. But the API call generator can't know if
456                         // instantiation failed, so we might not actually have
457                         // this instance. When that's the case, just skip the
458                         // API call and keep going.
459                         continue;
460                     }
461                 };
462                 let store = store.as_mut().unwrap();
463 
464                 let funcs = instance
465                     .exports(&mut *store)
466                     .filter_map(|e| match e.into_extern() {
467                         Extern::Func(f) => Some(f.clone()),
468                         _ => None,
469                     })
470                     .collect::<Vec<_>>();
471 
472                 if funcs.is_empty() {
473                     continue;
474                 }
475 
476                 let nth = nth % funcs.len();
477                 let f = &funcs[nth];
478                 let ty = f.ty(&store);
479                 let params = dummy::dummy_values(ty.params());
480                 let _ = f.call(store, &params);
481             }
482         }
483     }
484 }
485 
486 /// Executes the wast `test` spectest with the `config` specified.
487 ///
488 /// Ensures that spec tests pass regardless of the `Config`.
489 pub fn spectest(fuzz_config: crate::generators::Config, test: crate::generators::SpecTest) {
490     crate::init_fuzzing();
491     log::debug!("running {:?} with {:?}", test.file, fuzz_config);
492     let mut config = fuzz_config.to_wasmtime();
493     config.wasm_reference_types(false);
494     config.wasm_bulk_memory(false);
495     config.wasm_module_linking(false);
496     config.wasm_multi_memory(false);
497     let mut store = create_store(&Engine::new(&config).unwrap());
498     if fuzz_config.consume_fuel {
499         store.add_fuel(u64::max_value()).unwrap();
500     }
501     let mut wast_context = WastContext::new(store);
502     wast_context.register_spectest().unwrap();
503     wast_context
504         .run_buffer(test.file, test.contents.as_bytes())
505         .unwrap();
506 }
507 
508 /// Execute a series of `table.get` and `table.set` operations.
509 pub fn table_ops(
510     fuzz_config: crate::generators::Config,
511     ops: crate::generators::table_ops::TableOps,
512 ) {
513     let _ = env_logger::try_init();
514 
515     let num_dropped = Arc::new(AtomicUsize::new(0));
516 
517     {
518         let mut config = fuzz_config.to_wasmtime();
519         config.wasm_reference_types(true);
520         let engine = Engine::new(&config).unwrap();
521         let mut store = create_store(&engine);
522         if fuzz_config.consume_fuel {
523             store.add_fuel(u64::max_value()).unwrap();
524         }
525 
526         let wasm = ops.to_wasm_binary();
527         log_wasm(&wasm);
528         let module = match Module::new(&engine, &wasm) {
529             Ok(m) => m,
530             Err(_) => return,
531         };
532 
533         // To avoid timeouts, limit the number of explicit GCs we perform per
534         // test case.
535         const MAX_GCS: usize = 5;
536 
537         let num_gcs = AtomicUsize::new(0);
538         let gc = Func::wrap(&mut store, move |mut caller: Caller<'_, StoreLimits>| {
539             if num_gcs.fetch_add(1, SeqCst) < MAX_GCS {
540                 caller.gc();
541             }
542         });
543 
544         let instance = Instance::new(&mut store, &module, &[gc.into()]).unwrap();
545         let run = instance.get_func(&mut store, "run").unwrap();
546 
547         let args: Vec<_> = (0..ops.num_params())
548             .map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone())))))
549             .collect();
550         let _ = run.call(&mut store, &args);
551     }
552 
553     assert_eq!(num_dropped.load(SeqCst), ops.num_params() as usize);
554     return;
555 
556     struct CountDrops(Arc<AtomicUsize>);
557 
558     impl Drop for CountDrops {
559         fn drop(&mut self) {
560             self.0.fetch_add(1, SeqCst);
561         }
562     }
563 }
564 
565 /// Configuration options for wasm-smith such that generated modules always
566 /// conform to certain specifications.
567 #[derive(Default, Debug, Arbitrary, Clone)]
568 pub struct DifferentialWasmiModuleConfig;
569 
570 impl wasm_smith::Config for DifferentialWasmiModuleConfig {
571     fn allow_start_export(&self) -> bool {
572         false
573     }
574 
575     fn min_funcs(&self) -> usize {
576         1
577     }
578 
579     fn max_funcs(&self) -> usize {
580         1
581     }
582 
583     fn min_memories(&self) -> u32 {
584         1
585     }
586 
587     fn max_memories(&self) -> usize {
588         1
589     }
590 
591     fn max_imports(&self) -> usize {
592         0
593     }
594 
595     fn min_exports(&self) -> usize {
596         2
597     }
598 
599     fn max_memory_pages(&self, _is_64: bool) -> u64 {
600         1
601     }
602 
603     fn memory_max_size_required(&self) -> bool {
604         true
605     }
606 }
607 
608 /// Perform differential execution between Cranelift and wasmi, diffing the
609 /// resulting memory image when execution terminates. This relies on the
610 /// module-under-test to be instrumented to bound the execution time. Invoke
611 /// with a module generated by `wasm-smith` using the
612 /// `DiferentialWasmiModuleConfig` configuration type for best results.
613 ///
614 /// May return `None` if we early-out due to a rejected fuzz config; these
615 /// should be rare if modules are generated appropriately.
616 pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> {
617     crate::init_fuzzing();
618 
619     // Instantiate wasmi module and instance.
620     let wasmi_module = wasmi::Module::from_buffer(&wasm[..]).ok()?;
621     let wasmi_instance =
622         wasmi::ModuleInstance::new(&wasmi_module, &wasmi::ImportsBuilder::default()).ok()?;
623     let wasmi_instance = wasmi_instance.assert_no_start();
624 
625     // TODO(paritytech/wasmi#19): wasmi does not currently canonicalize NaNs. To avoid spurious
626     // fuzz failures, for now let's fuzz only integer Wasm programs.
627     if wasmi_module.deny_floating_point().is_err() {
628         return None;
629     }
630 
631     // Instantiate wasmtime module and instance.
632     let mut wasmtime_config = config.to_wasmtime();
633     wasmtime_config.cranelift_nan_canonicalization(true);
634     let wasmtime_engine = Engine::new(&wasmtime_config).unwrap();
635     let mut wasmtime_store = create_store(&wasmtime_engine);
636     if config.consume_fuel {
637         wasmtime_store.add_fuel(u64::max_value()).unwrap();
638     }
639     let wasmtime_module =
640         Module::new(&wasmtime_engine, &wasm).expect("Wasmtime can compile module");
641     let wasmtime_instance = Instance::new(&mut wasmtime_store, &wasmtime_module, &[])
642         .expect("Wasmtime can instantiate module");
643 
644     // Introspect wasmtime module to find name of an exported function and of an
645     // exported memory. Stop when we have one of each. (According to the config
646     // above, there should be at most one of each.)
647     let (func_name, memory_name) = {
648         let mut func_name = None;
649         let mut memory_name = None;
650         for e in wasmtime_module.exports() {
651             match e.ty() {
652                 wasmtime::ExternType::Func(..) => func_name = Some(e.name().to_string()),
653                 wasmtime::ExternType::Memory(..) => memory_name = Some(e.name().to_string()),
654                 _ => {}
655             }
656             if func_name.is_some() && memory_name.is_some() {
657                 break;
658             }
659         }
660         (func_name?, memory_name?)
661     };
662 
663     let wasmi_mem_export = wasmi_instance.export_by_name(&memory_name[..]).unwrap();
664     let wasmi_mem = wasmi_mem_export.as_memory().unwrap();
665     let wasmi_main_export = wasmi_instance.export_by_name(&func_name[..]).unwrap();
666     let wasmi_main = wasmi_main_export.as_func().unwrap();
667     let wasmi_val = wasmi::FuncInstance::invoke(&wasmi_main, &[], &mut wasmi::NopExternals);
668 
669     let wasmtime_mem = wasmtime_instance
670         .get_memory(&mut wasmtime_store, &memory_name[..])
671         .expect("memory export is present");
672     let wasmtime_main = wasmtime_instance
673         .get_func(&mut wasmtime_store, &func_name[..])
674         .expect("function export is present");
675     let wasmtime_vals = wasmtime_main.call(&mut wasmtime_store, &[]);
676     let wasmtime_val = wasmtime_vals.map(|v| v.iter().next().cloned());
677 
678     debug!(
679         "Successful execution: wasmi returned {:?}, wasmtime returned {:?}",
680         wasmi_val, wasmtime_val
681     );
682 
683     let show_wat = || {
684         if let Ok(s) = wasmprinter::print_bytes(&wasm[..]) {
685             eprintln!("wat:\n{}\n", s);
686         }
687     };
688 
689     match (&wasmi_val, &wasmtime_val) {
690         (&Ok(Some(wasmi::RuntimeValue::I32(a))), &Ok(Some(Val::I32(b)))) if a == b => {}
691         (&Ok(Some(wasmi::RuntimeValue::F32(a))), &Ok(Some(Val::F32(b))))
692             if f32_equal(a.to_bits(), b) => {}
693         (&Ok(Some(wasmi::RuntimeValue::I64(a))), &Ok(Some(Val::I64(b)))) if a == b => {}
694         (&Ok(Some(wasmi::RuntimeValue::F64(a))), &Ok(Some(Val::F64(b))))
695             if f64_equal(a.to_bits(), b) => {}
696         (&Ok(None), &Ok(None)) => {}
697         (&Err(_), &Err(_)) => {}
698         _ => {
699             show_wat();
700             panic!(
701                 "Values do not match: wasmi returned {:?}; wasmtime returned {:?}",
702                 wasmi_val, wasmtime_val
703             );
704         }
705     }
706 
707     if wasmi_mem.current_size().0 != wasmtime_mem.size(&wasmtime_store) as usize {
708         show_wat();
709         panic!("resulting memories are not the same size");
710     }
711 
712     // Wasmi memory may be stored non-contiguously; copy it out to a contiguous chunk.
713     let mut wasmi_buf: Vec<u8> = vec![0; wasmtime_mem.data_size(&wasmtime_store)];
714     wasmi_mem
715         .get_into(0, &mut wasmi_buf[..])
716         .expect("can access wasmi memory");
717 
718     let wasmtime_slice = wasmtime_mem.data(&wasmtime_store);
719 
720     if wasmi_buf.len() >= 64 {
721         debug!("-> First 64 bytes of wasmi heap: {:?}", &wasmi_buf[0..64]);
722         debug!(
723             "-> First 64 bytes of Wasmtime heap: {:?}",
724             &wasmtime_slice[0..64]
725         );
726     }
727 
728     if &wasmi_buf[..] != &wasmtime_slice[..] {
729         show_wat();
730         panic!("memory contents are not equal");
731     }
732 
733     Some(())
734 }
735 
736 #[derive(Default)]
737 struct SignalOnDrop {
738     state: Arc<(Mutex<bool>, Condvar)>,
739     thread: Option<std::thread::JoinHandle<()>>,
740 }
741 
742 impl SignalOnDrop {
743     fn spawn_timeout(&mut self, dur: Duration, closure: impl FnOnce() + Send + 'static) {
744         let state = self.state.clone();
745         let start = Instant::now();
746         self.thread = Some(std::thread::spawn(move || {
747             // Using our mutex/condvar we wait here for the first of `dur` to
748             // pass or the `SignalOnDrop` instance to get dropped.
749             let (lock, cvar) = &*state;
750             let mut signaled = lock.lock().unwrap();
751             while !*signaled {
752                 // Adjust our requested `dur` based on how much time has passed.
753                 let dur = match dur.checked_sub(start.elapsed()) {
754                     Some(dur) => dur,
755                     None => break,
756                 };
757                 let (lock, result) = cvar.wait_timeout(signaled, dur).unwrap();
758                 signaled = lock;
759                 // If we timed out for sure then there's no need to continue
760                 // since we'll just abort on the next `checked_sub` anyway.
761                 if result.timed_out() {
762                     break;
763                 }
764             }
765             drop(signaled);
766 
767             closure();
768         }));
769     }
770 }
771 
772 impl Drop for SignalOnDrop {
773     fn drop(&mut self) {
774         if let Some(thread) = self.thread.take() {
775             let (lock, cvar) = &*self.state;
776             // Signal our thread that we've been dropped and wake it up if it's
777             // blocked.
778             let mut g = lock.lock().unwrap();
779             *g = true;
780             cvar.notify_one();
781             drop(g);
782 
783             // ... and then wait for the thread to exit to ensure we clean up
784             // after ourselves.
785             thread.join().unwrap();
786         }
787     }
788 }
789