Skip to content

Commit cff9f12

Browse files
authored
feat: alloc feature, RacyBox, and RacyPtr::addr
2 parents a3b2af3 + 9c0e4e5 commit cff9f12

File tree

3 files changed

+223
-36
lines changed

3 files changed

+223
-36
lines changed

ecmascript_atomics/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ categories.workspace = true
1212

1313
[dependencies]
1414
ecmascript_atomics_gen_copy = { path = "../gen_copy", version = "=0.1.0" }
15+
16+
[features]
17+
alloc = []

ecmascript_atomics/src/generated.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
// Copyright of the originating code is owned by Firefox authors and Mozilla,
66
// modifications by Aapo Alasuutari.
77

8+
//! This file is a Rust version of Firefox's Atomics internals code. In Firefox
9+
//! the code is generated using a Python script called
10+
//! "GenerateAtomicOperations.py". As this is sort of a manual run of said
11+
//! script with the output converted into Rust, the file is called
12+
//! `generated.rs`.
13+
//!
814
//! See the comment in [./lib.rs] for details.
915
1016
use core::ptr::NonNull;

ecmascript_atomics/src/lib.rs

Lines changed: 214 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
#![no_std]
99

10-
//! # Racy atomic operations
1110
//!
1211
//! This library provides atomic operations that match the ECMAScript
1312
//! specification's memory model: this is effectively the same memory model as
@@ -79,9 +78,15 @@
7978
//!
8079
//! [GenerateAtomicOperations.py]: https://searchfox.org/firefox-main/source/js/src/jit/GenerateAtomicOperations.py
8180
81+
#[cfg(feature = "alloc")]
82+
extern crate alloc;
83+
8284
mod generated;
8385
mod unordered_copy;
8486

87+
#[cfg(feature = "alloc")]
88+
use core::alloc::Layout;
89+
8590
use core::{
8691
cell::UnsafeCell, hint::assert_unchecked, marker::PhantomData, mem::MaybeUninit, ptr::NonNull,
8792
};
@@ -152,11 +157,123 @@ pub fn atomic_pause() {
152157
core::hint::spin_loop();
153158
}
154159

