use super::blas::Op;
use crate::math::complex::{Complex, Float};
use core::mem::MaybeUninit;
use core::ptr;
#[cfg(all(target_arch = "aarch64", target_endian = "little", not(miri)))]
#[path = "gemm/aarch64.rs"]
mod kernels;
#[cfg(not(all(target_arch = "aarch64", target_endian = "little", not(miri))))]
#[path = "gemm/portable.rs"]
mod kernels;
pub(super) trait Real: Float {
const MR: usize;
const NR: usize;
const KC: usize;
const MC: usize;
const NC: usize;
const SKINNY: usize;
fn width(r: usize) -> usize;
#[allow(clippy::too_many_arguments)]
unsafe fn kernel(
nr: usize,
kc: usize,
a: *const Self,
sa: usize,
b: *const Self,
c: *mut Self,
ldc: usize,
beta: Self,
);
fn fma(c: Self, a: Self, b: Self) -> Self;
#[allow(clippy::too_many_arguments)]
unsafe fn pack_t(
dst: *mut Self,
src: *const Self,
ld: usize,
h: usize,
kc: usize,
w: usize,
scale: Self,
);
#[allow(clippy::too_many_arguments)]
unsafe fn direct(
m: usize,
n: usize,
k: usize,
alpha: Self,
a: *const Self,
lda: usize,
b: *const Self,
rsb: usize,
csb: usize,
beta: Self,
c: *mut Self,
ldc: usize,
);
#[allow(clippy::too_many_arguments)]
unsafe fn direct_complex<const CJ: bool>(
m: usize,
n: usize,
k: usize,
alpha: Complex<Self>,
a: *const Complex<Self>,
lda: usize,
b: *const Complex<Self>,
rsb: usize,
csb: usize,
beta: Complex<Self>,
c: *mut Complex<Self>,
ldc: usize,
);
}
trait Elem: Copy + PartialEq {
type R: Real;
const W: usize;
const ZERO: Self;
const ONE: Self;
unsafe fn pack_a(dst: *mut Self::R, a: *const Self, lda: usize, op: Op, h: usize, kc: usize);
#[allow(clippy::too_many_arguments)]
unsafe fn pack_b(
dst: *mut Self::R,
b: *const Self,
ldb: usize,
op: Op,
kc: usize,
h: usize,
nr: usize,
alpha: Self,
);
}
impl<R: Real> Elem for R {
type R = R;
const W: usize = 1;
const ZERO: Self = R::ZERO;
const ONE: Self = R::ONE;
#[inline(always)]
unsafe fn pack_a(dst: *mut R, a: *const R, lda: usize, op: Op, h: usize, kc: usize) {
unsafe {
if op == Op::N {
pack_n(dst, a, lda, h, kc, R::MR, R::ONE);
} else {
R::pack_t(dst, a, lda, h, kc, R::MR, R::ONE);
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn pack_b(
dst: *mut R,
b: *const R,
ldb: usize,
op: Op,
kc: usize,
h: usize,
nr: usize,
alpha: R,
) {
unsafe {
if op == Op::N {
R::pack_t(dst, b, ldb, h, kc, nr, alpha);
} else {
pack_n(dst, b, ldb, h, kc, nr, alpha);
}
}
}
}
impl<R: Real> Elem for Complex<R> {
type R = R;
const W: usize = 2;
const ZERO: Self = Complex::new(R::ZERO, R::ZERO);
const ONE: Self = Complex::new(R::ONE, R::ZERO);
#[inline(always)]
unsafe fn pack_a(dst: *mut R, a: *const Self, lda: usize, op: Op, h: usize, kc: usize) {
let mr = R::MR / 2;
unsafe {
for p in 0..kc {
let row = dst.add(2 * p * R::MR);
for i in 0..h {
let x = match op {
Op::N => *a.add(i + p * lda),
Op::T => *a.add(p + i * lda),
Op::C => (*a.add(p + i * lda)).conj(),
};
*row.add(2 * i) = x.re;
*row.add(2 * i + 1) = x.im;
*row.add(R::MR + 2 * i) = -x.im;
*row.add(R::MR + 2 * i + 1) = x.re;
}
for i in h..mr {
for q in [2 * i, 2 * i + 1, R::MR + 2 * i, R::MR + 2 * i + 1] {
*row.add(q) = R::ZERO;
}
}
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn pack_b(
dst: *mut R,
b: *const Self,
ldb: usize,
op: Op,
kc: usize,
h: usize,
nr: usize,
alpha: Self,
) {
unsafe {
for p in 0..kc {
let row = dst.add(2 * p * nr);
for j in 0..h {
let x = match op {
Op::N => *b.add(p + j * ldb),
Op::T => *b.add(j + p * ldb),
Op::C => (*b.add(j + p * ldb)).conj(),
};
let x = x * alpha;
*row.add(j) = x.re;
*row.add(nr + j) = x.im;
}
for j in h..nr {
*row.add(j) = R::ZERO;
*row.add(nr + j) = R::ZERO;
}
}
}
}
}
#[inline(always)]
unsafe fn pack_n<R: Real>(
dst: *mut R,
src: *const R,
ld: usize,
h: usize,
kc: usize,
w: usize,
scale: R,
) {
unsafe {
if h == w && scale == R::ONE {
for p in 0..kc {
ptr::copy_nonoverlapping(src.add(p * ld), dst.add(p * w), w);
}
} else {
for p in 0..kc {
let (s, d) = (src.add(p * ld), dst.add(p * w));
for r in 0..h {
*d.add(r) = *s.add(r) * scale;
}
for r in h..w {
*d.add(r) = R::ZERO;
}
}
}
}
}
const IN_PLACE: usize = 16;
const STACK: usize = 4096;
#[repr(C, align(128))]
struct Stack<R>([MaybeUninit<R>; STACK]);
#[inline(never)]
#[allow(clippy::too_many_arguments)]
unsafe fn blocked<E: Elem>(
m: usize,
n: usize,
k: usize,
alpha: E,
a: *const E,
lda: usize,
opa: Op,
b: *const E,
ldb: usize,
opb: Op,
beta: E::R,
c: *mut E::R,
ldc: usize,
) {
let w = E::W;
let (mr, nr) = (E::R::MR / w, E::R::NR);
let kcb = k.div_ceil(k.div_ceil(E::R::KC / w));
let mcb = m.div_ceil(m.div_ceil(E::R::MC / w)).next_multiple_of(mr);
let ncb = n.div_ceil(n.div_ceil(E::R::NC)).next_multiple_of(nr);
let (kc_max, mc_max, nc_max) = (kcb, mcb, ncb + nr);
let a_len = mc_max * kc_max * w * w;
let b_len = nc_max * kc_max * w;
let run = |buf: *mut E::R| {
let (ap, bp) = (buf, buf.wrapping_add(a_len.next_multiple_of(16)));
let (zero, one) = (<E::R as Float>::ZERO, <E::R as Float>::ONE);
let mut tile = [zero; 128];
unsafe {
let mut jc = 0;
while jc < n {
let nc = ncb.min(n - jc);
let mut pc = 0;
while pc < k {
let kc = kcb.min(k - pc);
let beta = if pc == 0 { beta } else { one };
let mut jr = 0;
while jr < nc {
let nr = E::R::width(nc - jr);
let (j, h) = (jc + jr, nr.min(nc - jr));
let bs = if opb == Op::N {
pc + j * ldb
} else {
j + pc * ldb
};
E::pack_b(bp.add(jr * kc * w), b.add(bs), ldb, opb, kc, h, nr, alpha);
jr += nr;
}
let in_place = w == 1 && opa == Op::N && nc <= IN_PLACE * nr;
let mut ic = 0;
while ic < m {
let mc = mcb.min(m - ic);
let mut jr = 0;
while jr < nc {
let nr = E::R::width(nc - jr);
let nn = nr.min(nc - jr);
let bt = bp.add(jr * kc * w);
let mut ir = 0;
while ir < mc {
let mm = mr.min(mc - ir);
let i = ic + ir;
let as_ = if opa == Op::N {
i + pc * lda
} else {
pc + i * lda
};
let (at, sa) = if in_place && mm == mr {
(a.add(as_).cast::<E::R>(), lda)
} else {
let at = ap.add(ir * kc * w * w);
if jr == 0 {
E::pack_a(at, a.add(as_), lda, opa, mm, kc);
}
(at.cast_const(), E::R::MR)
};
let ct = c.add((ic + ir) * w + (jc + jr) * ldc);
if mm == mr && nn == nr {
E::R::kernel(nr, kc * w, at, sa, bt, ct, ldc, beta);
} else {
let t = tile.as_mut_ptr();
E::R::kernel(nr, kc * w, at, sa, bt, t, E::R::MR, zero);
for j in 0..nn {
for i in 0..mm * w {
let x = ct.add(i + j * ldc);
let y = *t.add(i + j * E::R::MR);
*x = if beta == zero {
y
} else {
E::R::fma(y, beta, *x)
};
}
}
}
ir += mr;
}
jr += nr;
}
ic += mc;
}
pc += kc;
}
jc += nc;
}
}
};
let need = a_len.next_multiple_of(16) + b_len;
if need <= STACK {
let mut s = Stack::<E::R>([const { MaybeUninit::uninit() }; STACK]);
run(s.0.as_mut_ptr().cast());
} else {
let mut v: Vec<E::R> = Vec::with_capacity(need + 32);
let p = v.as_mut_ptr();
let skip = p.align_offset(128).min(32);
run(p.wrapping_add(skip));
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn tile<R: Real, const MR: usize, const NR: usize>(
k: usize,
a: *const R,
lda: usize,
b: *const R,
rsb: usize,
csb: usize,
c: *mut R,
ldc: usize,
alpha: R,
beta: R,
) {
unsafe {
let mut acc = [[R::ZERO; MR]; NR];
for p in 0..k {
let mut col = [R::ZERO; MR];
for (i, x) in col.iter_mut().enumerate() {
*x = *a.add(i + p * lda);
}
for (j, sums) in acc.iter_mut().enumerate() {
let y = *b.add(p * rsb + j * csb);
for (s, x) in sums.iter_mut().zip(&col) {
*s = R::fma(*s, *x, y);
}
}
}
for (j, sums) in acc.iter().enumerate() {
for (i, s) in sums.iter().enumerate() {
let x = c.add(i + j * ldc);
*x = if beta == R::ZERO {
alpha * *s
} else {
R::fma(beta * *x, alpha, *s)
};
}
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
pub(super) unsafe fn tiles<R: Real, const MR: usize>(
n: usize,
k: usize,
a: *const R,
lda: usize,
b: *const R,
rsb: usize,
csb: usize,
c: *mut R,
ldc: usize,
alpha: R,
beta: R,
) {
unsafe {
let mut j = 0;
while j + 4 <= n {
tile::<R, MR, 4>(
k,
a,
lda,
b.add(j * csb),
rsb,
csb,
c.add(j * ldc),
ldc,
alpha,
beta,
);
j += 4;
}
let (b, c) = (b.wrapping_add(j * csb), c.wrapping_add(j * ldc));
match n - j {
3 => tile::<R, MR, 3>(k, a, lda, b, rsb, csb, c, ldc, alpha, beta),
2 => tile::<R, MR, 2>(k, a, lda, b, rsb, csb, c, ldc, alpha, beta),
1 => tile::<R, MR, 1>(k, a, lda, b, rsb, csb, c, ldc, alpha, beta),
_ => {}
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
pub(super) unsafe fn complex_tile<R: Real, const MR: usize, const NR: usize, const CJ: bool>(
k: usize,
a: *const Complex<R>,
lda: usize,
b: *const Complex<R>,
rsb: usize,
csb: usize,
c: *mut Complex<R>,
ldc: usize,
alpha: Complex<R>,
beta: Complex<R>,
) {
unsafe {
let mut re = [[R::ZERO; MR]; NR];
let mut im = [[R::ZERO; MR]; NR];
for p in 0..k {
let mut col = [Complex::<R>::ZERO; MR];
for (i, x) in col.iter_mut().enumerate() {
*x = *a.add(i + p * lda);
}
for j in 0..NR {
let y = *b.add(p * rsb + j * csb);
let yi = if CJ { -y.im } else { y.im };
for (i, x) in col.iter().enumerate() {
re[j][i] = R::fma(R::fma(re[j][i], x.re, y.re), -x.im, yi);
im[j][i] = R::fma(R::fma(im[j][i], x.re, yi), x.im, y.re);
}
}
}
for j in 0..NR {
for i in 0..MR {
let x = c.add(i + j * ldc);
let s = alpha * Complex::new(re[j][i], im[j][i]);
*x = if beta == Complex::ZERO {
s
} else {
s + beta * *x
};
}
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
pub(super) unsafe fn complex_tiles<R: Real, const MR: usize, const CJ: bool>(
n: usize,
k: usize,
a: *const Complex<R>,
lda: usize,
b: *const Complex<R>,
rsb: usize,
csb: usize,
c: *mut Complex<R>,
ldc: usize,
alpha: Complex<R>,
beta: Complex<R>,
) {
unsafe {
let mut j = 0;
while j + 4 <= n {
let (bj, cj) = (b.add(j * csb), c.add(j * ldc));
complex_tile::<R, MR, 4, CJ>(k, a, lda, bj, rsb, csb, cj, ldc, alpha, beta);
j += 4;
}
let (b, c) = (b.wrapping_add(j * csb), c.wrapping_add(j * ldc));
match n - j {
3 => complex_tile::<R, MR, 3, CJ>(k, a, lda, b, rsb, csb, c, ldc, alpha, beta),
2 => complex_tile::<R, MR, 2, CJ>(k, a, lda, b, rsb, csb, c, ldc, alpha, beta),
1 => complex_tile::<R, MR, 1, CJ>(k, a, lda, b, rsb, csb, c, ldc, alpha, beta),
_ => {}
}
}
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn is_small(
m: usize,
n: usize,
k: usize,
lda: usize,
opa: Op,
ldb: usize,
opb: Op,
ldc: usize,
) -> bool {
if m.saturating_mul(n).saturating_mul(k) > SMALL_MNK || (opa != Op::N && m * k > SMALL_A) {
return false;
}
let a = if opa == Op::N {
lda * (k - 1) + m
} else {
m * k
};
let b = if opb == Op::N {
ldb * (n - 1) + k
} else {
ldb * (k - 1) + n
};
let c = ldc * (n - 1) + m;
k <= 8 || a.saturating_add(b).saturating_add(c) <= SMALL_SPAN
}
const SMALL_A: usize = 4096;
const SMALL_MNK: usize = 64 * 64 * 64;
const SMALL_SPAN: usize = 3 * 64 * 64;
#[inline(always)]
unsafe fn with_a<E: Copy + Conj, X>(
m: usize,
k: usize,
a: *const E,
lda: usize,
op: Op,
f: impl FnOnce(*const E, usize) -> X,
) -> X {
if op == Op::N {
f(a, lda)
} else {
unsafe { transposed(m, k, a, lda, op, f) }
}
}
#[inline(never)]
unsafe fn transposed<E: Copy + Conj, X>(
m: usize,
k: usize,
a: *const E,
lda: usize,
op: Op,
f: impl FnOnce(*const E, usize) -> X,
) -> X {
let mut t = [const { MaybeUninit::<E>::uninit() }; SMALL_A];
let d = t.as_mut_ptr().cast::<E>();
unsafe {
for i in 0..m {
let s = a.add(i * lda);
for p in 0..k {
let x = *s.add(p);
*d.add(i + p * m) = if op == Op::C { x.conj() } else { x };
}
}
}
f(d, m)
}
trait Conj {
fn conj(self) -> Self;
}
impl<R: Real> Conj for R {
#[inline(always)]
fn conj(self) -> Self {
self
}
}
impl<R: Real> Conj for Complex<R> {
#[inline(always)]
fn conj(self) -> Self {
Complex::conj(self)
}
}
#[inline(never)]
#[allow(clippy::too_many_arguments)]
unsafe fn skinny<R: Real>(
m: usize,
n: usize,
k: usize,
alpha: R,
a: *const R,
lda: usize,
opa: Op,
b: *const R,
ldb: usize,
opb: Op,
beta: R,
c: *mut R,
ldc: usize,
) {
let kcb = k.div_ceil(k.div_ceil(R::KC));
let (rsb, csb) = if opb == Op::N { (1, ldb) } else { (ldb, 1) };
let mut buf: Vec<R> = Vec::with_capacity(m * kcb);
let d = buf.as_mut_ptr();
unsafe {
let mut pc = 0;
while pc < k {
let kc = kcb.min(k - pc);
if opa == Op::N {
for p in 0..kc {
ptr::copy_nonoverlapping(a.add((pc + p) * lda), d.add(p * m), m);
}
} else {
for i in 0..m {
let s = a.add(pc + i * lda);
for p in 0..kc {
*d.add(i + p * m) = *s.add(p);
}
}
}
let beta = if pc == 0 { beta } else { R::ONE };
R::direct(
m,
n,
kc,
alpha,
d,
m,
b.add(pc * rsb),
rsb,
csb,
beta,
c,
ldc,
);
pc += kc;
}
}
}
unsafe fn scale<E: Elem + core::ops::Mul<Output = E>>(
m: usize,
n: usize,
beta: E,
c: *mut E,
ldc: usize,
) {
if beta == E::ONE {
return;
}
unsafe {
for j in 0..n {
let col = c.add(j * ldc);
for i in 0..m {
*col.add(i) = if beta == E::ZERO {
E::ZERO
} else {
*col.add(i) * beta
};
}
}
}
}
#[inline]
#[track_caller]
fn check(len: usize, rows: usize, cols: usize, ld: usize) {
if rows > 0 && cols > 0 {
assert!(ld >= rows, "the leading dimension is shorter than a column");
let end = (cols - 1).checked_mul(ld).and_then(|x| x.checked_add(rows));
assert!(
end.is_some_and(|e| e <= len),
"the block is outside the slice"
);
}
}
#[allow(clippy::too_many_arguments)]
#[track_caller]
pub(super) fn real<R: Real>(
m: usize,
n: usize,
k: usize,
alpha: R,
a: &[R],
lda: usize,
opa: Op,
b: &[R],
ldb: usize,
opb: Op,
beta: R,
c: &mut [R],
ldc: usize,
) {
let (ar, ac) = if opa == Op::N { (m, k) } else { (k, m) };
let (br, bc) = if opb == Op::N { (k, n) } else { (n, k) };
check(a.len(), ar, ac, lda);
check(b.len(), br, bc, ldb);
check(c.len(), m, n, ldc);
if m == 0 || n == 0 {
return;
}
let (a, b, c) = (a.as_ptr(), b.as_ptr(), c.as_mut_ptr());
unsafe {
if k == 0 || alpha == R::ZERO {
scale(m, n, beta, c, ldc);
} else if is_small(m, n, k, lda, opa, ldb, opb, ldc) {
let (rsb, csb) = if opb == Op::N { (1, ldb) } else { (ldb, 1) };
with_a(m, k, a, lda, opa, |a, lda| {
R::direct(m, n, k, alpha, a, lda, b, rsb, csb, beta, c, ldc)
});
} else if m <= R::SKINNY {
skinny(m, n, k, alpha, a, lda, opa, b, ldb, opb, beta, c, ldc);
} else {
blocked(m, n, k, alpha, a, lda, opa, b, ldb, opb, beta, c, ldc);
}
}
}
#[allow(clippy::too_many_arguments)]
#[track_caller]
pub(super) fn complex<R: Real>(
m: usize,
n: usize,
k: usize,
alpha: Complex<R>,
a: &[Complex<R>],
lda: usize,
opa: Op,
b: &[Complex<R>],
ldb: usize,
opb: Op,
beta: Complex<R>,
c: &mut [Complex<R>],
ldc: usize,
) {
let (ar, ac) = if opa == Op::N { (m, k) } else { (k, m) };
let (br, bc) = if opb == Op::N { (k, n) } else { (n, k) };
check(a.len(), ar, ac, lda);
check(b.len(), br, bc, ldb);
check(c.len(), m, n, ldc);
if m == 0 || n == 0 {
return;
}
let (a, b, c) = (a.as_ptr(), b.as_ptr(), c.as_mut_ptr());
unsafe {
if k == 0 || alpha == Complex::ZERO {
scale(m, n, beta, c, ldc);
return;
}
if is_small(m, n, k, lda, opa, ldb, opb, ldc) {
let (rsb, csb) = if opb == Op::N { (1, ldb) } else { (ldb, 1) };
with_a(m, k, a, lda, opa, |a, lda| {
if opb == Op::C {
R::direct_complex::<true>(m, n, k, alpha, a, lda, b, rsb, csb, beta, c, ldc);
} else {
R::direct_complex::<false>(m, n, k, alpha, a, lda, b, rsb, csb, beta, c, ldc);
}
});
return;
}
let beta = if beta.im == R::ZERO {
beta.re
} else {
scale(m, n, beta, c, ldc);
R::ONE
};
blocked(
m,
n,
k,
alpha,
a,
lda,
opa,
b,
ldb,
opb,
beta,
c.cast(),
2 * ldc,
);
}
}