fstd 0.1.2

A fast standard library for Rust.
Documentation
use crate::rand::u64 as random_u64;
use std::hash::{BuildHasher, Hasher};

/// wyhash default parameters.
pub(crate) const WYP: [u64; 5] = [
    0xa0761d6478bd642f,
    0xe7037ed1a0b428db,
    0x8ebc6af09c88c6e3,
    0x589965cc75374cc3,
    0x1d8e4e27c47d124f,
];

// pub(crate) const SECRETS: [u64;4]
include!(concat!(env!("OUT_DIR"), "/wyhash_secret.rs"));

const ROUND_LEN: usize = 48;

#[inline(always)]
fn r8(data: &[u8]) -> u64 {
    u64::from(data[7]) << 56
        | u64::from(data[6]) << 48
        | u64::from(data[5]) << 40
        | u64::from(data[4]) << 32
        | u64::from(data[3]) << 24
        | u64::from(data[2]) << 16
        | u64::from(data[1]) << 8
        | u64::from(data[0])
}

#[inline(always)]
fn r4(data: &[u8]) -> u64 {
    u64::from(data[3]) << 24
        | u64::from(data[2]) << 16
        | u64::from(data[1]) << 8
        | u64::from(data[0])
}

#[inline(always)]
fn wymix(a: u64, b: u64) -> u64 {
    let v = (a as u128).wrapping_mul(b as u128);
    ((v >> 64) ^ v) as u64
}

#[inline(always)]
fn wymum(a: u64, b: u64) -> (u64, u64) {
    let r = (a as u128).wrapping_mul(b as u128);
    (r as u64, (r >> 64) as u64)
}

// Wyhash final version 4 with a minor change.
#[inline]
#[allow(unused)]
fn wyhash_once(data: &[u8], seed: u64) -> u64 {
    let mut a: u64;
    let mut b: u64;
    let mut seed = seed ^ wymix(seed ^ SECRETS[0], SECRETS[1]); // seed^=_wymix(seed^secret[0],secret[1]);
    let length = data.len();
    if length <= 16 {
        (a, b) = wyhash_a_b_0_16(data, length)
    } else {
        let mut see1 = seed;
        let mut see2 = seed;
        let mut p = 0usize;
        let mut i = data.len();
        if i > ROUND_LEN {
            while i > ROUND_LEN {
                wyhash_round(&mut seed, &mut see1, &mut see2, data, p);
                p += ROUND_LEN;
                i -= ROUND_LEN;
            }
            seed ^= see1 ^ see2;
        }
        while i > 16 {
            // seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed);  i-=16; p+=16;
            seed = wymix(r8(&data[p..]) ^ SECRETS[1], r8(&data[p + 8..]) ^ seed);
            p += 16;
            i -= 16;
        }
        // Replaced these two lines, it has a bad performance for the digest algorithm.
        // a = r8(&data[p + i - 16..]);
        // b = r8(&data[p + i - 8..]);
        (a, b) = wyhash_a_b_0_16(&data[p..], i)
    }
    a ^= SECRETS[1];
    b ^= seed;
    (a, b) = wymum(a, b);
    wymix(a ^ SECRETS[0] ^ (length as u64), b ^ SECRETS[1])
}

#[inline]
#[allow(unused)]
fn wyhash_once_fv4(data: &[u8], seed: u64) -> u64 {
    let mut a: u64;
    let mut b: u64;
    let mut seed = seed ^ wymix(seed ^ SECRETS[0], SECRETS[1]); // seed^=_wymix(seed^secret[0],secret[1]);
    let length = data.len();
    if length <= 16 {
        (a, b) = wyhash_a_b_0_16(data, length)
    } else {
        let mut see1 = seed;
        let mut see2 = seed;
        let mut p = 0usize;
        let mut i = data.len();
        if i > ROUND_LEN {
            while i > ROUND_LEN {
                wyhash_round(&mut seed, &mut see1, &mut see2, data, p);
                p += ROUND_LEN;
                i -= ROUND_LEN;
            }
            seed ^= see1 ^ see2;
        }
        while i > 16 {
            // seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed);  i-=16; p+=16;
            seed = wymix(r8(&data[p..]) ^ SECRETS[1], r8(&data[p + 8..]) ^ seed);
            p += 16;
            i -= 16;
        }
        // Replaced these two lines, it has a bad performance for the digest algorithm.
        a = r8(&data[p + i - 16..]);
        b = r8(&data[p + i - 8..]);
        // (a, b) = wyhash_a_b_0_16(&data[p..], i)
    }
    a ^= SECRETS[1];
    b ^= seed;
    (a, b) = wymum(a, b);
    wymix(a ^ SECRETS[0] ^ (length as u64), b ^ SECRETS[1])
}

#[inline(always)]
fn wyhash_round(seed: &mut u64, see1: &mut u64, see2: &mut u64, data: &[u8], p: usize) {
    // seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed);
    // see1=_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^see1);
    // see2=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see2);
    *seed = wymix(r8(&data[p..]) ^ SECRETS[1], r8(&data[p + 8..]) ^ *seed);
    *see1 = wymix(
        r8(&data[p + 16..]) ^ SECRETS[2],
        r8(&data[p + 24..]) ^ *see1,
    );
    *see2 = wymix(
        r8(&data[p + 32..]) ^ SECRETS[3],
        r8(&data[p + 40..]) ^ *see2,
    );
}

