Path: blob/main/crates/fuzzing/src/generators/value.rs
1693 views
//! Generate Wasm values, primarily for differential execution.12use arbitrary::{Arbitrary, Unstructured};3use std::hash::Hash;4use wasmtime::HeapType;56/// A value passed to and from evaluation. Note that reference types are not7/// (yet) supported.8#[derive(Clone, Debug)]9#[expect(missing_docs, reason = "self-describing fields")]10pub enum DiffValue {11I32(i32),12I64(i64),13F32(u32),14F64(u64),15V128(u128),16FuncRef { null: bool },17ExternRef { null: bool },18AnyRef { null: bool },19ExnRef { null: bool },20ContRef { null: bool },21}2223impl DiffValue {24fn ty(&self) -> DiffValueType {25match self {26DiffValue::I32(_) => DiffValueType::I32,27DiffValue::I64(_) => DiffValueType::I64,28DiffValue::F32(_) => DiffValueType::F32,29DiffValue::F64(_) => DiffValueType::F64,30DiffValue::V128(_) => DiffValueType::V128,31DiffValue::FuncRef { .. } => DiffValueType::FuncRef,32DiffValue::ExternRef { .. } => DiffValueType::ExternRef,33DiffValue::AnyRef { .. } => DiffValueType::AnyRef,34DiffValue::ExnRef { .. } => DiffValueType::ExnRef,35DiffValue::ContRef { .. } => DiffValueType::ContRef,36}37}3839/// Generate a [`DiffValue`] of the given `ty` type.40///41/// This function will bias the returned value 50% of the time towards one42/// of a set of known values (e.g., NaN, -1, 0, infinity, etc.).43pub fn arbitrary_of_type(44u: &mut Unstructured<'_>,45ty: DiffValueType,46) -> arbitrary::Result<Self> {47use DiffValueType::*;48let val = match ty {49I32 => DiffValue::I32(biased_arbitrary_value(u, KNOWN_I32_VALUES)?),50I64 => DiffValue::I64(biased_arbitrary_value(u, KNOWN_I64_VALUES)?),51F32 => {52// TODO once `to_bits` is stable as a `const` function, move53// this to a `const` definition.54let known_f32_values = &[55f32::NAN.to_bits(),56f32::INFINITY.to_bits(),57f32::NEG_INFINITY.to_bits(),58f32::MIN.to_bits(),59(-1.0f32).to_bits(),60(0.0f32).to_bits(),61(1.0f32).to_bits(),62f32::MAX.to_bits(),63];64let bits = biased_arbitrary_value(u, known_f32_values)?;6566// If the chosen bits are NaN then always use the canonical bit67// pattern of NaN to enable better compatibility with engines68// where arbitrary NaN patterns can't make their way into wasm69// (e.g. v8 through JS can't do that).70let bits = if f32::from_bits(bits).is_nan() {71f32::NAN.to_bits()72} else {73bits74};75DiffValue::F32(bits)76}77F64 => {78// TODO once `to_bits` is stable as a `const` function, move79// this to a `const` definition.80let known_f64_values = &[81f64::NAN.to_bits(),82f64::INFINITY.to_bits(),83f64::NEG_INFINITY.to_bits(),84f64::MIN.to_bits(),85(-1.0f64).to_bits(),86(0.0f64).to_bits(),87(1.0f64).to_bits(),88f64::MAX.to_bits(),89];90let bits = biased_arbitrary_value(u, known_f64_values)?;91// See `f32` above for why canonical NaN patterns are always92// used.93let bits = if f64::from_bits(bits).is_nan() {94f64::NAN.to_bits()95} else {96bits97};98DiffValue::F64(bits)99}100V128 => {101// Generate known values for each sub-type of V128.102let ty: DiffSimdTy = u.arbitrary()?;103match ty {104DiffSimdTy::I8x16 => {105let mut i8 = || biased_arbitrary_value(u, KNOWN_I8_VALUES).map(|b| b as u8);106let vector = u128::from_le_bytes([107i8()?,108i8()?,109i8()?,110i8()?,111i8()?,112i8()?,113i8()?,114i8()?,115i8()?,116i8()?,117i8()?,118i8()?,119i8()?,120i8()?,121i8()?,122i8()?,123]);124DiffValue::V128(vector)125}126DiffSimdTy::I16x8 => {127let mut i16 =128|| biased_arbitrary_value(u, KNOWN_I16_VALUES).map(i16::to_le_bytes);129let vector: Vec<u8> = i16()?130.into_iter()131.chain(i16()?)132.chain(i16()?)133.chain(i16()?)134.chain(i16()?)135.chain(i16()?)136.chain(i16()?)137.chain(i16()?)138.collect();139DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))140}141DiffSimdTy::I32x4 => {142let mut i32 =143|| biased_arbitrary_value(u, KNOWN_I32_VALUES).map(i32::to_le_bytes);144let vector: Vec<u8> = i32()?145.into_iter()146.chain(i32()?)147.chain(i32()?)148.chain(i32()?)149.collect();150DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))151}152DiffSimdTy::I64x2 => {153let mut i64 =154|| biased_arbitrary_value(u, KNOWN_I64_VALUES).map(i64::to_le_bytes);155let vector: Vec<u8> = i64()?.into_iter().chain(i64()?).collect();156DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))157}158DiffSimdTy::F32x4 => {159let mut f32 = || {160Self::arbitrary_of_type(u, DiffValueType::F32).map(|v| match v {161DiffValue::F32(v) => v.to_le_bytes(),162_ => unreachable!(),163})164};165let vector: Vec<u8> = f32()?166.into_iter()167.chain(f32()?)168.chain(f32()?)169.chain(f32()?)170.collect();171DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))172}173DiffSimdTy::F64x2 => {174let mut f64 = || {175Self::arbitrary_of_type(u, DiffValueType::F64).map(|v| match v {176DiffValue::F64(v) => v.to_le_bytes(),177_ => unreachable!(),178})179};180let vector: Vec<u8> = f64()?.into_iter().chain(f64()?).collect();181DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))182}183}184}185186// TODO: this isn't working in most engines so just always pass a187// null in which if an engine supports this is should at least188// support doing that.189FuncRef => DiffValue::FuncRef { null: true },190ExternRef => DiffValue::ExternRef { null: true },191AnyRef => DiffValue::AnyRef { null: true },192ExnRef => DiffValue::ExnRef { null: true },193ContRef => DiffValue::ContRef { null: true },194};195arbitrary::Result::Ok(val)196}197}198199const KNOWN_I8_VALUES: &[i8] = &[i8::MIN, -1, 0, 1, i8::MAX];200const KNOWN_I16_VALUES: &[i16] = &[i16::MIN, -1, 0, 1, i16::MAX];201const KNOWN_I32_VALUES: &[i32] = &[i32::MIN, -1, 0, 1, i32::MAX];202const KNOWN_I64_VALUES: &[i64] = &[i64::MIN, -1, 0, 1, i64::MAX];203204/// Helper function to pick a known value from the list of `known_values` half205/// the time.206fn biased_arbitrary_value<'a, T>(207u: &mut Unstructured<'a>,208known_values: &[T],209) -> arbitrary::Result<T>210where211T: Arbitrary<'a> + Copy,212{213let pick_from_known_values: bool = u.arbitrary()?;214if pick_from_known_values {215Ok(*u.choose(known_values)?)216} else {217u.arbitrary()218}219}220221impl<'a> Arbitrary<'a> for DiffValue {222fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {223let ty: DiffValueType = u.arbitrary()?;224DiffValue::arbitrary_of_type(u, ty)225}226}227228impl Hash for DiffValue {229fn hash<H: std::hash::Hasher>(&self, state: &mut H) {230self.ty().hash(state);231match self {232DiffValue::I32(n) => n.hash(state),233DiffValue::I64(n) => n.hash(state),234DiffValue::F32(n) => n.hash(state),235DiffValue::F64(n) => n.hash(state),236DiffValue::V128(n) => n.hash(state),237DiffValue::ExternRef { null } => null.hash(state),238DiffValue::FuncRef { null } => null.hash(state),239DiffValue::AnyRef { null } => null.hash(state),240DiffValue::ExnRef { null } => null.hash(state),241DiffValue::ContRef { null } => null.hash(state),242}243}244}245246/// Implement equality checks. Note that floating-point values are not compared247/// bit-for-bit in the case of NaNs: because Wasm floating-point numbers may be248/// [arithmetic NaNs with arbitrary payloads] and Wasm operations are [not249/// required to propagate NaN payloads], we simply check that both sides are250/// NaNs here. We could be more strict, though: we could check that the NaN251/// signs are equal and that [canonical NaN payloads remain canonical].252///253/// [arithmetic NaNs with arbitrary payloads]:254/// https://webassembly.github.io/spec/core/bikeshed/index.html#floating-point%E2%91%A0255/// [not required to propagate NaN payloads]:256/// https://webassembly.github.io/spec/core/bikeshed/index.html#floating-point-operations%E2%91%A0257/// [canonical NaN payloads remain canonical]:258/// https://webassembly.github.io/spec/core/bikeshed/index.html#nan-propagation%E2%91%A0259impl PartialEq for DiffValue {260fn eq(&self, other: &Self) -> bool {261match (self, other) {262(Self::I32(l0), Self::I32(r0)) => l0 == r0,263(Self::I64(l0), Self::I64(r0)) => l0 == r0,264(Self::V128(l0), Self::V128(r0)) => l0 == r0,265(Self::F32(l0), Self::F32(r0)) => {266let l0 = f32::from_bits(*l0);267let r0 = f32::from_bits(*r0);268l0 == r0 || (l0.is_nan() && r0.is_nan())269}270(Self::F64(l0), Self::F64(r0)) => {271let l0 = f64::from_bits(*l0);272let r0 = f64::from_bits(*r0);273l0 == r0 || (l0.is_nan() && r0.is_nan())274}275(Self::FuncRef { null: a }, Self::FuncRef { null: b }) => a == b,276(Self::ExternRef { null: a }, Self::ExternRef { null: b }) => a == b,277(Self::AnyRef { null: a }, Self::AnyRef { null: b }) => a == b,278(Self::ExnRef { null: a }, Self::ExnRef { null: b }) => a == b,279(Self::ContRef { null: a }, Self::ContRef { null: b }) => a == b,280_ => false,281}282}283}284285/// Enumerate the supported value types.286#[derive(Copy, Clone, Debug, Arbitrary, Hash)]287#[expect(missing_docs, reason = "self-describing variants")]288pub enum DiffValueType {289I32,290I64,291F32,292F64,293V128,294FuncRef,295ExternRef,296AnyRef,297ExnRef,298ContRef,299}300301impl TryFrom<wasmtime::ValType> for DiffValueType {302type Error = &'static str;303fn try_from(ty: wasmtime::ValType) -> Result<Self, Self::Error> {304use wasmtime::ValType::*;305match ty {306I32 => Ok(Self::I32),307I64 => Ok(Self::I64),308F32 => Ok(Self::F32),309F64 => Ok(Self::F64),310V128 => Ok(Self::V128),311Ref(r) => match (r.is_nullable(), r.heap_type()) {312(true, HeapType::Func) => Ok(Self::FuncRef),313(true, HeapType::Extern) => Ok(Self::ExternRef),314(true, HeapType::Any) => Ok(Self::AnyRef),315(true, HeapType::I31) => Ok(Self::AnyRef),316(true, HeapType::None) => Ok(Self::AnyRef),317(true, HeapType::Exn) => Ok(Self::ExnRef),318(true, HeapType::Cont) => Ok(Self::ContRef),319_ => Err("non-null reference types are not supported yet"),320},321}322}323}324325/// Enumerate the types of v128.326#[derive(Copy, Clone, Debug, Arbitrary, Hash)]327pub enum DiffSimdTy {328I8x16,329I16x8,330I32x4,331I64x2,332F32x4,333F64x2,334}335336337