Port v8 fuzzer to the new framework (#4739)
* Port v8 fuzzer to the new framework This commit aims to improve the support for the new "meta" differential fuzzer added in #4515 by ensuring that all existing differential fuzzing is migrated to this new fuzzer. This PR includes features such as: * The V8 differential execution is migrated to the new framework. * `Config::set_differential_config` no longer force-disables wasm features, instead allowing them to be enabled as per the fuzz input. * `DiffInstance::{hash, hash}` was replaced with `DiffInstance::get_{memory,global}` to allow more fine-grained assertions. * Support for `FuncRef` and `ExternRef` have been added to `DiffValue` and `DiffValueType`. For now though generating an arbitrary `ExternRef` and `FuncRef` simply generates a null value. * Arbitrary `DiffValue::{F32,F64}` values are guaranteed to use canonical NaN representations to fix an issue with v8 where with the v8 engine we can't communicate non-canonical NaN values through JS. * `DiffEngine::evaluate` allows "successful failure" for cases where engines can't support that particular invocation, for example v8 can't support `v128` arguments or return values. * Smoke tests were added for each engine to ensure that a simple wasm module works at PR-time. * Statistics printed from the main fuzzer now include percentage-rates for chosen engines as well as percentage rates for styles-of-module. There's also a few small refactorings here and there but mostly just things I saw along the way. * Update the fuzzing README
This commit is contained in:
@@ -27,14 +27,23 @@ pub struct Config {
|
||||
|
||||
impl Config {
|
||||
/// Indicates that this configuration is being used for differential
|
||||
/// execution so only a single function should be generated since that's all
|
||||
/// that's going to be exercised.
|
||||
/// execution.
|
||||
///
|
||||
/// The purpose of this function is to update the configuration which was
|
||||
/// generated to be compatible with execution in multiple engines. The goal
|
||||
/// is to produce the exact same result in all engines so we need to paper
|
||||
/// over things like nan differences and memory/table behavior differences.
|
||||
pub fn set_differential_config(&mut self) {
|
||||
let config = &mut self.module_config.config;
|
||||
|
||||
// Disable the start function for now.
|
||||
//
|
||||
// TODO: should probably allow this after testing it works with the new
|
||||
// differential setup in all engines.
|
||||
config.allow_start_export = false;
|
||||
|
||||
// Make sure there's a type available for the function.
|
||||
// Make it more likely that there are types available to generate a
|
||||
// function with.
|
||||
config.min_types = 1;
|
||||
config.max_types = config.max_types.max(1);
|
||||
|
||||
@@ -70,15 +79,6 @@ impl Config {
|
||||
// can paper over NaN differences between engines.
|
||||
config.canonicalize_nans = true;
|
||||
|
||||
// When diffing against a non-wasmtime engine then disable wasm
|
||||
// features to get selectively re-enabled against each differential
|
||||
// engine.
|
||||
config.bulk_memory_enabled = false;
|
||||
config.reference_types_enabled = false;
|
||||
config.simd_enabled = false;
|
||||
config.memory64_enabled = false;
|
||||
config.threads_enabled = false;
|
||||
|
||||
// If using the pooling allocator, update the instance limits too
|
||||
if let InstanceAllocationStrategy::Pooling {
|
||||
instance_limits: limits,
|
||||
@@ -103,34 +103,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Force `self` to be a configuration compatible with `other`. This is
|
||||
/// useful for differential execution to avoid unhelpful fuzz crashes when
|
||||
/// one engine has a feature enabled and the other does not.
|
||||
pub fn make_compatible_with(&mut self, other: &Self) {
|
||||
// Use the same `wasm-smith` configuration as `other` because this is
|
||||
// used for determining what Wasm features are enabled in the engine
|
||||
// (see `to_wasmtime`).
|
||||
self.module_config = other.module_config.clone();
|
||||
|
||||
// Use the same allocation strategy between the two configs.
|
||||
//
|
||||
// Ideally this wouldn't be necessary, but, during differential
|
||||
// evaluation, if the `lhs` is using ondemand and the `rhs` is using the
|
||||
// pooling allocator (or vice versa), then the module may have been
|
||||
// generated in such a way that is incompatible with the other
|
||||
// allocation strategy.
|
||||
//
|
||||
// We can remove this in the future when it's possible to access the
|
||||
// fields of `wasm_smith::Module` to constrain the pooling allocator
|
||||
// based on what was actually generated.
|
||||
self.wasmtime.strategy = other.wasmtime.strategy.clone();
|
||||
if let InstanceAllocationStrategy::Pooling { .. } = &other.wasmtime.strategy {
|
||||
// Also use the same memory configuration when using the pooling
|
||||
// allocator.
|
||||
self.wasmtime.memory_config = other.wasmtime.memory_config.clone();
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses this configuration and the supplied source of data to generate
|
||||
/// a wasm module.
|
||||
///
|
||||
@@ -416,6 +388,31 @@ pub struct WasmtimeConfig {
|
||||
native_unwind_info: bool,
|
||||
}
|
||||
|
||||
impl WasmtimeConfig {
|
||||
/// Force `self` to be a configuration compatible with `other`. This is
|
||||
/// useful for differential execution to avoid unhelpful fuzz crashes when
|
||||
/// one engine has a feature enabled and the other does not.
|
||||
pub fn make_compatible_with(&mut self, other: &Self) {
|
||||
// Use the same allocation strategy between the two configs.
|
||||
//
|
||||
// Ideally this wouldn't be necessary, but, during differential
|
||||
// evaluation, if the `lhs` is using ondemand and the `rhs` is using the
|
||||
// pooling allocator (or vice versa), then the module may have been
|
||||
// generated in such a way that is incompatible with the other
|
||||
// allocation strategy.
|
||||
//
|
||||
// We can remove this in the future when it's possible to access the
|
||||
// fields of `wasm_smith::Module` to constrain the pooling allocator
|
||||
// based on what was actually generated.
|
||||
self.strategy = other.strategy.clone();
|
||||
if let InstanceAllocationStrategy::Pooling { .. } = &other.strategy {
|
||||
// Also use the same memory configuration when using the pooling
|
||||
// allocator.
|
||||
self.memory_config = other.memory_config.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
enum OptLevel {
|
||||
None,
|
||||
|
||||
@@ -13,6 +13,8 @@ pub enum DiffValue {
|
||||
F32(u32),
|
||||
F64(u64),
|
||||
V128(u128),
|
||||
FuncRef { null: bool },
|
||||
ExternRef { null: bool },
|
||||
}
|
||||
|
||||
impl DiffValue {
|
||||
@@ -23,6 +25,8 @@ impl DiffValue {
|
||||
DiffValue::F32(_) => DiffValueType::F32,
|
||||
DiffValue::F64(_) => DiffValueType::F64,
|
||||
DiffValue::V128(_) => DiffValueType::V128,
|
||||
DiffValue::FuncRef { .. } => DiffValueType::FuncRef,
|
||||
DiffValue::ExternRef { .. } => DiffValueType::ExternRef,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +55,18 @@ impl DiffValue {
|
||||
(1.0f32).to_bits(),
|
||||
f32::MAX.to_bits(),
|
||||
];
|
||||
DiffValue::F32(biased_arbitrary_value(u, known_f32_values)?)
|
||||
let bits = biased_arbitrary_value(u, known_f32_values)?;
|
||||
|
||||
// If the chosen bits are NAN then always use the canonical bit
|
||||
// pattern of nan to enable better compatibility with engines
|
||||
// where arbitrary nan patterns can't make their way into wasm
|
||||
// (e.g. v8 through JS can't do that).
|
||||
let bits = if f32::from_bits(bits).is_nan() {
|
||||
f32::NAN.to_bits()
|
||||
} else {
|
||||
bits
|
||||
};
|
||||
DiffValue::F32(bits)
|
||||
}
|
||||
F64 => {
|
||||
// TODO once `to_bits` is stable as a `const` function, move
|
||||
@@ -66,9 +81,23 @@ impl DiffValue {
|
||||
(1.0f64).to_bits(),
|
||||
f64::MAX.to_bits(),
|
||||
];
|
||||
DiffValue::F64(biased_arbitrary_value(u, known_f64_values)?)
|
||||
let bits = biased_arbitrary_value(u, known_f64_values)?;
|
||||
// See `f32` above for why canonical nan patterns are always
|
||||
// used.
|
||||
let bits = if f64::from_bits(bits).is_nan() {
|
||||
f64::NAN.to_bits()
|
||||
} else {
|
||||
bits
|
||||
};
|
||||
DiffValue::F64(bits)
|
||||
}
|
||||
V128 => DiffValue::V128(biased_arbitrary_value(u, KNOWN_U128_VALUES)?),
|
||||
|
||||
// TODO: this isn't working in most engines so just always pass a
|
||||
// null in which if an engine supports this is should at least
|
||||
// support doing that.
|
||||
FuncRef => DiffValue::FuncRef { null: true },
|
||||
ExternRef => DiffValue::ExternRef { null: true },
|
||||
};
|
||||
arbitrary::Result::Ok(val)
|
||||
}
|
||||
@@ -111,6 +140,8 @@ impl Hash for DiffValue {
|
||||
DiffValue::F32(n) => n.hash(state),
|
||||
DiffValue::F64(n) => n.hash(state),
|
||||
DiffValue::V128(n) => n.hash(state),
|
||||
DiffValue::ExternRef { null } => null.hash(state),
|
||||
DiffValue::FuncRef { null } => null.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,13 +175,15 @@ impl PartialEq for DiffValue {
|
||||
let r0 = f64::from_bits(*r0);
|
||||
l0 == r0 || (l0.is_nan() && r0.is_nan())
|
||||
}
|
||||
(Self::FuncRef { null: a }, Self::FuncRef { null: b }) => a == b,
|
||||
(Self::ExternRef { null: a }, Self::ExternRef { null: b }) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumerate the supported value types.
|
||||
#[derive(Clone, Debug, Arbitrary, Hash)]
|
||||
#[derive(Copy, Clone, Debug, Arbitrary, Hash)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum DiffValueType {
|
||||
I32,
|
||||
@@ -158,6 +191,8 @@ pub enum DiffValueType {
|
||||
F32,
|
||||
F64,
|
||||
V128,
|
||||
FuncRef,
|
||||
ExternRef,
|
||||
}
|
||||
|
||||
impl TryFrom<wasmtime::ValType> for DiffValueType {
|
||||
@@ -170,8 +205,8 @@ impl TryFrom<wasmtime::ValType> for DiffValueType {
|
||||
F32 => Ok(Self::F32),
|
||||
F64 => Ok(Self::F64),
|
||||
V128 => Ok(Self::V128),
|
||||
FuncRef => Err("unable to convert reference types"),
|
||||
ExternRef => Err("unable to convert reference types"),
|
||||
FuncRef => Ok(Self::FuncRef),
|
||||
ExternRef => Ok(Self::ExternRef),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user