[TSAN] Honor failure memory orders in AtomicCAS

LLVM has lifted strong requirements for CAS failure memory orders in 431e3138a and 819e0d105e.

Add support for honoring them in `AtomicCAS`.

https://github.com/google/sanitizers/issues/970

Differential Revision: https://reviews.llvm.org/D99434
This commit is contained in:
Bruno Cardoso Lopes 2021-05-13 00:35:22 -07:00
parent b2186a69c1
commit fd184c062c
3 changed files with 210 additions and 13 deletions

View File

@ -402,34 +402,45 @@ static T NoTsanAtomicCAS(volatile T *a, T c, T v, morder mo, morder fmo) {
template<typename T>
static bool AtomicCAS(ThreadState *thr, uptr pc,
volatile T *a, T *c, T v, morder mo, morder fmo) {
(void)fmo; // Unused because llvm does not pass it yet.
// 31.7.2.18: "The failure argument shall not be memory_order_release
// nor memory_order_acq_rel". LLVM (2021-05) fallbacks to Monotonic
// (mo_relaxed) when those are used.
CHECK(IsLoadOrder(fmo));
MemoryWriteAtomic(thr, pc, (uptr)a, SizeLog<T>());
SyncVar *s = 0;
bool write_lock = mo != mo_acquire && mo != mo_consume;
if (mo != mo_relaxed) {
bool write_lock = IsReleaseOrder(mo);
if (mo != mo_relaxed || fmo != mo_relaxed)
s = ctx->metamap.GetOrCreateAndLock(thr, pc, (uptr)a, write_lock);
T cc = *c;
T pr = func_cas(a, cc, v);
bool success = pr == cc;
if (!success) {
*c = pr;
mo = fmo;
}
if (s) {
thr->fast_state.IncrementEpoch();
// Can't increment epoch w/o writing to the trace as well.
TraceAddEvent(thr, thr->fast_state, EventTypeMop, 0);
if (IsAcqRelOrder(mo))
if (success && IsAcqRelOrder(mo))
AcquireReleaseImpl(thr, pc, &s->clock);
else if (IsReleaseOrder(mo))
else if (success && IsReleaseOrder(mo))
ReleaseImpl(thr, pc, &s->clock);
else if (IsAcquireOrder(mo))
AcquireImpl(thr, pc, &s->clock);
}
T cc = *c;
T pr = func_cas(a, cc, v);
if (s) {
if (write_lock)
s->mtx.Unlock();
else
s->mtx.ReadUnlock();
}
if (pr == cc)
return true;
*c = pr;
return false;
return success;
}
template<typename T>

View File

@ -0,0 +1,106 @@
// RUN: %clangxx_tsan -O1 %s %link_libcxx_tsan -o %t && %deflake %env_tsan_opts=atexit_sleep_ms=50 %run %t 2>&1 | FileCheck --check-prefix=CHECK-REPORT %s
#include <atomic>
#include <cassert>
#include <stdio.h>
#include <thread>
#define NUM_ORDS 16
#define NUM_THREADS NUM_ORDS * 2
struct node {
int val;
};
std::atomic<node *> _nodes[NUM_THREADS] = {};
void f1(int i) {
auto n = new node();
n->val = 42;
_nodes[i].store(n, std::memory_order_release);
}
template <int version>
void f2(int i, std::memory_order mo, std::memory_order fmo) {
node *expected = nullptr;
while (expected == nullptr) {
_nodes[i].compare_exchange_weak(expected, nullptr, mo, fmo);
};
++expected->val;
assert(expected->val == 43);
}
struct MemOrdSuccFail {
std::memory_order mo;
std::memory_order fmo;
};
MemOrdSuccFail OrdList[NUM_ORDS] = {
{std::memory_order_release, std::memory_order_relaxed},
{std::memory_order_release, std::memory_order_acquire},
{std::memory_order_release, std::memory_order_consume},
{std::memory_order_release, std::memory_order_seq_cst},
{std::memory_order_acq_rel, std::memory_order_relaxed},
{std::memory_order_acq_rel, std::memory_order_acquire},
{std::memory_order_acq_rel, std::memory_order_consume},
{std::memory_order_acq_rel, std::memory_order_seq_cst},
{std::memory_order_seq_cst, std::memory_order_relaxed},
{std::memory_order_seq_cst, std::memory_order_acquire},
{std::memory_order_seq_cst, std::memory_order_consume},
{std::memory_order_seq_cst, std::memory_order_seq_cst},
{std::memory_order_relaxed, std::memory_order_relaxed},
{std::memory_order_relaxed, std::memory_order_acquire},
{std::memory_order_relaxed, std::memory_order_consume},
{std::memory_order_relaxed, std::memory_order_seq_cst},
};
int main() {
std::thread threads[NUM_THREADS];
int ords = 0;
// Instantiate a new f2 for each MO so we can dedup reports and actually
// make sure relaxed FMO triggers a warning for every different MO.
for (unsigned t = 0; t < 8; t += 2) {
threads[t] = std::thread(f1, t);
threads[t + 1] = std::thread(f2<0>, t, OrdList[ords].mo, OrdList[ords].fmo);
threads[t].join();
threads[t + 1].join();
ords++;
}
for (unsigned t = 8; t < 16; t += 2) {
threads[t] = std::thread(f1, t);
threads[t + 1] = std::thread(f2<1>, t, OrdList[ords].mo, OrdList[ords].fmo);
threads[t].join();
threads[t + 1].join();
ords++;
}
for (unsigned t = 16; t < 24; t += 2) {
threads[t] = std::thread(f1, t);
threads[t + 1] = std::thread(f2<2>, t, OrdList[ords].mo, OrdList[ords].fmo);
threads[t].join();
threads[t + 1].join();
ords++;
}
for (unsigned t = 24; t < 32; t += 2) {
threads[t] = std::thread(f1, t);
threads[t + 1] = std::thread(f2<3>, t, OrdList[ords].mo, OrdList[ords].fmo);
threads[t].join();
threads[t + 1].join();
ords++;
}
fprintf(stderr, "DONE\n");
return 0;
}
// CHECK-REPORT: WARNING: ThreadSanitizer: data race
// CHECK-REPORT: WARNING: ThreadSanitizer: data race
// CHECK-REPORT: WARNING: ThreadSanitizer: data race
// CHECK-REPORT: WARNING: ThreadSanitizer: data race
// CHECK-REPORT: DONE
// CHECK-REPORT: ThreadSanitizer: reported 4 warnings

View File

@ -349,41 +349,61 @@ entry:
define void @atomic8_cas_monotonic(i8* %a) nounwind uwtable {
entry:
cmpxchg i8* %a, i8 0, i8 1 monotonic monotonic, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 monotonic acquire, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 monotonic seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic8_cas_monotonic
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 0, i32 0), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 0, i32 2), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 0, i32 5), !dbg
define void @atomic8_cas_acquire(i8* %a) nounwind uwtable {
entry:
cmpxchg i8* %a, i8 0, i8 1 acquire monotonic, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 acquire acquire, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 acquire seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic8_cas_acquire
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 2, i32 0), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 2, i32 2), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 2, i32 5), !dbg
define void @atomic8_cas_release(i8* %a) nounwind uwtable {
entry:
cmpxchg i8* %a, i8 0, i8 1 release monotonic, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 release acquire, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 release seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic8_cas_release
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 3, i32 0), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 3, i32 2), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 3, i32 5), !dbg
define void @atomic8_cas_acq_rel(i8* %a) nounwind uwtable {
entry:
cmpxchg i8* %a, i8 0, i8 1 acq_rel monotonic, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 acq_rel acquire, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 acq_rel seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic8_cas_acq_rel
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 4, i32 0), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 4, i32 2), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 4, i32 5), !dbg
define void @atomic8_cas_seq_cst(i8* %a) nounwind uwtable {
entry:
cmpxchg i8* %a, i8 0, i8 1 seq_cst monotonic, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 seq_cst acquire, !dbg !7
cmpxchg i8* %a, i8 0, i8 1 seq_cst seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic8_cas_seq_cst
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 5, i32 0), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 5, i32 2), !dbg
; CHECK: call i8 @__tsan_atomic8_compare_exchange_val(i8* %a, i8 0, i8 1, i32 5, i32 5), !dbg
define i16 @atomic16_load_unordered(i16* %a) nounwind uwtable {
@ -733,41 +753,61 @@ entry:
define void @atomic16_cas_monotonic(i16* %a) nounwind uwtable {
entry:
cmpxchg i16* %a, i16 0, i16 1 monotonic monotonic, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 monotonic acquire, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 monotonic seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic16_cas_monotonic
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 0, i32 0), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 0, i32 2), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 0, i32 5), !dbg
define void @atomic16_cas_acquire(i16* %a) nounwind uwtable {
entry:
cmpxchg i16* %a, i16 0, i16 1 acquire monotonic, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 acquire acquire, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 acquire seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic16_cas_acquire
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 2, i32 0), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 2, i32 2), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 2, i32 5), !dbg
define void @atomic16_cas_release(i16* %a) nounwind uwtable {
entry:
cmpxchg i16* %a, i16 0, i16 1 release monotonic, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 release acquire, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 release seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic16_cas_release
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 3, i32 0), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 3, i32 2), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 3, i32 5), !dbg
define void @atomic16_cas_acq_rel(i16* %a) nounwind uwtable {
entry:
cmpxchg i16* %a, i16 0, i16 1 acq_rel monotonic, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 acq_rel acquire, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 acq_rel seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic16_cas_acq_rel
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 4, i32 0), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 4, i32 2), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 4, i32 5), !dbg
define void @atomic16_cas_seq_cst(i16* %a) nounwind uwtable {
entry:
cmpxchg i16* %a, i16 0, i16 1 seq_cst monotonic, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 seq_cst acquire, !dbg !7
cmpxchg i16* %a, i16 0, i16 1 seq_cst seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic16_cas_seq_cst
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 5, i32 0), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 5, i32 2), !dbg
; CHECK: call i16 @__tsan_atomic16_compare_exchange_val(i16* %a, i16 0, i16 1, i32 5, i32 5), !dbg
define i32 @atomic32_load_unordered(i32* %a) nounwind uwtable {
@ -1117,41 +1157,61 @@ entry:
define void @atomic32_cas_monotonic(i32* %a) nounwind uwtable {
entry:
cmpxchg i32* %a, i32 0, i32 1 monotonic monotonic, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 monotonic acquire, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 monotonic seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic32_cas_monotonic
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 0, i32 0), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 0, i32 2), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 0, i32 5), !dbg
define void @atomic32_cas_acquire(i32* %a) nounwind uwtable {
entry:
cmpxchg i32* %a, i32 0, i32 1 acquire monotonic, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 acquire acquire, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 acquire seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic32_cas_acquire
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 2, i32 0), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 2, i32 2), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 2, i32 5), !dbg
define void @atomic32_cas_release(i32* %a) nounwind uwtable {
entry:
cmpxchg i32* %a, i32 0, i32 1 release monotonic, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 release acquire, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 release seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic32_cas_release
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 3, i32 0), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 3, i32 2), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 3, i32 5), !dbg
define void @atomic32_cas_acq_rel(i32* %a) nounwind uwtable {
entry:
cmpxchg i32* %a, i32 0, i32 1 acq_rel monotonic, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 acq_rel acquire, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 acq_rel seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic32_cas_acq_rel
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 4, i32 0), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 4, i32 2), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 4, i32 5), !dbg
define void @atomic32_cas_seq_cst(i32* %a) nounwind uwtable {
entry:
cmpxchg i32* %a, i32 0, i32 1 seq_cst monotonic, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 seq_cst acquire, !dbg !7
cmpxchg i32* %a, i32 0, i32 1 seq_cst seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic32_cas_seq_cst
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 5, i32 0), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 5, i32 2), !dbg
; CHECK: call i32 @__tsan_atomic32_compare_exchange_val(i32* %a, i32 0, i32 1, i32 5, i32 5), !dbg
define i64 @atomic64_load_unordered(i64* %a) nounwind uwtable {
@ -1521,41 +1581,61 @@ entry:
define void @atomic64_cas_monotonic(i64* %a) nounwind uwtable {
entry:
cmpxchg i64* %a, i64 0, i64 1 monotonic monotonic, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 monotonic acquire, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 monotonic seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic64_cas_monotonic
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 0, i32 0), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 0, i32 2), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 0, i32 5), !dbg
define void @atomic64_cas_acquire(i64* %a) nounwind uwtable {
entry:
cmpxchg i64* %a, i64 0, i64 1 acquire monotonic, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 acquire acquire, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 acquire seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic64_cas_acquire
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 2, i32 0), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 2, i32 2), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 2, i32 5), !dbg
define void @atomic64_cas_release(i64* %a) nounwind uwtable {
entry:
cmpxchg i64* %a, i64 0, i64 1 release monotonic, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 release acquire, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 release seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic64_cas_release
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 3, i32 0), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 3, i32 2), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 3, i32 5), !dbg
define void @atomic64_cas_acq_rel(i64* %a) nounwind uwtable {
entry:
cmpxchg i64* %a, i64 0, i64 1 acq_rel monotonic, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 acq_rel acquire, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 acq_rel seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic64_cas_acq_rel
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 4, i32 0), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 4, i32 2), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 4, i32 5), !dbg
define void @atomic64_cas_seq_cst(i64* %a) nounwind uwtable {
entry:
cmpxchg i64* %a, i64 0, i64 1 seq_cst monotonic, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 seq_cst acquire, !dbg !7
cmpxchg i64* %a, i64 0, i64 1 seq_cst seq_cst, !dbg !7
ret void, !dbg !7
}
; CHECK-LABEL: atomic64_cas_seq_cst
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 5, i32 0), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 5, i32 2), !dbg
; CHECK: call i64 @__tsan_atomic64_compare_exchange_val(i64* %a, i64 0, i64 1, i32 5, i32 5), !dbg
define void @atomic64_cas_seq_cst_ptr_ty(i8** %a, i8* %v1, i8* %v2) nounwind uwtable {