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
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+
8284mod generated;
8385mod unordered_copy;
8486
87+ #[ cfg( feature = "alloc" ) ]
88+ use core:: alloc:: Layout ;
89+
8590use 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]
187305pub 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+
321477mod 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> {}
8791049unsafe impl < T : RacyStorage > Sync for Racy < ' _ , T > { }
8801050
8811051impl < 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