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.
This commit is contained in:
overlookmotel 2025-01-18 01:47:07 +00:00
parent bfd0b0da17
commit 3fff7d293b
4 changed files with 118 additions and 48 deletions

View file

@ -9,13 +9,13 @@ use crate::ast::*;
#[cfg(target_pointer_width = "64")]
const _: () = {
assert!(size_of::<BooleanLiteral>() == 12usize);
assert!(align_of::<BooleanLiteral>() == 4usize);
assert!(size_of::<BooleanLiteral>() == 16usize);
assert!(align_of::<BooleanLiteral>() == 8usize);
assert!(offset_of!(BooleanLiteral, span) == 0usize);
assert!(offset_of!(BooleanLiteral, value) == 8usize);
assert!(size_of::<NullLiteral>() == 8usize);
assert!(align_of::<NullLiteral>() == 4usize);
assert!(align_of::<NullLiteral>() == 8usize);
assert!(offset_of!(NullLiteral, span) == 0usize);
assert!(size_of::<NumericLiteral>() == 40usize);
@ -88,7 +88,7 @@ const _: () = {
assert!(offset_of!(LabelIdentifier, name) == 8usize);
assert!(size_of::<ThisExpression>() == 8usize);
assert!(align_of::<ThisExpression>() == 4usize);
assert!(align_of::<ThisExpression>() == 8usize);
assert!(offset_of!(ThisExpression, span) == 0usize);
assert!(size_of::<ArrayExpression>() == 56usize);
@ -101,7 +101,7 @@ const _: () = {
assert!(align_of::<ArrayExpressionElement>() == 8usize);
assert!(size_of::<Elision>() == 8usize);
assert!(align_of::<Elision>() == 4usize);
assert!(align_of::<Elision>() == 8usize);
assert!(offset_of!(Elision, span) == 0usize);
assert!(size_of::<ObjectExpression>() == 56usize);
@ -312,7 +312,7 @@ const _: () = {
assert!(offset_of!(SequenceExpression, expressions) == 8usize);
assert!(size_of::<Super>() == 8usize);
assert!(align_of::<Super>() == 4usize);
assert!(align_of::<Super>() == 8usize);
assert!(offset_of!(Super, span) == 0usize);
assert!(size_of::<AwaitExpression>() == 24usize);
@ -375,7 +375,7 @@ const _: () = {
assert!(offset_of!(VariableDeclarator, definite) == 64usize);
assert!(size_of::<EmptyStatement>() == 8usize);
assert!(align_of::<EmptyStatement>() == 4usize);
assert!(align_of::<EmptyStatement>() == 8usize);
assert!(offset_of!(EmptyStatement, span) == 0usize);
assert!(size_of::<ExpressionStatement>() == 24usize);
@ -499,7 +499,7 @@ const _: () = {
assert!(offset_of!(CatchParameter, pattern) == 8usize);
assert!(size_of::<DebuggerStatement>() == 8usize);
assert!(align_of::<DebuggerStatement>() == 4usize);
assert!(align_of::<DebuggerStatement>() == 8usize);
assert!(offset_of!(DebuggerStatement, span) == 0usize);
assert!(size_of::<BindingPattern>() == 32usize);
@ -898,59 +898,59 @@ const _: () = {
assert!(align_of::<TSTupleElement>() == 8usize);
assert!(size_of::<TSAnyKeyword>() == 8usize);
assert!(align_of::<TSAnyKeyword>() == 4usize);
assert!(align_of::<TSAnyKeyword>() == 8usize);
assert!(offset_of!(TSAnyKeyword, span) == 0usize);
assert!(size_of::<TSStringKeyword>() == 8usize);
assert!(align_of::<TSStringKeyword>() == 4usize);
assert!(align_of::<TSStringKeyword>() == 8usize);
assert!(offset_of!(TSStringKeyword, span) == 0usize);
assert!(size_of::<TSBooleanKeyword>() == 8usize);
assert!(align_of::<TSBooleanKeyword>() == 4usize);
assert!(align_of::<TSBooleanKeyword>() == 8usize);
assert!(offset_of!(TSBooleanKeyword, span) == 0usize);
assert!(size_of::<TSNumberKeyword>() == 8usize);
assert!(align_of::<TSNumberKeyword>() == 4usize);
assert!(align_of::<TSNumberKeyword>() == 8usize);
assert!(offset_of!(TSNumberKeyword, span) == 0usize);
assert!(size_of::<TSNeverKeyword>() == 8usize);
assert!(align_of::<TSNeverKeyword>() == 4usize);
assert!(align_of::<TSNeverKeyword>() == 8usize);
assert!(offset_of!(TSNeverKeyword, span) == 0usize);
assert!(size_of::<TSIntrinsicKeyword>() == 8usize);
assert!(align_of::<TSIntrinsicKeyword>() == 4usize);
assert!(align_of::<TSIntrinsicKeyword>() == 8usize);
assert!(offset_of!(TSIntrinsicKeyword, span) == 0usize);
assert!(size_of::<TSUnknownKeyword>() == 8usize);
assert!(align_of::<TSUnknownKeyword>() == 4usize);
assert!(align_of::<TSUnknownKeyword>() == 8usize);
assert!(offset_of!(TSUnknownKeyword, span) == 0usize);
assert!(size_of::<TSNullKeyword>() == 8usize);
assert!(align_of::<TSNullKeyword>() == 4usize);
assert!(align_of::<TSNullKeyword>() == 8usize);
assert!(offset_of!(TSNullKeyword, span) == 0usize);
assert!(size_of::<TSUndefinedKeyword>() == 8usize);
assert!(align_of::<TSUndefinedKeyword>() == 4usize);
assert!(align_of::<TSUndefinedKeyword>() == 8usize);
assert!(offset_of!(TSUndefinedKeyword, span) == 0usize);
assert!(size_of::<TSVoidKeyword>() == 8usize);
assert!(align_of::<TSVoidKeyword>() == 4usize);
assert!(align_of::<TSVoidKeyword>() == 8usize);
assert!(offset_of!(TSVoidKeyword, span) == 0usize);
assert!(size_of::<TSSymbolKeyword>() == 8usize);
assert!(align_of::<TSSymbolKeyword>() == 4usize);
assert!(align_of::<TSSymbolKeyword>() == 8usize);
assert!(offset_of!(TSSymbolKeyword, span) == 0usize);
assert!(size_of::<TSThisType>() == 8usize);
assert!(align_of::<TSThisType>() == 4usize);
assert!(align_of::<TSThisType>() == 8usize);
assert!(offset_of!(TSThisType, span) == 0usize);
assert!(size_of::<TSObjectKeyword>() == 8usize);
assert!(align_of::<TSObjectKeyword>() == 4usize);
assert!(align_of::<TSObjectKeyword>() == 8usize);
assert!(offset_of!(TSObjectKeyword, span) == 0usize);
assert!(size_of::<TSBigIntKeyword>() == 8usize);
assert!(align_of::<TSBigIntKeyword>() == 4usize);
assert!(align_of::<TSBigIntKeyword>() == 8usize);
assert!(offset_of!(TSBigIntKeyword, span) == 0usize);
assert!(size_of::<TSTypeReference>() == 32usize);
@ -1272,7 +1272,7 @@ const _: () = {
assert!(offset_of!(JSDocNonNullableType, postfix) == 24usize);
assert!(size_of::<JSDocUnknownType>() == 8usize);
assert!(align_of::<JSDocUnknownType>() == 4usize);
assert!(align_of::<JSDocUnknownType>() == 8usize);
assert!(offset_of!(JSDocUnknownType, span) == 0usize);
assert!(size_of::<JSXElement>() == 56usize);
@ -1303,11 +1303,11 @@ const _: () = {
assert!(offset_of!(JSXFragment, children) == 24usize);
assert!(size_of::<JSXOpeningFragment>() == 8usize);
assert!(align_of::<JSXOpeningFragment>() == 4usize);
assert!(align_of::<JSXOpeningFragment>() == 8usize);
assert!(offset_of!(JSXOpeningFragment, span) == 0usize);
assert!(size_of::<JSXClosingFragment>() == 8usize);
assert!(align_of::<JSXClosingFragment>() == 4usize);
assert!(align_of::<JSXClosingFragment>() == 8usize);
assert!(offset_of!(JSXClosingFragment, span) == 0usize);
assert!(size_of::<JSXElementName>() == 16usize);
@ -1337,7 +1337,7 @@ const _: () = {
assert!(align_of::<JSXExpression>() == 8usize);
assert!(size_of::<JSXEmptyExpression>() == 8usize);
assert!(align_of::<JSXEmptyExpression>() == 4usize);
assert!(align_of::<JSXEmptyExpression>() == 8usize);
assert!(offset_of!(JSXEmptyExpression, span) == 0usize);
assert!(size_of::<JSXAttributeItem>() == 16usize);
@ -1385,7 +1385,7 @@ const _: () = {
assert!(align_of::<CommentPosition>() == 1usize);
assert!(size_of::<Comment>() == 16usize);
assert!(align_of::<Comment>() == 4usize);
assert!(align_of::<Comment>() == 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::<UpdateOperator>() == 1usize);
assert!(size_of::<Span>() == 8usize);
assert!(align_of::<Span>() == 4usize);
assert!(align_of::<Span>() == 8usize);
assert!(offset_of!(Span, start) == 0usize);
assert!(offset_of!(Span, end) == 4usize);
@ -1449,8 +1449,8 @@ const _: () = {
assert!(size_of::<Term>() == 16usize);
assert!(align_of::<Term>() == 8usize);
assert!(size_of::<BoundaryAssertion>() == 12usize);
assert!(align_of::<BoundaryAssertion>() == 4usize);
assert!(size_of::<BoundaryAssertion>() == 16usize);
assert!(align_of::<BoundaryAssertion>() == 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::<Character>() == 16usize);
assert!(align_of::<Character>() == 4usize);
assert!(align_of::<Character>() == 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::<CharacterKind>() == 1usize);
assert!(align_of::<CharacterKind>() == 1usize);
assert!(size_of::<CharacterClassEscape>() == 12usize);
assert!(align_of::<CharacterClassEscape>() == 4usize);
assert!(size_of::<CharacterClassEscape>() == 16usize);
assert!(align_of::<CharacterClassEscape>() == 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::<Dot>() == 8usize);
assert!(align_of::<Dot>() == 4usize);
assert!(align_of::<Dot>() == 8usize);
assert!(offset_of!(Dot, span) == 0usize);
assert!(size_of::<CharacterClass>() == 48usize);
@ -1518,7 +1518,7 @@ const _: () = {
assert!(align_of::<CharacterClassContents>() == 8usize);
assert!(size_of::<CharacterClassRange>() == 40usize);
assert!(align_of::<CharacterClassRange>() == 4usize);
assert!(align_of::<CharacterClassRange>() == 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::<Modifiers>() == 16usize);
assert!(align_of::<Modifiers>() == 4usize);
assert!(align_of::<Modifiers>() == 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::<IndexedReference>() == 12usize);
assert!(align_of::<IndexedReference>() == 4usize);
assert!(size_of::<IndexedReference>() == 16usize);
assert!(align_of::<IndexedReference>() == 8usize);
assert!(offset_of!(IndexedReference, span) == 0usize);
assert!(offset_of!(IndexedReference, index) == 8usize);

View file

@ -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<Span> for LabeledSpan {
}
}
// Skip hashing `_align` field
impl Hash for Span {
#[inline] // We exclusively use `FxHasher`, which produces small output hashing `u32`s
fn hash<H: Hasher>(&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<T: 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::<Span>() == 8);
#[cfg(target_pointer_width = "64")]
const _: () = assert!(align_of::<Span>() == 8);
#[cfg(not(target_pointer_width = "64"))]
const _: () = assert!(align_of::<Span>() == 4);
}

View file

@ -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)]

View file

@ -300,5 +300,7 @@ lazy_static! {
("Cell<Option<ReferenceId>>", PlatformLayout::of::<u32>()),
// Unsupported: this is a `bitflags` generated type, we don't expand macros
("RegExpFlags", PlatformLayout::of::<u8>()),
// `PointerAlign` is a field of `Span`. ZST with pointer alignment.
("PointerAlign", PlatformLayout(Layout::known(0, 8, 0), Layout::known(0, 4, 0))),
]);
}