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