Change:
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);

Discussion:
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.