Skip to content
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
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/helpers/stack/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod capacity;
mod non_empty;
mod sparse;
mod standard;

use capacity::StackCapacity;
pub use non_empty::NonEmptyStack;
pub use sparse::SparseStack;
pub use standard::Stack;
5 changes: 4 additions & 1 deletion crates/oxc_transformer/src/helpers/stack/non_empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use super::StackCapacity;
/// The fact that the stack is never empty makes all operations except `pop` infallible.
/// `last` and `last_mut` are branchless.
///
/// The trade-off is that you cannot create a `NonEmptyStack` without allocating (unlike `Vec`).
/// The trade-off is that you cannot create a `NonEmptyStack` without allocating.
/// If that is not a good trade-off for your use case, prefer [`Stack`], which can be empty.
///
/// To simplify implementation, zero size types are not supported (e.g. `NonEmptyStack<()>`).
///
Expand Down Expand Up @@ -47,6 +48,8 @@ use super::StackCapacity;
/// 2. Stack could grow downwards, like `bumpalo` allocator does. This would probably make `pop` use
/// 1 less register, but at the cost that the stack can never grow in place, which would incur more
/// memory copies when the stack grows.
///
/// [`Stack`]: super::Stack
pub struct NonEmptyStack<T> {
/// Pointer to last entry on stack.
/// Points *to* last entry, not *after* last entry.
Expand Down
21 changes: 13 additions & 8 deletions crates/oxc_transformer/src/helpers/stack/sparse.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::NonEmptyStack;
use super::{NonEmptyStack, Stack};

/// Stack which is sparsely filled.
///
Expand All @@ -23,18 +23,23 @@ use super::NonEmptyStack;
///
/// When the stack grows and reallocates, `SparseStack` has less memory to copy, which is a performance
/// win too.
///
/// To simplify implementation, zero size types are not supported (`SparseStack<()>`).
pub struct SparseStack<T> {
has_values: NonEmptyStack<bool>,
values: Vec<T>,
values: Stack<T>,
}

impl<T> SparseStack<T> {
/// Create new `SparseStack`.
///
/// # Panics
/// Panics if `T` is a zero-sized type.
pub fn new() -> Self {
// `has_values` starts with a single empty entry, which will never be popped off.
// This means `take_last`, `last_or_init`, and `last_mut_or_init` can all be infallible,
// as there's always an entry on the stack to read.
Self { has_values: NonEmptyStack::new(false), values: vec![] }
Self { has_values: NonEmptyStack::new(false), values: Stack::new() }
}

/// Push an entry to the stack.
Expand Down Expand Up @@ -62,7 +67,7 @@ impl<T> SparseStack<T> {
// This invariant is maintained in `push`, `take_last`, `last_or_init`, and `last_mut_or_init`.
// We maintain it here too because we just popped from `self.has_values`, so that `true`
// has been consumed at the same time we consume its corresponding value from `self.values`.
let value = unsafe { self.values.pop().unwrap_unchecked() };
let value = unsafe { self.values.pop_unchecked() };
Some(value)
} else {
None
Expand All @@ -77,7 +82,7 @@ impl<T> SparseStack<T> {
debug_assert!(!self.values.is_empty());
// SAFETY: Last `self.has_values` is only `true` if there's a corresponding value in `self.values`.
// This invariant is maintained in `push`, `pop`, `take_last`, `last_or_init`, and `last_mut_or_init`.
let value = unsafe { self.values.last().unwrap_unchecked() };
let value = unsafe { self.values.last_unchecked() };
Some(value)
} else {
None
Expand All @@ -96,7 +101,7 @@ impl<T> SparseStack<T> {
// This invariant is maintained in `push`, `pop`, `last_or_init`, and `last_mut_or_init`.
// We maintain it here too because we just set last `self.has_values` to `false`
// at the same time as we consume the corresponding value from `self.values`.
let value = unsafe { self.values.pop().unwrap_unchecked() };
let value = unsafe { self.values.pop_unchecked() };
Some(value)
} else {
None
Expand All @@ -118,7 +123,7 @@ impl<T> SparseStack<T> {
// This invariant is maintained in `push`, `pop`, `take_last`, and `last_mut_or_init`.
// Here either last `self.has_values` was already `true`, or it's just been set to `true`
// and a value pushed to `self.values` above.
unsafe { self.values.last().unwrap_unchecked() }
unsafe { self.values.last_unchecked() }
}

/// Initialize the value for last entry on the stack, if it has no value already.
Expand All @@ -135,7 +140,7 @@ impl<T> SparseStack<T> {
// This invariant is maintained in `push`, `pop`, `take_last`, and `last_or_init`.
// Here either last `self.has_values` was already `true`, or it's just been set to `true`
// and a value pushed to `self.values` above.
unsafe { self.values.last_mut().unwrap_unchecked() }
unsafe { self.values.last_mut_unchecked() }
}

/// Get number of entries on the stack.
Expand Down
Loading