Skip to content

Commit

Permalink
tsan: new runtime (v3)
Browse files Browse the repository at this point in the history
This change switches tsan to the new runtime which features:
 - 2x smaller shadow memory (2x of app memory)
 - faster fully vectorized race detection
 - small fixed-size vector clocks (512b)
 - fast vectorized vector clock operations
 - unlimited number of alive threads/goroutimes

Depends on D112602.

Reviewed By: melver

Differential Revision: https://reviews.llvm.org/D112603
  • Loading branch information
dvyukov committed Dec 13, 2021
1 parent 921e89c commit b332134
Show file tree
Hide file tree
Showing 48 changed files with 2,303 additions and 2,136 deletions.
2 changes: 2 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class MUTEX ThreadRegistry {
return threads_.empty() ? nullptr : threads_[tid];
}

u32 NumThreadsLocked() const { return threads_.size(); }

u32 CreateThread(uptr user_id, bool detached, u32 parent_tid, void *arg);

typedef void (*ThreadCallback)(ThreadContextBase *tctx, void *arg);
Expand Down
2 changes: 1 addition & 1 deletion compiler-rt/lib/tsan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ if(COMPILER_RT_TSAN_DEBUG_OUTPUT)
endif()

# Add the actual runtime library.
option(TSAN_USE_OLD_RUNTIME "Use the old tsan runtime (temporal option for emergencies)." OFF)
option(TSAN_USE_OLD_RUNTIME "Use the old tsan runtime (temporal option for emergencies)." ON)
if (TSAN_USE_OLD_RUNTIME)
add_subdirectory(rtl-old)
else()
Expand Down
12 changes: 9 additions & 3 deletions compiler-rt/lib/tsan/check_analyze.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,27 @@ check() {
fi
}

# All hot functions must contain no PUSH/POP
# and no CALLs (everything is tail-called).
for f in write1 write2 write4 write8; do
check $f rsp 1
check $f push 2
check $f push 0
check $f pop 0
check $f call 0
done

for f in read1 read2 read4 read8; do
check $f rsp 1
check $f push 3
check $f push 0
check $f pop 0
check $f call 0
done

for f in func_entry func_exit; do
check $f rsp 0
check $f push 0
check $f pop 0
check $f call 1 # TraceSwitch()
check $f call 0
done

echo LGTM
1 change: 1 addition & 0 deletions compiler-rt/lib/tsan/go/build.bat
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type ^
..\rtl\tsan_suppressions.cpp ^
..\rtl\tsan_sync.cpp ^
..\rtl\tsan_stack_trace.cpp ^
..\rtl\tsan_vector_clock.cpp ^
..\..\sanitizer_common\sanitizer_allocator.cpp ^
..\..\sanitizer_common\sanitizer_common.cpp ^
..\..\sanitizer_common\sanitizer_flags.cpp ^
Expand Down
1 change: 1 addition & 0 deletions compiler-rt/lib/tsan/go/buildgo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ SRCS="
../rtl/tsan_stack_trace.cpp
../rtl/tsan_suppressions.cpp
../rtl/tsan_sync.cpp
../rtl/tsan_vector_clock.cpp
../../sanitizer_common/sanitizer_allocator.cpp
../../sanitizer_common/sanitizer_common.cpp
../../sanitizer_common/sanitizer_common_libcdep.cpp
Expand Down
2 changes: 1 addition & 1 deletion compiler-rt/lib/tsan/go/tsan_go.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ void __tsan_malloc(ThreadState *thr, uptr pc, uptr p, uptr sz) {
}

void __tsan_free(uptr p, uptr sz) {
ctx->metamap.FreeRange(get_cur_proc(), p, sz);
ctx->metamap.FreeRange(get_cur_proc(), p, sz, false);
}

