Skip to content
Closed
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
3 changes: 2 additions & 1 deletion crates/oxc_data_structures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ ropey = { workspace = true, optional = true }

[features]
default = []
all = ["assert_unchecked", "code_buffer", "inline_string", "rope", "stack"]
all = ["assert_unchecked", "code_buffer", "inline_string", "nonmax", "rope", "stack"]
assert_unchecked = []
code_buffer = ["assert_unchecked"]
inline_string = []
nonmax = ["assert_unchecked"]
rope = ["dep:ropey"]
stack = ["assert_unchecked"]
3 changes: 3 additions & 0 deletions crates/oxc_data_structures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub mod code_buffer;
#[cfg(feature = "inline_string")]
pub mod inline_string;

#[cfg(feature = "nonmax")]
pub mod nonmax;

#[cfg(feature = "rope")]
pub mod rope;

Expand Down
37 changes: 37 additions & 0 deletions crates/oxc_data_structures/src/nonmax/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! [`NonZeroU32`] equivalent, except with the illegal value at the top of the range (`u32::MAX`).
//!
//! [`NonMaxU32`] can represent any number from 0 to `u32::MAX - 1` inclusive.
//!
//! `NonMaxU32` has a niche, so `Option<NonMaxU32>` is 4 bytes.
//!
//! On *nix, Mac, and WASI, this type is completely zero cost.
//!
//! On Windows, we wrap a `NonZeroU32` and XOR it with `u32::MAX` during conversion to/from `u32`,
//! same as `nonmax` crate does. This does have a (small) cost.
//!
//! # Hashing
//! Note that the Unix and Windows versions will produce different hashes from each other.
//!
//! [`NonZeroU32`]: std::num::NonZeroU32

// Version for *nix, Mac and WASI.
// `os::fd` is only available on these platforms.
// https://github.com/rust-lang/rust/blob/75530e9f72a1990ed2305e16fd51d02f47048f12/library/std/src/os/mod.rs#L185-L186
#[cfg(any(unix, target_os = "hermit", target_os = "wasi"))]
mod unix;
#[cfg(any(unix, target_os = "hermit", target_os = "wasi"))]
pub use unix::NonMaxU32;

// Version for other platforms (primarily Windows)
#[cfg(not(any(unix, target_os = "hermit", target_os = "wasi")))]
mod windows;
#[cfg(not(any(unix, target_os = "hermit", target_os = "wasi")))]
pub use windows::NonMaxU32;

// Implementations which are shared between both versions
mod shared;
pub use shared::TryFromU32Error;

// Tests
#[cfg(test)]
mod test;
78 changes: 78 additions & 0 deletions crates/oxc_data_structures/src/nonmax/shared.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// All methods are either zero cost, very cheap, or delegate, so marked `#[inline(always)]`
#![expect(clippy::inline_always)]

use std::{
convert::TryFrom,
error::Error,
fmt::{self, Display},
};

use super::NonMaxU32;

impl NonMaxU32 {
/// [`NonMaxU32`] with the value zero (0).
pub const ZERO: Self = {
// SAFETY: 0 is a valid value
unsafe { Self::new_unchecked(0) }
};

/// [`NonMaxU32`] with the maximum value zero (`u32::MAX - 1`).
pub const MAX: Self = {
// SAFETY: `u32::MAX - 1` is a valid value
unsafe { Self::new_unchecked(u32::MAX - 1) }
};
}

impl Default for NonMaxU32 {
#[inline(always)]
fn default() -> Self {
Self::ZERO
}
}

impl From<NonMaxU32> for u32 {
#[inline(always)]
fn from(value: NonMaxU32) -> u32 {
value.get()
}
}

impl TryFrom<u32> for NonMaxU32 {
type Error = TryFromU32Error;

#[inline(always)]
fn try_from(n: u32) -> Result<Self, TryFromU32Error> {
// Note: Conversion from `Option` to `Result` here is zero-cost
Self::new(n).ok_or(TryFromU32Error(()))
}
}

macro_rules! impl_fmt {
($Trait:ident) => {
impl fmt::$Trait for NonMaxU32 {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::$Trait::fmt(&self.get(), f)
}
}
};
}

