From 3fff7d293b0e997430ffe2347b502ba02edfb07d Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:47:07 +0000 Subject: [PATCH] perf(span): align `Span` same as `usize` (#8298) `Span` consists of 2 x `u32` (8 bytes total). Align `Span` on 8 bytes on 64-bit platforms. This means that, on 64-bit platforms, `Span` can be treated as equivalent to a `u64` and stored in a single register (instead of requiring 2). A side-effect is that all AST structs also become aligned on 8. This will be a useful property later on as we can remove alignment calculations from `Allocator::alloc` (since everything now has same alignment). `BooleanLiteral` (and `BoundaryAssertion`, `CharacterClassEscape` and `IndexedReference` from `oxc_regular_expression` crate) increase from 12 bytes to 16 bytes due to the higher alignment. But this makes no practical difference as they'd almost always end up with padding around them in arena anyway, as they'll be surrounded by 8-aligned types. --- .../oxc_ast/src/generated/assert_layouts.rs | 76 +++++++++--------- crates/oxc_span/src/span/mod.rs | 80 +++++++++++++++++-- crates/oxc_span/src/span/types.rs | 8 +- tasks/ast_tools/src/passes/calc_layout.rs | 2 + 4 files changed, 118 insertions(+), 48 deletions(-) diff --git a/crates/oxc_ast/src/generated/assert_layouts.rs b/crates/oxc_ast/src/generated/assert_layouts.rs index 8e5bf9b0fa026..45e1b8d6e59cf 100644 --- a/crates/oxc_ast/src/generated/assert_layouts.rs +++ b/crates/oxc_ast/src/generated/assert_layouts.rs @@ -9,13 +9,13 @@ use crate::ast::*; #[cfg(target_pointer_width = "64")] const _: () = { - assert!(size_of::() == 12usize); - assert!(align_of::() == 4usize); + assert!(size_of::() == 16usize); + assert!(align_of::() == 8usize); assert!(offset_of!(BooleanLiteral, span) == 0usize); assert!(offset_of!(BooleanLiteral, value) == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(NullLiteral, span) == 0usize); assert!(size_of::() == 40usize); @@ -88,7 +88,7 @@ const _: () = { assert!(offset_of!(LabelIdentifier, name) == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(ThisExpression, span) == 0usize); assert!(size_of::() == 56usize); @@ -101,7 +101,7 @@ const _: () = { assert!(align_of::() == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Elision, span) == 0usize); assert!(size_of::() == 56usize); @@ -312,7 +312,7 @@ const _: () = { assert!(offset_of!(SequenceExpression, expressions) == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Super, span) == 0usize); assert!(size_of::() == 24usize); @@ -375,7 +375,7 @@ const _: () = { assert!(offset_of!(VariableDeclarator, definite) == 64usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(EmptyStatement, span) == 0usize); assert!(size_of::() == 24usize); @@ -499,7 +499,7 @@ const _: () = { assert!(offset_of!(CatchParameter, pattern) == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(DebuggerStatement, span) == 0usize); assert!(size_of::() == 32usize); @@ -898,59 +898,59 @@ const _: () = { assert!(align_of::() == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSAnyKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSStringKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSBooleanKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSNumberKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSNeverKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSIntrinsicKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSUnknownKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSNullKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSUndefinedKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSVoidKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSSymbolKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSThisType, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSObjectKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSBigIntKeyword, span) == 0usize); assert!(size_of::() == 32usize); @@ -1272,7 +1272,7 @@ const _: () = { assert!(offset_of!(JSDocNonNullableType, postfix) == 24usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(JSDocUnknownType, span) == 0usize); assert!(size_of::() == 56usize); @@ -1303,11 +1303,11 @@ const _: () = { assert!(offset_of!(JSXFragment, children) == 24usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(JSXOpeningFragment, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(JSXClosingFragment, span) == 0usize); assert!(size_of::() == 16usize); @@ -1337,7 +1337,7 @@ const _: () = { assert!(align_of::() == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(JSXEmptyExpression, span) == 0usize); assert!(size_of::() == 16usize); @@ -1385,7 +1385,7 @@ const _: () = { assert!(align_of::() == 1usize); assert!(size_of::() == 16usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Comment, span) == 0usize); assert!(offset_of!(Comment, attached_to) == 8usize); assert!(offset_of!(Comment, kind) == 12usize); @@ -1415,7 +1415,7 @@ const _: () = { assert!(align_of::() == 1usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Span, start) == 0usize); assert!(offset_of!(Span, end) == 4usize); @@ -1449,8 +1449,8 @@ const _: () = { assert!(size_of::() == 16usize); assert!(align_of::() == 8usize); - assert!(size_of::() == 12usize); - assert!(align_of::() == 4usize); + assert!(size_of::() == 16usize); + assert!(align_of::() == 8usize); assert!(offset_of!(BoundaryAssertion, span) == 0usize); assert!(offset_of!(BoundaryAssertion, kind) == 8usize); @@ -1475,7 +1475,7 @@ const _: () = { assert!(offset_of!(Quantifier, body) == 40usize); assert!(size_of::() == 16usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Character, span) == 0usize); assert!(offset_of!(Character, kind) == 8usize); assert!(offset_of!(Character, value) == 12usize); @@ -1483,8 +1483,8 @@ const _: () = { assert!(size_of::() == 1usize); assert!(align_of::() == 1usize); - assert!(size_of::() == 12usize); - assert!(align_of::() == 4usize); + assert!(size_of::() == 16usize); + assert!(align_of::() == 8usize); assert!(offset_of!(CharacterClassEscape, span) == 0usize); assert!(offset_of!(CharacterClassEscape, kind) == 8usize); @@ -1500,7 +1500,7 @@ const _: () = { assert!(offset_of!(UnicodePropertyEscape, value) == 32usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Dot, span) == 0usize); assert!(size_of::() == 48usize); @@ -1518,7 +1518,7 @@ const _: () = { assert!(align_of::() == 8usize); assert!(size_of::() == 40usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(CharacterClassRange, span) == 0usize); assert!(offset_of!(CharacterClassRange, min) == 8usize); assert!(offset_of!(CharacterClassRange, max) == 24usize); @@ -1548,7 +1548,7 @@ const _: () = { assert!(offset_of!(IgnoreGroup, body) == 24usize); assert!(size_of::() == 16usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Modifiers, span) == 0usize); assert!(offset_of!(Modifiers, enabling) == 8usize); assert!(offset_of!(Modifiers, disabling) == 11usize); @@ -1559,8 +1559,8 @@ const _: () = { assert!(offset_of!(Modifier, multiline) == 1usize); assert!(offset_of!(Modifier, sticky) == 2usize); - assert!(size_of::() == 12usize); - assert!(align_of::() == 4usize); + assert!(size_of::() == 16usize); + assert!(align_of::() == 8usize); assert!(offset_of!(IndexedReference, span) == 0usize); assert!(offset_of!(IndexedReference, index) == 8usize); diff --git a/crates/oxc_span/src/span/mod.rs b/crates/oxc_span/src/span/mod.rs index 4d66624b04a9d..fe7e024d59cfa 100644 --- a/crates/oxc_span/src/span/mod.rs +++ b/crates/oxc_span/src/span/mod.rs @@ -1,4 +1,8 @@ -use std::ops::{Index, IndexMut, Range}; +use std::{ + fmt::{self, Debug}, + hash::{Hash, Hasher}, + ops::{Index, IndexMut, Range}, +}; use miette::{LabeledSpan, SourceOffset, SourceSpan}; @@ -9,6 +13,18 @@ pub use types::Span; /// An Empty span useful for creating AST nodes. pub const SPAN: Span = Span::new(0, 0); +/// Zero-sized type which has pointer alignment (8 on 64-bit, 4 on 32-bit). +#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +struct PointerAlign([usize; 0]); + +impl PointerAlign { + #[inline] + const fn new() -> Self { + Self([]) + } +} + impl Span { /// Create a new [`Span`] from a start and end position. /// @@ -19,7 +35,7 @@ impl Span { /// #[inline] pub const fn new(start: u32, end: u32) -> Self { - Self { start, end } + Self { start, end, _align: PointerAlign::new() } } /// Create a new empty [`Span`] that starts and ends at an offset position. @@ -34,7 +50,7 @@ impl Span { /// assert_eq!(fifth, Span::new(5, 5)); /// ``` pub fn empty(at: u32) -> Self { - Self { start: at, end: at } + Self::new(at, at) } /// Create a new [`Span`] starting at `start` and covering `size` bytes. @@ -362,6 +378,23 @@ impl From for LabeledSpan { } } +// Skip hashing `_align` field +impl Hash for Span { + #[inline] // We exclusively use `FxHasher`, which produces small output hashing `u32`s + fn hash(&self, hasher: &mut H) { + self.start.hash(hasher); + self.end.hash(hasher); + } +} + +// Skip `_align` field in `Debug` output +#[expect(clippy::missing_fields_in_debug)] +impl Debug for Span { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Span").field("start", &self.start).field("end", &self.end).finish() + } +} + /// Get the span for an AST node pub trait GetSpan { /// Get the [`Span`] for an AST node @@ -413,13 +446,29 @@ mod test { } #[test] + #[expect(clippy::items_after_statements)] fn test_hash() { use std::hash::{DefaultHasher, Hash, Hasher}; - let mut first = DefaultHasher::new(); - let mut second = DefaultHasher::new(); - Span::new(0, 5).hash(&mut first); - Span::new(0, 5).hash(&mut second); - assert_eq!(first.finish(), second.finish()); + fn hash(value: T) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() + } + + let first_hash = hash(Span::new(0, 5)); + let second_hash = hash(Span::new(0, 5)); + assert_eq!(first_hash, second_hash); + + // Check `_align` field does not alter hash + #[derive(Hash)] + #[repr(C)] + struct PlainSpan { + start: u32, + end: u32, + } + + let plain_hash = hash(PlainSpan { start: 0, end: 5 }); + assert_eq!(plain_hash, first_hash); } #[test] @@ -481,3 +530,18 @@ mod test { let _ = span.shrink(5); } } + +#[cfg(test)] +mod size_asserts { + use std::mem::{align_of, size_of}; + + use super::Span; + + const _: () = assert!(size_of::() == 8); + + #[cfg(target_pointer_width = "64")] + const _: () = assert!(align_of::() == 8); + + #[cfg(not(target_pointer_width = "64"))] + const _: () = assert!(align_of::() == 4); +} diff --git a/crates/oxc_span/src/span/types.rs b/crates/oxc_span/src/span/types.rs index 9260b9f45d8e2..def2d8619434d 100644 --- a/crates/oxc_span/src/span/types.rs +++ b/crates/oxc_span/src/span/types.rs @@ -1,6 +1,8 @@ use oxc_ast_macros::ast; use oxc_estree::ESTree; +use super::PointerAlign; + /// A range in text, represented by a zero-indexed start and end offset. /// /// It is a logical error for `end` to be less than `start`. @@ -57,9 +59,8 @@ use oxc_estree::ESTree; /// [`expand`]: Span::expand /// [`shrink`]: Span::shrink #[ast(visit)] -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[generate_derive(ESTree)] -#[non_exhaustive] // Disallow struct expression constructor `Span {}` #[estree(no_type, always_flatten)] pub struct Span { /// The zero-based start offset of the span @@ -67,6 +68,9 @@ pub struct Span { /// The zero-based end offset of the span. This may be equal to [`start`](Span::start) if /// the span is empty, but should not be less than it. pub end: u32, + /// Align `Span` on 8 on 64-bit platforms + #[estree(skip)] + pub(super) _align: PointerAlign, } #[cfg(test)] diff --git a/tasks/ast_tools/src/passes/calc_layout.rs b/tasks/ast_tools/src/passes/calc_layout.rs index d113caef693d9..d59f4198c17ef 100644 --- a/tasks/ast_tools/src/passes/calc_layout.rs +++ b/tasks/ast_tools/src/passes/calc_layout.rs @@ -300,5 +300,7 @@ lazy_static! { ("Cell>", PlatformLayout::of::()), // Unsupported: this is a `bitflags` generated type, we don't expand macros ("RegExpFlags", PlatformLayout::of::()), + // `PointerAlign` is a field of `Span`. ZST with pointer alignment. + ("PointerAlign", PlatformLayout(Layout::known(0, 8, 0), Layout::known(0, 4, 0))), ]); }