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