Skip to content

Commit

Permalink
Merge pull request bytecodealliance#2453 from cfallin/differential-fu…
Browse files Browse the repository at this point in the history
…zz-interp

Add differential fuzzing against wasmi (a Wasm interpreter).
  • Loading branch information
cfallin authored Dec 2, 2020
2 parents 9ac7d01 + bbdea06 commit b93381e
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 22 deletions.
80 changes: 78 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion crates/fuzzing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ wasmparser = "0.68.0"
wasmprinter = "0.2.15"
wasmtime = { path = "../wasmtime" }
wasmtime-wast = { path = "../wast" }
wasm-smith = "0.1.10"
wasm-smith = "0.1.12"
wasmi = "0.7.0"

[dev-dependencies]
wat = "1.0.28"

[features]
experimental_x64 = ["wasmtime/experimental_x64"]
202 changes: 184 additions & 18 deletions crates/fuzzing/src/oracles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
pub mod dummy;

use arbitrary::Arbitrary;
use dummy::dummy_imports;
use log::debug;
use std::cell::Cell;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
Expand Down Expand Up @@ -249,24 +251,8 @@ pub fn differential_execution(
(Val::I32(lhs), Val::I32(rhs)) if lhs == rhs => continue,
(Val::I64(lhs), Val::I64(rhs)) if lhs == rhs => continue,
(Val::V128(lhs), Val::V128(rhs)) if lhs == rhs => continue,
(Val::F32(lhs), Val::F32(rhs)) => {
let lhs = f32::from_bits(*lhs);
let rhs = f32::from_bits(*rhs);
if lhs == rhs || (lhs.is_nan() && rhs.is_nan()) {
continue;
} else {
fail()
}
}
(Val::F64(lhs), Val::F64(rhs)) => {
let lhs = f64::from_bits(*lhs);
let rhs = f64::from_bits(*rhs);
if lhs == rhs || (lhs.is_nan() && rhs.is_nan()) {
continue;
} else {
fail()
}
}
(Val::F32(lhs), Val::F32(rhs)) if f32_equal(*lhs, *rhs) => continue,
(Val::F64(lhs), Val::F64(rhs)) if f64_equal(*lhs, *rhs) => continue,
(Val::ExternRef(_), Val::ExternRef(_))
| (Val::FuncRef(_), Val::FuncRef(_)) => continue,
_ => fail(),
Expand All @@ -278,6 +264,18 @@ pub fn differential_execution(
}
}

fn f32_equal(a: u32, b: u32) -> bool {
let a = f32::from_bits(a);
let b = f32::from_bits(b);
a == b || (a.is_nan() && b.is_nan())
}

fn f64_equal(a: u64, b: u64) -> bool {
let a = f64::from_bits(a);
let b = f64::from_bits(b);
a == b || (a.is_nan() && b.is_nan())
}

/// Invoke the given API calls.
pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
use crate::generators::api::ApiCall;
Expand Down Expand Up @@ -479,3 +477,171 @@ pub fn table_ops(config: crate::generators::Config, ops: crate::generators::tabl
}
}
}

/// Configuration options for wasm-smith such that generated modules always
/// conform to certain specifications.
#[derive(Default, Debug, Arbitrary)]
pub struct DifferentialWasmiModuleConfig;

impl wasm_smith::Config for DifferentialWasmiModuleConfig {
fn allow_start_export(&self) -> bool {
false
}

fn min_funcs(&self) -> usize {
1
}

fn max_funcs(&self) -> usize {
1
}

fn min_memories(&self) -> u32 {
1
}

fn max_memories(&self) -> u32 {
1
}

fn max_imports(&self) -> usize {
0
}

fn min_exports(&self) -> usize {
2
}

fn max_memory_pages(&self) -> u32 {
1
}

fn memory_max_size_required(&self) -> bool {
true
}
}

