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             _ => false,
271         }
272     }
273 }
274 
275 /// Enumerate the supported value types.
276 #[derive(Copy, Clone, Debug, Arbitrary, Hash)]
277 #[expect(missing_docs, reason = "self-describing variants")]
278 pub enum DiffValueType {
279     I32,
280     I64,
281     F32,
282     F64,
283     V128,
284     FuncRef,
285     ExternRef,
286     AnyRef,
287 }
288 
289 impl TryFrom<wasmtime::ValType> for DiffValueType {
290     type Error = &'static str;
291     fn try_from(ty: wasmtime::ValType) -> Result<Self, Self::Error> {
292         use wasmtime::ValType::*;
293         match ty {
294             I32 => Ok(Self::I32),
295             I64 => Ok(Self::I64),
296             F32 => Ok(Self::F32),
297             F64 => Ok(Self::F64),
298             V128 => Ok(Self::V128),
299             Ref(r) => match (r.is_nullable(), r.heap_type()) {
300                 (true, HeapType::Func) => Ok(Self::FuncRef),
301                 (true, HeapType::Extern) => Ok(Self::ExternRef),
302                 (true, HeapType::Any) => Ok(Self::AnyRef),
303                 (true, HeapType::I31) => Ok(Self::AnyRef),
304                 (true, HeapType::None) => Ok(Self::AnyRef),
305                 _ => Err("non-funcref and non-externref reference types are not supported yet"),
306             },
307         }
308     }
309 }
310 
311 /// Enumerate the types of v128.
312 #[derive(Copy, Clone, Debug, Arbitrary, Hash)]
313 pub enum DiffSimdTy {
314     I8x16,
315     I16x8,
316     I32x4,
317     I64x2,
318     F32x4,
319     F64x2,
320 }
321