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 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ project-root = "0.2.2"
rayon = "1.10.0"
ropey = "1.6.1"
rust-lapper = "1.1.0"
rustversion = "1.0.20"
ryu-js = "1.0.2"
saphyr = "0.0.4"
schemars = "0.8.22"
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_ast_visit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ doctest = false
[dependencies]
oxc_allocator = { workspace = true }
oxc_ast = { workspace = true }
oxc_data_structures = { workspace = true, features = ["pointer_ext"], optional = true }
oxc_span = { workspace = true }
oxc_syntax = { workspace = true }

Expand All @@ -29,6 +30,7 @@ default = []
serialize = [
"oxc_allocator/serialize",
"oxc_ast/serialize",
"oxc_data_structures",
"oxc_span/serialize",
"oxc_syntax/serialize",
]
Expand Down
23 changes: 3 additions & 20 deletions crates/oxc_ast_visit/src/utf8_to_utf16.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::{cmp::min, slice};

use oxc_ast::ast::{Comment, Program};
use oxc_data_structures::pointer_ext::PointerExt;
use oxc_span::Span;
use oxc_syntax::module_record::{ModuleRecord, VisitMutModuleRecord};

Expand Down Expand Up @@ -551,7 +552,7 @@ fn build_translations(source_text: &str, translations: &mut Vec<Translation>) {
if chunk.contains_unicode() {
// SAFETY: `ptr` is equal to or after `start_ptr`. Both are within bounds of `bytes`.
// `ptr` is derived from `start_ptr`.
let offset = unsafe { offset_from(ptr, start_ptr) };
let offset = unsafe { ptr.offset_from_usize(start_ptr) };
process_slice(chunk.as_slice(), offset);
}

Expand All @@ -572,29 +573,11 @@ fn build_translations(source_text: &str, translations: &mut Vec<Translation>) {
let last_chunk = unsafe { slice::from_raw_parts(ptr, remaining_len) };
// SAFETY: `ptr` is after `start_ptr`. Both are within bounds of `bytes`.
// `ptr` is derived from `start_ptr`.
let offset = unsafe { offset_from(ptr, start_ptr) };
let offset = unsafe { ptr.offset_from_usize(start_ptr) };
process_slice(last_chunk, offset);
}
}

/// Calculate distance in bytes from `from_ptr` to `to_ptr`.
///
/// # SAFETY
/// * `from_ptr` must be before or equal to `to_ptr`.
/// * Both pointers must point to within the same object (or the end of the object).
/// * Both pointers must be derived from the same original pointer.
#[inline]
unsafe fn offset_from(to_ptr: *const u8, from_ptr: *const u8) -> usize {
debug_assert!(to_ptr as usize >= from_ptr as usize);

// SAFETY: Caller `from_ptr` and `to_ptr` are both derived from same original pointer,
// and in bounds of same object.
// Both pointers are `*const u8`, so alignment and stride requirements are not relevant.
let offset = unsafe { to_ptr.offset_from(from_ptr) };
// SAFETY: Caller guarantees `from_ptr` is before or equal to `to_ptr`, so `offset >= 0`
unsafe { usize::try_from(offset).unwrap_unchecked() }
}

#[cfg(test)]
mod test {
use oxc_allocator::Allocator;
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ doctest = true
[dependencies]
oxc_allocator = { workspace = true }
oxc_ast = { workspace = true }
oxc_data_structures = { workspace = true, features = ["code_buffer", "stack"] }
oxc_data_structures = { workspace = true, features = ["code_buffer", "pointer_ext", "stack"] }
oxc_index = { workspace = true }
oxc_semantic = { workspace = true }
oxc_sourcemap = { workspace = true }
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_codegen/src/str.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::slice;

use oxc_ast::ast::StringLiteral;
use oxc_data_structures::assert_unchecked;
use oxc_data_structures::{assert_unchecked, pointer_ext::PointerExt};
use oxc_syntax::identifier::{LS, NBSP, PS};

use crate::Codegen;
Expand Down Expand Up @@ -197,8 +197,7 @@ impl PrintStringState<'_> {
// and the iterator only advances, so current position of `bytes` must be on or after `chunk_start`
let len = unsafe {
let bytes_ptr = self.bytes.as_slice().as_ptr();
let offset = bytes_ptr.offset_from(self.chunk_start);
usize::try_from(offset).unwrap_unchecked()
bytes_ptr.offset_from_usize(self.chunk_start)
};

// SAFETY: `chunk_start` is within bounds of original `&str`.
Expand Down
6 changes: 4 additions & 2 deletions crates/oxc_data_structures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ doctest = true

[dependencies]
ropey = { workspace = true, optional = true }
rustversion = { workspace = true, optional = true }

[features]
default = []
all = ["assert_unchecked", "code_buffer", "inline_string", "rope", "stack"]
all = ["assert_unchecked", "code_buffer", "inline_string", "pointer_ext", "rope", "stack"]
assert_unchecked = []
code_buffer = ["assert_unchecked"]
inline_string = ["assert_unchecked"]
pointer_ext = ["dep:rustversion"]
rope = ["dep:ropey"]
stack = ["assert_unchecked"]
stack = ["pointer_ext"]
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 = "pointer_ext")]
pub mod pointer_ext;

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

