Coverage Report

Created: 2026-07-05 07:39

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