impl_fmt!(Debug);
impl_fmt!(Display);
impl_fmt!(Binary);
impl_fmt!(Octal);
impl_fmt!(LowerHex);
impl_fmt!(UpperHex);

/// Error type for failed conversion from [`u32`] to [`NonMaxU32`].
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct TryFromU32Error(());

impl Error for TryFromU32Error {}

impl Display for TryFromU32Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
"Out of range conversion to `NonMaxU32` attempted".fmt(fmt)
}
}
91 changes: 91 additions & 0 deletions crates/oxc_data_structures/src/nonmax/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use super::*;

#[test]
fn construct() {
let zero = NonMaxU32::new(0).unwrap();
assert_eq!(zero.get(), 0);

let some = NonMaxU32::new(19).unwrap();
assert_eq!(some.get(), 19);

let max_minus_one = NonMaxU32::new(u32::MAX - 1).unwrap();
assert_eq!(max_minus_one.get(), u32::MAX - 1);

let max = NonMaxU32::new(u32::MAX);
assert_eq!(max, None);
}

#[test]
fn construct_unchecked() {
// SAFETY: 0 is a valid value
let zero = unsafe { NonMaxU32::new_unchecked(0) };
assert_eq!(zero.get(), 0);

// SAFETY: 19 is a valid value
let some = unsafe { NonMaxU32::new_unchecked(19) };
assert_eq!(some.get(), 19);

// SAFETY: `u32::MAX - 1` is a valid value
let max_minus_one = unsafe { NonMaxU32::new_unchecked(u32::MAX - 1) };
assert_eq!(max_minus_one.get(), u32::MAX - 1);
}

#[test]
fn convert() {
let zero = NonMaxU32::try_from(0u32).unwrap();
let zero = u32::from(zero);
assert_eq!(zero, 0);

NonMaxU32::try_from(u32::MAX).unwrap_err();
}

#[test]
fn eq() {
let zero = NonMaxU32::new(0).unwrap();
let one = NonMaxU32::new(1).unwrap();
let two = NonMaxU32::new(2).unwrap();
assert_eq!(zero, zero);
assert_eq!(one, one);
assert_eq!(two, two);

assert_ne!(zero, one);
assert_ne!(zero, two);
assert_ne!(one, two);
assert_ne!(one, zero);
assert_ne!(two, zero);
assert_ne!(two, one);
}

#[test]
fn cmp() {
let zero = NonMaxU32::new(0).unwrap();
let one = NonMaxU32::new(1).unwrap();
let two = NonMaxU32::new(2).unwrap();
assert!(zero < one);
assert!(one < two);
assert!(two > one);
assert!(one > zero);
}

#[test]
fn constants() {
let zero = NonMaxU32::ZERO;
let max = NonMaxU32::MAX;
assert_eq!(zero.get(), 0);
assert_eq!(max.get(), u32::MAX - 1);
}

#[test]
fn fmt() {
let zero = NonMaxU32::new(0).unwrap();
let some = NonMaxU32::new(19).unwrap();
let max_minus_one = NonMaxU32::new(u32::MAX - 1).unwrap();
for value in [zero, some, max_minus_one].iter().copied() {
assert_eq!(format!("{}", value.get()), format!("{}", value)); // Display
assert_eq!(format!("{:?}", value.get()), format!("{:?}", value)); // Debug
assert_eq!(format!("{:b}", value.get()), format!("{:b}", value)); // Binary
assert_eq!(format!("{:o}", value.get()), format!("{:o}", value)); // Octal
assert_eq!(format!("{:x}", value.get()), format!("{:x}", value)); // LowerHex
assert_eq!(format!("{:X}", value.get()), format!("{:X}", value)); // UpperHex
}
}
130 changes: 130 additions & 0 deletions crates/oxc_data_structures/src/nonmax/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Conversion methods are zero cost, and other methods just delegate, so marked `#[inline(always)]`
#![expect(clippy::inline_always)]

use crate::assert_unchecked;