void __tsan_go_start(ThreadState *parent, ThreadState **pthr, void *pc) {
Expand Down
1 change: 0 additions & 1 deletion compiler-rt/lib/tsan/rtl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ set(TSAN_HEADERS
tsan_symbolize.h
tsan_sync.h
tsan_trace.h
tsan_update_shadow_word.inc
tsan_vector_clock.h
)

Expand Down
23 changes: 19 additions & 4 deletions compiler-rt/lib/tsan/rtl/tsan_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ enum class Epoch : u16 {};
constexpr uptr kEpochBits = 14;
constexpr Epoch kEpochZero = static_cast<Epoch>(0);
constexpr Epoch kEpochOver = static_cast<Epoch>(1 << kEpochBits);
constexpr Epoch kEpochLast = static_cast<Epoch>((1 << kEpochBits) - 1);

inline Epoch EpochInc(Epoch epoch) {
return static_cast<Epoch>(static_cast<u16>(epoch) + 1);
}

inline bool EpochOverflow(Epoch epoch) { return epoch == kEpochOver; }

const int kClkBits = 42;
const unsigned kMaxTidReuse = (1 << (64 - kClkBits)) - 1;
Expand Down Expand Up @@ -107,7 +114,7 @@ const uptr kShadowCnt = 4;
const uptr kShadowCell = 8;

// Single shadow value.
typedef u64 RawShadow;
enum class RawShadow : u32 {};
const uptr kShadowSize = sizeof(RawShadow);

// Shadow memory is kShadowMultiplier times larger than user memory.
Expand Down Expand Up @@ -184,10 +191,13 @@ MD5Hash md5_hash(const void *data, uptr size);
struct Processor;
struct ThreadState;
class ThreadContext;
struct TidSlot;
struct Context;
struct ReportStack;
class ReportDesc;
class RegionAlloc;
struct Trace;
struct TracePart;

typedef uptr AccessType;

Expand All @@ -198,6 +208,8 @@ enum : AccessType {
kAccessVptr = 1 << 2, // read or write of an object virtual table pointer
kAccessFree = 1 << 3, // synthetic memory access during memory freeing
kAccessExternalPC = 1 << 4, // access PC can have kExternalPCBit set
kAccessCheckOnly = 1 << 5, // check for races, but don't store
kAccessNoRodata = 1 << 6, // don't check for .rodata marker
};

// Descriptor of user's memory block.
Expand All @@ -219,16 +231,19 @@ enum ExternalTag : uptr {
// as 16-bit values, see tsan_defs.h.
};

enum MutexType {
MutexTypeTrace = MutexLastCommon,
MutexTypeReport,
enum {
MutexTypeReport = MutexLastCommon,
MutexTypeSyncVar,
MutexTypeAnnotations,
MutexTypeAtExit,
MutexTypeFired,
MutexTypeRacy,
MutexTypeGlobalProc,
MutexTypeInternalAlloc,
MutexTypeTrace,
MutexTypeSlot,
MutexTypeSlots,
MutexTypeMultiSlot,
};

} // namespace __tsan
Expand Down
9 changes: 9 additions & 0 deletions compiler-rt/lib/tsan/rtl/tsan_dense_alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ class DenseSlabAlloc {
return atomic_load_relaxed(&fillpos_) * kL2Size * sizeof(T);
}

template <typename Func>
void ForEach(Func func) {
SpinMutexLock lock(&mtx_);
uptr fillpos = atomic_load_relaxed(&fillpos_);
for (uptr l1 = 0; l1 < fillpos; l1++) {
for (IndexT l2 = l1 == 0 ? 1 : 0; l2 < kL2Size; l2++) func(&map_[l1][l2]);
}
}

private:
T *map_[kL1Size];
SpinMutex mtx_;
Expand Down
6 changes: 0 additions & 6 deletions compiler-rt/lib/tsan/rtl/tsan_flags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,6 @@ void InitializeFlags(Flags *f, const char *env, const char *env_option_name) {

if (common_flags()->help) parser.PrintFlagDescriptions();

if (f->history_size < 0 || f->history_size > 7) {
Printf("ThreadSanitizer: incorrect value for history_size"
" (must be [0..7])\n");
Die();
}

if (f->io_sync < 0 || f->io_sync > 2) {
Printf("ThreadSanitizer: incorrect value for io_sync"
" (must be [0..2])\n");
Expand Down
10 changes: 3 additions & 7 deletions compiler-rt/lib/tsan/rtl/tsan_flags.inc
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,10 @@ TSAN_FLAG(bool, stop_on_start, false,
"Stops on start until __tsan_resume() is called (for debugging).")
TSAN_FLAG(bool, running_on_valgrind, false,
"Controls whether RunningOnValgrind() returns true or false.")
// There are a lot of goroutines in Go, so we use smaller history.
TSAN_FLAG(
int, history_size, SANITIZER_GO ? 1 : 3,
"Per-thread history size, controls how many previous memory accesses "
"are remembered per thread. Possible values are [0..7]. "
"history_size=0 amounts to 32K memory accesses. Each next value doubles "
"the amount of memory accesses, up to history_size=7 that amounts to "
"4M memory accesses. The default value is 2 (128K memory accesses).")
uptr, history_size, 0,
"Per-thread history size,"
" controls how many extra previous memory accesses are remembered per thread.")
TSAN_FLAG(int, io_sync, 1,
"Controls level of synchronization implied by IO operations. "
"0 - no synchronization "
Expand Down
3 changes: 2 additions & 1 deletion compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1981,6 +1981,7 @@ static void ReportErrnoSpoiling(ThreadState *thr, uptr pc) {
static void CallUserSignalHandler(ThreadState *thr, bool sync, bool acquire,
int sig, __sanitizer_siginfo *info,
void *uctx) {
CHECK(thr->slot);
__sanitizer_sigaction *sigactions = interceptor_ctx()->sigactions;
if (acquire)
Acquire(thr, 0, (uptr)&sigactions[sig]);
Expand Down Expand Up @@ -2268,7 +2269,7 @@ struct dl_iterate_phdr_data {
};

static bool IsAppNotRodata(uptr addr) {
return IsAppMem(addr) && *MemToShadow(addr) != kShadowRodata;
return IsAppMem(addr) && *MemToShadow(addr) != Shadow::kRodata;
}

static int dl_iterate_phdr_cb(__sanitizer_dl_phdr_info *info, SIZE_T size,
Expand Down
87 changes: 46 additions & 41 deletions compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,9 @@ static T AtomicLoad(ThreadState *thr, uptr pc, const volatile T *a, morder mo) {
T v = NoTsanAtomicLoad(a, mo);
SyncVar *s = ctx->metamap.GetSyncIfExists((uptr)a);
if (s) {
ReadLock l(&s->mtx);
AcquireImpl(thr, pc, &s->clock);
SlotLocker locker(thr);
ReadLock lock(&s->mtx);
thr->clock.Acquire(s->clock);
// Re-read under sync mutex because we need a consistent snapshot
// of the value and the clock we acquire.
v = NoTsanAtomicLoad(a, mo);
Expand Down Expand Up @@ -270,33 +271,36 @@ static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v,
NoTsanAtomicStore(a, v, mo);
return;
}
__sync_synchronize();
SyncVar *s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false);
Lock l(&s->mtx);
thr->fast_state.IncrementEpoch();
// Can't increment epoch w/o writing to the trace as well.
TraceAddEvent(thr, thr->fast_state, EventTypeMop, 0);
ReleaseStoreImpl(thr, pc, &s->clock);
NoTsanAtomicStore(a, v, mo);
SlotLocker locker(thr);
{
auto s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false);
Lock lock(&s->mtx);
thr->clock.ReleaseStore(&s->clock);
NoTsanAtomicStore(a, v, mo);
}
IncrementEpoch(thr);
}

template <typename T, T (*F)(volatile T *v, T op)>
static T AtomicRMW(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) {
MemoryAccess(thr, pc, (uptr)a, AccessSize<T>(), kAccessWrite | kAccessAtomic);
if (LIKELY(mo == mo_relaxed))
return F(a, v);
SyncVar *s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false);
Lock l(&s->mtx);
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))
AcquireReleaseImpl(thr, pc, &s->clock);
else if (IsReleaseOrder(mo))
ReleaseImpl(thr, pc, &s->clock);
else if (IsAcquireOrder(mo))
AcquireImpl(thr, pc, &s->clock);
return F(a, v);
SlotLocker locker(thr);
{
auto s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false);
RWLock lock(&s->mtx, IsReleaseOrder(mo));
if (IsAcqRelOrder(mo))
thr->clock.ReleaseAcquire(&s->clock);
else if (IsReleaseOrder(mo))
thr->clock.Release(&s->clock);
else if (IsAcquireOrder(mo))
thr->clock.Acquire(s->clock);
v = F(a, v);
}
if (IsReleaseOrder(mo))
IncrementEpoch(thr);
return v;
}