160+
/// Owning handle to a slab of memory with the ECMAScript Atomics memory model.
161+
/// The slab is initially allocated using the global allocator for
162+
/// initialisation before being deallocated as Rust memory and reallocated as
163+
/// ECMAScript memory.
164+
///
165+
/// When dropped, the handle exits the the ECMAScript memory model, reallocates
166+
/// the memory as Rust memory, and the deallocates it using the global
167+
/// allocator. Any [`RacyPtr`](RacyPtr)s derived from the allocation are
168+
/// invalidated on drop.
169+
///
170+
/// The memory can be freely shared to multiple threads for use and all APIs on
171+
/// the memory are guaranteed to not cause undefined behaviour even when data
172+
/// races or mixed-size atomics are used. Tearing may occur when using the copy
173+
/// APIs, meaning that usage should generally be careful and try to avoid both
174+
/// data races and mixed-size atomics.
175+
///
176+
/// # Soundness
177+
///
178+
/// The memory behind this handle is not and must not be read as Rust memory.
179+
/// Any Rust reads or writes into the memory are undefined behaviour.
180+
#[cfg(feature = "alloc")]
181+
pub struct RacyBox<T: RacyStorage> {
182+
ptr: RacyPtr<T>,
183+
len: usize,
184+
}
185+
186+
#[cfg(feature = "alloc")]
187+
#[derive(Debug)]
188+
pub enum RacyBoxNewError {
189+
SizeOverflow,
190+
LayoutError,
191+
AllocationFailure,
192+
}
193+
194+
#[cfg(feature = "alloc")]
195+
impl<T: RacyStorage> RacyBox<T> {
196+
/// Create a new racy heap allocation containing a single racy storage
197+
/// value.
198+
pub fn new(value: T) -> Result<Self, RacyBoxNewError> {
199+
// SAFETY: T has non-zero size and a multiplier of 1 is always safe.
200+
let layout = unsafe { Self::create_layout(1).unwrap_unchecked() };
201+
// SAFETY: Layout has non-zero size.
202+
let ptr = unsafe { alloc::alloc::alloc(layout) };
203+
let Some(ptr) = NonNull::new(ptr) else {
204+
return Err(RacyBoxNewError::AllocationFailure);
205+
};
206+
let ptr = ptr.cast::<T>();
207+
// SAFETY: data has been allocated successfully.
208+
unsafe { ptr.write(value) };
209+
// SAFETY: we are the exclusive referrer to ptr.
210+
let ptr = unsafe { enter_racy_memory(ptr.cast::<T>()) };
211+
Ok(Self { ptr, len: 1 })
212+
}
213+
214+
/// Create a new racy heap allocation for a slice of racy storage. The
215+
/// storage is initialised to all-zero.
216+
pub fn with_capacity(capacity: usize) -> Result<Self, RacyBoxNewError> {
217+
let layout = Self::create_layout(capacity)?;
218+
// SAFETY: Layout has non-zero size.
219+
let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
220+
let Some(ptr) = NonNull::new(ptr) else {
221+
return Err(RacyBoxNewError::AllocationFailure);
222+
};
223+
// SAFETY: we are the exclusive referrer to ptr.
224+
let ptr = unsafe { enter_racy_memory(ptr.cast::<T>()) };
225+
Ok(Self { ptr, len: capacity })
226+
}
227+
228+
/// Access the racy atomic memory using a shared slice.
229+
#[inline(always)]
230+
pub const fn as_slice(&self) -> RacySlice<'_, T> {
231+
// SAFETY: type guarantees proper allocation.
232+
unsafe { RacySlice::from_raw_parts(self.ptr, self.len) }
233+
}
234+
235+
/// Create a Layout for a RacyBox allocation of the given capacity.
236+
#[inline]
237+
const fn create_layout(capacity: usize) -> Result<Layout, RacyBoxNewError> {
238+
const {
239+
// SAFETY: Checking at compile time.
240+
unsafe {
241+
assert_unchecked(size_of::<T>() > 0);
242+
};
243+
}
244+
let Some(size) = size_of::<T>().checked_mul(capacity) else {
245+
return Err(RacyBoxNewError::SizeOverflow);
246+
};
247+
let Ok(layout) = Layout::from_size_align(size, align_of::<T>()) else {
248+
return Err(RacyBoxNewError::LayoutError);
249+
};
250+
Ok(layout)
251+
}
252+
}
253+
254+
#[cfg(feature = "alloc")]
255+
impl<T: RacyStorage> Drop for RacyBox<T> {
256+
fn drop(&mut self) {
257+
// SAFETY: type guarantees we're the only owner of self.ptr.
258+
let ptr = unsafe { exit_racy_memory(self.ptr) };
259+
// SAFETY: layout calculation has succeeded before so it must succeed
260+
// now.
261+
let layout = unsafe { Self::create_layout(self.len).unwrap_unchecked() };
262+
// SAFETY: ptr is a block of memory currently allocated via the global
263+
// allocator and layout is the same layout that was used to allocate
264+
// the block of memory.
265+
unsafe { alloc::alloc::dealloc(ptr.as_ptr().cast(), layout) };
266+
}
267+
}
268+
155269
/// Opaque handle to a slab of memory with the ECMAScript Atomics memory model.
156270
/// The slab must be created using the [`enter`] method and must be turned back
157271
/// into Rust memory using the [`exit`] method (note; this must be strictly
158272
/// synchronised with all possible users of the racy atomic memory).
159273
///
274+
/// Any [`RacyPtr`](RacyPtr)s derived from the allocation are invalidated on
275+
/// [`exit`].
276+
///
160277
/// The memory can be freely shared to multiple threads for use and all APIs on
161278
/// the memory are guaranteed to not cause undefined behaviour even when data
162279
/// races or mixed-size atomics are used. Tearing may occur when using the copy
@@ -184,6 +301,7 @@ pub fn atomic_pause() {
184301
///
185302
/// [`enter`]: crate::RacyMemory::enter
186303
/// [`exit`]: crate::RacyMemory::exit
304+
#[must_use]
187305
pub struct RacyMemory<T: RacyStorage> {
188306
ptr: RacyPtr<T>,
189307
len: usize,
@@ -199,7 +317,8 @@ impl<T: RacyStorage> RacyMemory<T> {
199317
///
200318
/// # Safety
201319
///
202-
/// `ptr` must be a pointer to `len` bytes of readable and writable memory.
320+
/// `ptr` must be an exclusive, owning pointer to `len` bytes of readable
321+
/// and writable memory.
203322
///
204323
/// # Soundness
205324
///
@@ -210,20 +329,8 @@ impl<T: RacyStorage> RacyMemory<T> {
210329
#[inline]
211330
#[must_use]
212331
pub unsafe fn enter(ptr: NonNull<u8>, len: usize) -> RacyMemory<u8> {
213-
let mut ptr = ptr.as_ptr();
214-
// SAFETY: noop.
215-
unsafe {
216-
core::arch::asm!(
217-
"/* Magic spell: let {} no longer be memory in Rust's eyes! */",
218-
// Note: ptr is and out parameter so that the assembly block
219-
// can conceptually deallocate the original ptr, removing its
220-
// provenance, and return a new ptr with difference provenance.
221-
inlateout(reg) ptr,
222-
options(nostack, preserves_flags)
223-
)
224-
}
225-
// SAFETY: Magic spell always returns non-null pointers.
226-
let ptr = RacyPtr::from_ptr(unsafe { NonNull::new_unchecked(ptr.cast()) });
332+
// SAFETY: function precondition.
333+
let ptr = unsafe { enter_racy_memory(ptr) };
227334
RacyMemory::from_raw_parts(ptr, len)
228335
}
229336

@@ -238,25 +345,31 @@ impl<T: RacyStorage> RacyMemory<T> {
238345
/// See [`enter`] for details.
239346
///
240347
/// [`enter`]: crate::RacyMemory::enter
348+
#[inline]
349+
#[must_use]
241350
pub unsafe fn enter_ptr(ptr: NonNull<T>) -> Self {
242-
let RacyMemory { ptr, .. } = unsafe { Self::enter(ptr.cast(), size_of::<T>()) };
351+
// SAFETY: function precondition.
352+
let ptr = unsafe { enter_racy_memory(ptr) };
243353
Self::from_raw_parts(ptr.cast(), 1)
244354
}
245355

246356
/// Move a typed memory allocation slice into the ECMAScript memory model.
247357
///
248358
/// # Safety
249359
///
250-
/// `ptr` must point to a valid, uniquely owned `T`.
360+
/// `ptr` must point to a valid, uniquely owned `[T]`.
251361
///
252362
/// # Soudness
253363
///
254364
/// See [`enter`] for details.
255365
///
256366
/// [`enter`]: crate::RacyMemory::enter
367+
#[inline]
368+
#[must_use]
257369
pub unsafe fn enter_slice(ptr: NonNull<[T]>) -> Self {
258370
let len = ptr.len();
259-
let RacyMemory { ptr, .. } = unsafe { Self::enter(ptr.cast(), size_of::<T>() * len) };
371+
// SAFETY: function precondition.
372+
let ptr = unsafe { enter_racy_memory(ptr.cast::<T>()) };
260373
Self::from_raw_parts(ptr.cast(), len)
261374
}
262375

@@ -277,20 +390,12 @@ impl<T: RacyStorage> RacyMemory<T> {
277390
#[inline]
278391
#[must_use]
279392
pub unsafe fn exit(self) -> (NonNull<T>, usize) {
280-
let mut ptr = self.ptr.as_ptr().as_ptr();
281-
// SAFETY: noop.
282-
unsafe {
283-
core::arch::asm!(
284-
"/* Magic spell: let {} be memory in Rust's eyes! */",
285-
// Note: ptr is and out parameter so that the assembly block
286-
// can conceptually deallocate the ECMAScript memory, allocate
287-
// new Rust memory, and return a pointer to it.
288-
inlateout(reg) ptr,
289-
options(nostack, preserves_flags)
290-
)
291-
}
292-
// SAFETY: Magic spell always returns non-null pointers.
293-
(unsafe { NonNull::new_unchecked(ptr.cast()) }, self.len)
393+
// SAFETY: self.ptr is an owning pointer, all RacySlices to this memory
394+
// created using safe APIs are invalidated by this call, and we must be
395+
// the only referrer to this pointer if all safety preconditions have
396+
// been followed.
397+
let ptr = unsafe { exit_racy_memory(self.ptr) };
398+
(ptr, self.len)
294399
}
295400

296401
/// Access the racy atomic memory using a shared slice.
@@ -318,6 +423,57 @@ impl<T: RacyStorage> RacyMemory<T> {
318423
}
319424
}
320425

426+
/// Deallocates an owned Rust memory and returns a pointer to a new ECMAScript
427+
/// memory allocation of the same size, containing the same data as the Rust
428+
/// memory did.
429+
///
430+
/// # Safety
431+
///
432+
/// The pointer must be an exclusive, owning pointer. No other referrers to the
433+
/// memory are allowed.
434+
unsafe fn enter_racy_memory<T: RacyStorage>(ptr: NonNull<T>) -> RacyPtr<T> {
435+
let mut ptr = ptr.as_ptr();
436+
// SAFETY: noop.
437+
unsafe {
438+
core::arch::asm!(
439+
"/* Magic spell: let {} no longer be memory in Rust's eyes! */",
440+
// Note: ptr is and out parameter so that the assembly block
441+
// can conceptually deallocate the original ptr, removing its
442+
// provenance, and return a new ptr with difference provenance.
443+
inlateout(reg) ptr,
444+
options(nostack, preserves_flags)
445+
)
446+
}
447+
// SAFETY: Magic spell always returns non-null pointers.
448+
RacyPtr::from_ptr(unsafe { NonNull::new_unchecked(ptr.cast()) })
449+
}
450+
451+
/// Deallocates racy ECMAScript memory and returns a pointer to a new Rust
452+
/// memory allocation of the same size, containing the same data as the
453+
/// ECMAScript memory did.
454+
///
455+
/// # Safety
456+
///
457+
/// The pointer must be an owning pointer, deallocation must be strictly
458+
/// synchronised between threads, and the pointer must not have already been
459+
/// deallocated.
460+
unsafe fn exit_racy_memory<T: RacyStorage>(ptr: RacyPtr<T>) -> NonNull<T> {
461+
let mut ptr = ptr.as_ptr().as_ptr();
462+
// SAFETY: noop.
463+
unsafe {
464+
core::arch::asm!(
465+
"/* Magic spell: let {} be memory in Rust's eyes! */",
466+
// Note: ptr is and out parameter so that the assembly block
467+
// can conceptually deallocate the ECMAScript memory, allocate
468+
// new Rust memory, and return a pointer to it.
469+
inlateout(reg) ptr,
470+
options(nostack, preserves_flags)
471+
)
472+
}
473+
// SAFETY: RacyPtr is guaranteed to be non-null.
474+
unsafe { NonNull::new_unchecked(ptr) }.cast()
475+
}
476+
321477
mod private {
322478
pub trait Sealed: 'static + Copy + Eq + Send + Sync + core::fmt::Display {}
323479

@@ -865,9 +1021,23 @@ impl<T: RacyStorage> RacyPtr<T> {
8651021
pub const fn cast<U: RacyStorage>(self) -> RacyPtr<U> {
8661022
RacyPtr(self.0.cast(), PhantomData)
8671023
}
1024+
1025+
/// Converts a racy pointer into a racy reference.
1026+
///
1027+
/// # Safety
1028+
///
1029+
/// When converting a racy pointer into a racy reference, there are several
1030+
/// rules that must be followed:
1031+
///
1032+
/// * The pointer must be properly aligned.
1033+
/// * It must point to a valid value of type `T`.
1034+
/// * The backing racy memory must not have been exited.
1035+
pub const unsafe fn as_racy<'a>(self) -> Racy<'a, T> {
1036+
Racy::from_ptr(self.0)
1037+
}
8681038
}
8691039

870-
/// An opaque pointer to memory implementing the ECMAScript atomic memory
1040+
/// An opaque reference to memory implementing the ECMAScript atomic memory
8711041
/// model.
8721042
#[derive(Clone, Copy)]
8731043
#[repr(transparent)]
@@ -879,14 +1049,22 @@ unsafe impl<T: RacyStorage> Send for Racy<'_, T> {}
8791049
unsafe impl<T: RacyStorage> Sync for Racy<'_, T> {}
8801050

8811051
impl<T: RacyStorage> Racy<'_, T> {
882-
fn from_ptr(ptr: NonNull<()>) -> Self {
1052+
const fn from_ptr(ptr: NonNull<()>) -> Self {
8831053
Self(ptr, PhantomData)
8841054
}
8851055

886-
fn as_ptr(&self) -> NonNull<()> {
1056+
const fn as_ptr(&self) -> NonNull<()> {
8871057
self.0
8881058
}
8891059

1060+
/// Get the address of the racy atomic integer. The result is not a valid
1061+
/// pointer for reading or writing through under any circumstances. It is
1062+
/// only okay to use the address value itself. This is offered for Futex
1063+
/// implementation.
1064+
pub const fn addr(&self) -> *const () {
1065+
self.0.as_ptr()
1066+
}
1067+
8901068
/// Stores a value into the racy atomic integer if the current value is the
8911069
/// same as the `current` value.
8921070
///

0 commit comments

Comments
 (0)