tsan: switch to the new sanitizer_common mutex

Now that sanitizer_common mutex has feature-parity with tsan mutex,
switch tsan to the sanitizer_common mutex and remove tsan's custom mutex.

Reviewed By: vitalybuka, melver

Differential Revision: https://reviews.llvm.org/D106379
This commit is contained in:
Dmitry Vyukov 2021-07-20 13:31:10 +02:00
parent 8924d8e37e
commit 0118a64934
18 changed files with 48 additions and 516 deletions

View File

@ -149,9 +149,9 @@ class CheckedMutex {
};
// Reader-writer mutex.
class MUTEX Mutex2 {
class MUTEX Mutex {
public:
constexpr Mutex2(MutexType type = MutexUnchecked) : checked_(type) {}
constexpr Mutex(MutexType type = MutexUnchecked) : checked_(type) {}
void Lock() ACQUIRE() {
checked_.Lock();
@ -329,8 +329,8 @@ class MUTEX Mutex2 {
static constexpr u64 kWriterLock = 1ull << (3 * kCounterWidth);
static constexpr u64 kWriterSpinWait = 1ull << (3 * kCounterWidth + 1);
Mutex2(const Mutex2 &) = delete;
void operator=(const Mutex2 &) = delete;
Mutex(const Mutex &) = delete;
void operator=(const Mutex &) = delete;
};
void FutexWait(atomic_uint32_t *p, u32 cmp);
@ -477,6 +477,8 @@ typedef GenericScopedLock<StaticSpinMutex> SpinMutexLock;
typedef GenericScopedLock<BlockingMutex> BlockingMutexLock;
typedef GenericScopedLock<RWMutex> RWMutexLock;
typedef GenericScopedReadLock<RWMutex> RWMutexReadLock;
typedef GenericScopedLock<Mutex> Lock;
typedef GenericScopedReadLock<Mutex> ReadLock;
} // namespace __sanitizer

View File

@ -158,12 +158,12 @@ TEST(SanitizerCommon, BlockingMutex) {
check_locked(mtx);
}
TEST(SanitizerCommon, Mutex2) {
Mutex2 mtx;
TestData<Mutex2> data(&mtx);
TEST(SanitizerCommon, Mutex) {
Mutex mtx;
TestData<Mutex> data(&mtx);
pthread_t threads[kThreads];
for (int i = 0; i < kThreads; i++)
PTHREAD_CREATE(&threads[i], 0, read_write_thread<Mutex2>, &data);
PTHREAD_CREATE(&threads[i], 0, read_write_thread<Mutex>, &data);
for (int i = 0; i < kThreads; i++) PTHREAD_JOIN(threads[i], 0);
}

View File

@ -39,7 +39,6 @@ set(TSAN_SOURCES
rtl/tsan_malloc_mac.cpp
rtl/tsan_md5.cpp
rtl/tsan_mman.cpp
rtl/tsan_mutex.cpp
rtl/tsan_mutexset.cpp
rtl/tsan_preinit.cpp
rtl/tsan_report.cpp
@ -94,7 +93,6 @@ set(TSAN_HEADERS
rtl/tsan_interface_inl.h
rtl/tsan_interface_java.h
rtl/tsan_mman.h
rtl/tsan_mutex.h
rtl/tsan_mutexset.h
rtl/tsan_platform.h
rtl/tsan_ppc_regs.h

View File

@ -4,7 +4,6 @@ type ^
..\rtl\tsan_clock.cpp ^
..\rtl\tsan_flags.cpp ^
..\rtl\tsan_md5.cpp ^
..\rtl\tsan_mutex.cpp ^
..\rtl\tsan_report.cpp ^
..\rtl\tsan_rtl.cpp ^
..\rtl\tsan_rtl_mutex.cpp ^

View File

@ -9,7 +9,6 @@ SRCS="
../rtl/tsan_flags.cpp
../rtl/tsan_interface_atomic.cpp
../rtl/tsan_md5.cpp
../rtl/tsan_mutex.cpp
../rtl/tsan_report.cpp
../rtl/tsan_rtl.cpp
../rtl/tsan_rtl_mutex.cpp

View File

@ -15,6 +15,7 @@
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "ubsan/ubsan_platform.h"
// Setup defaults for compile definitions.
@ -172,6 +173,17 @@ enum ExternalTag : uptr {
// as 16-bit values, see tsan_defs.h.
};
enum MutexType {
MutexTypeTrace = MutexLastCommon,
MutexTypeReport,
MutexTypeSyncVar,
MutexTypeAnnotations,
MutexTypeAtExit,
MutexTypeFired,
MutexTypeRacy,
MutexTypeGlobalProc,
};
} // namespace __tsan
#endif // TSAN_DEFS_H

View File

@ -20,7 +20,6 @@
#include "sanitizer_common/sanitizer_common.h"
#include "tsan_defs.h"
#include "tsan_mutex.h"
namespace __tsan {

View File

@ -267,7 +267,7 @@ ScopedInterceptor::~ScopedInterceptor() {
if (!thr_->ignore_interceptors) {
ProcessPendingSignals(thr_);
FuncExit(thr_);
CheckNoLocks(thr_);
CheckedMutex::CheckNoLocks();
}
}

View File

@ -15,7 +15,6 @@
#include "sanitizer_common/sanitizer_stacktrace.h"
#include "sanitizer_common/sanitizer_vector.h"
#include "tsan_interface_ann.h"
#include "tsan_mutex.h"
#include "tsan_report.h"
#include "tsan_rtl.h"
#include "tsan_mman.h"
@ -38,7 +37,7 @@ class ScopedAnnotation {
~ScopedAnnotation() {
FuncExit(thr_);
CheckNoLocks(thr_);
CheckedMutex::CheckNoLocks();
}
private:
ThreadState *const thr_;

View File

@ -12,7 +12,6 @@
#include "tsan_interface_java.h"
#include "tsan_rtl.h"
#include "tsan_mutex.h"
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_placement_new.h"

View File

@ -1,280 +0,0 @@
//===-- tsan_mutex.cpp ----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of ThreadSanitizer (TSan), a race detector.
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_libc.h"
#include "tsan_mutex.h"
#include "tsan_platform.h"
#include "tsan_rtl.h"
namespace __tsan {
// Simple reader-writer spin-mutex. Optimized for not-so-contended case.
// Readers have preference, can possibly starvate writers.
// The table fixes what mutexes can be locked under what mutexes.
// E.g. if the row for MutexTypeThreads contains MutexTypeReport,
// then Report mutex can be locked while under Threads mutex.
// The leaf mutexes can be locked under any other mutexes.
// Recursive locking is not supported.
#if SANITIZER_DEBUG && !SANITIZER_GO
const MutexType MutexTypeLeaf = (MutexType)-1;
static MutexType CanLockTab[MutexTypeCount][MutexTypeCount] = {
/*0 MutexTypeInvalid*/ {},
/*1 MutexTypeTrace*/ {MutexTypeLeaf},
/*2 MutexTypeThreads*/ {MutexTypeReport},
/*3 MutexTypeReport*/ {MutexTypeSyncVar,
MutexTypeMBlock, MutexTypeJavaMBlock},
/*4 MutexTypeSyncVar*/ {MutexTypeDDetector},
/*5 MutexTypeSyncTab*/ {}, // unused
/*6 MutexTypeSlab*/ {MutexTypeLeaf},
/*7 MutexTypeAnnotations*/ {},
/*8 MutexTypeAtExit*/ {MutexTypeSyncVar},
/*9 MutexTypeMBlock*/ {MutexTypeSyncVar},
/*10 MutexTypeJavaMBlock*/ {MutexTypeSyncVar},
/*11 MutexTypeDDetector*/ {},
/*12 MutexTypeFired*/ {MutexTypeLeaf},
/*13 MutexTypeRacy*/ {MutexTypeLeaf},
/*14 MutexTypeGlobalProc*/ {},
};
static bool CanLockAdj[MutexTypeCount][MutexTypeCount];
#endif
void InitializeMutex() {
#if SANITIZER_DEBUG && !SANITIZER_GO
// Build the "can lock" adjacency matrix.
// If [i][j]==true, then one can lock mutex j while under mutex i.
const int N = MutexTypeCount;
int cnt[N] = {};
bool leaf[N] = {};
for (int i = 1; i < N; i++) {
for (int j = 0; j < N; j++) {
MutexType z = CanLockTab[i][j];
if (z == MutexTypeInvalid)
continue;
if (z == MutexTypeLeaf) {
CHECK(!leaf[i]);
leaf[i] = true;
continue;
}
CHECK(!CanLockAdj[i][(int)z]);
CanLockAdj[i][(int)z] = true;
cnt[i]++;
}
}
for (int i = 0; i < N; i++) {
CHECK(!leaf[i] || cnt[i] == 0);
}
// Add leaf mutexes.
for (int i = 0; i < N; i++) {
if (!leaf[i])
continue;
for (int j = 0; j < N; j++) {
if (i == j || leaf[j] || j == MutexTypeInvalid)
continue;
CHECK(!CanLockAdj[j][i]);
CanLockAdj[j][i] = true;
}
}
// Build the transitive closure.
bool CanLockAdj2[MutexTypeCount][MutexTypeCount];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
CanLockAdj2[i][j] = CanLockAdj[i][j];
}
}
for (int k = 0; k < N; k++) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (CanLockAdj2[i][k] && CanLockAdj2[k][j]) {
CanLockAdj2[i][j] = true;
}
}
}
}
#if 0
Printf("Can lock graph:\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
Printf("%d ", CanLockAdj[i][j]);
}
Printf("\n");
}
Printf("Can lock graph closure:\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
Printf("%d ", CanLockAdj2[i][j]);
}
Printf("\n");
}
#endif
// Verify that the graph is acyclic.
for (int i = 0; i < N; i++) {
if (CanLockAdj2[i][i]) {
Printf("Mutex %d participates in a cycle\n", i);
Die();
}
}
#endif
}
InternalDeadlockDetector::InternalDeadlockDetector() {
// Rely on zero initialization because some mutexes can be locked before ctor.
}
#if SANITIZER_DEBUG && !SANITIZER_GO
void InternalDeadlockDetector::Lock(MutexType t) {
// Printf("LOCK %d @%zu\n", t, seq_ + 1);
CHECK_GT(t, MutexTypeInvalid);
CHECK_LT(t, MutexTypeCount);
u64 max_seq = 0;
u64 max_idx = MutexTypeInvalid;
for (int i = 0; i != MutexTypeCount; i++) {
if (locked_[i] == 0)
continue;
CHECK_NE(locked_[i], max_seq);
if (max_seq < locked_[i]) {
max_seq = locked_[i];
max_idx = i;
}
}
locked_[t] = ++seq_;
if (max_idx == MutexTypeInvalid)
return;
// Printf(" last %d @%zu\n", max_idx, max_seq);
if (!CanLockAdj[max_idx][t]) {
Printf("ThreadSanitizer: internal deadlock detected\n");
Printf("ThreadSanitizer: can't lock %d while under %zu\n",
t, (uptr)max_idx);
CHECK(0);
}
}
void InternalDeadlockDetector::Unlock(MutexType t) {
// Printf("UNLO %d @%zu #%zu\n", t, seq_, locked_[t]);
CHECK(locked_[t]);
locked_[t] = 0;
}
void InternalDeadlockDetector::CheckNoLocks() {
for (int i = 0; i != MutexTypeCount; i++) {
CHECK_EQ(locked_[i], 0);
}
}
#endif
void CheckNoLocks(ThreadState *thr) {
#if SANITIZER_DEBUG && !SANITIZER_GO
thr->internal_deadlock_detector.CheckNoLocks();
#endif
}
const uptr kUnlocked = 0;
const uptr kWriteLock = 1;
const uptr kReadLock = 2;
class Backoff {
public:
Backoff()
: iter_() {
}
bool Do() {
if (iter_++ < kActiveSpinIters)
proc_yield(kActiveSpinCnt);
else
internal_sched_yield();
return true;
}
u64 Contention() const {
u64 active = iter_ % kActiveSpinIters;
u64 passive = iter_ - active;
return active + 10 * passive;
}
private:
int iter_;
static const int kActiveSpinIters = 10;
static const int kActiveSpinCnt = 20;
};
Mutex::Mutex(MutexType type) {
CHECK_GT(type, MutexTypeInvalid);
CHECK_LT(type, MutexTypeCount);
#if SANITIZER_DEBUG
type_ = type;
#endif
atomic_store(&state_, kUnlocked, memory_order_relaxed);
}
Mutex::~Mutex() {
CHECK_EQ(atomic_load(&state_, memory_order_relaxed), kUnlocked);
}
void Mutex::Lock() {
#if SANITIZER_DEBUG && !SANITIZER_GO
cur_thread()->internal_deadlock_detector.Lock(type_);
#endif
uptr cmp = kUnlocked;
if (atomic_compare_exchange_strong(&state_, &cmp, kWriteLock,
memory_order_acquire))
return;
for (Backoff backoff; backoff.Do();) {
if (atomic_load(&state_, memory_order_relaxed) == kUnlocked) {
cmp = kUnlocked;
if (atomic_compare_exchange_weak(&state_, &cmp, kWriteLock,
memory_order_acquire)) {
return;
}
}
}
}
void Mutex::Unlock() {
uptr prev = atomic_fetch_sub(&state_, kWriteLock, memory_order_release);
(void)prev;
DCHECK_NE(prev & kWriteLock, 0);
#if SANITIZER_DEBUG && !SANITIZER_GO
cur_thread()->internal_deadlock_detector.Unlock(type_);
#endif
}
void Mutex::ReadLock() {
#if SANITIZER_DEBUG && !SANITIZER_GO
cur_thread()->internal_deadlock_detector.Lock(type_);
#endif
uptr prev = atomic_fetch_add(&state_, kReadLock, memory_order_acquire);
if ((prev & kWriteLock) == 0)
return;
for (Backoff backoff; backoff.Do();) {
prev = atomic_load(&state_, memory_order_acquire);
if ((prev & kWriteLock) == 0) {
return;
}
}
}
void Mutex::ReadUnlock() {
uptr prev = atomic_fetch_sub(&state_, kReadLock, memory_order_release);
(void)prev;
DCHECK_EQ(prev & kWriteLock, 0);
DCHECK_GT(prev & ~kWriteLock, 0);
#if SANITIZER_DEBUG && !SANITIZER_GO
cur_thread()->internal_deadlock_detector.Unlock(type_);
#endif
}
void Mutex::CheckLocked() {
CHECK_NE(atomic_load(&state_, memory_order_relaxed), 0);
}
} // namespace __tsan

