Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(transformer): introduce NonEmptyStack #6092

Merged
merged 1 commit into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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