It was brought up on cppreference atomic_compare_exchange Talk page that the existing implementations of std::atomic_compare_exchange_weak
compute the boolea
Quoting Duncan Forster from the linked page:
The important thing to remember is that the hardware implementation of CAS only returns 1 value (the old value) not two (old plus boolean)
So there's one instruction - the (atomic) CAS - which actually operates on memory, and then another instruction to convert the (atomically-assigned) result into the expected boolean.
Since the value in %rax
was set atomically and can't then be affected by another thread, there is no race here.
The quote is anyway false, since ZF is also set depending on the CAS result (ie, it does return both the old value and the boolean). The fact the flag isn't used might be a missed optimisation, or the cmpq might be faster, but it doesn't affect correctness.
For reference, consider decomposing compare_exchange_weak
like this pseudocode:
T compare_exchange_weak_value(atomic *obj, T *expected, T desired) {
// setup ...
lock cmpxchgq %rcx, (%rsp) // actual CAS
return %rax; // actual destination value
}
bool compare_exchange_weak_bool(atomic *obj, T *expected, T desired) {
// CAS is atomic
T actual = compare_exchange_weak_value(obj, expected, desired);
// now we figure out if it worked
return actual == *expected;
}
Do you agree the CAS is properly atomic?
If the unconditional store to expected is really what you wanted to ask about (instead of the perfectly safe comparison), I agree with Sebastian that it's a bug.
For reference, you can work around it by forcing the unconditional store into a local, and making the potentially-visible store conditional again:
struct node {
int data;
node* next;
};
std::atomic head;
void push(int data) {
node* new_node = new node{data};
node* cur_head = head.load(std::memory_order_relaxed);
do {
new_node->next = cur_head;
} while (!head.compare_exchange_weak(cur_head, new_node,
std::memory_order_release, std::memory_order_relaxed));
}