Skip to main content

wy/
lib.rs

1//! Rust implementation of [wyhash](https://github.com/wangyi-fudan/wyhash) algorithm
2
3#![warn(missing_docs)]
4#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
5#![no_std]
6
7mod utils;
8
9use utils::*;
10
11use core::sync::atomic::{AtomicU64, Ordering};
12
13const DEFAULT_SECRET: [u64; 5] = [0xa0761d6478bd642f, 0xe7037ed1a0b428db, 0x8ebc6af09c88c6e3, 0x589965cc75374cc3, 0x1d8e4e27c47d124f];
14const SEED_MOD: u64 = DEFAULT_SECRET[0];
15
16///Generates random number with specified seed.
17///
18///Do note that the same number is generated for each seed value.
19///
20///User must modify `seed` value to generate new unique value.
21///
22///Not intended to be used normally, instead you should use [Random](struct.Random.html) or [AtomicRandom](struct.AtomicRandom.html)
23pub fn random(seed: u64) -> u64 {
24    const SEED_EXTRA: u64 = DEFAULT_SECRET[1];
25    let mut seed_extra = seed ^ SEED_EXTRA;
26
27    #[cfg(target_pointer_width = "32")]
28    {
29        seed_extra *= (seed_extra >> 32) | (seed_extra << 32);
30
31        (seed * ((seed >> 32) | (seed << 32 ))) ^ ((seed_extra >> 32) | (seed_extra << 32))
32    }
33
34    #[cfg(target_pointer_width = "64")]
35    {
36        let mut seed = seed;
37
38        let seeds = u128::from(seed) * u128::from(seed_extra);
39        seed = seeds as u64;
40        seed_extra = (seeds >> 64) as u64;
41        seed ^ seed_extra
42    }
43}
44
45///Wyhash based PRNG.
46pub struct Random {
47    seed: u64,
48}
49
50impl Random {
51    #[inline(always)]
52    ///Creates new instance with supplied seed.
53    pub const fn new(seed: u64) -> Self {
54        Self {
55            seed
56        }
57    }
58
59    #[inline(always)]
60    ///Consumes self returning current seed.
61    pub const fn into_seed(self) -> u64 {
62        self.seed
63    }
64
65    #[inline(always)]
66    ///Generates new number
67    pub fn gen(&mut self) -> u64 {
68        self.seed = self.seed.wrapping_add(SEED_MOD);
69        random(self.seed)
70    }
71}
72
73///Atomic Wyhash based PRNG.
74///
75///Comparing to plain [Random](struct.Random.html) it stores seed in atomic
76///allowing it to be used concurrently.
77pub struct AtomicRandom {
78    seed: AtomicU64,
79}
80
81impl AtomicRandom {
82    #[inline(always)]
83    ///Creates new instance with supplied seed.
84    pub const fn new(seed: u64) -> Self {
85        Self {
86            seed: AtomicU64::new(seed.wrapping_add(SEED_MOD))
87        }
88    }
89
90    #[inline(always)]
91    ///Consumes self returning current seed.
92    pub fn into_seed(self) -> u64 {
93        self.seed.into_inner()
94    }
95
96    #[inline(always)]
97    ///Sets new seed value.
98    pub fn set_seed(&self, seed: u64) {
99        self.seed.swap(seed.wrapping_add(SEED_MOD), Ordering::SeqCst);
100    }
101
102    #[inline(always)]
103    ///Generates new number
104    pub fn gen(&self) -> u64 {
105        //We increment initially on creation.
106        //This way, previous value is always modified seed.
107        //And `add` stores next seed value to use.
108        let seed = self.seed.fetch_add(SEED_MOD, Ordering::SeqCst);
109        random(seed)
110    }
111}
112
113///Hashing algorithm optimized for 32 bit
114pub fn hash32(mut data: &[u8], mut seed: u32) -> u32 {
115    const HASH_MOD: u64 = 0x53c5ca59;
116    const HASH_MOD2: u64 = 0x74743c1b;
117
118    let mut seed_extra = data.len() as u32;
119    seed ^= (data.len() >> 32) as u32;
120
121    let mut mix = (seed as u64 ^ HASH_MOD) * (seed_extra as u64 ^ HASH_MOD2);
122    seed = mix as u32;
123    seed_extra = (mix >> 32) as u32;
124
125    while data.len() > 8 {
126        seed ^= read_u32(data);
127        data = &data[4..];
128
129        seed_extra ^= read_u32(data);
130        data = &data[4..];
131
132        mix = (seed as u64 ^ HASH_MOD) * (seed_extra as u64 ^ HASH_MOD2);
133        seed = mix as u32;
134        seed_extra = (mix >> 32) as u32;
135    }
136
137    if data.len() >= 4 {
138        seed ^= read_u32(data);
139        data = &data[data.len() - 4..];
140
141        seed_extra ^= read_u32(data);
142    } else if data.len() > 0 {
143        seed ^= read_part_u32(data);
144    }
145
146    mix = (seed as u64 ^ HASH_MOD) * (seed_extra as u64 ^ HASH_MOD2);
147    seed = mix as u32;
148    seed_extra = (mix >> 32) as u32;
149
150    mix = (seed as u64 ^ HASH_MOD) * (seed_extra as u64 ^ HASH_MOD2);
151    seed = mix as u32;
152    seed_extra = (mix >> 32) as u32;
153
154    seed ^ seed_extra
155}
156
157#[inline(always)]
158///Alias to `hash` with `DEFAULT_SECRET`
159pub fn def_hash(data: &[u8], seed: u64) -> u64 {
160    hash(data, seed, &DEFAULT_SECRET)
161}
162
163///Hashing algorithm optimized for small input (<= 64).
164pub fn hash(mut data: &[u8], mut seed: u64, secret: &[u64; 5]) -> u64 {
165    let len = data.len() as u64;
166    #[cold]
167    fn unlikely_branch<'a>(mut data: &'a [u8], seed: &mut u64, secret: &[u64; 5]) -> &'a [u8] {
168        let mut seed_extra = *seed;
169
170        loop {
171            *seed = mix(read_u64(data, 0) ^ secret[1], read_u64(data, 8) ^ *seed) ^ mix(read_u64(data, 16) ^ secret[2], read_u64(data, 24) ^ *seed);
172            seed_extra = mix(read_u64(data, 32) ^ secret[3], read_u64(data, 40) ^ seed_extra) ^ mix(read_u64(data, 48) ^ secret[4], read_u64(data, 56) ^ seed_extra);
173
174            data = &data[64..];
175            if data.len() <= 64 {
176                break;
177            }
178        }
179
180        data
181    }
182
183    if data.len() > 64 {
184        data = unlikely_branch(data, &mut seed, secret);
185    }
186
187    while data.len() > 16 {
188        seed = mix(read_u64(data, 0) ^ secret[1], read_u64(data, 8) ^ seed);
189
190        data = &data[16..]
191    }
192
193    if data.len() > 8 {
194        mix(secret[1] ^ len, mix(read_u64(data, 0) ^ secret[0], read_u64(data, data.len() as isize - 8) ^ seed))
195    } else if data.len() >= 4 {
196        mix(secret[1] ^ len, mix(read_u32(data) as u64 ^ secret[0], read_u32(&data[data.len() - 4..]) as u64 ^ seed))
197    } else if data.len() > 0 {
198        mix(secret[1] ^ len, mix(read_part_u32(data) as u64 ^ secret[0], seed))
199    } else {
200        mix(secret[1] ^ len, mix(secret[0], seed))
201    }
202}