#![allow(clippy::items_after_statements)]
#[cfg(feature = "bench-internals")]
pub mod scalar;
#[cfg(not(feature = "bench-internals"))]
pub(crate) mod scalar;
#[cfg(test)]
mod proptest;
#[allow(
clippy::cast_possible_wrap,
clippy::cast_ptr_alignment,
clippy::incompatible_msrv,
clippy::inline_always,
clippy::ptr_as_ptr,
clippy::unreadable_literal,
clippy::verbose_bit_mask,
clippy::wildcard_imports
)]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub(crate) mod x86;
#[cfg(target_arch = "aarch64")]
pub(crate) mod arm;
pub(crate) mod wasm;
pub(crate) type AxpyFn = unsafe fn(c: u8, x: &[u8], y: &mut [u8]);
pub(crate) type ScaleFn = unsafe fn(c: u8, x: &[u8], y: &mut [u8]);
pub(crate) type ScaleInplaceFn = unsafe fn(c: u8, y: &mut [u8]);
pub(crate) struct KernelSet {
pub(crate) axpy: AxpyFn,
pub(crate) scale: ScaleFn,
pub(crate) scale_inplace: ScaleInplaceFn,
pub(crate) name: &'static str,
}
#[cfg(feature = "std")]
mod runtime {
#[cfg(target_arch = "aarch64")]
use super::arm;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use super::x86;
#[cfg(not(target_arch = "aarch64"))]
use super::{scalar_axpy_wrapper, scalar_scale_inplace_wrapper, scalar_scale_wrapper};
use super::{AxpyFn, KernelSet, ScaleFn, ScaleInplaceFn};
use std::sync::OnceLock;
static KERNEL: OnceLock<KernelSet> = OnceLock::new();
pub(crate) fn get() -> &'static KernelSet {
KERNEL.get_or_init(detect)
}
fn detect() -> KernelSet {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
if is_x86_feature_detected!("gfni")
&& is_x86_feature_detected!("avx512f")
&& is_x86_feature_detected!("avx512bw")
{
return KernelSet {
axpy: x86::gfni_avx512::axpy_gfni_avx512 as AxpyFn,
scale: x86::gfni_avx512::scale_gfni_avx512 as ScaleFn,
scale_inplace: x86::gfni_avx512::scale_inplace_gfni_avx512 as ScaleInplaceFn,
name: "gfni+avx512 (tier1)",
};
}
if is_x86_feature_detected!("gfni") && is_x86_feature_detected!("avx2") {
return KernelSet {
axpy: x86::gfni_avx2::axpy_gfni_avx2 as AxpyFn,
scale: x86::gfni_avx2::scale_gfni_avx2 as ScaleFn,
scale_inplace: x86::gfni_avx2::scale_inplace_gfni_avx2 as ScaleInplaceFn,
name: "gfni+avx2 (tier2)",
};
}
if is_x86_feature_detected!("gfni") && is_x86_feature_detected!("sse4.2") {
return KernelSet {
axpy: x86::gfni_sse::axpy_gfni_sse as AxpyFn,
scale: x86::gfni_sse::scale_gfni_sse as ScaleFn,
scale_inplace: x86::gfni_sse::scale_inplace_gfni_sse as ScaleInplaceFn,
name: "gfni+sse4.2 (tier3)",
};
}
if is_x86_feature_detected!("avx512f")
&& is_x86_feature_detected!("avx512bw")
&& is_x86_feature_detected!("ssse3")
{
return KernelSet {
axpy: x86::avx512_ssse3::axpy_avx512_ssse3 as AxpyFn,
scale: x86::avx512_ssse3::scale_avx512_ssse3 as ScaleFn,
scale_inplace: x86::avx512_ssse3::scale_inplace_avx512_ssse3 as ScaleInplaceFn,
name: "avx512+ssse3 (tier4)",
};
}
if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("ssse3") {
return KernelSet {
axpy: x86::avx2_ssse3::axpy_avx2_ssse3 as AxpyFn,
scale: x86::avx2_ssse3::scale_avx2_ssse3 as ScaleFn,
scale_inplace: x86::avx2_ssse3::scale_inplace_avx2_ssse3 as ScaleInplaceFn,
name: "avx2+ssse3 (tier5)",
};
}
if is_x86_feature_detected!("ssse3") {
return KernelSet {
axpy: x86::ssse3::axpy_ssse3 as AxpyFn,
scale: x86::ssse3::scale_ssse3 as ScaleFn,
scale_inplace: x86::ssse3::scale_inplace_ssse3 as ScaleInplaceFn,
name: "ssse3 (tier6)",
};
}
}
#[cfg(target_arch = "aarch64")]
{
return KernelSet {
axpy: arm::neon::axpy_neon as AxpyFn,
scale: arm::neon::scale_neon as ScaleFn,
scale_inplace: arm::neon::scale_inplace_neon as ScaleInplaceFn,
name: "neon (tier7)",
};
}
#[cfg(not(target_arch = "aarch64"))]
KernelSet {
axpy: scalar_axpy_wrapper as AxpyFn,
scale: scalar_scale_wrapper as ScaleFn,
scale_inplace: scalar_scale_inplace_wrapper as ScaleInplaceFn,
name: "scalar (tier9)",
}
}
}
#[cfg(all(feature = "std", not(target_arch = "aarch64")))]
unsafe fn scalar_axpy_wrapper(c: u8, x: &[u8], y: &mut [u8]) {
scalar::axpy(c, x, y);
}
#[cfg(all(feature = "std", not(target_arch = "aarch64")))]
unsafe fn scalar_scale_wrapper(c: u8, x: &[u8], y: &mut [u8]) {
scalar::scale(c, x, y);
}
#[cfg(all(feature = "std", not(target_arch = "aarch64")))]
unsafe fn scalar_scale_inplace_wrapper(c: u8, y: &mut [u8]) {
scalar::scale_inplace(c, y);
}
#[inline]
pub fn axpy(c: u8, x: &[u8], y: &mut [u8]) {
assert_eq!(
x.len(),
y.len(),
"rlnc_simdx::kernel::axpy: length mismatch"
);
assert!(
!ranges_overlap(x.as_ptr(), x.len(), y.as_ptr(), y.len()),
"rlnc_simdx::kernel::axpy: overlapping buffers are not allowed"
);
#[cfg(feature = "std")]
unsafe {
(runtime::get().axpy)(c, x, y);
}
#[cfg(not(feature = "std"))]
axpy_static(c, x, y)
}
#[inline]
pub fn scale(c: u8, x: &[u8], y: &mut [u8]) {
assert_eq!(
x.len(),
y.len(),
"rlnc_simdx::kernel::scale: length mismatch"
);
assert!(
!ranges_overlap(x.as_ptr(), x.len(), y.as_ptr(), y.len()),
"rlnc_simdx::kernel::scale: overlapping buffers are not allowed; use scale_inplace for in-place"
);
#[cfg(feature = "std")]
unsafe {
(runtime::get().scale)(c, x, y);
}
#[cfg(not(feature = "std"))]
scale_static(c, x, y)
}
#[inline]
pub fn scale_inplace(c: u8, y: &mut [u8]) {
#[cfg(feature = "std")]
unsafe {
(runtime::get().scale_inplace)(c, y);
}
#[cfg(not(feature = "std"))]
scale_inplace_static(c, y)
}
#[inline]
pub fn axpy_multi(coeffs: &[u8], sources: &[&[u8]], y: &mut [u8]) {
assert_eq!(
coeffs.len(),
sources.len(),
"axpy_multi: coeffs/sources len"
);
for (i, src) in sources.iter().enumerate() {
assert_eq!(
src.len(),
y.len(),
"axpy_multi: source[{i}] length mismatch"
);
}
for (i, src) in sources.iter().enumerate() {
assert!(
!ranges_overlap(src.as_ptr(), src.len(), y.as_ptr(), y.len()),
"axpy_multi: source[{i}] overlaps destination"
);
}
#[cfg(feature = "std")]
let axpy_kernel = runtime::get().axpy;
const BLOCK: usize = 4096;
let n = y.len();
let mut off = 0usize;
while off < n {
let end = (off + BLOCK).min(n);
for (i, &c) in coeffs.iter().enumerate() {
if c != 0 {
let source_block = &sources[i][off..end];
let destination_block = &mut y[off..end];
#[cfg(feature = "std")]
unsafe {
axpy_kernel(c, source_block, destination_block);
}
#[cfg(not(feature = "std"))]
axpy_static(c, source_block, destination_block);
}
}
off = end;
}
}
#[inline]
fn ranges_overlap(a: *const u8, a_len: usize, b: *const u8, b_len: usize) -> bool {
if a_len == 0 || b_len == 0 {
return false;
}
let a0 = a as usize;
let b0 = b as usize;
let a1 = a0 + a_len;
let b1 = b0 + b_len;
a0 < b1 && b0 < a1
}
#[inline]
#[must_use]
pub fn dot(a: &[u8], b: &[u8]) -> u8 {
assert_eq!(a.len(), b.len(), "rlnc_simdx::kernel::dot: length mismatch");
scalar::dot(a, b)
}
#[must_use]
pub fn active_kernel_name() -> &'static str {
#[cfg(feature = "std")]
{
runtime::get().name
}
#[cfg(not(feature = "std"))]
{
active_kernel_name_static()
}
}
#[cfg(not(feature = "std"))]
#[inline]
fn axpy_static(c: u8, x: &[u8], y: &mut [u8]) {
#[cfg(all(target_family = "wasm", target_feature = "simd128"))]
return unsafe { wasm::simd128::axpy_wasm(c, x, y) };
#[cfg(target_arch = "aarch64")]
return unsafe { arm::neon::axpy_neon(c, x, y) };
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
#[cfg(all(
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
return unsafe { x86::gfni_avx512::axpy_gfni_avx512(c, x, y) };
#[cfg(all(
target_feature = "gfni",
target_feature = "avx2",
not(all(target_feature = "avx512f", target_feature = "avx512bw"))
))]
return unsafe { x86::gfni_avx2::axpy_gfni_avx2(c, x, y) };
#[cfg(all(
target_feature = "avx2",
target_feature = "ssse3",
not(target_feature = "gfni")
))]
return unsafe { x86::avx2_ssse3::axpy_avx2_ssse3(c, x, y) };
#[cfg(all(target_feature = "ssse3", not(target_feature = "avx2")))]
return unsafe { x86::ssse3::axpy_ssse3(c, x, y) };
}
scalar::axpy(c, x, y)
}
#[cfg(not(feature = "std"))]
#[inline]
fn scale_static(c: u8, x: &[u8], y: &mut [u8]) {
#[cfg(all(target_family = "wasm", target_feature = "simd128"))]
return unsafe { wasm::simd128::scale_wasm(c, x, y) };
#[cfg(target_arch = "aarch64")]
return unsafe { arm::neon::scale_neon(c, x, y) };
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
#[cfg(all(
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
return unsafe { x86::gfni_avx512::scale_gfni_avx512(c, x, y) };
#[cfg(all(
target_feature = "gfni",
target_feature = "avx2",
not(all(target_feature = "avx512f", target_feature = "avx512bw"))
))]
return unsafe { x86::gfni_avx2::scale_gfni_avx2(c, x, y) };
#[cfg(all(
target_feature = "avx2",
target_feature = "ssse3",
not(target_feature = "gfni")
))]
return unsafe { x86::avx2_ssse3::scale_avx2_ssse3(c, x, y) };
#[cfg(all(target_feature = "ssse3", not(target_feature = "avx2")))]
return unsafe { x86::ssse3::scale_ssse3(c, x, y) };
}
scalar::scale(c, x, y)
}
#[cfg(not(feature = "std"))]
#[inline]
fn scale_inplace_static(c: u8, y: &mut [u8]) {
#[cfg(all(target_family = "wasm", target_feature = "simd128"))]
return unsafe { wasm::simd128::scale_inplace_wasm(c, y) };
#[cfg(target_arch = "aarch64")]
return unsafe { arm::neon::scale_inplace_neon(c, y) };
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
#[cfg(all(
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
return unsafe { x86::gfni_avx512::scale_inplace_gfni_avx512(c, y) };
#[cfg(all(
target_feature = "gfni",
target_feature = "avx2",
not(all(target_feature = "avx512f", target_feature = "avx512bw"))
))]
return unsafe { x86::gfni_avx2::scale_inplace_gfni_avx2(c, y) };
#[cfg(all(
target_feature = "avx2",
target_feature = "ssse3",
not(target_feature = "gfni")
))]
return unsafe { x86::avx2_ssse3::scale_inplace_avx2_ssse3(c, y) };
#[cfg(all(target_feature = "ssse3", not(target_feature = "avx2")))]
return unsafe { x86::ssse3::scale_inplace_ssse3(c, y) };
}
scalar::scale_inplace(c, y)
}
#[cfg(not(feature = "std"))]
fn active_kernel_name_static() -> &'static str {
#[cfg(all(target_family = "wasm", target_feature = "simd128"))]
return "wasm-simd128 (tier8)";
#[cfg(target_arch = "aarch64")]
return "neon (tier7)";
#[cfg(all(target_feature = "gfni", target_feature = "avx512f"))]
return "gfni+avx512 (tier1)";
#[cfg(all(
target_feature = "gfni",
target_feature = "avx2",
not(target_feature = "avx512f")
))]
return "gfni+avx2 (tier2)";
#[cfg(all(
target_feature = "avx2",
target_feature = "ssse3",
not(target_feature = "gfni")
))]
return "avx2+ssse3 (tier5)";
#[cfg(all(target_feature = "ssse3", not(target_feature = "avx2")))]
return "ssse3 (tier6)";
"scalar (tier9)"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn axpy_round_trip() {
let c = 0x53u8;
let x: Vec<u8> = (0u8..128).collect();
let mut y = vec![0u8; 128];
axpy(c, &x, &mut y);
axpy(c, &x, &mut y);
assert_eq!(y, vec![0u8; 128]);
}
#[test]
fn scale_axpy_consistency() {
let c = 0xC7u8;
let x: Vec<u8> = (1u8..=64).collect();
let mut y_scale = vec![0u8; 64];
let mut y_axpy = vec![0u8; 64];
scale(c, &x, &mut y_scale);
axpy(c, &x, &mut y_axpy);
assert_eq!(y_scale, y_axpy);
}
#[test]
fn kernel_name_not_empty() {
let name = active_kernel_name();
assert!(!name.is_empty());
println!("Active kernel: {name}");
}
#[test]
#[cfg(feature = "std")]
fn runtime_dispatch_selects_best() {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let name = active_kernel_name();
if is_x86_feature_detected!("gfni") && is_x86_feature_detected!("avx512f") {
assert!(name.contains("tier1"), "Expected tier1, got: {name}");
} else if is_x86_feature_detected!("gfni") && is_x86_feature_detected!("avx2") {
assert!(name.contains("tier2"), "Expected tier2, got: {name}");
} else if is_x86_feature_detected!("ssse3") {
assert!(
name.contains("tier3")
|| name.contains("tier4")
|| name.contains("tier5")
|| name.contains("tier6"),
"Expected SSSE3-tier, got: {name}"
);
}
}
#[cfg(target_arch = "aarch64")]
assert_eq!(active_kernel_name(), "neon (tier7)");
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
assert!(!active_kernel_name().is_empty());
}
}