LLVM Bugzilla is read-only and represents the historical archive of all LLVM issues filled before November 26, 2021. Use github to submit LLVM bugs

Bug 18899 - atomic::compare_exchange_weak invents a write on success
Summary: atomic::compare_exchange_weak invents a write on success
Status: RESOLVED FIXED
Alias: None
Product: clang
Classification: Unclassified
Component: C++11 (show other bugs)
Version: trunk
Hardware: PC Linux
: P normal
Assignee: Unassigned Clang Bugs
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2014-02-19 07:08 PST by Sebastian Redl
Modified: 2014-05-05 13:48 PDT (History)
6 users (show)

See Also:
Fixed By Commit(s):


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Sebastian Redl 2014-02-19 07:08:44 PST
The standard says that compare_exchange_* writes to the atomic object (atomically) if the comparison of the current value and `expected` is true, and to `expected` (also, if you interpret the wording literally, atomically, but this is impossible since `expected` is not an atomic object) if the comparison is false. So in response to this SO thread:

http://stackoverflow.com/questions/21879331/is-stdatomic-compare-exchange-weak-thread-unsafe-by-design/21881095#21881095

I wrote a little test:

struct node {
  int data;
  node* next;
};

std::atomic<node*> head;

void push(int data) {
  node* new_node = new node{data};
  new_node->next = head.load(std::memory_order_relaxed);
  while (!head.compare_exchange_weak(new_node->next, new_node,
     std::memory_order_release, std::memory_order_relaxed)) {}
}

Here's the code Clang generates (clang++ -std=c++11 -S -O2):

  .globl  _Z4pushi
  .align  16, 0x90
  .type _Z4pushi,@function
_Z4pushi:                               # @_Z4pushi
  .cfi_startproc
# BB#0:                                 # %entry
  pushq %rbx
.Ltmp2:
  .cfi_def_cfa_offset 16
.Ltmp3:
  .cfi_offset %rbx, -16
  # Allocate and initialize node
  movl  %edi, %ebx
  movl  $16, %edi
  callq _Znwm
  movq  %rax, %rcx
  movl  %ebx, (%rcx)
  movq  $0, 8(%rcx)
  # new_node->next = head.load(...)
  movq  head(%rip), %rdx
  movq  %rdx, 8(%rcx)
  .align  16, 0x90
.LBB0_1:                                # %while.cond
                                        # =>This Inner Loop Header: Depth=1
  # compare_exchange
  movq  %rdx, %rax
  lock
  cmpxchgq  %rcx, head(%rip)
  # Problematic: Writing back to `expected`
  movq  %rax, 8(%rcx)
  cmpq  %rdx, %rax
  movq  %rax, %rdx
  jne .LBB0_1
# BB#2:                                 # %while.end
  popq  %rbx
  ret
.Ltmp4:
  .size _Z4pushi, .Ltmp4-_Z4pushi
  .cfi_endproc

Note that Clang writes to `expected` unconditionally. In theory, %rax contains the same thing as 8(%rcx); however, it is still an invented write if the comparison is successful. This is bad. Note that if there is another thread that loops, waiting for `head` to be non-null, and then immediately grabs it and reads and/or writes to head->next, that operation races with the write to `expected`. More concretely, if the pushing thread is pre-empted immediately after the cmpxchg, head already has the new value. A different thread could now overwrite `head->next`. When the pushing thread continues, it will overwrite `expected` (which aliases `head->next`) again, losing the update from the other thread.

The integrity of compare_exchange_weak uses relies on the idea that if the comparison succeeds, the data has been published and must not be modified anymore. But Clang inserts the spurious write, unseen by the programmer.
Comment 1 Sebastian Redl 2014-02-20 08:23:15 PST
GCC has the same bug:
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=60272

And ICC too, according to SO.
Comment 2 Seth 2014-02-21 09:30:02 PST
libc++ implements these using the __c11_atomic_* builtins, and the llvm IR output for __c11_atomic_compare_exchange_weak appears to show the bug.
Comment 3 David Majnemer 2014-03-03 01:28:27 PST
A patch is out for review: http://llvm-reviews.chandlerc.com/D2922
Comment 4 David Majnemer 2014-05-05 13:48:15 PDT
Fixed in r203493.