#[inline(always)]
fn wyhash_a_b_0_16(data: &[u8], length: usize) -> (u64, u64) {
    debug_assert!(length <= 16);
    if length >= 4 {
        // a=(_wyr4(p)<<32)|_wyr4(p+((len>>3)<<2)); b=(_wyr4(p+len-4)<<32)|_wyr4(p+len-4-((len>>3)<<2));
        let a = r4(&data[..]) << 32 | r4(&data[((length >> 3) << 2)..]);
        let b = r4(&data[length - 4..]) << 32 | r4(&data[length - 4 - ((length >> 3) << 2)..]);
        (a, b)
    } else if length > 0 {
        // a=_wyr3(p,len); b=0;
        let a = u64::from(data[0]) << 16
            | u64::from(data[length >> 1]) << 8
            | u64::from(data[length - 1]);
        (a, 0)
    } else {
        (0, 0)
    }
}

#[derive(Clone, Debug)]
#[repr(align(8))]
struct Align64<T>(T);

#[derive(Clone, Debug)]
pub struct WyHasher {
    seed: u64,
    see1: u64,
    see2: u64,
    buffer: Align64<[u8; ROUND_LEN]>,
    buffer_len: usize,
    input_len: usize,
}

impl WyHasher {
    pub fn new() -> Self {
        WyHasher::with_seed(random_u64(..))
    }

    fn with_seed(seed: u64) -> Self {
        let new_seed = seed ^ wymix(seed ^ SECRETS[0], SECRETS[1]);
        WyHasher {
            seed: new_seed,
            see1: new_seed,
            see2: new_seed,
            buffer: Align64([0; ROUND_LEN]),
            buffer_len: 0,
            input_len: 0,
        }
    }

    fn write(&mut self, bytes: &[u8]) {
        self.input_len += bytes.len();

        if bytes.len() + self.buffer_len <= ROUND_LEN {
            self.buffer.0[self.buffer_len..bytes.len() + self.buffer_len].copy_from_slice(bytes);
            self.buffer_len += bytes.len();
        } else {
            // Need to consume extra bytes, and store remain bytes in the buffer.
            self.buffer.0[self.buffer_len..].copy_from_slice(&bytes[..ROUND_LEN - self.buffer_len]);
            let mut seed = self.seed;
            let mut see1 = self.see1;
            let mut see2 = self.see2;

            // Consume bytes in the buffer.
            wyhash_round(&mut seed, &mut see1, &mut see2, &self.buffer.0, 0);

            let mut bytes_consumed = ROUND_LEN - self.buffer_len;
            // how many bytes need to be consumed or stored
            let mut new_buffer_len = bytes.len() + self.buffer_len - ROUND_LEN;

            while new_buffer_len > ROUND_LEN {
                wyhash_round(
                    &mut seed,
                    &mut see1,
                    &mut see2,
                    &bytes[bytes_consumed..bytes_consumed + ROUND_LEN],
                    0,
                );
                bytes_consumed += ROUND_LEN;
                new_buffer_len -= ROUND_LEN;
            }
            self.buffer_len = new_buffer_len;
            if new_buffer_len > 0 {
                self.buffer.0[..new_buffer_len].copy_from_slice(&bytes[bytes_consumed..]);
            }
            self.seed = seed;
            self.see1 = see1;
            self.see2 = see2;
        }
    }

    fn finish(&self) -> u64 {
        let mut buffer_len = self.buffer_len; // remeaning bytes in the buffer
        let input_len = self.input_len;
        let data = self.buffer.0;
        let mut a: u64;
        let mut b: u64;
        let mut seed = self.seed;
        if input_len < 16 {
            (a, b) = wyhash_a_b_0_16(&data, input_len);
        } else {
            if input_len > ROUND_LEN {
                // At least 1 * ROUND_LEN bytes has been consumed.
                seed ^= self.see1 ^ self.see2;
            }
            // Consume remeaning bytes.
            let mut p = 0;
            while buffer_len > 16 {
                seed = wymix(r8(&data[p..]) ^ SECRETS[1], r8(&data[p + 8..]) ^ seed);
                p += 16;
                buffer_len -= 16;
            }
            // Replaced these two lines, it has a bad performance for the digest algorithm.
            // a = r8(&data[p + buffer_len - 16..]);
            // b = r8(&data[p + buffer_len - 8..]);
            (a, b) = wyhash_a_b_0_16(&data[p..], buffer_len)
        }
        a ^= SECRETS[1];
        b ^= seed;
        (a, b) = wymum(a, b);
        wymix(a ^ SECRETS[0] ^ (input_len as u64), b ^ SECRETS[1])
    }
}

impl Hasher for WyHasher {
    fn write(&mut self, bytes: &[u8]) {
        self.write(bytes)
    }

    fn finish(&self) -> u64 {
        self.finish()
    }
}

impl BuildHasher for WyHasher {
    type Hasher = WyHasher;

    #[inline(always)]
    fn build_hasher(&self) -> Self::Hasher {
        WyHasher::new()
    }
}

impl Default for WyHasher {
    fn default() -> Self {
        WyHasher::new()
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use super::*;

    const SEED: u64 = 12345678910;

    #[test]
    fn simple_test() {
        fn wyhash_once_t(b: &[u8]) -> u64 {
            wyhash_once(b, SEED)
        }

        fn wyhash_digest_t(b: &[u8]) -> u64 {
            let mut hasher = WyHasher::with_seed(SEED);
            hasher.write(b);
            hasher.finish()
        }

        let mut x: [u8; 1000] = [0; 1000];
        for i in 0..x.len() {
            x[i] = i as u8;
        }
        for i in 0..x.len() {
            let b = &x[..i];
            let v1 = wyhash_once_t(b);
            let v2 = wyhash_digest_t(b);
            assert_eq!(v1, v2, "index: {}", i);
        }
    }

    #[test]
    fn hashmap_test() {
        let mut m = HashMap::with_hasher(WyHasher::new());
        for i in 0..100 {
            m.insert(i, i);
        }
    }
}