Skip to content

Commit

Permalink
perf(transformer): introduce NonEmptyStack (#6092)
Browse files Browse the repository at this point in the history
`NonEmptyStack` is a stack structure, optimized for fast push/pop and reading/writing the last entry on the stack. By always having 1 at least one entry, `last` and `last_mut` can be made branchless and extremely cheap (only 1 CPU op).

Use `NonEmptyStack` as one of the backing stores in `SparseStack`.
  • Loading branch information
overlookmotel committed Sep 27, 2024
1 parent 1399d2c commit ad4ef31
Show file tree
Hide file tree
Showing 6 changed files with 773 additions and 37 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/oxc_transformer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ oxc_span = { workspace = true }
oxc_syntax = { workspace = true, features = ["to_js_string"] }
oxc_traverse = { workspace = true }

assert-unchecked = { workspace = true }
base64 = { workspace = true }
dashmap = { workspace = true }
indexmap = { workspace = true }
Expand Down
143 changes: 143 additions & 0 deletions crates/oxc_transformer/src/helpers/stack/capacity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use std::mem::{align_of, size_of};

/// Trait for defining maximum and default capacity of stacks.
///
/// `MAX_CAPACITY` and `MAX_CAPACITY_BYTES` being calculated correctly is required for soundness
/// of stack types.
pub trait StackCapacity {
/// Type that the stack contains
type Item: Sized;

/// Maximum capacity of stack.
///
/// This is guaranteed to be a legal size for a stack of `Item`s, without exceeding Rust's
/// allocation size limits.
///
/// From [`std::alloc::Layout`]'s docs:
/// > size, when rounded up to the nearest multiple of align, must not overflow `isize`
/// > (i.e., the rounded value must be less than or equal to `isize::MAX`).
const MAX_CAPACITY: usize = {
// This assertion is not needed as next line will cause a compile failure anyway
// if `size_of::<Self::Item>() == 0`, due to division by zero.
// But keep it anyway as soundness depends on it.
assert!(size_of::<Self::Item>() > 0, "Zero sized types are not supported");
// As it's always true that `size_of::<T>() >= align_of::<T>()` and `/` rounds down,
// this fulfills `Layout`'s alignment requirement
let max_capacity = isize::MAX as usize / size_of::<Self::Item>();
assert!(max_capacity > 0);
max_capacity
};

/// Maximum capacity of stack in bytes
const MAX_CAPACITY_BYTES: usize = {
let capacity_bytes = Self::MAX_CAPACITY * size_of::<Self::Item>();
// Just double-checking `Layout`'s alignment requirement is fulfilled
assert!(capacity_bytes <= isize::MAX as usize + 1 - align_of::<Self::Item>());
capacity_bytes
};

/// Default capacity of stack.
///
/// Same defaults as [`std::vec::Vec`] uses.
const DEFAULT_CAPACITY: usize = {
// It's impossible for this to exceed `MAX_CAPACITY` because `size_of::<T>() >= align_of::<T>()`
match size_of::<Self::Item>() {
1 => 8,
size if size <= 1024 => 4,
_ => 1,
}
};

/// Default capacity of stack in bytes
const DEFAULT_CAPACITY_BYTES: usize = Self::DEFAULT_CAPACITY * size_of::<Self::Item>();
}

#[cfg(test)]
#[expect(clippy::assertions_on_constants)]
mod tests {
use super::*;

const ISIZE_MAX: usize = isize::MAX as usize;
const ISIZE_MAX_PLUS_ONE: usize = ISIZE_MAX + 1;

#[test]
fn bool() {
struct TestStack;
impl StackCapacity for TestStack {
type Item = bool;
}
assert_eq!(TestStack::MAX_CAPACITY, ISIZE_MAX);
assert_eq!(TestStack::MAX_CAPACITY_BYTES, ISIZE_MAX);
assert_eq!(TestStack::DEFAULT_CAPACITY, 8);
assert_eq!(TestStack::DEFAULT_CAPACITY_BYTES, 8);
}

#[test]
fn u64() {
struct TestStack;
impl StackCapacity for TestStack {
type Item = u64;
}
assert_eq!(TestStack::MAX_CAPACITY, ISIZE_MAX / 8);
assert_eq!(TestStack::MAX_CAPACITY_BYTES, TestStack::MAX_CAPACITY * 8);
assert!(TestStack::MAX_CAPACITY_BYTES <= ISIZE_MAX_PLUS_ONE - 8);
assert_eq!(TestStack::DEFAULT_CAPACITY, 4);
assert_eq!(TestStack::DEFAULT_CAPACITY_BYTES, 32);
}

#[test]
fn u32_pair() {
struct TestStack;
impl StackCapacity for TestStack {
type Item = [u32; 2];
}
assert_eq!(TestStack::MAX_CAPACITY, ISIZE_MAX / 8);
assert_eq!(TestStack::MAX_CAPACITY_BYTES, TestStack::MAX_CAPACITY * 8);
assert!(TestStack::MAX_CAPACITY_BYTES <= ISIZE_MAX_PLUS_ONE - 4);
assert_eq!(TestStack::DEFAULT_CAPACITY, 4);
assert_eq!(TestStack::DEFAULT_CAPACITY_BYTES, 32);
}

#[test]
fn u32_triple() {
struct TestStack;
impl StackCapacity for TestStack {
type Item = [u32; 3];
}
assert_eq!(TestStack::MAX_CAPACITY, ISIZE_MAX / 12);
assert_eq!(TestStack::MAX_CAPACITY_BYTES, TestStack::MAX_CAPACITY * 12);
assert!(TestStack::MAX_CAPACITY_BYTES <= ISIZE_MAX_PLUS_ONE - 4);
assert_eq!(TestStack::DEFAULT_CAPACITY, 4);
assert_eq!(TestStack::DEFAULT_CAPACITY_BYTES, 48);
}

#[test]
fn large_low_alignment() {
struct TestStack;
impl StackCapacity for TestStack {
type Item = [u16; 1000];
}
assert_eq!(TestStack::MAX_CAPACITY, ISIZE_MAX / 2000);
assert_eq!(TestStack::MAX_CAPACITY_BYTES, TestStack::MAX_CAPACITY * 2000);
assert!(TestStack::MAX_CAPACITY_BYTES <= ISIZE_MAX_PLUS_ONE - 2);
assert_eq!(TestStack::DEFAULT_CAPACITY, 1);
assert_eq!(TestStack::DEFAULT_CAPACITY_BYTES, 2000);
}

#[test]
fn large_high_alignment() {
#[repr(align(4096))]
#[expect(dead_code)]
struct TestItem(u8);

struct TestStack;
impl StackCapacity for TestStack {
type Item = TestItem;
}
assert_eq!(TestStack::MAX_CAPACITY, ISIZE_MAX / 4096);
assert_eq!(TestStack::MAX_CAPACITY_BYTES, TestStack::MAX_CAPACITY * 4096);
assert!(TestStack::MAX_CAPACITY_BYTES <= ISIZE_MAX_PLUS_ONE - 4096);
assert_eq!(TestStack::DEFAULT_CAPACITY, 1);
assert_eq!(TestStack::DEFAULT_CAPACITY_BYTES, 4096);
}
}
4 changes: 4 additions & 0 deletions crates/oxc_transformer/src/helpers/stack/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mod capacity;
mod non_empty;
mod sparse;

use capacity::StackCapacity;
pub use non_empty::NonEmptyStack;
pub use sparse::SparseStack;
Loading

0 comments on commit ad4ef31

Please sign in to comment.