View File

@ -1,87 +0,0 @@
//===-- tsan_mutex.h --------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of ThreadSanitizer (TSan), a race detector.
//
//===----------------------------------------------------------------------===//
#ifndef TSAN_MUTEX_H
#define TSAN_MUTEX_H
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "tsan_defs.h"
namespace __tsan {
enum MutexType {
MutexTypeInvalid,
MutexTypeTrace,
MutexTypeThreads,
MutexTypeReport,
MutexTypeSyncVar,
MutexTypeSyncTab,
MutexTypeSlab,
MutexTypeAnnotations,
MutexTypeAtExit,
MutexTypeMBlock,
MutexTypeJavaMBlock,
MutexTypeDDetector,
MutexTypeFired,
MutexTypeRacy,
MutexTypeGlobalProc,
// This must be the last.
MutexTypeCount
};
class Mutex {
public:
explicit Mutex(MutexType type);
~Mutex();
void Lock();
void Unlock();
void ReadLock();
void ReadUnlock();
void CheckLocked();
private:
atomic_uintptr_t state_;
#if SANITIZER_DEBUG
MutexType type_;
#endif
Mutex(const Mutex&);
void operator = (const Mutex&);
};
typedef GenericScopedLock<Mutex> Lock;
typedef GenericScopedReadLock<Mutex> ReadLock;
class InternalDeadlockDetector {
public:
InternalDeadlockDetector();
void Lock(MutexType t);
void Unlock(MutexType t);
void CheckNoLocks();
private:
u64 seq_;
u64 locked_[MutexTypeCount];
};
void InitializeMutex();
// Checks that the current thread does not hold any runtime locks
// (e.g. when returning from an interceptor).
void CheckNoLocks(ThreadState *thr);
} // namespace __tsan
#endif // TSAN_MUTEX_H

