highway/
builder.rs

1#![allow(unsafe_code)]
2
3use crate::key::Key;
4use crate::traits::HighwayHash;
5use core::{default::Default, fmt::Debug, mem::ManuallyDrop};
6
7#[cfg(target_arch = "aarch64")]
8use crate::aarch64::NeonHash;
9#[cfg(not(any(
10    all(target_family = "wasm", target_feature = "simd128"),
11    target_arch = "aarch64"
12)))]
13use crate::portable::PortableHash;
14#[cfg(all(target_family = "wasm", target_feature = "simd128"))]
15use crate::wasm::WasmHash;
16#[cfg(target_arch = "x86_64")]
17use crate::{AvxHash, SseHash};
18
19/// This union is purely for performance. Originally it was an enum, but Rust /
20/// LLVM had a hard time optimizing it and would include memcpy's that would
21/// dominate profiles.
22union HighwayChoices {
23    #[cfg(not(any(
24        all(target_family = "wasm", target_feature = "simd128"),
25        target_arch = "aarch64"
26    )))]
27    portable: ManuallyDrop<PortableHash>,
28    #[cfg(target_arch = "x86_64")]
29    avx: ManuallyDrop<AvxHash>,
30    #[cfg(target_arch = "x86_64")]
31    sse: ManuallyDrop<SseHash>,
32    #[cfg(target_arch = "aarch64")]
33    neon: ManuallyDrop<NeonHash>,
34    #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
35    wasm: ManuallyDrop<WasmHash>,
36}
37
38/// `HighwayHash` implementation that selects best hash implementation at runtime.
39pub struct HighwayHasher {
40    tag: u8,
41    inner: HighwayChoices,
42}
43
44impl Debug for HighwayHasher {
45    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46        let mut debug = f.debug_struct("HighwayHasher");
47        debug.field("tag", &self.tag);
48
49        match self.tag {
50            #[cfg(not(any(
51                all(target_family = "wasm", target_feature = "simd128"),
52                target_arch = "aarch64"
53            )))]
54            0 => debug.field("hasher", unsafe { &self.inner.portable }),
55            #[cfg(target_arch = "x86_64")]
56            1 => debug.field("hasher", unsafe { &self.inner.avx }),
57            #[cfg(target_arch = "x86_64")]
58            2 => debug.field("hasher", unsafe { &self.inner.sse }),
59            #[cfg(target_arch = "aarch64")]
60            3 => debug.field("hasher", unsafe { &self.inner.neon }),
61            #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
62            4 => debug.field("hasher", unsafe { &self.inner.wasm }),
63            _ => unsafe { core::hint::unreachable_unchecked() },
64        };
65
66        debug.finish()
67    }
68}
69
70impl Clone for HighwayHasher {
71    fn clone(&self) -> Self {
72        let tag = self.tag;
73        match tag {
74            #[cfg(not(any(
75                all(target_family = "wasm", target_feature = "simd128"),
76                target_arch = "aarch64"
77            )))]
78            0 => HighwayHasher {
79                tag,
80                inner: HighwayChoices {
81                    portable: unsafe { self.inner.portable.clone() },
82                },
83            },
84            #[cfg(target_arch = "x86_64")]
85            1 => HighwayHasher {
86                tag,
87                inner: HighwayChoices {
88                    avx: unsafe { self.inner.avx.clone() },
89                },
90            },
91            #[cfg(target_arch = "x86_64")]
92            2 => HighwayHasher {
93                tag,
94                inner: HighwayChoices {
95                    sse: unsafe { self.inner.sse.clone() },
96                },
97            },
98            #[cfg(target_arch = "aarch64")]
99            3 => HighwayHasher {
100                tag,
101                inner: HighwayChoices {
102                    neon: unsafe { self.inner.neon.clone() },
103                },
104            },
105            #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
106            4 => HighwayHasher {
107                tag,
108                inner: HighwayChoices {
109                    wasm: unsafe { self.inner.wasm.clone() },
110                },
111            },
112            _ => unsafe { core::hint::unreachable_unchecked() },
113        }
114    }
115}
116
117impl HighwayHash for HighwayHasher {
118    #[inline]
119    fn append(&mut self, data: &[u8]) {
120        self.append(data);
121    }
122
123    #[inline]
124    fn finalize64(mut self) -> u64 {
125        Self::finalize64(&mut self)
126    }
127
128    #[inline]
129    fn finalize128(mut self) -> [u64; 2] {
130        Self::finalize128(&mut self)
131    }
132
133    #[inline]
134    fn finalize256(mut self) -> [u64; 4] {
135        Self::finalize256(&mut self)
136    }
137
138    #[inline]
139    fn checkpoint(&self) -> [u8; 164] {
140        Self::checkpoint(self)
141    }
142}
143
144impl HighwayHasher {
145    /// Creates a new hasher based on compilation and runtime capabilities
146    #[must_use]
147    pub fn new(key: Key) -> Self {
148        #[cfg(target_arch = "x86_64")]
149        {
150            if cfg!(target_feature = "avx2") {
151                let avx = ManuallyDrop::new(unsafe { AvxHash::force_new(key) });
152                return HighwayHasher {
153                    tag: 1,
154                    inner: HighwayChoices { avx },
155                };
156            } else if cfg!(target_feature = "sse4.1") {
157                let sse = ManuallyDrop::new(unsafe { SseHash::force_new(key) });
158                return HighwayHasher {
159                    tag: 2,
160                    inner: HighwayChoices { sse },
161                };
162            } else {
163                // Ideally we'd use `AvxHash::new` here, but it triggers a memcpy, so we
164                // duplicate the same logic to know if hasher can be enabled.
165                #[cfg(feature = "std")]
166                if is_x86_feature_detected!("avx2") {
167                    let avx = ManuallyDrop::new(unsafe { AvxHash::force_new(key) });
168                    return HighwayHasher {
169                        tag: 1,
170                        inner: HighwayChoices { avx },
171                    };
172                }
173
174                #[cfg(feature = "std")]
175                if is_x86_feature_detected!("sse4.1") {
176                    let sse = ManuallyDrop::new(unsafe { SseHash::force_new(key) });
177                    return HighwayHasher {
178                        tag: 2,
179                        inner: HighwayChoices { sse },
180                    };
181                }
182            }
183        }
184
185        #[cfg(target_arch = "aarch64")]
186        {
187            // Based on discussions here:
188            // https://github.com/nickbabcock/highway-rs/pull/51#discussion_r815247129
189            //
190            // It seems reasonable to assume the aarch64 is neon capable.
191            // If a case is found where that is not true, we can patch later.
192            let neon = ManuallyDrop::new(unsafe { NeonHash::force_new(key) });
193            HighwayHasher {
194                tag: 3,
195                inner: HighwayChoices { neon },
196            }
197        }
198
199        #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
200        {
201            let wasm = ManuallyDrop::new(WasmHash::new(key));
202            HighwayHasher {
203                tag: 4,
204                inner: HighwayChoices { wasm },
205            }
206        }
207
208        #[cfg(not(any(
209            all(target_family = "wasm", target_feature = "simd128"),
210            target_arch = "aarch64"
211        )))]
212        {
213            let portable = ManuallyDrop::new(PortableHash::new(key));
214            HighwayHasher {
215                tag: 0,
216                inner: HighwayChoices { portable },
217            }
218        }
219    }
220
221    /// Creates a new hasher based on compilation and runtime capabilities
222    #[must_use]
223    pub fn from_checkpoint(data: [u8; 164]) -> Self {
224        #[cfg(target_arch = "x86_64")]
225        {
226            if cfg!(target_feature = "avx2") {
227                let avx = ManuallyDrop::new(unsafe { AvxHash::force_from_checkpoint(data) });
228                return HighwayHasher {
229                    tag: 1,
230                    inner: HighwayChoices { avx },
231                };
232            } else if cfg!(target_feature = "sse4.1") {
233                let sse = ManuallyDrop::new(unsafe { SseHash::force_from_checkpoint(data) });
234                return HighwayHasher {
235                    tag: 2,
236                    inner: HighwayChoices { sse },
237                };
238            } else {
239                // Ideally we'd use `AvxHash::new` here, but it triggers a memcpy, so we
240                // duplicate the same logic to know if hasher can be enabled.
241                #[cfg(feature = "std")]
242                if is_x86_feature_detected!("avx2") {
243                    let avx = ManuallyDrop::new(unsafe { AvxHash::force_from_checkpoint(data) });
244                    return HighwayHasher {
245                        tag: 1,
246                        inner: HighwayChoices { avx },
247                    };
248                }
249
250                #[cfg(feature = "std")]
251                if is_x86_feature_detected!("sse4.1") {
252                    let sse = ManuallyDrop::new(unsafe { SseHash::force_from_checkpoint(data) });
253                    return HighwayHasher {
254                        tag: 2,
255                        inner: HighwayChoices { sse },
256                    };
257                }
258            }
259        }
260
261        #[cfg(target_arch = "aarch64")]
262        {
263            // Based on discussions here:
264            // https://github.com/nickbabcock/highway-rs/pull/51#discussion_r815247129
265            //
266            // It seems reasonable to assume the aarch64 is neon capable.
267            // If a case is found where that is not true, we can patch later.
268            let neon = ManuallyDrop::new(unsafe { NeonHash::force_from_checkpoint(data) });
269            HighwayHasher {
270                tag: 3,
271                inner: HighwayChoices { neon },
272            }
273        }
274
275        #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
276        {
277            let wasm = ManuallyDrop::new(WasmHash::from_checkpoint(data));
278            HighwayHasher {
279                tag: 4,
280                inner: HighwayChoices { wasm },
281            }
282        }
283
284        #[cfg(not(any(
285            all(target_family = "wasm", target_feature = "simd128"),
286            target_arch = "aarch64"
287        )))]
288        {
289            let portable = ManuallyDrop::new(PortableHash::from_checkpoint(data));
290            HighwayHasher {
291                tag: 0,
292                inner: HighwayChoices { portable },
293            }
294        }
295    }
296
297    fn append(&mut self, data: &[u8]) {
298        match self.tag {
299            #[cfg(not(any(
300                all(target_family = "wasm", target_feature = "simd128"),
301                target_arch = "aarch64"
302            )))]
303            0 => unsafe { &mut self.inner.portable }.append(data),
304            #[cfg(target_arch = "x86_64")]
305            1 => unsafe { &mut self.inner.avx }.append(data),
306            #[cfg(target_arch = "x86_64")]
307            2 => unsafe { &mut self.inner.sse }.append(data),
308            #[cfg(target_arch = "aarch64")]
309            3 => unsafe { &mut self.inner.neon }.append(data),
310            #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
311            4 => unsafe { &mut self.inner.wasm }.append(data),
312            _ => unsafe { core::hint::unreachable_unchecked() },
313        }
314    }
315
316    fn finalize64(&mut self) -> u64 {
317        match self.tag {
318            #[cfg(not(any(
319                all(target_family = "wasm", target_feature = "simd128"),
320                target_arch = "aarch64"
321            )))]
322            0 => unsafe { PortableHash::finalize64(&mut self.inner.portable) },
323            #[cfg(target_arch = "x86_64")]
324            1 => unsafe { AvxHash::finalize64(&mut self.inner.avx) },
325            #[cfg(target_arch = "x86_64")]
326            2 => unsafe { SseHash::finalize64(&mut self.inner.sse) },
327            #[cfg(target_arch = "aarch64")]
328            3 => unsafe { NeonHash::finalize64(&mut self.inner.neon) },
329            #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
330            4 => unsafe { WasmHash::finalize64(&mut self.inner.wasm) },
331            _ => unsafe { core::hint::unreachable_unchecked() },
332        }
333    }
334
335    fn finalize128(&mut self) -> [u64; 2] {
336        match self.tag {
337            #[cfg(not(any(
338                all(target_family = "wasm", target_feature = "simd128"),
339                target_arch = "aarch64"
340            )))]
341            0 => unsafe { PortableHash::finalize128(&mut self.inner.portable) },
342            #[cfg(target_arch = "x86_64")]
343            1 => unsafe { AvxHash::finalize128(&mut self.inner.avx) },
344            #[cfg(target_arch = "x86_64")]
345            2 => unsafe { SseHash::finalize128(&mut self.inner.sse) },
346            #[cfg(target_arch = "aarch64")]
347            3 => unsafe { NeonHash::finalize128(&mut self.inner.neon) },
348            #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
349            4 => unsafe { WasmHash::finalize128(&mut self.inner.wasm) },
350            _ => unsafe { core::hint::unreachable_unchecked() },
351        }
352    }
353
354    fn finalize256(&mut self) -> [u64; 4] {
355        match self.tag {
356            #[cfg(not(any(
357                all(target_family = "wasm", target_feature = "simd128"),
358                target_arch = "aarch64"
359            )))]
360            0 => unsafe { PortableHash::finalize256(&mut self.inner.portable) },
361            #[cfg(target_arch = "x86_64")]
362            1 => unsafe { AvxHash::finalize256(&mut self.inner.avx) },
363            #[cfg(target_arch = "x86_64")]
364            2 => unsafe { SseHash::finalize256(&mut self.inner.sse) },
365            #[cfg(target_arch = "aarch64")]
366            3 => unsafe { NeonHash::finalize256(&mut self.inner.neon) },
367            #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
368            4 => unsafe { WasmHash::finalize256(&mut self.inner.wasm) },
369            _ => unsafe { core::hint::unreachable_unchecked() },
370        }
371    }
372
373    fn checkpoint(&self) -> [u8; 164] {
374        match self.tag {
375            #[cfg(not(any(
376                all(target_family = "wasm", target_feature = "simd128"),
377                target_arch = "aarch64"
378            )))]
379            0 => unsafe { PortableHash::checkpoint(&self.inner.portable) },
380            #[cfg(target_arch = "x86_64")]
381            1 => unsafe { AvxHash::checkpoint(&self.inner.avx) },
382            #[cfg(target_arch = "x86_64")]
383            2 => unsafe { SseHash::checkpoint(&self.inner.sse) },
384            #[cfg(target_arch = "aarch64")]
385            3 => unsafe { NeonHash::checkpoint(&self.inner.neon) },
386            #[cfg(all(target_family = "wasm", target_feature = "simd128"))]
387            4 => unsafe { WasmHash::checkpoint(&self.inner.wasm) },
388            _ => unsafe { core::hint::unreachable_unchecked() },
389        }
390    }
391}
392
393impl Default for HighwayHasher {
394    fn default() -> Self {
395        HighwayHasher::new(Key::default())
396    }
397}
398
399impl_write!(HighwayHasher);
400impl_hasher!(HighwayHasher);
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    #[test]
407    fn test_has_debug_representation_with_data() {
408        let hasher = HighwayHasher::new(Key::default());
409        let output = format!("{:?}", &hasher);
410        assert!(output.contains("hasher: "));
411    }
412}