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