use super::kernels::KernelFloat;
pub(super) type RawKernel<F> = unsafe fn(usize, *const F, *const F, *mut F);
pub(super) type FusedKernel<F> = unsafe fn(usize, *const F, *const F, F, F, *mut F, isize);
#[derive(Clone, Copy)]
pub(super) struct Kernels<F> {
pub raw: RawKernel<F>,
pub fused: Option<FusedKernel<F>>,
}
pub(super) unsafe fn scalar<F: KernelFloat, const MR: usize, const NR: usize>(
k: usize,
a: *const F,
b: *const F,
tile: *mut F,
) {
let mut acc = [[F::ZERO; NR]; MR];
for p in 0..k {
for (i, row) in acc.iter_mut().enumerate() {
let ai = unsafe { *a.add(p * MR + i) };
for (j, value) in row.iter_mut().enumerate() {
*value = *value + ai * unsafe { *b.add(p * NR + j) };
}
}
}
for (i, row) in acc.iter().enumerate() {
for (j, &value) in row.iter().enumerate() {
unsafe { *tile.add(i * NR + j) = value };
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn avx2_fma() -> bool {
use std::sync::OnceLock;
static AVAILABLE: OnceLock<bool> = OnceLock::new();
*AVAILABLE.get_or_init(|| {
std::is_x86_feature_detected!("avx2") && std::is_x86_feature_detected!("fma")
})
}
pub(super) fn f64_kernels() -> Kernels<f64> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if avx2_fma() {
return Kernels {
raw: avx2::d6x8_raw,
fused: Some(avx2::d6x8_fused),
};
}
Kernels {
raw: scalar::<f64, 6, 8>,
fused: None,
}
}
pub(super) fn f32_kernels() -> Kernels<f32> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if avx2_fma() {
return Kernels {
raw: avx2::s6x16_raw,
fused: Some(avx2::s6x16_fused),
};
}
Kernels {
raw: scalar::<f32, 6, 16>,
fused: None,
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod avx2 {
#[cfg(target_arch = "x86")]
use std::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
macro_rules! accumulate {
($k:expr, $a:expr, $b:expr, $nr:expr, $half:expr, $zero:ident, $load:ident,
$set1:ident, $fma:ident) => {{
let (k, a, b) = ($k, $a, $b);
let mut acc = [$zero(); 12];
let mut p = 0;
macro_rules! step {
($q:expr) => {{
let q = $q;
let b0 = $load(b.add(q * $nr));
let b1 = $load(b.add(q * $nr + $half));
for i in 0..6 {
let ai = $set1(*a.add(q * 6 + i));
acc[2 * i] = $fma(ai, b0, acc[2 * i]);
acc[2 * i + 1] = $fma(ai, b1, acc[2 * i + 1]);
}
}};
}
while p + 2 <= k {
step!(p);
step!(p + 1);
p += 2;
}
if p < k {
step!(p);
}
acc
}};
}
#[inline]
unsafe fn prefetch_rows<T>(c: *mut T, rs: isize, bytes: usize) {
for i in 0..6 {
let row = c.wrapping_offset(i as isize * rs).cast::<i8>();
unsafe {
_mm_prefetch::<_MM_HINT_T0>(row);
_mm_prefetch::<_MM_HINT_T0>(row.wrapping_add(bytes - 1));
}
}
}
#[target_feature(enable = "avx2,fma")]
unsafe fn d6x8_acc(k: usize, a: *const f64, b: *const f64) -> [__m256d; 12] {
unsafe {
accumulate!(
k,
a,
b,
8,
4,
_mm256_setzero_pd,
_mm256_loadu_pd,
_mm256_set1_pd,
_mm256_fmadd_pd
)
}
}
#[target_feature(enable = "avx2,fma")]
pub(super) unsafe fn d6x8_raw(k: usize, a: *const f64, b: *const f64, tile: *mut f64) {
unsafe {
let acc = d6x8_acc(k, a, b);
for (i, v) in acc.iter().enumerate() {
_mm256_storeu_pd(tile.add(i * 4), *v);
}
}
}
#[target_feature(enable = "avx2,fma")]
pub(super) unsafe fn d6x8_fused(
k: usize,
a: *const f64,
b: *const f64,
alpha: f64,
beta: f64,
c: *mut f64,
rs: isize,
) {
unsafe {
prefetch_rows(c, rs, 8 * 8);
let acc = d6x8_acc(k, a, b);
let va = _mm256_set1_pd(alpha);
let vb = _mm256_set1_pd(beta);
for i in 0..6 {
let row = c.offset(i as isize * rs);
let mut x0 = _mm256_mul_pd(va, acc[2 * i]);
let mut x1 = _mm256_mul_pd(va, acc[2 * i + 1]);
if beta != 0.0 {
x0 = _mm256_fmadd_pd(vb, _mm256_loadu_pd(row), x0);
x1 = _mm256_fmadd_pd(vb, _mm256_loadu_pd(row.add(4)), x1);
}
_mm256_storeu_pd(row, x0);
_mm256_storeu_pd(row.add(4), x1);
}
}
}
#[target_feature(enable = "avx2,fma")]
unsafe fn s6x16_acc(k: usize, a: *const f32, b: *const f32) -> [__m256; 12] {
unsafe {
accumulate!(
k,
a,
b,
16,
8,
_mm256_setzero_ps,
_mm256_loadu_ps,
_mm256_set1_ps,
_mm256_fmadd_ps
)
}
}
#[target_feature(enable = "avx2,fma")]
pub(super) unsafe fn s6x16_raw(k: usize, a: *const f32, b: *const f32, tile: *mut f32) {
unsafe {
let acc = s6x16_acc(k, a, b);
for (i, v) in acc.iter().enumerate() {
_mm256_storeu_ps(tile.add(i * 8), *v);
}
}
}
#[target_feature(enable = "avx2,fma")]
pub(super) unsafe fn s6x16_fused(
k: usize,
a: *const f32,
b: *const f32,
alpha: f32,
beta: f32,
c: *mut f32,
rs: isize,
) {
unsafe {
prefetch_rows(c, rs, 16 * 4);
let acc = s6x16_acc(k, a, b);
let va = _mm256_set1_ps(alpha);
let vb = _mm256_set1_ps(beta);
for i in 0..6 {
let row = c.offset(i as isize * rs);
let mut x0 = _mm256_mul_ps(va, acc[2 * i]);
let mut x1 = _mm256_mul_ps(va, acc[2 * i + 1]);
if beta != 0.0 {
x0 = _mm256_fmadd_ps(vb, _mm256_loadu_ps(row), x0);
x1 = _mm256_fmadd_ps(vb, _mm256_loadu_ps(row.add(8)), x1);
}
_mm256_storeu_ps(row, x0);
_mm256_storeu_ps(row.add(8), x1);
}
}
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn dispatched_kernels_match_scalar_on_exact_small_integers() {
for k in [0usize, 1, 7, 255, 256, 257] {
let a: Vec<f64> = (0..k * 6).map(|i| (i % 7) as f64 - 3.0).collect();
let b: Vec<f64> = (0..k * 8).map(|i| (i % 5) as f64 - 2.0).collect();
let (mut portable, mut native) = ([0.0; 48], [0.0; 48]);
let kernels = f64_kernels();
unsafe {
scalar::<f64, 6, 8>(k, a.as_ptr(), b.as_ptr(), portable.as_mut_ptr());
(kernels.raw)(k, a.as_ptr(), b.as_ptr(), native.as_mut_ptr());
}
assert_eq!(portable, native);
if let Some(fused) = kernels.fused {
let mut c = vec![1.0; 6 * 11];
unsafe { fused(k, a.as_ptr(), b.as_ptr(), 2.0, 3.0, c.as_mut_ptr(), 11) };
for i in 0..6 {
for j in 0..8 {
assert_eq!(c[i * 11 + j], 2.0 * portable[i * 8 + j] + 3.0);
}
assert_eq!(&c[i * 11 + 8..i * 11 + 11], &[1.0; 3]);
}
}
let a: Vec<f32> = a.iter().map(|&x| x as f32).collect();
let b: Vec<f32> = (0..k * 16).map(|i| (i % 5) as f32 - 2.0).collect();
let (mut portable, mut native) = ([0.0f32; 96], [0.0f32; 96]);
let kernels = f32_kernels();
unsafe {
scalar::<f32, 6, 16>(k, a.as_ptr(), b.as_ptr(), portable.as_mut_ptr());
(kernels.raw)(k, a.as_ptr(), b.as_ptr(), native.as_mut_ptr());
}
assert_eq!(portable, native);
if let Some(fused) = kernels.fused {
let mut c = vec![f32::NAN; 6 * 16];
unsafe { fused(k, a.as_ptr(), b.as_ptr(), 1.0, 0.0, c.as_mut_ptr(), 16) };
assert_eq!(&c[..], &portable[..]);
}
}
}
}