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