View File

@ -422,7 +422,6 @@ void Initialize(ThreadState *thr) {
InitializeInterceptors();
CheckShadowMapping();
InitializePlatform();
InitializeMutex();
InitializeDynamicAnnotations();
#if !SANITIZER_GO
InitializeShadowMemory();
@ -1133,7 +1132,28 @@ void build_consistency_release() {}
} // namespace __tsan
#if SANITIZER_CHECK_DEADLOCKS
namespace __sanitizer {
using namespace __tsan;
MutexMeta mutex_meta[] = {
{MutexInvalid, "Invalid", {}},
{MutexThreadRegistry, "ThreadRegistry", {}},
{MutexTypeTrace, "Trace", {MutexLeaf}},
{MutexTypeReport, "Report", {MutexTypeSyncVar}},
{MutexTypeSyncVar, "SyncVar", {}},
{MutexTypeAnnotations, "Annotations", {}},
{MutexTypeAtExit, "AtExit", {MutexTypeSyncVar}},
{MutexTypeFired, "Fired", {MutexLeaf}},
{MutexTypeRacy, "Racy", {MutexLeaf}},
{MutexTypeGlobalProc, "GlobalProc", {}},
{},
};
void PrintMutexPC(uptr pc) { StackTrace(&pc, 1).Print(); }
} // namespace __sanitizer
#endif
#if !SANITIZER_GO
// Must be included in this file to make sure everything is inlined.
#include "tsan_interface_inl.h"
# include "tsan_interface_inl.h"
#endif

