From dfc604b5c8b81d5f1701197831ceb1d0ab4c7ebf Mon Sep 17 00:00:00 2001 From: Mert Can Altin Date: Sat, 26 Jul 2025 22:10:27 +0300 Subject: [PATCH 1/5] fix(es/minifier): Don't optimize Number properties when Number is shadowed (#10947) **Related issue:** - Closes https://github.com/swc-project/swc/issues/10938 --- crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs | 6 ++++++ .../swc_ecma_minifier/tests/fixture/issues/10938/input.js | 1 + .../swc_ecma_minifier/tests/fixture/issues/10938/output.js | 1 + 3 files changed, 8 insertions(+) create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/10938/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/10938/output.js diff --git a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs index b0b7529b9c78..88bc5896cc9f 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs @@ -145,6 +145,12 @@ impl Optimizer<'_> { span, .. }) if matches!(obj.as_ref(), Expr::Ident(ident) if &*ident.sym == "Number") => { + if let Expr::Ident(number_ident) = &**obj { + if number_ident.ctxt != self.ctx.expr_ctx.unresolved_ctxt { + return; + } + } + match &*prop.sym { "MIN_VALUE" => { report_change!("evaluate: `Number.MIN_VALUE` -> `5e-324`"); diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/10938/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/10938/input.js new file mode 100644 index 000000000000..3c0d9e0660b5 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/10938/input.js @@ -0,0 +1 @@ +let Number; console.log(Number.NaN); \ No newline at end of file diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/10938/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/10938/output.js new file mode 100644 index 000000000000..80fbce39b163 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/10938/output.js @@ -0,0 +1 @@ +console.log((void 0).NaN); From 940655364c4e82448cc7cb383d5e7bb2bcbf58a1 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Sat, 26 Jul 2025 16:04:48 -0700 Subject: [PATCH 2/5] Mark `atom!` allocated Atoms as having a 'static' lifetime and stop refcounting them The Clone and Drop routines will treat them like `inline` atoms but their storage will instead be in a `static` item. This should speed up initialization and access to `atom!` values since there is no more lazy lock just a simple pointer tag. This does complicate `eq` and `hash` a bit as well as `as_str` to handle the new location This follows a similar pattern used in turbopack as of https://github.com/vercel/next.js/pull/81994. Things are different here due to the use of `ThinArc` for storage, which required me to introduce a separate struct for the `&'static str` case. --- crates/hstr/src/dynamic.rs | 194 +++++++++++++++++++++++++++++++- crates/hstr/src/lib.rs | 147 ++++++++++++++++-------- crates/hstr/src/tagged_value.rs | 13 +-- crates/hstr/src/tests.rs | 14 ++- 4 files changed, 302 insertions(+), 66 deletions(-) diff --git a/crates/hstr/src/dynamic.rs b/crates/hstr/src/dynamic.rs index 4e49ef17a1c5..229f400528ac 100644 --- a/crates/hstr/src/dynamic.rs +++ b/crates/hstr/src/dynamic.rs @@ -9,7 +9,6 @@ use std::{ ptr::{self, NonNull}, }; -use rustc_hash::FxHasher; use triomphe::ThinArc; use crate::{ @@ -204,10 +203,143 @@ impl Storage for &'_ mut AtomStore { } #[inline(never)] -fn calc_hash(text: &str) -> u64 { - let mut hasher = FxHasher::default(); - text.hash(&mut hasher); - hasher.finish() +pub(crate) const fn calc_hash(text: &str) -> u64 { + hash_bytes(text.as_bytes()) +} + +// Nothing special, digits of pi. +const SEED1: u64 = 0x243f6a8885a308d3; +const SEED2: u64 = 0x13198a2e03707344; +const PREVENT_TRIVIAL_ZERO_COLLAPSE: u64 = 0xa4093822299f31d0; + +#[inline] +const fn multiply_mix(x: u64, y: u64) -> u64 { + #[cfg(target_pointer_width = "64")] + { + // We compute the full u64 x u64 -> u128 product, this is a single mul + // instruction on x86-64, one mul plus one mulhi on ARM64. + let full = (x as u128) * (y as u128); + let lo = full as u64; + let hi = (full >> 64) as u64; + + // The middle bits of the full product fluctuate the most with small + // changes in the input. This is the top bits of lo and the bottom bits + // of hi. We can thus make the entire output fluctuate with small + // changes to the input by XOR'ing these two halves. + lo ^ hi + + // Unfortunately both 2^64 + 1 and 2^64 - 1 have small prime factors, + // otherwise combining with + or - could result in a really strong hash, + // as: x * y = 2^64 * hi + lo = (-1) * hi + lo = lo - hi, + // (mod 2^64 + 1) x * y = 2^64 * hi + lo = 1 * hi + lo = + // lo + hi, (mod 2^64 - 1) Multiplicative hashing is universal + // in a field (like mod p). + } + + #[cfg(target_pointer_width = "32")] + { + // u64 x u64 -> u128 product is prohibitively expensive on 32-bit. + // Decompose into 32-bit parts. + let lx = x as u32; + let ly = y as u32; + let hx = (x >> 32) as u32; + let hy = (y >> 32) as u32; + + // u32 x u32 -> u64 the low bits of one with the high bits of the other. + let afull = (lx as u64) * (hy as u64); + let bfull = (hx as u64) * (ly as u64); + + // Combine, swapping low/high of one of them so the upper bits of the + // product of one combine with the lower bits of the other. + afull ^ bfull.rotate_right(32) + } +} + +// Const compatible helper function to read a u64 from a byte array at a given +// offset +const fn read_u64_le(bytes: &[u8], offset: usize) -> u64 { + (bytes[offset] as u64) + | ((bytes[offset + 1] as u64) << 8) + | ((bytes[offset + 2] as u64) << 16) + | ((bytes[offset + 3] as u64) << 24) + | ((bytes[offset + 4] as u64) << 32) + | ((bytes[offset + 5] as u64) << 40) + | ((bytes[offset + 6] as u64) << 48) + | ((bytes[offset + 7] as u64) << 56) +} + +// Const compatible helper function to read a u32 from a byte array at a given +// offset +const fn read_u32_le(bytes: &[u8], offset: usize) -> u32 { + (bytes[offset] as u32) + | ((bytes[offset + 1] as u32) << 8) + | ((bytes[offset + 2] as u32) << 16) + | ((bytes[offset + 3] as u32) << 24) +} + +/// Copied from `hash_bytes` of `rustc-hash`. +/// +/// See: https://github.com/rust-lang/rustc-hash/blob/dc5c33f1283de2da64d8d7a06401d91aded03ad4/src/lib.rs#L252-L297 +/// +/// --- +/// +/// A wyhash-inspired non-collision-resistant hash for strings/slices designed +/// by Orson Peters, with a focus on small strings and small codesize. +/// +/// The 64-bit version of this hash passes the SMHasher3 test suite on the full +/// 64-bit output, that is, f(hash_bytes(b) ^ f(seed)) for some good avalanching +/// permutation f() passed all tests with zero failures. When using the 32-bit +/// version of multiply_mix this hash has a few non-catastrophic failures where +/// there are a handful more collisions than an optimal hash would give. +/// +/// We don't bother avalanching here as we'll feed this hash into a +/// multiplication after which we take the high bits, which avalanches for us. +#[inline] +#[doc(hidden)] +const fn hash_bytes(bytes: &[u8]) -> u64 { + let len = bytes.len(); + let mut s0 = SEED1; + let mut s1 = SEED2; + + if len <= 16 { + // XOR the input into s0, s1. + if len >= 8 { + s0 ^= read_u64_le(bytes, 0); + s1 ^= read_u64_le(bytes, len - 8); + } else if len >= 4 { + s0 ^= read_u32_le(bytes, 0) as u64; + s1 ^= read_u32_le(bytes, len - 4) as u64; + } else if len > 0 { + let lo = bytes[0]; + let mid = bytes[len / 2]; + let hi = bytes[len - 1]; + s0 ^= lo as u64; + s1 ^= ((hi as u64) << 8) | mid as u64; + } + } else { + // Handle bulk (can partially overlap with suffix). + let mut off = 0; + while off < len - 16 { + let x = read_u64_le(bytes, off); + let y = read_u64_le(bytes, off + 8); + + // Replace s1 with a mix of s0, x, and y, and s0 with s1. + // This ensures the compiler can unroll this loop into two + // independent streams, one operating on s0, the other on s1. + // + // Since zeroes are a common input we prevent an immediate trivial + // collapse of the hash function by XOR'ing a constant with y. + let t = multiply_mix(s0 ^ x, PREVENT_TRIVIAL_ZERO_COLLAPSE ^ y); + s0 = s1; + s1 = t; + off += 16; + } + + s0 ^= read_u64_le(bytes, len - 16); + s1 ^= read_u64_le(bytes, len - 8); + } + + multiply_mix(s0, s1) ^ (len as u64) } type BuildEntryHasher = BuildHasherDefault; @@ -253,7 +385,11 @@ impl Hasher for EntryHasher { #[cfg(test)] mod tests { - use crate::{dynamic::GLOBAL_DATA, global_atom_store_gc, Atom}; + use std::hash::{Hash, Hasher}; + + use rustc_hash::FxHasher; + + use crate::{atom, dynamic::GLOBAL_DATA, global_atom_store_gc, Atom}; fn expect_size(expected: usize) { // This is a helper function to count the number of bytes in the global store. @@ -327,4 +463,50 @@ mod tests { global_atom_store_gc(); expect_size(0); } + + // Ensure that the hash value is the same as the one generated by FxHasher. + // + // This is important for `Borrow` implementation to be correct. + // Note that if we enable `nightly` feature of `rustc-hash`, we need to remove + // `state.write_u8(0xff);` from the hash implementation of `RcStr`. + #[test] + fn test_hash() { + const LONG_STRING: &str = "A very long long long string that would not be inlined"; + + { + let u64_value = super::hash_bytes(LONG_STRING.as_bytes()); + dbg!(u64_value); + let mut hasher = FxHasher::default(); + hasher.write_u64(u64_value); + let expected = hasher.finish(); + + println!("Expected: {expected:?}"); + } + + let str = Atom::from(LONG_STRING); + assert_eq!(fxhash(str.clone()), fxhash(LONG_STRING)); + assert_eq!(fxhash(str.clone()), fxhash(atom!(LONG_STRING))); + assert_eq!(fxhash((1, str, 1)), fxhash((1, LONG_STRING, 1))); + } + + fn fxhash(value: T) -> u64 { + let mut hasher = FxHasher::default(); + value.hash(&mut hasher); + hasher.finish() + } + + #[test] + fn static_items_are_not_in_the_store() { + const VALUE: &str = "hello a long string that cannot be inline"; + expect_size(0); + let long_str = atom!(VALUE); + expect_size(0); + let store_str = Atom::new(VALUE); + expect_size(1); + drop(store_str); + expect_size(1); + global_atom_store_gc(); + drop(long_str); + expect_size(0); + } } diff --git a/crates/hstr/src/lib.rs b/crates/hstr/src/lib.rs index f9cda1169cc0..cae290b6a61f 100644 --- a/crates/hstr/src/lib.rs +++ b/crates/hstr/src/lib.rs @@ -8,6 +8,7 @@ use std::{ mem::{self, forget, transmute}, num::NonZeroU8, ops::Deref, + ptr::NonNull, str::from_utf8_unchecked, }; @@ -15,7 +16,7 @@ use debug_unreachable::debug_unreachable; use once_cell::sync::Lazy; pub use crate::dynamic::{global_atom_store_gc, AtomStore}; -use crate::tagged_value::TaggedValue; +use crate::{dynamic::calc_hash, tagged_value::TaggedValue}; mod dynamic; mod global_store; @@ -109,6 +110,35 @@ pub const fn inline_atom(s: &str) -> Option { dynamic::inline_atom(s) } +#[doc(hidden)] +pub struct StaticStorage { + pub hash: u64, + pub value: &'static str, +} +#[doc(hidden)] +pub const fn static_atom_storage(value: &'static str) -> StaticStorage { + StaticStorage { + hash: calc_hash(value), + value, + } +} + +#[doc(hidden)] +#[inline(always)] +pub fn from_static(value: &'static StaticStorage) -> Atom { + let mut entry = value as *const _; + debug_assert!(0 == entry as u8 & TAG_MASK); + // Tag it as a static pointer + entry = ((entry as usize) | STATIC_TAG as usize) as *mut _; + let ptr: NonNull<_> = unsafe { + // Safety: references always return a non-null pointers + NonNull::new_unchecked(entry as *mut StaticStorage) + }; + Atom { + unsafe_data: TaggedValue::new_ptr(ptr), + } +} + /// Create an atom from a string literal. This atom is never dropped. #[macro_export] macro_rules! atom { @@ -119,13 +149,12 @@ macro_rules! atom { if INLINE.is_some() { INLINE.unwrap() } else { - // Otherwise we use a - #[inline(never)] + // Otherwise we use a static allocated payload to hold the hash and data and + // return a constructed pointer to it fn get_atom() -> $crate::Atom { - static CACHE: $crate::CachedAtom = - $crate::CachedAtom::new(|| $crate::Atom::from($s)); + static CACHE: $crate::StaticStorage = $crate::static_atom_storage($s); - (*CACHE).clone() + $crate::from_static(&CACHE) } get_atom() @@ -179,7 +208,11 @@ impl<'de> serde::de::Deserialize<'de> for Atom { String::deserialize(deserializer).map(Self::new) } } +// Data is stored in a ThinArc const DYNAMIC_TAG: u8 = 0b_00; +// Data is stored in a static field +const STATIC_TAG: u8 = 0b_10; +// Data is stored inline in the Atom uppermost bytes const INLINE_TAG: u8 = 0b_01; // len in upper nybble const INLINE_TAG_INIT: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(INLINE_TAG) }; const TAG_MASK: u8 = 0b_11; @@ -252,24 +285,6 @@ impl Atom { } impl Atom { - #[inline(never)] - fn get_hash(&self) -> u64 { - match self.tag() { - DYNAMIC_TAG => { - unsafe { crate::dynamic::deref_from(self.unsafe_data) } - .header - .header - .hash - } - INLINE_TAG => { - // This is passed as input to the caller's `Hasher` implementation, so it's okay - // that this isn't really a hash - self.unsafe_data.hash() - } - _ => unsafe { debug_unreachable!() }, - } - } - #[inline(never)] fn as_str(&self) -> &str { match self.tag() { @@ -277,14 +292,23 @@ impl Atom { let item = crate::dynamic::deref_from(self.unsafe_data); from_utf8_unchecked(transmute::<&[u8], &'static [u8]>(&item.slice)) }, - INLINE_TAG => { - let len = (self.unsafe_data.tag() & LEN_MASK) >> LEN_OFFSET; - let src = self.unsafe_data.data(); - unsafe { std::str::from_utf8_unchecked(&src[..(len as usize)]) } + INLINE_TAG => self.inline_as_str(), + STATIC_TAG => { + let storage = self.unsafe_data.get_ptr() as *const StaticStorage; + // SAFETY: these are only constructed from static item references + (unsafe { &*storage }).value } _ => unsafe { debug_unreachable!() }, } } + + // Same as `[as_str]` for when you know if is inline + fn inline_as_str(&self) -> &str { + debug_assert_eq!(self.tag(), INLINE_TAG); + let len = (self.unsafe_data.tag() & LEN_MASK) >> LEN_OFFSET; + let src = self.unsafe_data.data(); + unsafe { std::str::from_utf8_unchecked(&src[..(len as usize)]) } + } } #[cfg(test)] @@ -307,31 +331,37 @@ impl PartialEq for Atom { if self.unsafe_data == other.unsafe_data { return true; } - - // If one is inline and the other is not, the length is different. - // If one is static and the other is not, it's different. - if self.tag() != other.tag() { + // If one is inline and the other is not, the length must be different. + // If they are both inline and equal, the previous check would have returned + // true. + if self.tag() == INLINE_TAG || other.tag() == INLINE_TAG { return false; } - - if self.is_dynamic() && other.is_dynamic() { - let te = unsafe { crate::dynamic::deref_from(self.unsafe_data) }; - let oe = unsafe { crate::dynamic::deref_from(other.unsafe_data) }; - - if te.header.header.hash != oe.header.header.hash { - return false; + fn unpack<'a>(a: &'a Atom) -> (u64, &'a [u8]) { + match a.tag() { + STATIC_TAG => { + let storage = a.unsafe_data.get_ptr() as *const StaticStorage; + // SAFETY: these are only constructed from static item references + let storage = unsafe { &*storage }; + (storage.hash, storage.value.as_bytes()) + } + DYNAMIC_TAG => + // SAFETY: we have checked the tag + unsafe { + let item = crate::dynamic::deref_from(a.unsafe_data); + ( + item.header.header.hash, + // Extend the lifetime of the slice to the lifetime of the Atom it is + // derived from. + transmute::<&[u8], &'a [u8]>(&item.slice), + ) + }, + _ => unsafe { debug_unreachable!() }, } - - return te.slice == oe.slice; - } - - if self.get_hash() != other.get_hash() { - return false; } - - // If the store is different, the string may be the same, even though the - // `unsafe_data` is different - self.as_str() == other.as_str() + let (h1, s1) = unpack(self); + let (h2, s2) = unpack(other); + h1 == h2 && s1 == s2 } } @@ -340,7 +370,24 @@ impl Eq for Atom {} impl Hash for Atom { #[inline(always)] fn hash(&self, state: &mut H) { - state.write_u64(self.get_hash()); + match self.tag() { + STATIC_TAG => { + let storage = self.unsafe_data.get_ptr() as *const StaticStorage; + // SAFETY: these are only constructed from static item references + let storage = unsafe { &*storage }; + state.write_u64(storage.hash); + state.write_u8(0xff); + } + DYNAMIC_TAG => { + let item = unsafe { crate::dynamic::deref_from(self.unsafe_data) }; + state.write_u64(item.header.header.hash); + state.write_u8(0xff); + } + INLINE_TAG => { + self.inline_as_str().hash(state); + } + _ => unsafe { debug_unreachable!() }, + } } } diff --git a/crates/hstr/src/tagged_value.rs b/crates/hstr/src/tagged_value.rs index 11979f4863f0..8a635c919040 100644 --- a/crates/hstr/src/tagged_value.rs +++ b/crates/hstr/src/tagged_value.rs @@ -79,11 +79,6 @@ impl TaggedValue { } } - #[inline(always)] - pub fn hash(&self) -> u64 { - self.get_value() as _ - } - #[inline(always)] pub fn get_ptr(&self) -> *const c_void { #[cfg(any( @@ -93,7 +88,7 @@ impl TaggedValue { feature = "atom_size_128" ))] { - self.value.get() as usize as _ + (self.value.get() as usize & !(TAG_MASK as usize)) as _ } #[cfg(not(any( target_pointer_width = "32", @@ -101,8 +96,10 @@ impl TaggedValue { feature = "atom_size_64", feature = "atom_size_128" )))] - unsafe { - std::mem::transmute(Some(self.value)) + { + use crate::TAG_MASK; + + (self.value.as_ptr() as usize & !(TAG_MASK as usize)) as _ } } diff --git a/crates/hstr/src/tests.rs b/crates/hstr/src/tests.rs index ee2a6847959c..cb953ade1cb8 100644 --- a/crates/hstr/src/tests.rs +++ b/crates/hstr/src/tests.rs @@ -1,3 +1,7 @@ +use std::hash::{Hash, Hasher}; + +use rustc_hash::FxHasher; + use crate::{Atom, AtomStore}; fn store_with_atoms(texts: Vec<&str>) -> (AtomStore, Vec) { @@ -36,7 +40,7 @@ fn eager_drop() { a1.unsafe_data, a2.unsafe_data, "Different stores should have different addresses" ); - assert_eq!(a1.get_hash(), a2.get_hash(), "Same string should be equal"); + assert_eq!(get_hash(&a1), get_hash(&a2), "Same string should be equal"); assert_eq!(a1, a2, "Same string should be equal"); } @@ -52,7 +56,7 @@ fn store_multiple() { a1.unsafe_data, a2.unsafe_data, "Different stores should have different addresses" ); - assert_eq!(a1.get_hash(), a2.get_hash(), "Same string should be equal"); + assert_eq!(get_hash(&a1), get_hash(&a2), "Same string should be equal"); assert_eq!(a1, a2, "Same string should be equal"); } @@ -82,3 +86,9 @@ fn store_ref_count_dynamic() { drop(a2); assert_eq!(atoms[0].ref_count(), 1); } + +fn get_hash(value: &Atom) -> u64 { + let mut hasher = FxHasher::default(); + value.hash(&mut hasher); + hasher.finish() +} From 7bc07acbfa81702254e6920e92763b4b945b4dc8 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Sat, 26 Jul 2025 16:10:59 -0700 Subject: [PATCH 3/5] Mark this as a `patch` version update No public APIs have been modified so a patch release is approrpriate --- .changeset/itchy-cycles-compare.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/itchy-cycles-compare.md diff --git a/.changeset/itchy-cycles-compare.md b/.changeset/itchy-cycles-compare.md new file mode 100644 index 000000000000..f35b36bf1386 --- /dev/null +++ b/.changeset/itchy-cycles-compare.md @@ -0,0 +1,5 @@ +--- +hstr: patch +--- + +Mark `atom!` allocated Atoms as having a 'static' lifetime and stop refcounting them From bd439afa73758bacd93d02f2fd35d8208649d504 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Sat, 26 Jul 2025 16:30:38 -0700 Subject: [PATCH 4/5] fix wasm build --- crates/hstr/src/tagged_value.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/hstr/src/tagged_value.rs b/crates/hstr/src/tagged_value.rs index 8a635c919040..9ece1ccf0a4a 100644 --- a/crates/hstr/src/tagged_value.rs +++ b/crates/hstr/src/tagged_value.rs @@ -2,6 +2,8 @@ use std::{num::NonZeroU8, os::raw::c_void, ptr::NonNull, slice}; +use crate::TAG_MASK; + #[cfg(feature = "atom_size_128")] type RawTaggedValue = u128; #[cfg(any( @@ -97,8 +99,6 @@ impl TaggedValue { feature = "atom_size_128" )))] { - use crate::TAG_MASK; - (self.value.as_ptr() as usize & !(TAG_MASK as usize)) as _ } } From be4a6816577156b9048dbb5c65a1c33e2c34eaad Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Sat, 26 Jul 2025 19:52:15 -0700 Subject: [PATCH 5/5] Address the hash regression with pointers! --- crates/hstr/src/dynamic.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/hstr/src/dynamic.rs b/crates/hstr/src/dynamic.rs index 229f400528ac..1345ae1ab9cf 100644 --- a/crates/hstr/src/dynamic.rs +++ b/crates/hstr/src/dynamic.rs @@ -257,24 +257,18 @@ const fn multiply_mix(x: u64, y: u64) -> u64 { // Const compatible helper function to read a u64 from a byte array at a given // offset +#[inline(always)] const fn read_u64_le(bytes: &[u8], offset: usize) -> u64 { - (bytes[offset] as u64) - | ((bytes[offset + 1] as u64) << 8) - | ((bytes[offset + 2] as u64) << 16) - | ((bytes[offset + 3] as u64) << 24) - | ((bytes[offset + 4] as u64) << 32) - | ((bytes[offset + 5] as u64) << 40) - | ((bytes[offset + 6] as u64) << 48) - | ((bytes[offset + 7] as u64) << 56) + let array = unsafe { bytes.as_ptr().add(offset) } as *const [u8; 8]; + u64::from_le_bytes(unsafe { *array }) } // Const compatible helper function to read a u32 from a byte array at a given // offset +#[inline(always)] const fn read_u32_le(bytes: &[u8], offset: usize) -> u32 { - (bytes[offset] as u32) - | ((bytes[offset + 1] as u32) << 8) - | ((bytes[offset + 2] as u32) << 16) - | ((bytes[offset + 3] as u32) << 24) + let array = unsafe { bytes.as_ptr().add(offset) } as *const [u8; 4]; + u32::from_le_bytes(unsafe { *array }) } /// Copied from `hash_bytes` of `rustc-hash`.