From 63eb29868d71c359a169cadff9aafeb8e8edcab0 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:47:10 +0000 Subject: [PATCH] perf(span): compare `Span`s as single `u64`s (#8300) #8298 made `Span` aligned on 8 on 64-bit platforms. Utilize this property to compare `Span`s as a single `u64` instead of 2 x `u32`s. This removes some really weird assembly which compiler otherwise produces, using expensive SIMD operations for a simple comparison: https://godbolt.org/z/sEf9MGvsr Note: This only affects comparing `&Span`s. Makes no difference when comparing owned `Span`s. --- crates/oxc_span/src/span/mod.rs | 33 ++++++++++++++++++++++++++++++- crates/oxc_span/src/span/types.rs | 2 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/oxc_span/src/span/mod.rs b/crates/oxc_span/src/span/mod.rs index 0390062584f39..73d9e38282e90 100644 --- a/crates/oxc_span/src/span/mod.rs +++ b/crates/oxc_span/src/span/mod.rs @@ -108,8 +108,9 @@ impl Span { /// assert!(!Span::new(0, 5).is_unspanned()); /// assert!(!Span::new(5, 5).is_unspanned()); /// ``` + #[inline] pub const fn is_unspanned(self) -> bool { - self.start == SPAN.start && self.end == SPAN.end + self.const_eq(SPAN) } /// Check if this [`Span`] contains another [`Span`]. @@ -360,6 +361,21 @@ impl Span { ((self.start as u64) << 32) | (self.end as u64) } } + + /// Compare two [`Span`]s. + /// + /// Same as `PartialEq::eq`, but a const function, and takes owned `Span`s. + // + // `#[inline(always)]` because want to make sure this is inlined into `PartialEq::eq`. + #[expect(clippy::inline_always)] + #[inline(always)] + const fn const_eq(self, other: Self) -> bool { + if cfg!(target_pointer_width = "64") { + self.as_u64() == other.as_u64() + } else { + self.start == other.start && self.end == other.end + } + } } impl Index for str { @@ -397,6 +413,15 @@ impl From for LabeledSpan { } } +// On 64-bit platforms, compare `Span`s as single `u64`s, which is faster when used with `&Span` refs. +// https://godbolt.org/z/sEf9MGvsr +impl PartialEq for Span { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.const_eq(*other) + } +} + // Skip hashing `_align` field. // On 64-bit platforms, hash `Span` as a single `u64`, which is faster with `FxHash`. // https://godbolt.org/z/4fbvcsTxM @@ -511,7 +536,13 @@ mod test { fn test_eq() { assert_eq!(Span::new(0, 0), Span::new(0, 0)); assert_eq!(Span::new(0, 1), Span::new(0, 1)); + assert_eq!(Span::new(1, 5), Span::new(1, 5)); + assert_ne!(Span::new(0, 0), Span::new(0, 1)); + assert_ne!(Span::new(1, 5), Span::new(0, 5)); + assert_ne!(Span::new(1, 5), Span::new(2, 5)); + assert_ne!(Span::new(1, 5), Span::new(1, 4)); + assert_ne!(Span::new(1, 5), Span::new(1, 6)); } #[test] diff --git a/crates/oxc_span/src/span/types.rs b/crates/oxc_span/src/span/types.rs index def2d8619434d..ce1042a50f69d 100644 --- a/crates/oxc_span/src/span/types.rs +++ b/crates/oxc_span/src/span/types.rs @@ -59,7 +59,7 @@ use super::PointerAlign; /// [`expand`]: Span::expand /// [`shrink`]: Span::shrink #[ast(visit)] -#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord)] #[generate_derive(ESTree)] #[estree(no_type, always_flatten)] pub struct Span {