template<typename T>
Expand Down Expand Up @@ -416,27 +420,28 @@ static bool AtomicCAS(ThreadState *thr, uptr pc, volatile T *a, T *c, T v,
*c = pr;
return false;
}

SlotLocker locker(thr);
bool release = IsReleaseOrder(mo);
SyncVar *s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false);
RWLock l(&s->mtx, release);
T cc = *c;
T pr = func_cas(a, cc, v);
bool success = pr == cc;
if (!success) {
*c = pr;
mo = fmo;
bool success;
{
auto s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false);
RWLock lock(&s->mtx, release);
T cc = *c;
T pr = func_cas(a, cc, v);
success = pr == cc;
if (!success) {
*c = pr;
mo = fmo;
}
if (success && IsAcqRelOrder(mo))
thr->clock.ReleaseAcquire(&s->clock);
else if (success && IsReleaseOrder(mo))
thr->clock.Release(&s->clock);
else if (IsAcquireOrder(mo))
thr->clock.Acquire(s->clock);
}
thr->fast_state.IncrementEpoch();
// Can't increment epoch w/o writing to the trace as well.
TraceAddEvent(thr, thr->fast_state, EventTypeMop, 0);

if (success && IsAcqRelOrder(mo))
AcquireReleaseImpl(thr, pc, &s->clock);
else if (success && IsReleaseOrder(mo))
ReleaseImpl(thr, pc, &s->clock);
else if (IsAcquireOrder(mo))
AcquireImpl(thr, pc, &s->clock);
if (success && release)
IncrementEpoch(thr);
return success;
}

Expand Down
4 changes: 2 additions & 2 deletions compiler-rt/lib/tsan/rtl/tsan_interface_java.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ void __tsan_java_free(jptr ptr, jptr size) {
DCHECK_GE(ptr, jctx->heap_begin);
DCHECK_LE(ptr + size, jctx->heap_begin + jctx->heap_size);

ctx->metamap.FreeRange(thr->proc(), ptr, size);
ctx->metamap.FreeRange(thr->proc(), ptr, size, false);
}

void __tsan_java_move(jptr src, jptr dst, jptr size) {
Expand All @@ -133,7 +133,7 @@ void __tsan_java_move(jptr src, jptr dst, jptr size) {
// support that anymore as it contains addresses of accesses.
RawShadow *d = MemToShadow(dst);
RawShadow *dend = MemToShadow(dst + size);
internal_memset(d, 0, (dend - d) * sizeof(*d));
ShadowSet(d, dend, Shadow::kEmpty);
}

jptr __tsan_java_find(jptr *from_ptr, jptr to) {
Expand Down
Loading

0 comments on commit b332134

Please sign in to comment.