// diff_fast corpus program: the SSE2 double-precision intrinsics of core::arch::x86_64 (_pd / _sd, plus the // pd/ps/epi32 conversions and casts) on edge lanes (both zeros, infinities, limits, subnormals, quiet and // signaling NaNs of both signs with payloads, conversion boundaries) and random bits, bit-exact: every // result's bits (NaN payloads, compare masks, -0.0) feed one hash per intrinsic, printed as `D `. // Float -> integer conversions also run under each MXCSR rounding mode. x86-64 only (other hosts print one // line). ARCH_DETAIL= prints that intrinsic's every case instead (both sides), for a diverging hash. #![allow(unused_unsafe, deprecated, non_snake_case)] macro_rules! d { ($($a:tt)*) => { println!("D {}", format!($($a)*)) } } fn rounds(f: fn()) { let ms: u64 = std::env::var("DIFF_SLEEP_MS").ok().and_then(|v| v.parse().ok()).unwrap_or(0); for r in 0..4 { f(); if r < 3 && ms > 0 { std::thread::sleep(std::time::Duration::from_millis(ms)); } } } #[test] fn run() { rounds(work); } #[cfg(not(target_arch = "x86_64"))] fn work() { d!("sse2 pd: x86-64 only"); } #[cfg(target_arch = "x86_64")] fn work() { x::all(); } #[cfg(target_arch = "x86_64")] mod x { use core::arch::x86_64::*; use std::hint::black_box; struct Rng(u64); impl Rng { fn next(&mut self) -> u64 { self.0 ^= self.0 << 13; self.0 ^= self.0 >> 7; self.0 ^= self.0 << 17; self.0 } } const EDGE64: [u64; 40] = [ 0x0000_0000_0000_0000, // 0.0 0x8000_0000_0000_0000, // -0.0 0x3ff0_0000_0000_0000, // 1.0 0xbff0_0000_0000_0000, // -1.0 0x3fe0_0000_0000_0000, // 0.5 0xbfe0_0000_0000_0000, // -0.5 0x3ff8_0000_0000_0000, // 1.5 0x4004_0000_0000_0000, // 2.5 0xc004_0000_0000_0000, // -2.5 0x3fdf_ffff_ffff_ffff, // 0.49999999999999994 0x7ff0_0000_0000_0000, // inf 0xfff0_0000_0000_0000, // -inf 0x7fef_ffff_ffff_ffff, // f64::MAX 0xffef_ffff_ffff_ffff, // f64::MIN 0x0010_0000_0000_0000, // MIN_POSITIVE 0x0000_0000_0000_0001, // smallest subnormal 0x800f_ffff_ffff_ffff, // largest negative subnormal 0x41df_ffff_ffe0_0000, // 2147483647.5 0x41e0_0000_0000_0000, // 2147483648.0 0xc1e0_0000_0010_0000, // -2147483648.5 0xc1e0_0000_0020_0000, // -2147483649.0 0x41df_ffff_ffc0_0000, // 2147483647.0 0xc1e0_0000_0000_0000, // -2147483648.0 0x43e0_0000_0000_0000, // 9223372036854775808.0 0xc3e0_0000_0000_0000, // -9223372036854775808.0 0x43df_ffff_ffff_ffff, // 9223372036854774784.0 0x47ef_ffff_e000_0000, // f32::MAX 0x47ef_ffff_f000_0000, // f32::MAX + half ulp (rounds to inf in f32) 0x3690_0000_0000_0000, // ~1.0e-45 range: f32 subnormal territory 0x3ff0_0000_1000_0000, // 1 + 2^-24: an f32 rounding tie 0x4330_0000_0000_0001, // 2^52 + 1 0x7ff8_0000_0000_0000, // qNaN 0xfff8_0000_0000_0000, // -qNaN (Intel default NaN) 0x7ff0_0000_0000_0001, // sNaN 0xfff4_0000_1234_5678, // -sNaN with payload 0x7ff8_0000_1234_5678, // qNaN with payload 0x7fff_ffff_ffff_ffff, // qNaN all payload bits 0x4024_0000_0000_0000, // 10.0 0xc059_0000_0000_0000, // -100.0 0x3fb9_9999_9999_999a, // 0.1 ]; const EDGE32: [u32; 20] = [ 0x0000_0000, 0x8000_0000, 0x3f80_0000, 0xbf80_0000, 0x3fc0_0000, 0x4020_0000, 0xc020_0000, 0x7f80_0000, 0xff80_0000, 0x7f7f_ffff, 0x0000_0001, 0x4f00_0000, 0xcf00_0000, 0x4eff_ffff, 0xcf00_0001, 0x7fc0_0000, 0xffc0_0000, 0x7f80_0001, 0xffa0_1234, 0x7fc0_5678, ]; const EDGEI32: [i32; 8] = [0, 1, -1, i32::MAX, i32::MIN, 16777217, -16777217, 123456789]; const EDGEI64: [i64; 8] = [0, 1, -1, i64::MAX, i64::MIN, 9007199254740993, -9007199254740993, 1234567890123456789]; fn mix(h: &mut u64, x: u64) { *h = (*h ^ x).wrapping_mul(0x100000001b3).rotate_left(7); } fn pd(lo: u64, hi: u64) -> __m128d { black_box(unsafe { core::mem::transmute::<[u64; 2], __m128d>([lo, hi]) }) } fn ps(l: [u32; 4]) -> __m128 { black_box(unsafe { core::mem::transmute::<[u32; 4], __m128>(l) }) } fn si(l: [u32; 4]) -> __m128i { black_box(unsafe { core::mem::transmute::<[u32; 4], __m128i>(l) }) } fn bits_pd(v: __m128d) -> [u64; 2] { unsafe { core::mem::transmute(v) } } fn bits_ps(v: __m128) -> [u64; 2] { unsafe { core::mem::transmute(v) } } fn bits_si(v: __m128i) -> [u64; 2] { unsafe { core::mem::transmute(v) } } /// One intrinsic's results: a hash, or every case when ARCH_DETAIL names it. struct Out { name: &'static str, h: u64, n: u64, detail: bool, } impl Out { fn new(name: &'static str) -> Out { let detail = std::env::var("ARCH_DETAIL").map(|v| v == name).unwrap_or(false); Out { name, h: 0xcbf29ce484222325, n: 0, detail } } fn put(&mut self, input: &[u64], r: [u64; 2]) { if self.detail { println!("D {} {:016x?} -> {:016x} {:016x}", self.name, input, r[1], r[0]); } mix(&mut self.h, r[0]); mix(&mut self.h, r[1]); self.n += 1; } fn end(self) { d!("{} n={} {:016x}", self.name, self.n, self.h); } } /// f64 lane values: every edge, then random bits and random finite values. fn lanes64(rng: &mut Rng) -> Vec { let mut v = EDGE64.to_vec(); for _ in 0..24 { v.push(rng.next()); let f = (rng.next() as i64 as f64) / ((rng.next() >> 40) as f64 + 1.0); v.push(f.to_bits()); } v } // ---- packed f64 x f64 -> f64 (and masks): every (x, y) pair of lane values, both lanes macro_rules! bin_pd { ($rng:ident, $($f:ident)*) => {$({ #[inline(never)] fn op(a: __m128d, b: __m128d) -> __m128d { unsafe { $f(a, b) } } let mut o = Out::new(stringify!($f)); let v = lanes64($rng); for (i, &x) in v.iter().enumerate() { for (j, &y) in v.iter().enumerate() { let (x2, y2) = (v[(i + 7) % v.len()], v[(j * 3 + 1) % v.len()]); o.put(&[x, x2, y, y2], bits_pd(op(pd(x, x2), pd(y, y2)))); } } o.end(); })*}; } macro_rules! un_pd { ($rng:ident, $($f:ident)*) => {$({ #[inline(never)] fn op(a: __m128d) -> __m128d { unsafe { $f(a) } } let mut o = Out::new(stringify!($f)); let v = lanes64($rng); for (i, &x) in v.iter().enumerate() { let x2 = v[(i + 5) % v.len()]; o.put(&[x, x2], bits_pd(op(pd(x, x2)))); } o.end(); })*}; } // f64 pair -> i32 (comi, movemask) macro_rules! int_pd { ($rng:ident, $($f:ident)*) => {$({ #[inline(never)] fn op(a: __m128d, b: __m128d) -> i32 { unsafe { $f(a, b) } } let mut o = Out::new(stringify!($f)); let v = lanes64($rng); for (i, &x) in v.iter().enumerate() { for &y in v.iter() { let x2 = v[(i + 3) % v.len()]; o.put(&[x, x2, y], [op(pd(x, x2), pd(y, x)) as u32 as u64, 0]); } } o.end(); })*}; } #[inline(never)] fn movemask(a: __m128d) -> i32 { unsafe { _mm_movemask_pd(a) } } #[inline(never)] fn shuffles(rng: &mut Rng) { macro_rules! sh { ($($k:literal)*) => {$({ #[inline(never)] fn op(a: __m128d, b: __m128d) -> __m128d { unsafe { _mm_shuffle_pd::<$k>(a, b) } } let mut o = Out::new(concat!("_mm_shuffle_pd::<", stringify!($k), ">")); for _ in 0..16 { let (a, b, c, e) = (rng.next(), rng.next(), rng.next(), rng.next()); o.put(&[a, b, c, e], bits_pd(op(pd(a, b), pd(c, e)))); } o.end(); })*}; } sh!(0 1 2 3 4 255); } #[inline(never)] fn memory(rng: &mut Rng) { let v = lanes64(rng); let mut o = Out::new("loads"); for w in v.windows(3) { #[repr(C, align(16))] struct A([f64; 3]); let m = A([f64::from_bits(w[0]), f64::from_bits(w[1]), f64::from_bits(w[2])]); let p = black_box(m.0.as_ptr()); let a = pd(w[2], w[1]); unsafe { for r in [ _mm_load_pd(p), _mm_loadu_pd(p.add(1)), _mm_load_sd(p.add(1)), _mm_load1_pd(p), _mm_load_pd1(p.add(2)), _mm_loadr_pd(p), _mm_loadh_pd(a, p.add(1)), _mm_loadl_pd(a, p.add(2)), ] { o.put(w, bits_pd(r)); } } } o.end(); let mut o = Out::new("stores"); for w in v.windows(2) { #[repr(C, align(16))] struct A([u64; 4]); let a = pd(w[0], w[1]); for k in 0..9 { let mut m = A([0x1111_1111_1111_1111; 4]); let p = black_box(m.0.as_mut_ptr() as *mut f64); unsafe { match k { 0 => _mm_store_pd(p, a), 1 => _mm_storeu_pd(p.add(1), a), 2 => _mm_store_sd(p.add(1), a), 3 => _mm_store1_pd(p, a), 4 => _mm_store_pd1(p.add(2), a), 5 => _mm_storer_pd(p, a), 6 => _mm_storeh_pd(p.add(3), a), 7 => _mm_storel_pd(p, a), _ => _mm_stream_pd(p.add(2), a), } } let m = black_box(m); o.put(w, [m.0[0] ^ m.0[1].rotate_left(17), m.0[2] ^ m.0[3].rotate_left(29)]); } } o.end(); let mut o = Out::new("sets"); for w in v.windows(2) { let (x, y) = (black_box(f64::from_bits(w[0])), black_box(f64::from_bits(w[1]))); unsafe { for r in [_mm_set1_pd(x), _mm_set_pd1(y), _mm_set_pd(x, y), _mm_setr_pd(x, y), _mm_set_sd(y), _mm_setzero_pd()] { o.put(w, bits_pd(r)); } o.put(w, [_mm_cvtsd_f64(pd(w[0], w[1])).to_bits(), 0]); } } o.end(); } #[inline(never)] fn casts(rng: &mut Rng) { let mut o = Out::new("casts"); for _ in 0..32 { let (x, y) = (rng.next(), rng.next()); let l = [x as u32, (x >> 32) as u32, y as u32, (y >> 32) as u32]; unsafe { o.put(&[x, y], bits_ps(_mm_castpd_ps(pd(x, y)))); o.put(&[x, y], bits_si(_mm_castpd_si128(pd(x, y)))); o.put(&[x, y], bits_pd(_mm_castps_pd(ps(l)))); o.put(&[x, y], bits_si(_mm_castps_si128(ps(l)))); o.put(&[x, y], bits_pd(_mm_castsi128_pd(si(l)))); o.put(&[x, y], bits_ps(_mm_castsi128_ps(si(l)))); } } o.end(); } /// Float -> integer conversions under one MXCSR rounding mode, every other conversion once. #[inline(never)] fn conversions(rng: &mut Rng) { let v = lanes64(rng); let mut f32s: Vec = EDGE32.to_vec(); for &x in &v { f32s.push((f64::from_bits(x) as f32).to_bits()); } for _ in 0..16 { f32s.push(rng.next() as u32); } let csr = unsafe { _mm_getcsr() }; for (mode, rc) in [("nearest", 0u32), ("down", 1), ("up", 2), ("zero", 3)] { let mut o = Out::new(match mode { "nearest" => "f2i nearest", "down" => "f2i down", "up" => "f2i up", _ => "f2i zero", }); unsafe { _mm_setcsr((csr & !0x6000) | rc << 13) }; for (i, &x) in v.iter().enumerate() { let a = pd(x, v[(i + 1) % v.len()]); unsafe { o.put(&[x], bits_si(_mm_cvtpd_epi32(a))); o.put(&[x], bits_si(_mm_cvttpd_epi32(a))); o.put(&[x], [_mm_cvtsd_si32(a) as u32 as u64, _mm_cvttsd_si32(a) as u32 as u64]); o.put(&[x], [_mm_cvtsd_si64(a) as u64, _mm_cvttsd_si64(a) as u64]); o.put(&[x], [_mm_cvtsd_si64x(a) as u64, _mm_cvttsd_si64x(a) as u64]); } } for c in f32s.chunks(4) { if c.len() < 4 { continue; } let a = ps([c[0], c[1], c[2], c[3]]); unsafe { o.put(&[c[0] as u64, c[1] as u64], bits_si(_mm_cvtps_epi32(a))); o.put(&[c[0] as u64, c[1] as u64], bits_si(_mm_cvttps_epi32(a))); } } unsafe { _mm_setcsr(csr) }; o.end(); } let mut o = Out::new("float conversions"); for (i, &x) in v.iter().enumerate() { let y = v[(i + 11) % v.len()]; let a = pd(x, y); let f = f32s[i % f32s.len()]; let g = f32s[(i * 5 + 3) % f32s.len()]; let s = ps([f, g, 0x4049_0fdb, 0xc2f6_e979]); unsafe { o.put(&[x, y], bits_ps(_mm_cvtpd_ps(a))); o.put(&[f as u64, g as u64], bits_pd(_mm_cvtps_pd(s))); o.put(&[x, y], bits_ps(_mm_cvtsd_ss(s, a))); o.put(&[f as u64, g as u64], bits_pd(_mm_cvtss_sd(a, s))); } } o.end(); let mut o = Out::new("int conversions"); for (i, &k) in EDGEI32.iter().enumerate() { let k2 = EDGEI32[(i + 3) % 8]; let n = si([k as u32, k2 as u32, 0x7fff_ffff, 0x8000_0001]); let a = pd(v[i], v[i + 9]); unsafe { o.put(&[k as u64], bits_pd(_mm_cvtepi32_pd(n))); o.put(&[k as u64], bits_ps(_mm_cvtepi32_ps(n))); o.put(&[k as u64], bits_pd(_mm_cvtsi32_sd(a, black_box(k)))); } } for (i, &k) in EDGEI64.iter().enumerate() { let a = pd(v[i + 2], v[i + 5]); unsafe { o.put(&[k as u64], bits_pd(_mm_cvtsi64_sd(a, black_box(k)))); o.put(&[k as u64], bits_pd(_mm_cvtsi64x_sd(a, black_box(k)))); } } for _ in 0..32 { let (x, y) = (rng.next(), rng.next()); let n = si([x as u32, (x >> 32) as u32, y as u32, (y >> 32) as u32]); let a = pd(y, x); unsafe { o.put(&[x, y], bits_pd(_mm_cvtepi32_pd(n))); o.put(&[x, y], bits_ps(_mm_cvtepi32_ps(n))); o.put(&[x, y], bits_pd(_mm_cvtsi64_sd(a, x as i64 >> (y % 64)))); } } o.end(); } /// The fractal zoomer's inner step: z = z^2 + c on two lanes, escape by movemask. #[inline(never)] fn mandel() { let mut o = Out::new("mandel"); unsafe { let four = _mm_set1_pd(4.0); let two = _mm_set1_pd(2.0); for row in 0..8 { let ci = _mm_set1_pd(-1.2 + row as f64 * 0.3); let cr = _mm_setr_pd(-2.0 + row as f64 * 0.31, -0.5 + row as f64 * 0.07); let (mut zr, mut zi) = (_mm_setzero_pd(), _mm_setzero_pd()); let mut count = _mm_setzero_pd(); for _ in 0..200 { let zr2 = _mm_mul_pd(zr, zr); let zi2 = _mm_mul_pd(zi, zi); let mag = _mm_add_pd(zr2, zi2); let inside = _mm_cmplt_pd(mag, four); if _mm_movemask_pd(inside) == 0 { break; } count = _mm_add_pd(count, _mm_and_pd(inside, _mm_set1_pd(1.0))); zi = _mm_add_pd(_mm_mul_pd(_mm_mul_pd(two, zr), zi), ci); zr = _mm_add_pd(_mm_sub_pd(zr2, zi2), cr); } let mut out = [0.0f64; 2]; _mm_storeu_pd(out.as_mut_ptr(), _mm_div_pd(count, _mm_max_pd(_mm_sqrt_pd(_mm_add_pd(zr, two)), _mm_set1_pd(1.0)))); o.put(&[row], [out[0].to_bits(), out[1].to_bits()]); o.put(&[row], [movemask(_mm_cmpgt_pd(zr, zi)) as u64, _mm_movemask_pd(_mm_andnot_pd(zr, _mm_or_pd(zi, _mm_xor_pd(zr, ci)))) as u64]); } } o.end(); } pub fn all() { let rng = &mut Rng(0x9e37_79b9_7f4a_7c15); bin_pd!(rng, _mm_add_pd _mm_sub_pd _mm_mul_pd _mm_div_pd _mm_min_pd _mm_max_pd); bin_pd!(rng, _mm_and_pd _mm_andnot_pd _mm_or_pd _mm_xor_pd _mm_unpacklo_pd _mm_unpackhi_pd _mm_move_sd); bin_pd!(rng, _mm_cmpeq_pd _mm_cmpneq_pd _mm_cmplt_pd _mm_cmple_pd _mm_cmpgt_pd _mm_cmpge_pd); bin_pd!(rng, _mm_cmpord_pd _mm_cmpunord_pd _mm_cmpnlt_pd _mm_cmpnle_pd _mm_cmpngt_pd _mm_cmpnge_pd); bin_pd!(rng, _mm_add_sd _mm_sub_sd _mm_mul_sd _mm_div_sd _mm_min_sd _mm_max_sd _mm_sqrt_sd); bin_pd!(rng, _mm_cmpeq_sd _mm_cmpneq_sd _mm_cmplt_sd _mm_cmple_sd _mm_cmpgt_sd _mm_cmpge_sd); bin_pd!(rng, _mm_cmpord_sd _mm_cmpunord_sd _mm_cmpnlt_sd _mm_cmpnle_sd _mm_cmpngt_sd _mm_cmpnge_sd); un_pd!(rng, _mm_sqrt_pd); int_pd!(rng, _mm_comieq_sd _mm_comilt_sd _mm_comile_sd _mm_comigt_sd _mm_comige_sd _mm_comineq_sd); int_pd!(rng, _mm_ucomieq_sd _mm_ucomilt_sd _mm_ucomile_sd _mm_ucomigt_sd _mm_ucomige_sd _mm_ucomineq_sd); { let mut o = Out::new("_mm_movemask_pd"); for &x in &lanes64(rng) { for &y in EDGE64.iter() { o.put(&[x, y], [movemask(pd(x, y)) as u64, 0]); } } o.end(); } shuffles(rng); memory(rng); casts(rng); conversions(rng); mandel(); } }