Expand Down
169 changes: 169 additions & 0 deletions crates/oxc_data_structures/src/pointer_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//! Extension trait for pointers. See [`PointerExt`].

// TODO: Once our MSRV reaches v1.87.0, remove this trait and just use `offset_from_unsigned` directly.
// `#[expect(clippy::incompatible_msrv)]` below will trigger a warning when MSRV is bumped to 1.87.0.

#![expect(clippy::inline_always)]

use std::ptr::NonNull;

/// Extension trait for pointers.
///
/// Rust v1.87.0 introduced `offset_from_unsigned` and `byte_offset_from_unsigned` methods for pointers.
/// <https://doc.rust-lang.org/std/primitive.pointer.html#method.offset_from_unsigned>
///
/// These are implemented as intrinsics, and potentially gives the compiler more information
/// with which to make optimizations, compared to either:
///
/// * `end.offset_from(start) as usize`
/// * `usize::try_from(end.offset_from(start)).unwrap_unchecked()`
///
/// We want to use these methods, but they're not available on our current MSRV.
///
/// This trait provides alternatives `offset_from_usize` and `byte_offset_from_usize`.
///
/// * On Rust v1.87.0+, they use Rust's native methods.
/// * On earlier versions of Rust, they use a fallback.
#[expect(private_bounds)]
pub trait PointerExt: PointerExtImpl {
/// Calculates the distance between two pointers within the same allocation,
/// *where it's known that `self` is equal to or greater than `origin`*.
/// The returned value is in units of `T`: the distance in bytes is divided by `size_of::<T>()`.
///
/// # SAFETY
///
/// * The distance between the pointers must be non-negative (`self >= origin`).
///
/// * *All* the safety conditions of `offset_from` apply to this method as well;
/// see it for the full details.
///
/// See <https://doc.rust-lang.org/std/primitive.pointer.html#method.offset_from_unsigned>
/// for full details.
#[inline(always)]
unsafe fn offset_from_usize(self, origin: Self) -> usize {
// SAFETY: Same constraints as this method
unsafe { self.offset_from_usize_impl(origin) }
}

/// Calculates the distance between two pointers within the same allocation,
/// *where it's known that `self` is equal to or greater than `origin`*.
/// The returned value is in units of **bytes**.
///
/// # SAFETY
///
/// * The distance between the pointers must be non-negative (`self >= origin`).
///
/// * *All* the safety conditions of `offset_from` apply to this method as well;
/// see it for the full details.
///
/// See <https://doc.rust-lang.org/std/primitive.pointer.html#method.byte_offset_from_unsigned>
/// for full details.
unsafe fn byte_offset_from_usize(self, origin: Self) -> usize {
// SAFETY: Same constraints as this method
unsafe { self.byte_offset_from_usize_impl(origin) }
}
}

impl<T> PointerExt for *const T {}

impl<T> PointerExt for *mut T {}

impl<T> PointerExt for NonNull<T> {}

/// Trait that does the actual work.
///
/// This trait is not `pub`, to prevent [`PointerExt`] being implemented on other types
/// outside this module.
///
/// The other purpose of this trait is to avoid repeating the docs for the methods 12 times.
trait PointerExtImpl: Sized {
unsafe fn offset_from_usize_impl(self, origin: Self) -> usize;
unsafe fn byte_offset_from_usize_impl(self, origin: Self) -> usize;
}

/// Native version - just delegates to Rust's methods.
#[rustversion::since(1.87.0)]
#[expect(clippy::incompatible_msrv, clippy::undocumented_unsafe_blocks)]
const _: () = {
impl<T> PointerExtImpl for *const T {
#[inline(always)]
unsafe fn offset_from_usize_impl(self, origin: Self) -> usize {
unsafe { self.offset_from_unsigned(origin) }
}

#[inline(always)]
unsafe fn byte_offset_from_usize_impl(self, origin: Self) -> usize {
unsafe { self.byte_offset_from_unsigned(origin) }
}
}

impl<T> PointerExtImpl for *mut T {
#[inline(always)]
unsafe fn offset_from_usize_impl(self, origin: Self) -> usize {
unsafe { self.offset_from_unsigned(origin) }
}

#[inline(always)]
unsafe fn byte_offset_from_usize_impl(self, origin: Self) -> usize {
unsafe { self.byte_offset_from_unsigned(origin) }
}
}

impl<T> PointerExtImpl for NonNull<T> {
#[inline(always)]
unsafe fn offset_from_usize_impl(self, origin: Self) -> usize {
unsafe { self.offset_from_unsigned(origin) }
}

#[inline(always)]
unsafe fn byte_offset_from_usize_impl(self, origin: Self) -> usize {
unsafe { self.byte_offset_from_unsigned(origin) }
}
}
};

