/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 | | } |