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 {
ty(&self) -> DiffValueType25     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.).
arbitrary_of_type( u: &mut Unstructured<'_>, ty: DiffValueType, ) -> arbitrary::Result<Self>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.
biased_arbitrary_value<'a, T>( u: &mut Unstructured<'a>, known_values: &[T], ) -> arbitrary::Result<T> where T: Arbitrary<'a> + Copy,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 {
arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self>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 {
hash<H: std::hash::Hasher>(&self, state: &mut H)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 {
eq(&self, other: &Self) -> bool261     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;
try_from(ty: wasmtime::ValType) -> Result<Self, Self::Error>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