/// Fallback version. This is the best we can do prior to Rust v1.87.0.
#[rustversion::before(1.87.0)]
const _: () = {
impl<T> PointerExtImpl for *const T {
#[inline(always)]
unsafe fn offset_from_usize_impl(self, origin: Self) -> usize {
// SAFETY: Has same safety requirements as native `offset_from_unsigned` method
unsafe { usize::try_from(self.offset_from(origin)).unwrap_unchecked() }
}

#[inline(always)]
unsafe fn byte_offset_from_usize_impl(self, origin: Self) -> usize {
// SAFETY: Has same safety requirements as native `byte_offset_from_unsigned` method
unsafe { usize::try_from(self.byte_offset_from(origin)).unwrap_unchecked() }
}
}

impl<T> PointerExtImpl for *mut T {
#[inline(always)]
unsafe fn offset_from_usize_impl(self, origin: Self) -> usize {
// SAFETY: Has same safety requirements as native `offset_from_unsigned` method
unsafe { usize::try_from(self.offset_from(origin)).unwrap_unchecked() }
}

#[inline(always)]
unsafe fn byte_offset_from_usize_impl(self, origin: Self) -> usize {
// SAFETY: Has same safety requirements as native `byte_offset_from_unsigned` method
unsafe { usize::try_from(self.byte_offset_from(origin)).unwrap_unchecked() }
}
}

impl<T> PointerExtImpl for NonNull<T> {
#[inline(always)]
unsafe fn offset_from_usize_impl(self, origin: Self) -> usize {
// SAFETY: Has same safety requirements as native `offset_from_unsigned` method
unsafe { usize::try_from(self.offset_from(origin)).unwrap_unchecked() }
}

#[inline(always)]
unsafe fn byte_offset_from_usize_impl(self, origin: Self) -> usize {
// SAFETY: Has same safety requirements as native `byte_offset_from_unsigned` method
unsafe { usize::try_from(self.byte_offset_from(origin)).unwrap_unchecked() }
}
}
};
28 changes: 5 additions & 23 deletions crates/oxc_data_structures/src/stack/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
slice,
};

use crate::assert_unchecked;
use crate::pointer_ext::PointerExt;

use super::StackCapacity;

Expand Down Expand Up @@ -146,18 +146,12 @@ pub trait StackCommon<T>: StackCapacity<T> {
/// * `self.cursor()` must be `>= self.start()`.
/// * Byte distance between `self.cursor()` and `self.start()` must be a multiple of `size_of::<T>()`.
unsafe fn cursor_offset(&self) -> usize {
// `offset_from` returns offset in units of `T`.
// `offset_from_usize` returns offset in units of `T`.
// SAFETY: Caller guarantees `cursor` and `start` are derived from same pointer.
// This implies that both pointers are always within bounds of a single allocation.
// Caller guarantees `cursor >= start`.
// Caller guarantees distance between pointers is a multiple of `size_of::<T>()`.
// `assert_unchecked!` is to help compiler to optimize.
// See: https://doc.rust-lang.org/std/primitive.pointer.html#method.sub_ptr
#[expect(clippy::cast_sign_loss)]
unsafe {
assert_unchecked!(self.cursor() >= self.start());
self.cursor().offset_from(self.start()) as usize
}
unsafe { self.cursor().offset_from_usize(self.start()) }
}

/// Get capacity.
Expand All @@ -168,13 +162,7 @@ pub trait StackCommon<T>: StackCapacity<T> {
// * `start` and `end` are both within bounds of a single allocation.
// * `end` is always >= `start`.
// * Distance between `start` and `end` is always a multiple of `size_of::<T>()`.
// `assert_unchecked!` is to help compiler to optimize.
// See: https://doc.rust-lang.org/std/primitive.pointer.html#method.sub_ptr
#[expect(clippy::cast_sign_loss)]
unsafe {
assert_unchecked!(self.end() >= self.start());
self.end().offset_from(self.start()) as usize
}
unsafe { self.end().offset_from_usize(self.start()) }
}

/// Get capacity in bytes.
Expand All @@ -185,13 +173,7 @@ pub trait StackCommon<T>: StackCapacity<T> {
// * `start` and `end` are both within bounds of a single allocation.
// * `end` is always >= `start`.
// * Distance between `start` and `end` is always a multiple of `size_of::<T>()`.
// `assert_unchecked!` is to help compiler to optimize.
// See: https://doc.rust-lang.org/std/primitive.pointer.html#method.sub_ptr
#[expect(clippy::cast_sign_loss)]
unsafe {
assert_unchecked!(self.end() >= self.start());
self.end().byte_offset_from(self.start()) as usize
}
unsafe { self.end().byte_offset_from_usize(self.start()) }
}

/// Get contents of stack as a slice `&[T]`.
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_estree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ workspace = true
doctest = false

[dependencies]
oxc_data_structures = { workspace = true, features = ["code_buffer", "stack"], optional = true }
oxc_data_structures = { workspace = true, features = ["code_buffer", "pointer_ext", "stack"], optional = true }

itoa = { workspace = true, optional = true }
ryu-js = { workspace = true, optional = true }
Expand Down
Loading
Loading