Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/src/generators/value.rs
1693 views
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
fn ty(&self) -> DiffValueType {
26
match self {
27
DiffValue::I32(_) => DiffValueType::I32,
28
DiffValue::I64(_) => DiffValueType::I64,
29
DiffValue::F32(_) => DiffValueType::F32,
30
DiffValue::F64(_) => DiffValueType::F64,
31
DiffValue::V128(_) => DiffValueType::V128,
32
DiffValue::FuncRef { .. } => DiffValueType::FuncRef,
33
DiffValue::ExternRef { .. } => DiffValueType::ExternRef,
34
DiffValue::AnyRef { .. } => DiffValueType::AnyRef,
35
DiffValue::ExnRef { .. } => DiffValueType::ExnRef,
36
DiffValue::ContRef { .. } => DiffValueType::ContRef,
37
}
38
}
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
pub fn arbitrary_of_type(
45
u: &mut Unstructured<'_>,
46
ty: DiffValueType,
47
) -> arbitrary::Result<Self> {
48
use DiffValueType::*;
49
let val = match ty {
50
I32 => DiffValue::I32(biased_arbitrary_value(u, KNOWN_I32_VALUES)?),
51
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
let known_f32_values = &[
56
f32::NAN.to_bits(),
57
f32::INFINITY.to_bits(),
58
f32::NEG_INFINITY.to_bits(),
59
f32::MIN.to_bits(),
60
(-1.0f32).to_bits(),
61
(0.0f32).to_bits(),
62
(1.0f32).to_bits(),
63
f32::MAX.to_bits(),
64
];
65
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
let bits = if f32::from_bits(bits).is_nan() {
72
f32::NAN.to_bits()
73
} else {
74
bits
75
};
76
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
let known_f64_values = &[
82
f64::NAN.to_bits(),
83
f64::INFINITY.to_bits(),
84
f64::NEG_INFINITY.to_bits(),
85
f64::MIN.to_bits(),
86
(-1.0f64).to_bits(),
87
(0.0f64).to_bits(),
88
(1.0f64).to_bits(),
89
f64::MAX.to_bits(),
90
];
91
let bits = biased_arbitrary_value(u, known_f64_values)?;
92
// See `f32` above for why canonical NaN patterns are always
93
// used.
94
let bits = if f64::from_bits(bits).is_nan() {
95
f64::NAN.to_bits()
96
} else {
97
bits
98
};
99
DiffValue::F64(bits)
100
}
101
V128 => {
102
// Generate known values for each sub-type of V128.
103
let ty: DiffSimdTy = u.arbitrary()?;
104
match ty {
105
DiffSimdTy::I8x16 => {
106
let mut i8 = || biased_arbitrary_value(u, KNOWN_I8_VALUES).map(|b| b as u8);
107
let vector = u128::from_le_bytes([
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
i8()?,
121
i8()?,
122
i8()?,
123
i8()?,
124
]);
125
DiffValue::V128(vector)
126
}
127
DiffSimdTy::I16x8 => {
128
let mut i16 =
129
|| biased_arbitrary_value(u, KNOWN_I16_VALUES).map(i16::to_le_bytes);
130
let vector: Vec<u8> = i16()?
131
.into_iter()
132
.chain(i16()?)
133
.chain(i16()?)
134
.chain(i16()?)
135
.chain(i16()?)
136
.chain(i16()?)
137
.chain(i16()?)
138
.chain(i16()?)
139
.collect();
140
DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))
141
}
142
DiffSimdTy::I32x4 => {
143
let mut i32 =
144
|| biased_arbitrary_value(u, KNOWN_I32_VALUES).map(i32::to_le_bytes);
145
let vector: Vec<u8> = i32()?
146
.into_iter()
147
.chain(i32()?)
148
.chain(i32()?)
149
.chain(i32()?)
150
.collect();
151
DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))
152
}
153
DiffSimdTy::I64x2 => {
154
let mut i64 =
155
|| biased_arbitrary_value(u, KNOWN_I64_VALUES).map(i64::to_le_bytes);
156
let vector: Vec<u8> = i64()?.into_iter().chain(i64()?).collect();
157
DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))
158
}
159
DiffSimdTy::F32x4 => {
160
let mut f32 = || {
161
Self::arbitrary_of_type(u, DiffValueType::F32).map(|v| match v {
162
DiffValue::F32(v) => v.to_le_bytes(),
163
_ => unreachable!(),
164
})
165
};
166
let vector: Vec<u8> = f32()?
167
.into_iter()
168
.chain(f32()?)
169
.chain(f32()?)
170
.chain(f32()?)
171
.collect();
172
DiffValue::V128(u128::from_le_bytes(vector.try_into().unwrap()))
173
}
174
DiffSimdTy::F64x2 => {
175
let mut f64 = || {
176
Self::arbitrary_of_type(u, DiffValueType::F64).map(|v| match v {
177
DiffValue::F64(v) => v.to_le_bytes(),
178
_ => unreachable!(),
179
})
180
};
181
let vector: Vec<u8> = f64()?.into_iter().chain(f64()?).collect();
182
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
FuncRef => DiffValue::FuncRef { null: true },
191
ExternRef => DiffValue::ExternRef { null: true },
192
AnyRef => DiffValue::AnyRef { null: true },
193
ExnRef => DiffValue::ExnRef { null: true },
194
ContRef => DiffValue::ContRef { null: true },
195
};
196
arbitrary::Result::Ok(val)
197
}
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
fn biased_arbitrary_value<'a, T>(
208
u: &mut Unstructured<'a>,
209
known_values: &[T],
210
) -> arbitrary::Result<T>
211
where
212
T: Arbitrary<'a> + Copy,
213
{
214
let pick_from_known_values: bool = u.arbitrary()?;
215
if pick_from_known_values {
216
Ok(*u.choose(known_values)?)
217
} else {
218
u.arbitrary()
219
}
220
}
221
222
impl<'a> Arbitrary<'a> for DiffValue {
223
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
224
let ty: DiffValueType = u.arbitrary()?;
225
DiffValue::arbitrary_of_type(u, ty)
226
}
227
}
228
229
impl Hash for DiffValue {
230
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
231
self.ty().hash(state);
232
match self {
233
DiffValue::I32(n) => n.hash(state),
234
DiffValue::I64(n) => n.hash(state),
235
DiffValue::F32(n) => n.hash(state),
236
DiffValue::F64(n) => n.hash(state),
237
DiffValue::V128(n) => n.hash(state),
238
DiffValue::ExternRef { null } => null.hash(state),
239
DiffValue::FuncRef { null } => null.hash(state),
240
DiffValue::AnyRef { null } => null.hash(state),
241
DiffValue::ExnRef { null } => null.hash(state),
242
DiffValue::ContRef { null } => null.hash(state),
243
}
244
}
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
fn eq(&self, other: &Self) -> bool {
262
match (self, other) {
263
(Self::I32(l0), Self::I32(r0)) => l0 == r0,
264
(Self::I64(l0), Self::I64(r0)) => l0 == r0,
265
(Self::V128(l0), Self::V128(r0)) => l0 == r0,
266
(Self::F32(l0), Self::F32(r0)) => {
267
let l0 = f32::from_bits(*l0);
268
let r0 = f32::from_bits(*r0);
269
l0 == r0 || (l0.is_nan() && r0.is_nan())
270
}
271
(Self::F64(l0), Self::F64(r0)) => {
272
let l0 = f64::from_bits(*l0);
273
let r0 = f64::from_bits(*r0);
274
l0 == r0 || (l0.is_nan() && r0.is_nan())
275
}
276
(Self::FuncRef { null: a }, Self::FuncRef { null: b }) => a == b,
277
(Self::ExternRef { null: a }, Self::ExternRef { null: b }) => a == b,
278
(Self::AnyRef { null: a }, Self::AnyRef { null: b }) => a == b,
279
(Self::ExnRef { null: a }, Self::ExnRef { null: b }) => a == b,
280
(Self::ContRef { null: a }, Self::ContRef { null: b }) => a == b,
281
_ => false,
282
}
283
}
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
fn try_from(ty: wasmtime::ValType) -> Result<Self, Self::Error> {
305
use wasmtime::ValType::*;
306
match ty {
307
I32 => Ok(Self::I32),
308
I64 => Ok(Self::I64),
309
F32 => Ok(Self::F32),
310
F64 => Ok(Self::F64),
311
V128 => Ok(Self::V128),
312
Ref(r) => match (r.is_nullable(), r.heap_type()) {
313
(true, HeapType::Func) => Ok(Self::FuncRef),
314
(true, HeapType::Extern) => Ok(Self::ExternRef),
315
(true, HeapType::Any) => Ok(Self::AnyRef),
316
(true, HeapType::I31) => Ok(Self::AnyRef),
317
(true, HeapType::None) => Ok(Self::AnyRef),
318
(true, HeapType::Exn) => Ok(Self::ExnRef),
319
(true, HeapType::Cont) => Ok(Self::ContRef),
320
_ => Err("non-null reference types are not supported yet"),
321
},
322
}
323
}
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
}
336
337