View File

@ -129,7 +129,7 @@ bool ShouldReport(ThreadState *thr, ReportType typ) {
// We set thr->suppress_reports in the fork context.
// Taking any locking in the fork context can lead to deadlocks.
// If any locks are already taken, it's too late to do this check.
CheckNoLocks(thr);
CheckedMutex::CheckNoLocks();
// For the same reason check we didn't lock thread_registry yet.
if (SANITIZER_DEBUG)
ThreadRegistryLock l(ctx->thread_registry);
@ -596,7 +596,7 @@ static bool RaceBetweenAtomicAndFree(ThreadState *thr) {
}
void ReportRace(ThreadState *thr) {
CheckNoLocks(thr);
CheckedMutex::CheckNoLocks();
// Symbolizer makes lots of intercepted calls. If we try to process them,
// at best it will cause deadlocks on internal mutexes.

View File

@ -17,7 +17,6 @@
#include "sanitizer_common/sanitizer_deadlock_detector_interface.h"
#include "tsan_defs.h"
#include "tsan_clock.h"
#include "tsan_mutex.h"
#include "tsan_dense_alloc.h"
namespace __tsan {

View File

@ -13,7 +13,6 @@
#define TSAN_TRACE_H
#include "tsan_defs.h"
#include "tsan_mutex.h"
#include "tsan_stack_trace.h"
#include "tsan_mutexset.h"

View File

@ -3,7 +3,6 @@ set(TSAN_UNIT_TEST_SOURCES
tsan_dense_alloc_test.cpp
tsan_flags_test.cpp
tsan_mman_test.cpp
tsan_mutex_test.cpp
tsan_shadow_test.cpp
tsan_stack_test.cpp
tsan_sync_test.cpp

View File

@ -1,125 +0,0 @@
//===-- tsan_mutex_test.cpp -----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of ThreadSanitizer (TSan), a race detector.
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "tsan_mutex.h"
#include "gtest/gtest.h"
namespace __tsan {
template<typename MutexType>
class TestData {
public:
explicit TestData(MutexType *mtx)
: mtx_(mtx) {
for (int i = 0; i < kSize; i++)
data_[i] = 0;
}
void Write() {
Lock l(mtx_);
T v0 = data_[0];
for (int i = 0; i < kSize; i++) {
CHECK_EQ(data_[i], v0);
data_[i]++;
}
}
void Read() {
ReadLock l(mtx_);
T v0 = data_[0];
for (int i = 0; i < kSize; i++) {
CHECK_EQ(data_[i], v0);
}
}
void Backoff() {
volatile T data[kSize] = {};
for (int i = 0; i < kSize; i++) {
data[i]++;
CHECK_EQ(data[i], 1);
}
}
private:
typedef GenericScopedLock<MutexType> Lock;
static const int kSize = 64;
typedef u64 T;
MutexType *mtx_;
char pad_[kCacheLineSize];
T data_[kSize];
};
const int kThreads = 8;
const int kWriteRate = 1024;
#if SANITIZER_DEBUG
const int kIters = 16*1024;
#else
const int kIters = 64*1024;
#endif
template<typename MutexType>
static void *write_mutex_thread(void *param) {
TestData<MutexType> *data = (TestData<MutexType>*)param;
for (int i = 0; i < kIters; i++) {
data->Write();
data->Backoff();
}
return 0;
}
template<typename MutexType>
static void *read_mutex_thread(void *param) {
TestData<MutexType> *data = (TestData<MutexType>*)param;
for (int i = 0; i < kIters; i++) {
if ((i % kWriteRate) == 0)
data->Write();
else
data->Read();
data->Backoff();
}
return 0;
}
TEST(Mutex, Write) {
Mutex mtx(MutexTypeAnnotations);
TestData<Mutex> data(&mtx);
pthread_t threads[kThreads];
for (int i = 0; i < kThreads; i++)
pthread_create(&threads[i], 0, write_mutex_thread<Mutex>, &data);
for (int i = 0; i < kThreads; i++)
pthread_join(threads[i], 0);
}
TEST(Mutex, ReadWrite) {
Mutex mtx(MutexTypeAnnotations);
TestData<Mutex> data(&mtx);
pthread_t threads[kThreads];
for (int i = 0; i < kThreads; i++)
pthread_create(&threads[i], 0, read_mutex_thread<Mutex>, &data);
for (int i = 0; i < kThreads; i++)
pthread_join(threads[i], 0);
}
TEST(Mutex, SpinWrite) {
SpinMutex mtx;
TestData<SpinMutex> data(&mtx);
pthread_t threads[kThreads];
for (int i = 0; i < kThreads; i++)
pthread_create(&threads[i], 0, write_mutex_thread<SpinMutex>, &data);
for (int i = 0; i < kThreads; i++)
pthread_join(threads[i], 0);
}
} // namespace __tsan