use super::{Real, complex_tiles, tiles};
use crate::math::complex::Complex;
use core::arch::aarch64::*;
macro_rules! steps {
($kc:expr, $a:ident, $sa:ident, $b:ident, $acc:ident, $ld:ident, $fma:ident, $lanes:literal, $mv:literal, $nv:literal, [$($l:literal),*]) => {
for _ in 0..$kc {
let av: [_; $mv] = core::array::from_fn(|i| $ld($a.add(i * $lanes)));
let bv: [_; $nv] = core::array::from_fn(|q| $ld($b.add(q * $lanes)));
for q in 0..$nv {
for i in 0..$mv {
$($acc[$lanes * q + $l][i] = $fma::<$l>($acc[$lanes * q + $l][i], av[i], bv[q]);)*
}
}
$a = $a.wrapping_add($sa);
$b = $b.wrapping_add($lanes * $nv);
}
};
}
macro_rules! store {
($c:ident, $ldc:ident, $beta:ident, $acc:ident, $ld:ident, $st:ident, $add:ident, $fman:ident, $lanes:literal) => {
if $beta == 0.0 {
for (j, col) in $acc.iter().enumerate() {
for (i, v) in col.iter().enumerate() {
$st($c.add(i * $lanes + j * $ldc), *v);
}
}
} else if $beta == 1.0 {
for (j, col) in $acc.iter().enumerate() {
for (i, v) in col.iter().enumerate() {
let p = $c.add(i * $lanes + j * $ldc);
$st(p, $add($ld(p), *v));
}
}
} else {
for (j, col) in $acc.iter().enumerate() {
for (i, v) in col.iter().enumerate() {
let p = $c.add(i * $lanes + j * $ldc);
$st(p, $fman(*v, $ld(p), $beta));
}
}
}
};
}
macro_rules! kernel_f64 {
($name:ident, $nv:literal) => {
#[inline(never)]
unsafe fn $name(
kc: usize,
mut a: *const f64,
sa: usize,
mut b: *const f64,
c: *mut f64,
ldc: usize,
beta: f64,
) {
unsafe {
let mut acc = [[vdupq_n_f64(0.0); 4]; 2 * $nv];
steps!(
kc,
a,
sa,
b,
acc,
vld1q_f64,
vfmaq_laneq_f64,
2,
4,
$nv,
[0, 1]
);
store!(
c,
ldc,
beta,
acc,
vld1q_f64,
vst1q_f64,
vaddq_f64,
vfmaq_n_f64,
2
);
}
}
};
}
kernel_f64!(kernel_f64_6, 3);
kernel_f64!(kernel_f64_4, 2);
kernel_f64!(kernel_f64_2, 1);
macro_rules! kernel_f32 {
($name:ident, $nv:literal) => {
#[inline(never)]
unsafe fn $name(
kc: usize,
mut a: *const f32,
sa: usize,
mut b: *const f32,
c: *mut f32,
ldc: usize,
beta: f32,
) {
unsafe {
let mut acc = [[vdupq_n_f32(0.0); 2]; 4 * $nv];
steps!(
kc,
a,
sa,
b,
acc,
vld1q_f32,
vfmaq_laneq_f32,
4,
2,
$nv,
[0, 1, 2, 3]
);
store!(
c,
ldc,
beta,
acc,
vld1q_f32,
vst1q_f32,
vaddq_f32,
vfmaq_n_f32,
4
);
}
}
};
}
kernel_f32!(kernel_f32_12, 3);
kernel_f32!(kernel_f32_8, 2);
kernel_f32!(kernel_f32_4, 1);
#[inline(never)]
unsafe fn pack_t_f64(
dst: *mut f64,
src: *const f64,
ld: usize,
h: usize,
kc: usize,
w: usize,
scale: f64,
) {
unsafe {
let s = vdupq_n_f64(scale);
let mut p = 0;
while p + 2 <= kc {
let (d0, d1) = (dst.add(p * w), dst.add(p * w + w));
let mut r = 0;
while r + 2 <= h {
let x = vmulq_f64(vld1q_f64(src.add(r * ld + p)), s);
let y = vmulq_f64(vld1q_f64(src.add(r * ld + ld + p)), s);
vst1q_f64(d0.add(r), vzip1q_f64(x, y));
vst1q_f64(d1.add(r), vzip2q_f64(x, y));
r += 2;
}
if r < h {
*d0.add(r) = *src.add(r * ld + p) * scale;
*d1.add(r) = *src.add(r * ld + p + 1) * scale;
r += 1;
}
for r in r..w {
*d0.add(r) = 0.0;
*d1.add(r) = 0.0;
}
p += 2;
}
if p < kc {
let d = dst.add(p * w);
for r in 0..w {
*d.add(r) = if r < h {
*src.add(r * ld + p) * scale
} else {
0.0
};
}
}
}
}
#[inline(never)]
unsafe fn pack_t_f32(
dst: *mut f32,
src: *const f32,
ld: usize,
h: usize,
kc: usize,
w: usize,
scale: f32,
) {
unsafe {
let s = vdupq_n_f32(scale);
let mut p = 0;
while p + 4 <= kc {
let d = dst.add(p * w);
let mut r = 0;
while r + 4 <= h {
let x0 = vmulq_f32(vld1q_f32(src.add(r * ld + p)), s);
let x1 = vmulq_f32(vld1q_f32(src.add((r + 1) * ld + p)), s);
let x2 = vmulq_f32(vld1q_f32(src.add((r + 2) * ld + p)), s);
let x3 = vmulq_f32(vld1q_f32(src.add((r + 3) * ld + p)), s);
let t0 = vreinterpretq_f64_f32(vtrn1q_f32(x0, x1));
let t1 = vreinterpretq_f64_f32(vtrn2q_f32(x0, x1));
let t2 = vreinterpretq_f64_f32(vtrn1q_f32(x2, x3));
let t3 = vreinterpretq_f64_f32(vtrn2q_f32(x2, x3));
vst1q_f32(d.add(r), vreinterpretq_f32_f64(vtrn1q_f64(t0, t2)));
vst1q_f32(d.add(w + r), vreinterpretq_f32_f64(vtrn1q_f64(t1, t3)));
vst1q_f32(d.add(2 * w + r), vreinterpretq_f32_f64(vtrn2q_f64(t0, t2)));
vst1q_f32(d.add(3 * w + r), vreinterpretq_f32_f64(vtrn2q_f64(t1, t3)));
r += 4;
}
for r in r..w {
for q in 0..4 {
*d.add(q * w + r) = if r < h {
*src.add(r * ld + p + q) * scale
} else {
0.0
};
}
}
p += 4;
}
for p in p..kc {
let d = dst.add(p * w);
for r in 0..w {
*d.add(r) = if r < h {
*src.add(r * ld + p) * scale
} else {
0.0
};
}
}
}
}
macro_rules! real_tile {
($name:ident, $t:ident, $v:ident, $lanes:literal, $ld:ident, $st:ident, $dup:ident, $fman:ident, $muln:ident) => {
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn $name<const MV: usize, const NR: usize>(
k: usize,
a: *const $t,
lda: usize,
b: *const $t,
rsb: usize,
csb: usize,
c: *mut $t,
ldc: usize,
alpha: $t,
beta: $t,
) {
unsafe {
let mut acc = [[$dup(0.0); MV]; NR];
for p in 0..k {
let x: [$v; MV] = core::array::from_fn(|v| $ld(a.add(v * $lanes + 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(&x) {
*s = $fman(*s, *x, y);
}
}
}
for (j, sums) in acc.iter().enumerate() {
for (v, s) in sums.iter().enumerate() {
let d = c.add(v * $lanes + j * ldc);
let y = $muln(*s, alpha);
$st(
d,
if beta == 0.0 {
y
} else {
$fman(y, $ld(d), beta)
},
);
}
}
}
}
};
}
real_tile!(
tile_f64,
f64,
float64x2_t,
2,
vld1q_f64,
vst1q_f64,
vdupq_n_f64,
vfmaq_n_f64,
vmulq_n_f64
);
real_tile!(
tile_f32,
f32,
float32x4_t,
4,
vld1q_f32,
vst1q_f32,
vdupq_n_f32,
vfmaq_n_f32,
vmulq_n_f32
);
macro_rules! real_columns {
($tile:ident, $mv:literal, $nr:literal, $n:ident, $k:ident, $a:expr, $lda:ident, $b:ident, $rsb:ident, $csb:ident, $c:expr, $ldc:ident, $alpha:ident, $beta:ident) => {{
let (a, c) = ($a, $c);
let mut j = 0;
while j + $nr <= $n {
let (b, c) = ($b.add(j * $csb), c.add(j * $ldc));
$tile::<$mv, $nr>($k, a, $lda, b, $rsb, $csb, c, $ldc, $alpha, $beta);
j += $nr;
}
if $nr > 4 && j + 4 <= $n {
let (b, c) = ($b.add(j * $csb), c.add(j * $ldc));
$tile::<$mv, 4>($k, a, $lda, b, $rsb, $csb, c, $ldc, $alpha, $beta);
j += 4;
}
let (b, c) = ($b.wrapping_add(j * $csb), c.wrapping_add(j * $ldc));
match $n - j {
3 => $tile::<$mv, 3>($k, a, $lda, b, $rsb, $csb, c, $ldc, $alpha, $beta),
2 => $tile::<$mv, 2>($k, a, $lda, b, $rsb, $csb, c, $ldc, $alpha, $beta),
1 => $tile::<$mv, 1>($k, a, $lda, b, $rsb, $csb, c, $ldc, $alpha, $beta),
_ => {}
}
}};
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn complex_tile_f64<const MV: usize, const NR: usize, const CJ: bool>(
k: usize,
a: *const Complex<f64>,
lda: usize,
b: *const Complex<f64>,
rsb: usize,
csb: usize,
c: *mut Complex<f64>,
ldc: usize,
alpha: Complex<f64>,
beta: Complex<f64>,
) {
unsafe {
let z = vdupq_n_f64(0.0);
let mut re = [[z; MV]; NR];
let mut im = [[z; MV]; NR];
let (a, b) = (a.cast::<f64>(), b.cast::<f64>());
for p in 0..k {
let x: [float64x2x2_t; MV] =
core::array::from_fn(|v| vld2q_f64(a.add(2 * (2 * v + p * lda))));
for j in 0..NR {
let y = vld1q_f64(b.add(2 * (p * rsb + j * csb)));
for v in 0..MV {
re[j][v] = vfmaq_laneq_f64::<0>(re[j][v], x[v].0, y);
im[j][v] = vfmaq_laneq_f64::<0>(im[j][v], x[v].1, y);
if CJ {
re[j][v] = vfmaq_laneq_f64::<1>(re[j][v], x[v].1, y);
im[j][v] = vfmsq_laneq_f64::<1>(im[j][v], x[v].0, y);
} else {
re[j][v] = vfmsq_laneq_f64::<1>(re[j][v], x[v].1, y);
im[j][v] = vfmaq_laneq_f64::<1>(im[j][v], x[v].0, y);
}
}
}
}
let c = c.cast::<f64>();
let zero = beta.re == 0.0 && beta.im == 0.0;
for j in 0..NR {
for v in 0..MV {
let d = c.add(2 * (2 * v + j * ldc));
let (sr, si) = (re[j][v], im[j][v]);
let mut yr = vfmsq_n_f64(vmulq_n_f64(sr, alpha.re), si, alpha.im);
let mut yi = vfmaq_n_f64(vmulq_n_f64(si, alpha.re), sr, alpha.im);
if !zero {
let o = vld2q_f64(d);
yr = vfmsq_n_f64(vfmaq_n_f64(yr, o.0, beta.re), o.1, beta.im);
yi = vfmaq_n_f64(vfmaq_n_f64(yi, o.1, beta.re), o.0, beta.im);
}
vst2q_f64(d, float64x2x2_t(yr, yi));
}
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn complex_tile_f32<const MV: usize, const NR: usize, const CJ: bool>(
k: usize,
a: *const Complex<f32>,
lda: usize,
b: *const Complex<f32>,
rsb: usize,
csb: usize,
c: *mut Complex<f32>,
ldc: usize,
alpha: Complex<f32>,
beta: Complex<f32>,
) {
unsafe {
let z = vdupq_n_f32(0.0);
let mut re = [[z; MV]; NR];
let mut im = [[z; MV]; NR];
let (a, b) = (a.cast::<f32>(), b.cast::<f32>());
for p in 0..k {
let x: [float32x4x2_t; MV] =
core::array::from_fn(|v| vld2q_f32(a.add(2 * (4 * v + p * lda))));
for j in 0..NR {
let y = vld1_f32(b.add(2 * (p * rsb + j * csb)));
for v in 0..MV {
re[j][v] = vfmaq_lane_f32::<0>(re[j][v], x[v].0, y);
im[j][v] = vfmaq_lane_f32::<0>(im[j][v], x[v].1, y);
if CJ {
re[j][v] = vfmaq_lane_f32::<1>(re[j][v], x[v].1, y);
im[j][v] = vfmsq_lane_f32::<1>(im[j][v], x[v].0, y);
} else {
re[j][v] = vfmsq_lane_f32::<1>(re[j][v], x[v].1, y);
im[j][v] = vfmaq_lane_f32::<1>(im[j][v], x[v].0, y);
}
}
}
}
let c = c.cast::<f32>();
let zero = beta.re == 0.0 && beta.im == 0.0;
for j in 0..NR {
for v in 0..MV {
let d = c.add(2 * (4 * v + j * ldc));
let (sr, si) = (re[j][v], im[j][v]);
let mut yr = vfmsq_n_f32(vmulq_n_f32(sr, alpha.re), si, alpha.im);
let mut yi = vfmaq_n_f32(vmulq_n_f32(si, alpha.re), sr, alpha.im);
if !zero {
let o = vld2q_f32(d);
yr = vfmsq_n_f32(vfmaq_n_f32(yr, o.0, beta.re), o.1, beta.im);
yi = vfmaq_n_f32(vfmaq_n_f32(yi, o.1, beta.re), o.0, beta.im);
}
vst2q_f32(d, float32x4x2_t(yr, yi));
}
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
unsafe fn complex_tile_f32_2<const MV: usize, const NR: usize, const CJ: bool>(
k: usize,
a: *const Complex<f32>,
lda: usize,
b: *const Complex<f32>,
rsb: usize,
csb: usize,
c: *mut Complex<f32>,
ldc: usize,
alpha: Complex<f32>,
beta: Complex<f32>,
) {
unsafe {
let z = vdup_n_f32(0.0);
let mut re = [z; NR];
let mut im = [z; NR];
let (a, b) = (a.cast::<f32>(), b.cast::<f32>());
for p in 0..k {
let x = vld2_f32(a.add(2 * p * lda));
for j in 0..NR {
let y = vld1_f32(b.add(2 * (p * rsb + j * csb)));
re[j] = vfma_lane_f32::<0>(re[j], x.0, y);
im[j] = vfma_lane_f32::<0>(im[j], x.1, y);
if CJ {
re[j] = vfma_lane_f32::<1>(re[j], x.1, y);
im[j] = vfms_lane_f32::<1>(im[j], x.0, y);
} else {
re[j] = vfms_lane_f32::<1>(re[j], x.1, y);
im[j] = vfma_lane_f32::<1>(im[j], x.0, y);
}
}
}
let c = c.cast::<f32>();
let zero = beta.re == 0.0 && beta.im == 0.0;
for j in 0..NR {
let d = c.add(2 * j * ldc);
let (sr, si) = (re[j], im[j]);
let mut yr = vfms_n_f32(vmul_n_f32(sr, alpha.re), si, alpha.im);
let mut yi = vfma_n_f32(vmul_n_f32(si, alpha.re), sr, alpha.im);
if !zero {
let o = vld2_f32(d);
yr = vfms_n_f32(vfma_n_f32(yr, o.0, beta.re), o.1, beta.im);
yi = vfma_n_f32(vfma_n_f32(yi, o.1, beta.re), o.0, beta.im);
}
vst2_f32(d, float32x2x2_t(yr, yi));
}
}
}
macro_rules! complex_columns {
($tile:ident, $mv:literal, $cj:ident, $n:ident, $k:ident, $a:expr, $lda:ident, $b:ident, $rsb:ident, $csb:ident, $c:expr, $ldc:ident, $alpha:ident, $beta:ident) => {{
let (a, c) = ($a, $c);
let mut j = 0;
while j + 4 <= $n {
$tile::<$mv, 4, $cj>(
$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::<$mv, 3, $cj>($k, a, $lda, b, $rsb, $csb, c, $ldc, $alpha, $beta),
2 => $tile::<$mv, 2, $cj>($k, a, $lda, b, $rsb, $csb, c, $ldc, $alpha, $beta),
1 => $tile::<$mv, 1, $cj>($k, a, $lda, b, $rsb, $csb, c, $ldc, $alpha, $beta),
_ => {}
}
}};
}
impl Real for f64 {
const MR: usize = 8;
const NR: usize = 6;
const KC: usize = 512;
const MC: usize = 128;
const NC: usize = 1020;
const SKINNY: usize = 32;
#[inline(always)]
fn width(r: usize) -> usize {
if r >= 9 || r == 6 || r == 5 {
6
} else if r >= 3 {
4
} else {
2
}
}
#[inline(always)]
unsafe fn kernel(
nr: usize,
kc: usize,
a: *const f64,
sa: usize,
b: *const f64,
c: *mut f64,
ldc: usize,
beta: f64,
) {
unsafe {
match nr {
6 => kernel_f64_6(kc, a, sa, b, c, ldc, beta),
4 => kernel_f64_4(kc, a, sa, b, c, ldc, beta),
_ => kernel_f64_2(kc, a, sa, b, c, ldc, beta),
}
}
}
#[inline(always)]
fn fma(c: f64, a: f64, b: f64) -> f64 {
a.mul_add(b, c)
}
#[inline(always)]
unsafe fn pack_t(
dst: *mut f64,
src: *const f64,
ld: usize,
h: usize,
kc: usize,
w: usize,
scale: f64,
) {
unsafe { pack_t_f64(dst, src, ld, h, kc, w, scale) }
}
#[inline(never)]
unsafe fn direct(
m: usize,
n: usize,
k: usize,
alpha: f64,
a: *const f64,
lda: usize,
b: *const f64,
rsb: usize,
csb: usize,
beta: f64,
c: *mut f64,
ldc: usize,
) {
unsafe {
let mut i = 0;
while i + 8 <= m {
real_columns!(
tile_f64,
4,
4,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 8;
}
if i + 4 <= m {
real_columns!(
tile_f64,
2,
8,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 4;
}
if i + 2 <= m {
real_columns!(
tile_f64,
1,
8,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 2;
}
if i < m {
tiles::<f64, 1>(n, k, a.add(i), lda, b, rsb, csb, c.add(i), ldc, alpha, beta);
}
}
}
#[inline(never)]
unsafe fn direct_complex<const CJ: bool>(
m: usize,
n: usize,
k: usize,
alpha: Complex<f64>,
a: *const Complex<f64>,
lda: usize,
b: *const Complex<f64>,
rsb: usize,
csb: usize,
beta: Complex<f64>,
c: *mut Complex<f64>,
ldc: usize,
) {
unsafe {
let mut i = 0;
while i + 4 <= m {
complex_columns!(
complex_tile_f64,
2,
CJ,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 4;
}
if i + 2 <= m {
complex_columns!(
complex_tile_f64,
1,
CJ,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 2;
}
if i < m {
complex_tiles::<f64, 1, CJ>(
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta,
);
}
}
}
}
impl Real for f32 {
const MR: usize = 8;
const NR: usize = 12;
const KC: usize = 512;
const MC: usize = 128;
const NC: usize = 2040;
const SKINNY: usize = 32;
#[inline(always)]
fn width(r: usize) -> usize {
if r >= 17 || (9..=12).contains(&r) {
12
} else if r >= 5 {
8
} else {
4
}
}
#[inline(always)]
unsafe fn kernel(
nr: usize,
kc: usize,
a: *const f32,
sa: usize,
b: *const f32,
c: *mut f32,
ldc: usize,
beta: f32,
) {
unsafe {
match nr {
12 => kernel_f32_12(kc, a, sa, b, c, ldc, beta),
8 => kernel_f32_8(kc, a, sa, b, c, ldc, beta),
_ => kernel_f32_4(kc, a, sa, b, c, ldc, beta),
}
}
}
#[inline(always)]
fn fma(c: f32, a: f32, b: f32) -> f32 {
a.mul_add(b, c)
}
#[inline(always)]
unsafe fn pack_t(
dst: *mut f32,
src: *const f32,
ld: usize,
h: usize,
kc: usize,
w: usize,
scale: f32,
) {
unsafe { pack_t_f32(dst, src, ld, h, kc, w, scale) }
}
#[inline(never)]
unsafe fn direct(
m: usize,
n: usize,
k: usize,
alpha: f32,
a: *const f32,
lda: usize,
b: *const f32,
rsb: usize,
csb: usize,
beta: f32,
c: *mut f32,
ldc: usize,
) {
unsafe {
let mut i = 0;
while i + 16 <= m {
real_columns!(
tile_f32,
4,
4,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 16;
}
if i + 8 <= m {
real_columns!(
tile_f32,
2,
8,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 8;
}
if i + 4 <= m {
real_columns!(
tile_f32,
1,
8,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 4;
}
if i + 2 <= m {
tiles::<f32, 2>(n, k, a.add(i), lda, b, rsb, csb, c.add(i), ldc, alpha, beta);
i += 2;
}
if i < m {
tiles::<f32, 1>(n, k, a.add(i), lda, b, rsb, csb, c.add(i), ldc, alpha, beta);
}
}
}
#[inline(never)]
unsafe fn direct_complex<const CJ: bool>(
m: usize,
n: usize,
k: usize,
alpha: Complex<f32>,
a: *const Complex<f32>,
lda: usize,
b: *const Complex<f32>,
rsb: usize,
csb: usize,
beta: Complex<f32>,
c: *mut Complex<f32>,
ldc: usize,
) {
unsafe {
let mut i = 0;
while i + 8 <= m {
complex_columns!(
complex_tile_f32,
2,
CJ,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 8;
}
if i + 4 <= m {
complex_columns!(
complex_tile_f32,
1,
CJ,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 4;
}
if i + 2 <= m {
complex_columns!(
complex_tile_f32_2,
1,
CJ,
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta
);
i += 2;
}
if i < m {
complex_tiles::<f32, 1, CJ>(
n,
k,
a.add(i),
lda,
b,
rsb,
csb,
c.add(i),
ldc,
alpha,
beta,
);
}
}
}
}