Change:
This issue is part of the Strict Provenance Experiment - As of this writing, has many more operations than , like fetchadd, which is a problem if we want people to conform to strict provenance and admit when pointers are pointers. All the cases I found over std and rustc seemed to work fine with CAS operations, so closing the gap is seemingly only needed for the wider ecosystem. For instance, parkinglot does many Cute Tricks with fetchand and fetchsub: See also Morally, most atomic operations can be defined in terms of wrappingoffset, so I think it should be fine to support these operations. However it is interesting as to whether they should be defined to operate on "just the address" (as you would expect of wrappingoffset) or if they are allowed to "interact" with the raw bit representation and therefore things that are "not" addresses (e.g. the CHERI metadata). At worst we can say "we can't stop you, but if you do, you will be sad", but it would be good to know if we can do "better". (For those unaware, a pointer ("capability") on CHERI is 128-bit, but the address-space is ~64-bit. The pointer is a complicated compressed thing that does lots of Tricks to pack in at least an entire slice (so two more logically-64-bit addresses!) into there. If you do Sufficiently Bad things the hardware will mark the memory as ~corrupt and fault you if you try to use the pointer to read/write.)
(marked as both T-libs and T-compiler because I don't know if this is "just" a matter of using existing intrinsics or if the compiler needs to do some more work to do this)
I have time and motivation to take this this weekend, but a few questions. I'll probably just go ahead with it and make Choices if I don't get any good answers, but for discussion I'll bring it up now. I imagine the API will look something like this: This brings up a few questions, which is why before I said the design space here is nontrivial: , it's clear to me that is the right argument type, because you may want to in a pointer, this allows preserving its provenance. But for and it's tricky. 1b: Note that wants to use for a bit-clear operation: How can we express this? Does it need a [^1]? , , and coherent operations? should the units be for ? Symmetry with says "units of , but for essentially all uses I know of, it would be more useful for them to be byte offsets. Note that unlike with raw pointers, we can't tell people to change the type of the AtomicPtr easily. (I don't think we should encourage things like transmuting a to an in order to perform a cast, but maybe we are okay with this code just using ). [^1]: Note that unless I'm off my rocker, this is not* , since it would be rather than .
small note about the footnote: andnot is usually
The answer to these questions are in https://rust-, it seems.
That just clears a single bit -- at least for Miri there is no issue here. No idea how to make any sense of any of this on CHERI though. Cc What we would want is that this is equivalent to atomically doing with the old address bit-combined with the operand.
That's what you get on CHERI
I think I'm probably going to have to ignore cheri for this work, since we don't really have a pointer-sized integer except for usize. That said, it doesn't need to stabilize with urgency.
If we adopt a more "usize is sizet or ptraddrt" model then it would be fine using usize as the operand. If we don't, then uhhh, a lot of things become much more "funny" already. An appropriate note should suffice.
So I can take a 64bit integer and atomically bit-and it with a pointer (that has 128bit), meaning "bit-and this with the 64bit address part of the pointer please"? Other people say CHERI atomics are pointer-sized so I am confused now.
In Rust syntax, we need an operation that atomically does
That's exactly what we give you, semantically
I think this signature is a problem, because it's not clear what provenance the resulting pointer has. Does the resulting provenance come from or does the provenance come from ? Strictly speaking we'd need two variants of each binary atomic operation: one which takes provenance from the left, and one which takes provenance from the right.
Indeed, the signatures will all be changed to take as the second operand.

Discussion:
}; let ty = substs.type_at(0); <del> if int_type_width_signed(ty, bx.tcx()).is_some() || (ty.is_unsafe_ptr() && op == "xchg") { </del> <ins> if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_unsafe_ptr() { </ins> let mut ptr = args[0].immediate(); let mut val = args[1].immediate(); if ty.is_unsafe_ptr() {

} Err(prev) } <ins> /// Offsets the pointer's address by adding `val` (in units of `T`), /// returning the previous pointer. /// /// This is equivalent to using [`wrapping_add`] to atomically perform the /// equivalent of `ptr = ptr.wrapping_add(val);`. /// /// This method operates in units of `T`, which means that it cannot be used /// to offset the pointer by an amount which is not a multiple of /// `size_of::<T>()`. This can sometimes be inconvenient, as you may want to /// work with a deliberately misaligned pointer. In such cases, you may use /// the [`fetch_byte_add`](Self::fetch_byte_add) method instead. /// /// `fetch_ptr_add` takes an [`Ordering`] argument which describes the /// memory ordering of this operation. All ordering modes are possible. Note /// that using [`Acquire`] makes the store part of this operation /// [`Relaxed`], and using [`Release`] makes the load part [`Relaxed`]. /// /// **Note**: This method is only available on platforms that support atomic /// operations on [`AtomicPtr`]. /// /// [`wrapping_add`]: pointer::wrapping_add /// /// # Examples /// /// ``` /// #![feature(strict_provenance_atomic_ptr, strict_provenance)] /// use core::sync::atomic::{AtomicPtr, Ordering}; /// /// let atom = AtomicPtr::<i64>::new(core::ptr::null_mut()); /// assert_eq!(atom.fetch_ptr_add(1, Ordering::Relaxed).addr(), 0); /// // Note: units of `size_of::<i64>()`. /// assert_eq!(atom.load(Ordering::Relaxed).addr(), 8); /// ``` #[inline] #[cfg(target_has_atomic = "ptr")] #[unstable(feature = "strict_provenance_atomic_ptr", issue = "95228")] pub fn fetch_ptr_add(&self, val: usize, order: Ordering) -> *mut T { self.fetch_byte_add(val.wrapping_mul(core::mem::size_of::<T>()), order) } /// Offsets the pointer's address by subtracting `val` (in units of `T`), /// returning the previous pointer. /// /// This is equivalent to using [`wrapping_sub`] to atomically perform the /// equivalent of `ptr = ptr.wrapping_sub(val);`. /// /// This method operates in units of `T`, which means that it cannot be used /// to offset the pointer by an amount which is not a multiple of /// `size_of::<T>()`. This can sometimes be inconvenient, as you may want to /// work with a deliberately misaligned pointer. In such cases, you may use /// the [`fetch_byte_sub`](Self::fetch_byte_sub) method instead. /// /// `fetch_ptr_sub` takes an [`Ordering`] argument which describes the memory /// ordering of this operation. All ordering modes are possible. Note that /// using [`Acquire`] makes the store part of this operation [`Relaxed`], /// and using [`Release`] makes the load part [`Relaxed`]. /// /// **Note**: This method is only available on platforms that support atomic /// operations on [`AtomicPtr`]. /// /// [`wrapping_sub`]: pointer::wrapping_sub /// /// # Examples /// /// ``` /// #![feature(strict_provenance_atomic_ptr)] /// use core::sync::atomic::{AtomicPtr, Ordering}; /// /// let array = [1i32, 2i32]; /// let atom = AtomicPtr::new(array.as_ptr().wrapping_add(1) as *mut _); /// /// assert!(core::ptr::eq( ///     atom.fetch_ptr_sub(1, Ordering::Relaxed), ///     &array[1], /// )); /// assert!(core::ptr::eq(atom.load(Ordering::Relaxed), &array[0])); /// ``` #[inline] #[cfg(target_has_atomic = "ptr")] #[unstable(feature = "strict_provenance_atomic_ptr", issue = "95228")] pub fn fetch_ptr_sub(&self, val: usize, order: Ordering) -> *mut T { self.fetch_byte_sub(val.wrapping_mul(core::mem::size_of::<T>()), order) } /// Offsets the pointer's address by adding `val` *bytes*, returning the /// previous pointer. /// /// This is equivalent to using [`wrapping_add`] and [`cast`] to atomically /// perform `ptr = ptr.cast::<u8>().wrapping_add(val).cast::<T>()`. /// /// `fetch_byte_add` takes an [`Ordering`] argument which describes the /// memory ordering of this operation. All ordering modes are possible. Note /// that using [`Acquire`] makes the store part of this operation /// [`Relaxed`], and using [`Release`] makes the load part [`Relaxed`]. /// /// **Note**: This method is only available on platforms that support atomic /// operations on [`AtomicPtr`]. /// /// [`wrapping_add`]: pointer::wrapping_add /// [`cast`]: pointer::cast /// /// # Examples /// /// ``` /// #![feature(strict_provenance_atomic_ptr, strict_provenance)] /// use core::sync::atomic::{AtomicPtr, Ordering}; /// /// let atom = AtomicPtr::<i64>::new(core::ptr::null_mut()); /// assert_eq!(atom.fetch_byte_add(1, Ordering::Relaxed).addr(), 0); /// // Note: in units of bytes, not `size_of::<i64>()`. /// assert_eq!(atom.load(Ordering::Relaxed).addr(), 1); /// ``` #[inline] #[cfg(target_has_atomic = "ptr")] #[unstable(feature = "strict_provenance_atomic_ptr", issue = "95228")] pub fn fetch_byte_add(&self, val: usize, order: Ordering) -> *mut T { #[cfg(not(bootstrap))] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_add(self.p.get(), core::ptr::invalid_mut(val), order).cast() } #[cfg(bootstrap)] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_add(self.p.get().cast::<usize>(), val, order) as *mut T } } /// Offsets the pointer's address by subtracting `val` *bytes*, returning the /// previous pointer. /// /// This is equivalent to using [`wrapping_sub`] and [`cast`] to atomically /// perform `ptr = ptr.cast::<u8>().wrapping_sub(val).cast::<T>()`. /// /// `fetch_byte_sub` takes an [`Ordering`] argument which describes the /// memory ordering of this operation. All ordering modes are possible. Note /// that using [`Acquire`] makes the store part of this operation /// [`Relaxed`], and using [`Release`] makes the load part [`Relaxed`]. /// /// **Note**: This method is only available on platforms that support atomic /// operations on [`AtomicPtr`]. /// /// [`wrapping_sub`]: pointer::wrapping_sub /// [`cast`]: pointer::cast /// /// # Examples /// /// ``` /// #![feature(strict_provenance_atomic_ptr, strict_provenance)] /// use core::sync::atomic::{AtomicPtr, Ordering}; /// /// let atom = AtomicPtr::<i64>::new(core::ptr::invalid_mut(1)); /// assert_eq!(atom.fetch_byte_sub(1, Ordering::Relaxed).addr(), 1); /// assert_eq!(atom.load(Ordering::Relaxed).addr(), 0); /// ``` #[inline] #[cfg(target_has_atomic = "ptr")] #[unstable(feature = "strict_provenance_atomic_ptr", issue = "95228")] pub fn fetch_byte_sub(&self, val: usize, order: Ordering) -> *mut T { #[cfg(not(bootstrap))] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_sub(self.p.get(), core::ptr::invalid_mut(val), order).cast() } #[cfg(bootstrap)] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_sub(self.p.get().cast::<usize>(), val, order) as *mut T } } /// Performs a bitwise "or" operation on the address of the current pointer, /// and the argument `val`, and stores a pointer with provenance of the /// current pointer and the resulting address. /// /// This is equivalent equivalent to using [`map_addr`] to atomically /// perform `ptr = ptr.map_addr(|a| a | val)`. This can be used in tagged /// pointer schemes to atomically set tag bits. /// /// **Caveat**: This operation returns the previous value. To compute the /// stored value without losing provenance, you may use [`map_addr`]. For /// example: `a.fetch_or(val).map_addr(|a| a | val)`. /// /// `fetch_or` takes an [`Ordering`] argument which describes the memory /// ordering of this operation. All ordering modes are possible. Note that /// using [`Acquire`] makes the store part of this operation [`Relaxed`], /// and using [`Release`] makes the load part [`Relaxed`]. /// /// **Note**: This method is only available on platforms that support atomic /// operations on [`AtomicPtr`]. /// /// This API and its claimed semantics are part of the Strict Provenance /// experiment, see the [module documentation for `ptr`][crate::ptr] for /// details. /// /// [`map_addr`]: pointer::map_addr /// /// # Examples /// /// ``` /// #![feature(strict_provenance_atomic_ptr, strict_provenance)] /// use core::sync::atomic::{AtomicPtr, Ordering}; /// /// let pointer = &mut 3i64 as *mut i64; /// /// let atom = AtomicPtr::<i64>::new(pointer); /// // Tag the bottom bit of the pointer. /// assert_eq!(atom.fetch_or(1, Ordering::Relaxed).addr() & 1, 0); /// // Extract and untag. /// let tagged = atom.load(Ordering::Relaxed); /// assert_eq!(tagged.addr() & 1, 1); /// assert_eq!(tagged.map_addr(|p| p & !1), pointer); /// ``` #[inline] #[cfg(target_has_atomic = "ptr")] #[unstable(feature = "strict_provenance_atomic_ptr", issue = "95228")] pub fn fetch_or(&self, val: usize, order: Ordering) -> *mut T { #[cfg(not(bootstrap))] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_or(self.p.get(), core::ptr::invalid_mut(val), order).cast() } #[cfg(bootstrap)] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_or(self.p.get().cast::<usize>(), val, order) as *mut T } } /// Performs a bitwise "and" operation on the address of the current /// pointer, and the argument `val`, and stores a pointer with provenance of /// the current pointer and the resulting address. /// /// This is equivalent equivalent to using [`map_addr`] to atomically /// perform `ptr = ptr.map_addr(|a| a & val)`. This can be used in tagged /// pointer schemes to atomically unset tag bits. /// /// **Caveat**: This operation returns the previous value. To compute the /// stored value without losing provenance, you may use [`map_addr`]. For /// example: `a.fetch_and(val).map_addr(|a| a & val)`. /// /// `fetch_and` takes an [`Ordering`] argument which describes the memory /// ordering of this operation. All ordering modes are possible. Note that /// using [`Acquire`] makes the store part of this operation [`Relaxed`], /// and using [`Release`] makes the load part [`Relaxed`]. /// /// **Note**: This method is only available on platforms that support atomic /// operations on [`AtomicPtr`]. /// /// This API and its claimed semantics are part of the Strict Provenance /// experiment, see the [module documentation for `ptr`][crate::ptr] for /// details. /// /// [`map_addr`]: pointer::map_addr /// /// # Examples /// /// ``` /// #![feature(strict_provenance_atomic_ptr, strict_provenance)] /// use core::sync::atomic::{AtomicPtr, Ordering}; /// /// let pointer = &mut 3i64 as *mut i64; /// // A tagged pointer /// let atom = AtomicPtr::<i64>::new(pointer.map_addr(|a| a | 1)); /// assert_eq!(atom.fetch_or(1, Ordering::Relaxed).addr() & 1, 1); /// // Untag, and extract the previously tagged pointer. /// let untagged = atom.fetch_and(!1, Ordering::Relaxed) ///     .map_addr(|a| a & !1); /// assert_eq!(untagged, pointer); /// ``` #[inline] #[cfg(target_has_atomic = "ptr")] #[unstable(feature = "strict_provenance_atomic_ptr", issue = "95228")] pub fn fetch_and(&self, val: usize, order: Ordering) -> *mut T { #[cfg(not(bootstrap))] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_and(self.p.get(), core::ptr::invalid_mut(val), order).cast() } #[cfg(bootstrap)] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_and(self.p.get().cast::<usize>(), val, order) as *mut T } } /// Performs a bitwise "xor" operation on the address of the current /// pointer, and the argument `val`, and stores a pointer with provenance of /// the current pointer and the resulting address. /// /// This is equivalent equivalent to using [`map_addr`] to atomically /// perform `ptr = ptr.map_addr(|a| a ^ val)`. This can be used in tagged /// pointer schemes to atomically toggle tag bits. /// /// **Caveat**: This operation returns the previous value. To compute the /// stored value without losing provenance, you may use [`map_addr`]. For /// example: `a.fetch_xor(val).map_addr(|a| a ^ val)`. /// /// `fetch_xor` takes an [`Ordering`] argument which describes the memory /// ordering of this operation. All ordering modes are possible. Note that /// using [`Acquire`] makes the store part of this operation [`Relaxed`], /// and using [`Release`] makes the load part [`Relaxed`]. /// /// **Note**: This method is only available on platforms that support atomic /// operations on [`AtomicPtr`]. /// /// This API and its claimed semantics are part of the Strict Provenance /// experiment, see the [module documentation for `ptr`][crate::ptr] for /// details. /// /// [`map_addr`]: pointer::map_addr /// /// # Examples /// /// ``` /// #![feature(strict_provenance_atomic_ptr, strict_provenance)] /// use core::sync::atomic::{AtomicPtr, Ordering}; /// /// let pointer = &mut 3i64 as *mut i64; /// let atom = AtomicPtr::<i64>::new(pointer); /// /// // Toggle a tag bit on the pointer. /// atom.fetch_xor(1, Ordering::Relaxed); /// assert_eq!(atom.load(Ordering::Relaxed).addr() & 1, 1); /// ``` #[inline] #[cfg(target_has_atomic = "ptr")] #[unstable(feature = "strict_provenance_atomic_ptr", issue = "95228")] pub fn fetch_xor(&self, val: usize, order: Ordering) -> *mut T { #[cfg(not(bootstrap))] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_xor(self.p.get(), core::ptr::invalid_mut(val), order).cast() } #[cfg(bootstrap)] // SAFETY: data races are prevented by atomic intrinsics. unsafe { atomic_xor(self.p.get().cast::<usize>(), val, order) as *mut T } } </ins> } #[cfg(target_has_atomic_load_store = "8")]

assert_eq!(x.load(SeqCst), 0xf731); } <ins> #[test] #[cfg(any(not(target_arch = "arm"), target_os = "linux"))] // Missing intrinsic in compiler-builtins fn ptr_add_null() { let atom = AtomicPtr::<i64>::new(core::ptr::null_mut()); assert_eq!(atom.fetch_ptr_add(1, SeqCst).addr(), 0); assert_eq!(atom.load(SeqCst).addr(), 8); assert_eq!(atom.fetch_byte_add(1, SeqCst).addr(), 8); assert_eq!(atom.load(SeqCst).addr(), 9); assert_eq!(atom.fetch_ptr_sub(1, SeqCst).addr(), 9); assert_eq!(atom.load(SeqCst).addr(), 1); assert_eq!(atom.fetch_byte_sub(1, SeqCst).addr(), 1); assert_eq!(atom.load(SeqCst).addr(), 0); } #[test] #[cfg(any(not(target_arch = "arm"), target_os = "linux"))] // Missing intrinsic in compiler-builtins fn ptr_add_data() { let num = 0i64; let n = &num as *const i64 as *mut _; let atom = AtomicPtr::<i64>::new(n); assert_eq!(atom.fetch_ptr_add(1, SeqCst), n); assert_eq!(atom.load(SeqCst), n.wrapping_add(1)); assert_eq!(atom.fetch_ptr_sub(1, SeqCst), n.wrapping_add(1)); assert_eq!(atom.load(SeqCst), n); let bytes_from_n = |b| n.cast::<u8>().wrapping_add(b).cast::<i64>(); assert_eq!(atom.fetch_byte_add(1, SeqCst), n); assert_eq!(atom.load(SeqCst), bytes_from_n(1)); assert_eq!(atom.fetch_byte_add(5, SeqCst), bytes_from_n(1)); assert_eq!(atom.load(SeqCst), bytes_from_n(6)); assert_eq!(atom.fetch_byte_sub(1, SeqCst), bytes_from_n(6)); assert_eq!(atom.load(SeqCst), bytes_from_n(5)); assert_eq!(atom.fetch_byte_sub(5, SeqCst), bytes_from_n(5)); assert_eq!(atom.load(SeqCst), n); } #[test] #[cfg(any(not(target_arch = "arm"), target_os = "linux"))] // Missing intrinsic in compiler-builtins fn ptr_bitops() { let atom = AtomicPtr::<i64>::new(core::ptr::null_mut()); assert_eq!(atom.fetch_or(0b0111, SeqCst).addr(), 0); assert_eq!(atom.load(SeqCst).addr(), 0b0111); assert_eq!(atom.fetch_and(0b1101, SeqCst).addr(), 0b0111); assert_eq!(atom.load(SeqCst).addr(), 0b0101); assert_eq!(atom.fetch_xor(0b1111, SeqCst).addr(), 0b0101); assert_eq!(atom.load(SeqCst).addr(), 0b1010); } #[test] #[cfg(any(not(target_arch = "arm"), target_os = "linux"))] // Missing intrinsic in compiler-builtins fn ptr_bitops_tagging() { #[repr(align(16))] struct Tagme(u128); let tagme = Tagme(1000); let ptr = &tagme as *const Tagme as *mut Tagme; let atom: AtomicPtr<Tagme> = AtomicPtr::new(ptr); const MASK_TAG: usize = 0b1111; const MASK_PTR: usize = !MASK_TAG; assert_eq!(ptr.addr() & MASK_TAG, 0); assert_eq!(atom.fetch_or(0b0111, SeqCst), ptr); assert_eq!(atom.load(SeqCst), ptr.map_addr(|a| a | 0b111)); assert_eq!(atom.fetch_and(MASK_PTR | 0b0010, SeqCst), ptr.map_addr(|a| a | 0b111)); assert_eq!(atom.load(SeqCst), ptr.map_addr(|a| a | 0b0010)); assert_eq!(atom.fetch_xor(0b1011, SeqCst), ptr.map_addr(|a| a | 0b0010)); assert_eq!(atom.load(SeqCst), ptr.map_addr(|a| a | 0b1001)); assert_eq!(atom.fetch_and(MASK_PTR, SeqCst), ptr.map_addr(|a| a | 0b1001)); assert_eq!(atom.load(SeqCst), ptr); } </ins> static S_FALSE: AtomicBool = AtomicBool::new(false); static S_TRUE: AtomicBool = AtomicBool::new(true); static S_INT: AtomicIsize = AtomicIsize::new(0);

#![feature(slice_group_by)] #![feature(split_array)] #![feature(strict_provenance)] <ins> #![feature(strict_provenance_atomic_ptr)] </ins> #![feature(trusted_random_access)] #![feature(unsize)] #![feature(unzip_option)]