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 { 25 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.). 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. 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 { 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 { 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 { 261 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; 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