__sync_val_compare_and_swap vs __sync_bool_compare_and_swap
I was thinking about the return values of these two functions. The return value of the __sync_bool_compare_and_swap function seems to have obvious advantages, that is, I can use it to determine if a swap has occurred. However, I don't see a good use of the __sync_val_compare_and_swap return value.
First, let's have a function signature to reference (from the GCC docs minus var args):
type __sync_val_compare_and_swap (type *ptr, type oldval type newval);
I see that the return value of __sync_val_compare_and_swap is the old * ptr value. To be precise, this is the value that was seen as a result of the implementation of this function after the appropriate memory barriers were set. I state this clearly to satisfy the fact that between the call to __sync_val_compare_and_swap and the execution of the instructions to enforce the memory barrier, the * ptr value can easily change.
Now that the function returns, what can I do with this return value? There is no point in trying to compare it to * ptr, because * ptr can now be changed to other streams. Likewise, comparing newval and * ptr doesn't really help me (unless I'm blocking * ptr, which probably undermines my use of atomatics in the first place).
So all I have left to do is ask if the returned value == oldval, which is efficient (see below for caveat), is asking if a swap operation took place. So I could just use __sync_bool_compare_and_swap.
The caveat I just talked about is that the only small difference I see here is that it doesn't tell me if an exchange has taken place or not, it just tells me that at some point before the memory barrier was released * ptr has the same meaning as newval. I am considering the possibility that oldval == newval (although I would go out of my way to effectively implement a function to check these values first and not change them if they were the same, so this is probably a moot point). However, I don't see a situation where knowing this difference would affect me on the call site. In fact, I cannot imagine a situation where I would set oldval and newval equal.
My question is:
Is there any use case where the use of __sync_val_compare_and_swap and __sync_bool_compare_and_swap would not be equivalent, i.e. there is a situation where one provides more information than the other?
ASIDE
The reason I thought about it was because I found an implementation of __sync_val_compare_and_swap in terms of sync_bool_compare_and_swap that has a race:
inline int32_t __sync_val_compare_and_swap(volatile int32_t* ptr, int32_t oldval, int32_t newval)
{
int32_t ret = *ptr;
(void)__sync_bool_compare_and_swap(ptr, oldval, newval);
return ret;
}
Race when saving * ptr to ret, as * ptr might change before calling __sync_bool_compare_and_swap. This made me realize that I do not seem to be safe (without additional barriers or locks) to implement __sync_val_compare_and_swap in terms of sync_bool_compare_and_swap. This made me think that the former should provide more "information" than the latter, but from my question, I don't see that this is really the case.
source to share
The operation provided __sync_val_compare_and_swap
can always be implemented in terms __sync_bool_compare_and_swap
(and of course another direction is possible), so they are equivalent in cardinality. However, the implementation __sync_val_compare_and_swap
in terms is __sync_bool_compare_and_swap
not very efficient. It looks something like this:
for (;;) {
bool success = __sync_bool_compare_and_swap(ptr, oldval, newval);
if (success) return oldval;
type tmp = *ptr;
__sync_synchronize();
if (tmp != oldval) return tmp;
}
More work is needed because you can observe the crash __sync_bool_compare_and_swap
, but then read the new value from *ptr
that happens from oldval
.
As for why you might prefer behavior __sync_val_compare_and_swap
, the value that caused the failure can give you a starting point to retry the operation more efficiently, or it can indicate a meaningful cause for the failure of some operation that will not be "redone". See the code pthread_spin_trylock
in musl libc (for which I am the author) as an example :
There is a_cas
equivalent __sync_val_compare_and_swap
. This is a silly example in a way, since it just stores a branch or conditional move using the old value, but there are other situations where multiple old values are possible and knowing what caused the error to operate.
source to share