// # Implementation details
//
// `std::os::fd::BorrowedFd` is a wrapper around `std::os::fd::RawFd`, and is `#[repr(transparent)]`.
// `RawFd` is a type alias for `i32`. `i32` has same layout as `u32`.
// Therefore `BorrowedFd` has the same layout as `u32`.
// `NonZeroU32` is `#[repr(transparent)]`, and `ManuallyDrop` is also `#[repr(transparent)]`.
// Therefore `NonMaxU32` has same layout as `u32`.
//
// The feature that `BorrowedFd` brings is that it has a
// `#[rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE)]` attribute,
// meaning `u32::MAX` is an invalid bit pattern for the type. This invalid bit pattern forms a niche.
// <https://doc.rust-lang.org/stable/std/os/fd/struct.BorrowedFd.html>
//
// `BorrowedFd` and `OwnedFd` are the only public stable types in the standard library which exhibit
// this property. `OwnedFd` is not `Copy`, so we use `BorrowedFd`.
// We never use the `BorrowedFd` as an actual file descriptor, only utilize it for its layout.
//
// `BorrowedFd` is not `Drop`, but we wrap it in `ManuallyDrop` anyway, just to make sure.
//
// Because of the niche, `Option<NonMaxU32>` is 4 bytes.
//
// Unlike the `NonMaxU32` type from `nonmax` crate, this type has zero cost converting to and from `u32`.
// https://godbolt.org/z/cGaqhcco4
//
// `BorrowedFd` is only available on Unix-like platforms.
// We substitute a less efficient version on Windows, which does have a (small) conversion cost.

use std::{
cmp::Ordering,
hash::{Hash, Hasher},
mem::{ManuallyDrop, transmute},
os::fd::BorrowedFd,
};

/// [`NonZeroU32`] equivalent, except with the illegal value at the top of the range (`u32::MAX`).
///
/// [`NonMaxU32`] can represent any number from 0 to `u32::MAX - 1` inclusive.
///
/// `NonMaxU32` has a niche, so `Option<NonMaxU32>` is 4 bytes.
///
/// [`NonZeroU32`]: std::num::NonZeroU32
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct NonMaxU32(ManuallyDrop<BorrowedFd<'static>>);

impl NonMaxU32 {
/// Create [`NonMaxU32`] from [`u32`].
///
/// Returns `None` if `n` is `u32::MAX`.
#[inline(always)]
pub const fn new(n: u32) -> Option<Self> {
if n < u32::MAX {
// SAFETY: We just checked `n < u32::MAX`
Some(unsafe { Self::new_unchecked(n) })
} else {
None
}
}

/// Create [`NonMaxU32`] from [`u32`], without checks.
///
/// # SAFETY
/// `n` must not be `u32::MAX`.
#[inline(always)]
pub const unsafe fn new_unchecked(n: u32) -> Self {
// SAFETY: Caller guarantees `n` is not `u32::MAX`.
// See implementation details comment above.
unsafe { transmute::<u32, Self>(n) }
}

/// Convert [`NonMaxU32`] to [`u32`].
#[inline(always)]
pub const fn get(self) -> u32 {
// SAFETY: See implementation details comment above
let n = unsafe { transmute::<Self, u32>(self) };

// Make sure compiler understands return value cannot be `u32::MAX`.
// This may aid it to make optimizations in some cases.
// SAFETY: `NonMaxU32` cannot represent `u32::MAX`.
unsafe { assert_unchecked!(n < u32::MAX) };

n
}
}

impl Eq for NonMaxU32 {}

impl PartialEq for NonMaxU32 {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.get() == other.get()
}
}

impl Ord for NonMaxU32 {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.get().cmp(&other.get())
}
}

impl PartialOrd for NonMaxU32 {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Hash for NonMaxU32 {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.get().hash(state);
}
}

const _: () = {
assert!(size_of::<BorrowedFd>() == 4);
assert!(align_of::<BorrowedFd>() == 4);
assert!(size_of::<NonMaxU32>() == 4);
assert!(align_of::<NonMaxU32>() == 4);
assert!(size_of::<u32>() == 4);
assert!(align_of::<u32>() == 4);
assert!(size_of::<Option<NonMaxU32>>() == 4);
assert!(size_of::<Option<Option<NonMaxU32>>>() == 8);
};
Loading
Loading