Why does Rust not permit CAS operations with Release/Acquire ordering?

Using compare_exchange on atomic types in Rust with release as success and acquire on failure ordering is not permitted and will fail because "a failure ordering can't be stronger than a success ordering".

Digging around in the LLVM references I found this quote:

The success and failure ordering arguments specify how this cmpxchg synchronizes with other atomic operations. Both ordering parameters must be at least monotonic , the ordering constraint on failure must be no stronger than that on success, and the failure ordering cannot be either release or acq_rel .

However, I don't see why this should include release/acquire ordering. Neither of those is stronger than the other and I've come across some cases where this would be the correct ordering in my assessment. In these cases I would either use acquire-release/acquire ordering or release/relaxed and an acquire fence in the failure branch.

As is described in the Rust documentation for compare_exchange:

Therefore, using Acquire ordering for failure, which does only a load, would be a stronger ordering than the one you used for success (Relaxed).

I find that reasoning odd, the LLVM reference does not at all differentiate between the different parts of the operation, it just says the failure ordering can not be stronger than the success ordering.

This is implied by the definition of the release ordering, which only applies to write operations. If there is no write, there are no release semantics.

I am sorry, I really don't see how that follows. Yes, a failed CAS with release ordering does not write anything and therefore does not have to impose any ordering restrictions, that does not mean you can't have an interest to acquire whatever the other thread that succeeded in executing the same CAS has released.

So there is no real reason why you would want to impose this restriction and the relevant specifications say nothing about it either.

Perhaps not as relevant to Rust, but here is what GCC has to say about this issue (also no mention of a separate load and store part):

If desired is written into *ptr then true is returned and memory is affected according to the memory order specified by success_memorder. There are no restrictions on what memory order can be used here.

Otherwise, false is returned and memory is affected according to failure_memorder. This memory order cannot be __ATOMIC_RELEASE nor __ATOMIC_ACQ_REL . It also cannot be a stronger order than that specified by success_memorder.

This topic is better asked on users.rust-lang.org. internals is meant for work related the compiler internals and the specifying the Rust language, not general questions about why things are the way they are.

Acquire is stronger than Release wrt an atomic load.

Fine by me, I can post the same question on users again.

You could also say that release is stronger than acquire wrt to an atomic store, it doesn't really mean anything. In fact, I would argue that calling either stronger or weaker than the other doesn't make any sense at all, they are complementary. I would assume that this is the reason why specification (the memory model?) simply bans release as failure ordering outright. Calling and ordering weaker or stronger makes only sense when comparing sequentially consistent with either acquire/release or relaxed, for example.

It doesn't have to mention anything explicitly when discussing compare-exchange, because this is all covered by the definitions of the Acquire and Release orderings. If you read both the LLVM and GCC documentation you linked, you'll see that Acquire ordering is only defined for loads, and Release ordering is only defined for stores. (Well, the LLVM docs aren't super clear about acquire, but they defer to the C++ definition, which is)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.