/// Perform differential execution between Cranelift and wasmi, diffing the
/// resulting memory image when execution terminates. This relies on the
/// module-under-test to be instrumented to bound the execution time. Invoke
/// with a module generated by `wasm-smith` using the
/// `DiferentialWasmiModuleConfig` configuration type for best results.
///
/// May return `None` if we early-out due to a rejected fuzz config; these
/// should be rare if modules are generated appropriately.
pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> {
crate::init_fuzzing();

// Instantiate wasmi module and instance.
let wasmi_module = wasmi::Module::from_buffer(&wasm[..]).ok()?;
let wasmi_instance =
wasmi::ModuleInstance::new(&wasmi_module, &wasmi::ImportsBuilder::default()).ok()?;
let wasmi_instance = wasmi_instance.assert_no_start();

// TODO(paritytech/wasmi#19): wasmi does not currently canonicalize NaNs. To avoid spurious
// fuzz failures, for now let's fuzz only integer Wasm programs.
if wasmi_module.deny_floating_point().is_err() {
return None;
}

// Instantiate wasmtime module and instance.
let mut wasmtime_config = config.to_wasmtime();
wasmtime_config.cranelift_nan_canonicalization(true);
let wasmtime_engine = Engine::new(&wasmtime_config);
let wasmtime_store = Store::new(&wasmtime_engine);
let wasmtime_module =
Module::new(&wasmtime_engine, &wasm).expect("Wasmtime can compile module");
let wasmtime_instance = Instance::new(&wasmtime_store, &wasmtime_module, &[])
.expect("Wasmtime can instantiate module");

// Introspect wasmtime module to find name of an exported function and of an
// exported memory. Stop when we have one of each. (According to the config
// above, there should be at most one of each.)
let (func_name, memory_name) = {
let mut func_name = None;
let mut memory_name = None;
for e in wasmtime_module.exports() {
match e.ty() {
wasmtime::ExternType::Func(..) => func_name = Some(e.name().to_string()),
wasmtime::ExternType::Memory(..) => memory_name = Some(e.name().to_string()),
_ => {}
}
if func_name.is_some() && memory_name.is_some() {
break;
}
}
(func_name?, memory_name?)
};

let wasmi_mem_export = wasmi_instance.export_by_name(&memory_name[..]).unwrap();
let wasmi_mem = wasmi_mem_export.as_memory().unwrap();
let wasmi_main_export = wasmi_instance.export_by_name(&func_name[..]).unwrap();
let wasmi_main = wasmi_main_export.as_func().unwrap();
let wasmi_val = wasmi::FuncInstance::invoke(&wasmi_main, &[], &mut wasmi::NopExternals);

let wasmtime_mem = wasmtime_instance
.get_memory(&memory_name[..])
.expect("memory export is present");
let wasmtime_main = wasmtime_instance
.get_func(&func_name[..])
.expect("function export is present");
let wasmtime_vals = wasmtime_main.call(&[]);
let wasmtime_val = wasmtime_vals.map(|v| v.iter().next().cloned());

debug!(
"Successful execution: wasmi returned {:?}, wasmtime returned {:?}",
wasmi_val, wasmtime_val
);

let show_wat = || {
if let Ok(s) = wasmprinter::print_bytes(&wasm[..]) {
eprintln!("wat:\n{}\n", s);
}
};

match (&wasmi_val, &wasmtime_val) {
(&Ok(Some(wasmi::RuntimeValue::I32(a))), &Ok(Some(Val::I32(b)))) if a == b => {}
(&Ok(Some(wasmi::RuntimeValue::F32(a))), &Ok(Some(Val::F32(b))))
if f32_equal(a.to_bits(), b) => {}
(&Ok(Some(wasmi::RuntimeValue::I64(a))), &Ok(Some(Val::I64(b)))) if a == b => {}
(&Ok(Some(wasmi::RuntimeValue::F64(a))), &Ok(Some(Val::F64(b))))
if f64_equal(a.to_bits(), b) => {}
(&Ok(None), &Ok(None)) => {}
(&Err(_), &Err(_)) => {}
_ => {
show_wat();
panic!(
"Values do not match: wasmi returned {:?}; wasmtime returned {:?}",
wasmi_val, wasmtime_val
);
}
}

if wasmi_mem.current_size().0 != wasmtime_mem.size() as usize {
show_wat();
panic!("resulting memories are not the same size");
}

// Wasmi memory may be stored non-contiguously; copy it out to a contiguous chunk.
let mut wasmi_buf: Vec<u8> = vec![0; wasmtime_mem.data_size()];
wasmi_mem
.get_into(0, &mut wasmi_buf[..])
.expect("can access wasmi memory");

let wasmtime_slice = unsafe { wasmtime_mem.data_unchecked() };

if wasmi_buf.len() >= 64 {
debug!("-> First 64 bytes of wasmi heap: {:?}", &wasmi_buf[0..64]);
debug!(
"-> First 64 bytes of Wasmtime heap: {:?}",
&wasmtime_slice[0..64]
);
}

if &wasmi_buf[..] != &wasmtime_slice[..] {
show_wat();
panic!("memory contents are not equal");
}

Some(())
}
3 changes: 3 additions & 0 deletions crates/wasmtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ parallel-compilation = ["wasmtime-jit/parallel-compilation"]

# Enables support for automatic cache configuration to be enabled in `Config`.
cache = ["wasmtime-cache"]

# Enables support for new x64 backend.
experimental_x64 = ["wasmtime-jit/experimental_x64"]
Loading

0 comments on commit b93381e

Please sign in to comment.