use super::fused::{
FCHUNK, RunCursor, Window, dispatch_bin_kernel, dispatch_un_kernel, exec_fused_runs_generic,
node_elems,
};
#[cfg(target_arch = "x86_64")]
use super::fused::{exec_fused_runs_avx2, exec_fused_runs_avx512};
use super::*;
fn adversarial(n: usize, seed: u64, with_nan: bool) -> Vec<f64> {
let specials = [
if with_nan { f64::NAN } else { -7.25 },
1.5e-310, 0.0,
-0.0,
f64::INFINITY,
f64::NEG_INFINITY,
f64::MIN_POSITIVE, 5e-324, -5e-324,
2.2e-308, f64::MAX,
f64::MIN,
1.0,
-1.0,
1.5,
-2.5,
1e308,
-1e308,
3.5e-320, 0.1,
];
let mut x = seed.wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(1);
(0..n)
.map(|k| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
if k % 3 == 0 {
specials[(x as usize) % specials.len()]
} else {
let u = (x >> 11) as f64 / (1u64 << 53) as f64;
(u - 0.5) * 10f64.powi((x % 61) as i32 - 30)
}
})
.collect()
}
fn drive(with_nan: bool) {
const N: usize = 2500; let a = adversarial(N, 1, with_nan);
let b = adversarial(N, 2, with_nan);
let shifted = adversarial(N + 16, 3, with_nan);
let strided = adversarial(2 * N + 8, 4, with_nan);
let svals = [
if with_nan { f64::NAN } else { 3.75 },
-0.0,
2.5,
5e-324,
f64::INFINITY,
];
let inputs = vec![
FusedInput {
src: SrcRef::Slot(0),
shifted_ix: None,
src_shape: DimU::from_elem(N, 1),
elem_stride: 1,
load_reg: GroupIx::MAX,
index: None,
gather: None,
},
FusedInput {
src: SrcRef::Slot(1),
shifted_ix: None,
src_shape: DimU::from_elem(N, 1),
elem_stride: 1,
load_reg: GroupIx::MAX,
index: None,
gather: None,
},
FusedInput {
src: SrcRef::Slot(2),
shifted_ix: Some(0),
src_shape: DimU::from_elem(N + 16, 1),
elem_stride: 1,
load_reg: GroupIx::MAX,
index: None,
gather: None,
},
FusedInput {
src: SrcRef::Slot(3),
shifted_ix: Some(1),
src_shape: DimU::from_elem(2 * N + 8, 1),
elem_stride: 2,
load_reg: GroupIx::MAX, index: None,
gather: None,
},
];
let mut micro: Vec<MicroOp> = Vec::new();
let bins = [
BinCode::Add,
BinCode::Sub,
BinCode::Mul,
BinCode::Div,
BinCode::Pow,
BinCode::Min,
BinCode::Max,
BinCode::Eq,
BinCode::Ne,
BinCode::Lt,
BinCode::Le,
BinCode::Gt,
BinCode::Ge,
];
for (i, op) in bins.iter().enumerate() {
let (a, b) = match i % 4 {
0 => (MRef::In(0), MRef::In(1)),
1 => (MRef::In(2), MRef::In(0)),
2 => (MRef::Scal(i as GroupIx % 5), MRef::In(3)),
_ => (MRef::In(1), MRef::Scal((i as GroupIx + 2) % 5)),
};
let out = micro.len() as GroupIx;
micro.push(MicroOp::Bin { op: *op, a, b, out });
}
let uns = [
UnCode::Abs,
UnCode::Sqrt,
UnCode::Exp,
UnCode::Ln,
UnCode::Log10,
UnCode::Sin,
UnCode::Cos,
UnCode::Tanh,
UnCode::Floor,
UnCode::Ceil,
UnCode::Sign,
];
for (i, op) in uns.iter().enumerate() {
let a = match i % 3 {
0 => MRef::In(0),
1 => MRef::In(2),
_ => MRef::Reg(i as GroupIx), };
let out = micro.len() as GroupIx;
micro.push(MicroOp::Un { op: *op, a, out });
}
let out = micro.len() as GroupIx;
micro.push(MicroOp::Neg {
a: MRef::In(3),
out,
});
let out = micro.len() as GroupIx;
micro.push(MicroOp::Select {
cond: MRef::Reg(9), a: MRef::In(0),
b: MRef::In(1),
out,
});
let out = micro.len() as GroupIx;
micro.push(MicroOp::Select {
cond: MRef::In(2),
a: MRef::Reg(0),
b: MRef::Scal(1),
out,
});
let out = micro.len() as GroupIx;
micro.push(MicroOp::Select {
cond: MRef::Scal(2),
a: MRef::In(1),
b: MRef::In(0),
out,
});
let out = micro.len() as GroupIx;
micro.push(MicroOp::Mov {
a: MRef::In(3),
out,
});
let arith = [BinCode::Add, BinCode::Sub, BinCode::Mul, BinCode::Div];
for op1 in arith {
for op2 in arith {
for swap in [false, true] {
let out = micro.len() as GroupIx;
micro.push(MicroOp::Bin2 {
op1,
a: MRef::In(0),
b: MRef::In(1),
op2,
c: MRef::In(2),
swap,
out,
});
}
}
}
let ext = [
(BinCode::Mul, BinCode::Gt),
(BinCode::Mul, BinCode::Ge),
(BinCode::Mul, BinCode::Lt),
(BinCode::Mul, BinCode::Le),
(BinCode::Min, BinCode::Max),
(BinCode::Max, BinCode::Min),
(BinCode::Mul, BinCode::Min),
(BinCode::Min, BinCode::Mul),
(BinCode::Mul, BinCode::Max),
(BinCode::Max, BinCode::Mul),
];
for (i, (op1, op2)) in ext.iter().enumerate() {
for swap in [false, true] {
let out = micro.len() as GroupIx;
micro.push(MicroOp::Bin2 {
op1: *op1,
a: MRef::In(1),
b: MRef::In(2),
op2: *op2,
c: if i % 2 == 0 {
MRef::In(0)
} else {
MRef::Scal(i as GroupIx % 5)
},
swap,
out,
});
}
}
let mut pat = 0usize;
for op1 in arith {
for op2 in arith {
for op3 in arith {
for (swap2, swap3) in [(false, false), (true, false), (false, true), (true, true)] {
let (a, b, c, d) = match pat % 4 {
0 => (MRef::In(0), MRef::In(1), MRef::In(2), MRef::In(3)),
1 => (MRef::In(2), MRef::Scal(0), MRef::In(0), MRef::Scal(3)),
2 => (MRef::Scal(2), MRef::In(3), MRef::Scal(1), MRef::In(1)),
_ => (MRef::In(1), MRef::In(0), MRef::Reg(0), MRef::In(2)),
};
pat += 1;
let out = micro.len() as GroupIx;
micro.push(MicroOp::Bin3 {
op1,
a,
b,
op2,
c,
swap2,
op3,
d,
swap3,
out,
});
}
}
}
}
let n_ops = micro.len();
let n_regs = n_ops as GroupIx;
let mut inputs = inputs;
inputs[3].load_reg = n_regs;
let schedule = RunSchedule {
nodes: vec![
RunNode::Repeat {
count: 2,
body: 1,
out_step: 650,
in_step: SmallVec::from_slice(&[650, 2 * 650]),
},
RunNode::Run(FusedRun {
out_off: 0,
len: 650,
in_off: SmallVec::from_slice(&[5i64, 3]),
}),
RunNode::Run(FusedRun {
out_off: 1300,
len: (N - 1300) as u32,
in_off: SmallVec::from_slice(&[GHOST_OFF, 3 + 2 * 1300]),
}),
],
n_runs: 3,
depth: 1,
};
let fs = FusedSpec {
shape: DimU::from_elem(N, 1),
inputs,
scalars: Vec::new(), micro,
n_regs,
n_load_regs: 1,
n_splat_regs: 6, outputs: SmallVec::new(), direct: SmallVec::new(),
scan_fuse: SmallVec::new(),
schedule,
reduce: None,
interleave: None,
pre: None,
n_fused_instrs: 0,
n_folded_gathers: 0,
};
let bases: Vec<*const f64> = vec![a.as_ptr(), b.as_ptr(), shifted.as_ptr(), strided.as_ptr()];
let ne = node_elems(&fs.schedule.nodes);
let run_windows = |wider: u8, windows: &[(usize, usize, usize)]| -> Vec<Vec<f64>> {
let mut outbufs: Vec<Vec<f64>> = (0..n_ops).map(|_| vec![0.0f64; N]).collect();
let outs: Vec<(GroupIx, *mut f64)> = outbufs
.iter_mut()
.enumerate()
.map(|(i, buf)| (i as GroupIx, buf.as_mut_ptr()))
.collect();
let mut fregs = vec![0.0f64; (n_regs as usize + 1 + 6) * FCHUNK];
let mut cursor = RunCursor::for_spec(&fs);
for &(lo, hi, period) in windows {
let win = Window {
lo,
hi,
period,
node_elems: &ne,
};
match wider {
0 => unsafe {
exec_fused_runs_generic(
&fs,
&svals,
&bases,
&outs,
std::ptr::null_mut(),
&[],
&mut fregs,
&mut cursor,
win,
)
},
#[cfg(target_arch = "x86_64")]
1 => unsafe {
exec_fused_runs_avx2(
&fs,
&svals,
&bases,
&outs,
std::ptr::null_mut(),
&[],
&mut fregs,
&mut cursor,
win,
)
},
#[cfg(target_arch = "x86_64")]
2 => unsafe {
exec_fused_runs_avx512(
&fs,
&svals,
&bases,
&outs,
std::ptr::null_mut(),
&[],
&mut fregs,
&mut cursor,
win,
)
},
_ => panic!("level unavailable in this build"),
}
}
outbufs
};
let run_level = |wider: u8| run_windows(wider, &[(0, usize::MAX, 0)]);
let reference = run_level(0);
for cuts in [
&[0usize, 8, 650, 1024, 1296, 1304, 2048, N][..],
&[0, 1, 649, 651, 1299, 1301, N - 1, N],
&[0, 640, 1952, N],
] {
let windows: Vec<(usize, usize, usize)> =
cuts.windows(2).map(|w| (w[0], w[1], 0)).collect();
assert_bits_eq(&reference, &run_windows(0, &windows), "windows", with_nan);
let rev: Vec<(usize, usize, usize)> = windows.iter().rev().copied().collect();
assert_bits_eq(
&reference,
&run_windows(0, &rev),
"windows reversed",
with_nan,
);
}
for (period, cuts) in [
(650usize, &[0usize, 8, 333, 650][..]),
(1000, &[0, 1, 999, 1000]),
(N, &[0, 1300, N]),
] {
let windows: Vec<(usize, usize, usize)> =
cuts.windows(2).map(|w| (w[0], w[1], period)).collect();
assert_bits_eq(&reference, &run_windows(0, &windows), "periodic", with_nan);
}
#[cfg(target_arch = "x86_64")]
{
if std::arch::is_x86_feature_detected!("avx2") {
let got = run_level(1);
assert_bits_eq(&reference, &got, "avx2", with_nan);
}
if std::arch::is_x86_feature_detected!("avx512f")
&& std::arch::is_x86_feature_detected!("avx512vl")
&& std::arch::is_x86_feature_detected!("avx512dq")
&& std::arch::is_x86_feature_detected!("avx512bw")
{
let got = run_level(2);
assert_bits_eq(&reference, &got, "avx512", with_nan);
}
}
#[cfg(not(target_arch = "x86_64"))]
let _ = reference;
}
#[test]
fn simd_clone_bit_identity() {
drive(false);
}
#[test]
fn simd_clone_bit_identity_nan_inputs() {
drive(true);
}
fn assert_bits_eq(want: &[Vec<f64>], got: &[Vec<f64>], label: &str, nan_class: bool) {
for (op, (w, g)) in want.iter().zip(got.iter()).enumerate() {
for (k, (a, b)) in w.iter().zip(g.iter()).enumerate() {
if nan_class && a.is_nan() && b.is_nan() {
continue;
}
assert_eq!(
a.to_bits(),
b.to_bits(),
"{label}: micro-op {op} elem {k}: generic {a:?} ({:016x}) vs {label} {b:?} ({:016x})",
a.to_bits(),
b.to_bits()
);
}
}
}
#[test]
fn dispatch_arms_match_the_kernel_tables() {
use {BinCode as B, UnCode as U};
const XS: &[f64] = &[
0.0,
-0.0,
1.0,
-1.0,
0.5,
-3.25,
2.0,
0.999,
1.5,
f64::MIN_POSITIVE,
5e-324,
1e300,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NAN,
];
let bins = [
B::Add,
B::Sub,
B::Mul,
B::Div,
B::Pow,
B::Atan2,
B::Min,
B::Max,
B::Eq,
B::Ne,
B::Lt,
B::Le,
B::Gt,
B::Ge,
B::And,
B::Or,
B::Unknown,
];
let uns = [
U::Exp,
U::Ln,
U::Log10,
U::Sqrt,
U::Abs,
U::Sign,
U::Floor,
U::Ceil,
U::Sin,
U::Cos,
U::Tan,
U::Asin,
U::Acos,
U::Atan,
U::Sinh,
U::Cosh,
U::Tanh,
U::Asinh,
U::Acosh,
U::Atanh,
U::Not,
U::Unknown,
];
for op in bins {
for &x in XS {
for &y in XS {
macro_rules! k {
($f:expr) => {{
let f = $f;
f(x, y)
}};
}
let got: f64 = dispatch_bin_kernel!(&op, k);
let want = binary_kernel_of(op)(x, y);
assert_eq!(got.to_bits(), want.to_bits(), "{op:?}({x:?}, {y:?})");
}
}
}
for op in uns {
for &x in XS {
macro_rules! k {
($f:expr) => {{
let f = $f;
f(x)
}};
}
let got: f64 = dispatch_un_kernel!(&op, k);
let want = unary_kernel_of(op)(x);
assert_eq!(got.to_bits(), want.to_bits(), "{op:?}({x:?})");
}
}
}