Skip to content

Commit

Permalink
perf(span): align Span same as usize (#8298)
Browse files Browse the repository at this point in the history
`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.
  • Loading branch information
overlookmotel committed Jan 18, 2025
1 parent bfd0b0d commit 3fff7d2
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 48 deletions.
76 changes: 38 additions & 38 deletions crates/oxc_ast/src/generated/assert_layouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand All @@ -1475,16 +1475,16 @@ 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);

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);

Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand Down
80 changes: 72 additions & 8 deletions crates/oxc_span/src/span/mod.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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.
///
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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);
}
Loading

0 comments on commit 3fff7d2

Please sign in to comment.