wasmtime_fuzzing/generators/
value.rs

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