Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid resolving the Adler32Imp to use multiple times. #17

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions src/imp/mod.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
use core::sync::atomic::{AtomicPtr, Ordering};

pub mod avx2;
pub mod avx512;
pub mod scalar;
pub mod sse2;
pub mod ssse3;
pub mod wasm;

pub type Adler32Imp = fn(u16, u16, &[u8]) -> (u16, u16);
type Adler32Imp = fn(u16, u16, &[u8]) -> (u16, u16);

#[inline]
#[allow(non_snake_case)]
pub const fn _MM_SHUFFLE(z: u32, y: u32, x: u32, w: u32) -> i32 {
((z << 6) | (y << 4) | (x << 2) | w) as i32
}

pub fn get_imp() -> Adler32Imp {
fn get_imp() -> Adler32Imp {
avx512::get_imp()
.or_else(avx2::get_imp)
.or_else(ssse3::get_imp)
.or_else(sse2::get_imp)
.or_else(wasm::get_imp)
.unwrap_or(scalar::update)
}

#[inline]
const fn adler_imp_to_raw_pointer(imp: Adler32Imp) -> *mut () {
// Safety: Equivalent to `imp as usize as *mut ()`, but avoids pointer-to-int
// casts which are lossy in terms of provenance.
unsafe { core::mem::transmute(imp) }
}

// This either contains the resolver function (initially), or the
// already-resolved `Adler32Imp` (after the first call).
static IMP: AtomicPtr<()> = AtomicPtr::new(adler_imp_to_raw_pointer(resolve_and_call));
// Initial value of `IMP`. This resolves the implementation to use, stores it in
// IMP (so that all calls after the first skip resolving), and then forwards the
// arguments it gets to the implementation it resolved.
fn resolve_and_call(a: u16, b: u16, data: &[u8]) -> (u16, u16) {
let resolved_imp = get_imp();
let imp_as_raw_ptr = adler_imp_to_raw_pointer(resolved_imp);
// Ensure the next call goes directly to the resolved implementation.
IMP.store(imp_as_raw_ptr, Ordering::Relaxed);
// Forward the arguments on.
resolved_imp(a, b, data)
}

/// Loads and invokes the implementation, resolving it if needed (only needed
/// the first time through).
#[inline]
pub fn call(a: u16, b: u16, data: &[u8]) -> (u16, u16) {
let imp = IMP.load(Ordering::Relaxed);
// Safety: `IMP` only ever contains valid `Adler32Imp`s.
let imp: Adler32Imp = unsafe { core::mem::transmute(imp) };
imp(a, b, data)
}
16 changes: 2 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,11 @@ pub mod hash;
#[doc(hidden)]
pub mod imp;

pub use hash::*;
use imp::{get_imp, Adler32Imp};

/// An adler32 hash generator type.
#[derive(Clone)]
pub struct Adler32 {
a: u16,
b: u16,
update: Adler32Imp,
}

impl Adler32 {
Expand All @@ -116,9 +112,6 @@ impl Adler32 {

/// Constructs a new `Adler32` using existing checksum.
///
/// Potential overhead here due to runtime feature detection although in testing on 100k
/// and 10k random byte arrays it was not really noticeable.
///
/// # Examples
/// ```rust
/// use simd_adler32::Adler32;
Expand All @@ -129,13 +122,12 @@ impl Adler32 {
Self {
a: checksum as u16,
b: (checksum >> 16) as u16,
update: get_imp(),
}
}

/// Computes hash for supplied data and stores results in internal state.
pub fn write(&mut self, data: &[u8]) {
let (a, b) = (self.update)(self.a, self.b, data);
let (a, b) = imp::call(self.a, self.b, data);

self.a = a;
self.b = b;
Expand Down Expand Up @@ -181,11 +173,7 @@ pub trait Adler32Hash {

impl Default for Adler32 {
fn default() -> Self {
Self {
a: 1,
b: 0,
update: get_imp(),
}
Self { a: 1, b: 0 }
}
}

Expand Down