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 }
21 
22 impl 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 
194 const KNOWN_I8_VALUES: &[i8] = &[i8::MIN, -1, 0, 1, i8::MAX];
195 const KNOWN_I16_VALUES: &[i16] = &[i16::MIN, -1, 0, 1, i16::MAX];
196 const KNOWN_I32_VALUES: &[i32] = &[i32::MIN, -1, 0, 1, i32::MAX];
197 const 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.
201 fn biased_arbitrary_value<'a, T>(
202     u: &mut Unstructured<'a>,
203     known_values: &[T],
204 ) -> arbitrary::Result<T>
205 where
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 
216 impl<'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 
223 impl 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
252 impl 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")]
279 pub enum DiffValueType {
280     I32,
281     I64,
282     F32,
283     F64,
284     V128,
285     FuncRef,
286     ExternRef,
287     AnyRef,
288 }
289 
290 impl 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)]
314 pub enum DiffSimdTy {
315     I8x16,
316     I16x8,
317     I32x4,
318     I64x2,
319     F32x4,
320     F64x2,
321 }
322