blob: e118710120f51159792220b6a70ac9146c957b8b [file] [log] [blame]
/* Copyright (c) 2008-2010, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// This file is part of ThreadSanitizer, a dynamic data race detector.
// Author: Konstantin Serebryany.
// Author: Timur Iskhodzhanov.
// You can find the details on this tool at
// http://code.google.com/p/data-race-test
#include "thread_sanitizer.h"
#include "common_util.h"
#include "suppressions.h"
#include "ignore.h"
#include "ts_lock.h"
#include "ts_atomic_int.h"
#include "dense_multimap.h"
#include <stdarg.h>
// -------- Constants --------------- {{{1
// Segment ID (SID) is in range [1, kMaxSID-1]
// Segment Set ID (SSID) is in range [-kMaxSID+1, -1]
// This is not a compile-time constant, but it can only be changed at startup.
int kMaxSID = (1 << 23);
// Flush state after so many SIDs have been allocated. Set by command line flag.
int kMaxSIDBeforeFlush;
// Lock ID (LID) is in range [1, kMaxLID-1]
// Lock Set ID (LSID) is in range [-kMaxLID+1, -1]
const int kMaxLID = (1 << 23);
// This is not a compile-time constant, but it can be changed only at startup.
int kSizeOfHistoryStackTrace = 10;
// Maximal number of segments in a SegmentSet.
// If you change this constant, you also need to change several places
// in SegmentSet code.
const int kMaxSegmentSetSize = 4;
// -------- Globals --------------- {{{1
// If true, ignore all accesses in all threads.
bool global_ignore;
bool g_so_far_only_one_thread = false;
bool g_has_entered_main = false;
bool g_has_exited_main = false;
size_t g_last_flush_time;
// Incremented on each Lock and Unlock. Used by LockHistory.
uint32_t g_lock_era = 0;
uintptr_t g_nacl_mem_start = (uintptr_t)-1;
uintptr_t g_nacl_mem_end = (uintptr_t)-1;
bool g_race_verifier_active = false;
bool debug_expected_races = false;
bool debug_benign_races = false;
bool debug_malloc = false;
bool debug_free = false;
bool debug_thread = false;
bool debug_ignore = false;
bool debug_rtn = false;
bool debug_lock = false;
bool debug_wrap = false;
bool debug_ins = false;
bool debug_shadow_stack = false;
bool debug_happens_before = false;
bool debug_cache = false;
bool debug_race_verifier = false;
bool debug_atomic = false;
#define PrintfIf(flag, ...) \
do { if ((flag)) Printf(__VA_ARGS__); } while ((void)0, 0)
// -------- TIL --------------- {{{1
// ThreadSanitizer Internal lock (scoped).
class TIL {
public:
TIL(TSLock *lock, int lock_site, bool need_locking = true) :
lock_(lock),
need_locking_(need_locking) {
DCHECK(lock_);
if (need_locking_ && (TS_SERIALIZED == 0)) {
lock_->Lock();
G_stats->lock_sites[lock_site]++;
}
}
~TIL() {
if (need_locking_ && (TS_SERIALIZED == 0))
lock_->Unlock();
}
private:
TSLock *lock_;
bool need_locking_;
};
static TSLock *ts_lock;
static TSLock *ts_ignore_below_lock;
#ifdef TS_LLVM
void ThreadSanitizerLockAcquire() {
ts_lock->Lock();
}
void ThreadSanitizerLockRelease() {
ts_lock->Unlock();
}
#endif
static INLINE void AssertTILHeld() {
if (TS_SERIALIZED == 0 && DEBUG_MODE) {
ts_lock->AssertHeld();
}
}
// -------- Util ----------------------------- {{{1
// Can't use ANNOTATE_UNPROTECTED_READ, it may get instrumented.
template <class T>
inline T INTERNAL_ANNOTATE_UNPROTECTED_READ(const volatile T &x) {
ANNOTATE_IGNORE_READS_BEGIN();
T res = x;
ANNOTATE_IGNORE_READS_END();
return res;
}
static string RemoveFilePrefix(string str) {
for (size_t i = 0; i < G_flags->file_prefix_to_cut.size(); i++) {
string prefix_to_cut = G_flags->file_prefix_to_cut[i];
size_t pos = str.find(prefix_to_cut);
if (pos != string::npos) {
str = str.substr(pos + prefix_to_cut.size());
}
}
if (str.find("./") == 0) { // remove leading ./
str = str.substr(2);
}
return str;
}
string PcToRtnNameAndFilePos(uintptr_t pc) {
G_stats->pc_to_strings++;
string img_name;
string file_name;
string rtn_name;
int line_no = -1;
PcToStrings(pc, G_flags->demangle, &img_name, &rtn_name,
&file_name, &line_no);
if (G_flags->demangle && !G_flags->full_stack_frames)
rtn_name = NormalizeFunctionName(rtn_name);
file_name = RemoveFilePrefix(file_name);
if (file_name == "") {
return rtn_name + " " + RemoveFilePrefix(img_name);
}
char buff[10];
snprintf(buff, sizeof(buff), "%d", line_no);
return rtn_name + " " + file_name + ":" + buff;
}
// -------- ID ---------------------- {{{1
// We wrap int32_t into ID class and then inherit various ID type from ID.
// This is done in an attempt to implement type safety of IDs, i.e.
// to make it impossible to make implicit cast from one ID type to another.
class ID {
public:
typedef int32_t T;
explicit ID(T id) : id_(id) {}
ID(const ID &id) : id_(id.id_) {}
INLINE bool operator == (const ID &id) const { return id_ == id.id_; }
bool operator != (const ID &id) const { return id_ != id.id_; }
bool operator < (const ID &id) const { return id_ < id.id_; }
bool operator > (const ID &id) const { return id_ > id.id_; }
bool operator >= (const ID &id) const { return id_ >= id.id_; }
bool operator <= (const ID &id) const { return id_ <= id.id_; }
bool IsValid() const { return id_ >= 0; }
const ID &operator = (const ID &id) {
this->id_ = id.id_;
return *this;
}
T raw() const { return id_; }
private:
T id_;
};
// Thread ID.
// id >= 0
class TID: public ID {
public:
static const int32_t kInvalidTID;
explicit TID(T id) : ID(id) {}
TID() : ID(kInvalidTID) {}
bool valid() const { return raw() >= 0; }
};
const int32_t TID::kInvalidTID = -1;
// Segment ID.
// id > 0 && id < kMaxSID
class SID: public ID {
public:
explicit SID(T id) : ID(id) {}
SID() : ID(0) {}
bool valid() const { return raw() > 0 && raw() < kMaxSID; }
};
// Lock ID.
// id > 0 && id < kMaxLID
class LID: public ID {
public:
explicit LID(T id) : ID(id) {}
LID() : ID(0) {}
bool valid() const { return raw() > 0 && raw() < kMaxLID; }
};
// LockSet ID.
// Empty lockset: id == 0
// Singleton: id > 0 (id == Lock's id)
// Tuple: id < 0
class LSID: public ID {
public:
explicit LSID(T id) : ID(id) {}
LSID() : ID(INT_MAX) {}
bool valid() const {
return raw() < kMaxLID && raw() > -(kMaxLID);
}
bool IsEmpty() const { return raw() == 0; }
bool IsSingleton() const { return raw() > 0; }
LID GetSingleton() const { return LID(raw()); }
};
// SegmentSet ID.
// Empty SegmentSet: id == 0
// Singleton: id > 0 (id == Segment's id)
// Tuple: id < 0
class SSID: public ID {
public:
explicit SSID(T id) : ID(id) {}
explicit SSID(SID sid) : ID(sid.raw()) {}
SSID(): ID(INT_MAX) {}
bool valid() const {
return raw() != 0 && raw() < kMaxSID && raw() > -kMaxSID;
}
bool IsValidOrEmpty() { return raw() < kMaxSID && raw() > -kMaxSID; }
bool IsEmpty() const { return raw() == 0; }
bool IsSingleton() const {return raw() > 0; }
bool IsTuple() const {return raw() < 0; }
SID GetSingleton() const {
DCHECK(IsSingleton());
return SID(raw());
}
// TODO(timurrrr): need to start SegmentSetArray indices from 1
// to avoid "int ???() { return -raw() - 1; }"
};
// -------- Colors ----------------------------- {{{1
// Colors for ansi terminals and for html.
const char *c_bold = "";
const char *c_red = "";
const char *c_green = "";
const char *c_magenta = "";
const char *c_cyan = "";
const char *c_blue = "";
const char *c_yellow = "";
const char *c_default = "";
// -------- Forward decls ------ {{{1
static void ForgetAllStateAndStartOver(TSanThread *thr, const char *reason);
static void FlushStateIfOutOfSegments(TSanThread *thr);
static int32_t raw_tid(TSanThread *t);
// -------- Simple Cache ------ {{{1
#include "ts_simple_cache.h"
// -------- PairCache & IntPairToIntCache ------ {{{1
template <typename A, typename B, typename Ret,
int kHtableSize, int kArraySize = 8>
class PairCache {
public:
PairCache() {
CHECK(kHtableSize >= 0);
CHECK(sizeof(Entry) == sizeof(A) + sizeof(B) + sizeof(Ret));
Flush();
}
void Flush() {
memset(this, 0, sizeof(*this));
// Change the first hashtable entry so it doesn't match (0,0) on Lookup.
if (kHtableSize != 0)
memset(&htable_[0], 1, sizeof(Entry));
// Any Lookup should fail now.
for (int i = 0; i < kHtableSize; i++) {
Ret tmp;
DCHECK(!Lookup(htable_[i].a, htable_[i].b, &tmp));
}
CHECK(array_pos_ == 0);
CHECK(array_filled_ == false);
}
void Insert(A a, B b, Ret v) {
// fill the hash table
if (kHtableSize != 0) {
uint32_t idx = compute_idx(a, b);
htable_[idx].Fill(a, b, v);
}
// fill the array
Ret dummy;
if (kArraySize != 0 && !ArrayLookup(a, b, &dummy)) {
array_[array_pos_ % kArraySize].Fill(a, b, v);
array_pos_ = (array_pos_ + 1) % kArraySize;
if (array_pos_ > kArraySize)
array_filled_ = true;
}
}
INLINE bool Lookup(A a, B b, Ret *v) {
// check the array
if (kArraySize != 0 && ArrayLookup(a, b, v)) {
G_stats->ls_cache_fast++;
return true;
}
// check the hash table.
if (kHtableSize != 0) {
uint32_t idx = compute_idx(a, b);
Entry & prev_e = htable_[idx];
if (prev_e.Match(a, b)) {
*v = prev_e.v;
return true;
}
}
return false;
}
private:
struct Entry {
A a;
B b;
Ret v;
void Fill(A a, B b, Ret v) {
this->a = a;
this->b = b;
this->v = v;
}
bool Match(A a, B b) const {
return this->a == a && this->b == b;
}
};
INLINE bool ArrayLookup(A a, B b, Ret *v) {
for (int i = 0; i < (array_filled_ ? kArraySize : array_pos_); i++) {
Entry & entry = array_[i];
if (entry.Match(a, b)) {
*v = entry.v;
return true;
}
}
return false;
}
uint32_t compute_idx(A a, B b) {
if (kHtableSize == 0)
return 0;
else
return combine2(a, b) % kHtableSize;
}
static uint32_t combine2(int a, int b) {
return (a << 16) ^ b;
}
static uint32_t combine2(SSID a, SID b) {
return combine2(a.raw(), b.raw());
}
Entry htable_[kHtableSize];
Entry array_[kArraySize];
// array_pos_ - next element to write to the array_ (mod kArraySize)
// array_filled_ - set to true once we write the last element of the array
int array_pos_;
bool array_filled_;
};
template<int kHtableSize, int kArraySize = 8>
class IntPairToIntCache
: public PairCache<int, int, int, kHtableSize, kArraySize> {};
// -------- FreeList --------------- {{{1
class FreeList {
public:
FreeList(int obj_size, int chunk_size)
: list_(0),
obj_size_(obj_size),
chunk_size_(chunk_size) {
CHECK_GE(obj_size_, static_cast<int>(sizeof(NULL)));
CHECK((obj_size_ % sizeof(NULL)) == 0);
CHECK_GE(chunk_size_, 1);
}
void *Allocate() {
if (!list_)
AllocateNewChunk();
CHECK(list_);
List *head = list_;
list_ = list_->next;
return reinterpret_cast<void*>(head);
}
void Deallocate(void *ptr) {
if (DEBUG_MODE) {
memset(ptr, 0xac, obj_size_);
}
List *new_head = reinterpret_cast<List*>(ptr);
new_head->next = list_;
list_ = new_head;
}
private:
void AllocateNewChunk() {
CHECK(list_ == NULL);
uint8_t *new_mem = new uint8_t[obj_size_ * chunk_size_];
if (DEBUG_MODE) {
memset(new_mem, 0xab, obj_size_ * chunk_size_);
}
for (int i = 0; i < chunk_size_; i++) {
List *new_head = reinterpret_cast<List*>(new_mem + obj_size_ * i);
new_head->next = list_;
list_ = new_head;
}
}
struct List {
struct List *next;
};
List *list_;
const int obj_size_;
const int chunk_size_;
};
// -------- StackTrace -------------- {{{1
class StackTraceFreeList {
public:
uintptr_t *GetNewMemForStackTrace(size_t capacity) {
DCHECK(capacity <= (size_t)G_flags->num_callers);
return reinterpret_cast<uintptr_t*>(free_lists_[capacity]->Allocate());
}
void TakeStackTraceBack(uintptr_t *mem, size_t capacity) {
DCHECK(capacity <= (size_t)G_flags->num_callers);
free_lists_[capacity]->Deallocate(mem);
}
StackTraceFreeList() {
size_t n = G_flags->num_callers + 1;
free_lists_ = new FreeList *[n];
free_lists_[0] = NULL;
for (size_t i = 1; i < n; i++) {
free_lists_[i] = new FreeList((i+2) * sizeof(uintptr_t), 1024);
}
}
private:
FreeList **free_lists_; // Array of G_flags->num_callers lists.
};
static StackTraceFreeList *g_stack_trace_free_list;
class StackTrace {
public:
static StackTrace *CreateNewEmptyStackTrace(size_t size,
size_t capacity = 0) {
ScopedMallocCostCenter cc("StackTrace::CreateNewEmptyStackTrace()");
DCHECK(g_stack_trace_free_list);
DCHECK(size != 0);
if (capacity == 0)
capacity = size;
uintptr_t *mem = g_stack_trace_free_list->GetNewMemForStackTrace(capacity);
DCHECK(mem);
StackTrace *res = new(mem) StackTrace(size, capacity);
return res;
}
static void Delete(StackTrace *trace) {
if (!trace) return;
DCHECK(g_stack_trace_free_list);
g_stack_trace_free_list->TakeStackTraceBack(
reinterpret_cast<uintptr_t*>(trace), trace->capacity());
}
size_t size() const { return size_; }
size_t capacity() const { return capacity_; }
void set_size(size_t size) {
CHECK(size <= capacity());
size_ = size;
}
void Set(size_t i, uintptr_t pc) {
arr_[i] = pc;
}
uintptr_t Get(size_t i) const {
return arr_[i];
}
static bool CutStackBelowFunc(const string func_name) {
for (size_t i = 0; i < G_flags->cut_stack_below.size(); i++) {
if (StringMatch(G_flags->cut_stack_below[i], func_name)) {
return true;
}
}
return false;
}
static string EmbeddedStackTraceToString(const uintptr_t *emb_trace, size_t n,
const char *indent = " ") {
string res = "";
const int kBuffSize = 10000;
char *buff = new char [kBuffSize];
for (size_t i = 0; i < n; i++) {
if (!emb_trace[i]) break;
string rtn_and_file = PcToRtnNameAndFilePos(emb_trace[i]);
if (rtn_and_file.find("(below main) ") == 0 ||
rtn_and_file.find("ThreadSanitizerStartThread ") == 0)
break;
if (i == 0) res += c_bold;
if (G_flags->show_pc) {
snprintf(buff, kBuffSize, "%s#%-2d %p: ",
indent, static_cast<int>(i),
reinterpret_cast<void*>(emb_trace[i]));
} else {
snprintf(buff, kBuffSize, "%s#%-2d ", indent, static_cast<int>(i));
}
res += buff;
res += rtn_and_file;
if (i == 0) res += c_default;
res += "\n";
// don't print after main ...
if (rtn_and_file.find("main ") == 0)
break;
// ... and after some default functions (see ThreadSanitizerParseFlags())
// and some more functions specified via command line flag.
string rtn = NormalizeFunctionName(PcToRtnName(emb_trace[i], true));
if (CutStackBelowFunc(rtn))
break;
}
delete [] buff;
return res;
}
string ToString(const char *indent = " ") const {
if (!this) return "NO STACK TRACE\n";
if (size() == 0) return "EMPTY STACK TRACE\n";
return EmbeddedStackTraceToString(arr_, size(), indent);
}
void PrintRaw() const {
for (size_t i = 0; i < size(); i++) {
Printf("%p ", arr_[i]);
}
Printf("\n");
}
static bool Equals(const StackTrace *t1, const StackTrace *t2) {
if (t1->size_ != t2->size_) return false;
for (size_t i = 0; i < t1->size_; i++) {
if (t1->arr_[i] != t2->arr_[i]) return false;
}
return true;
}
struct Less {
bool operator() (const StackTrace *t1, const StackTrace *t2) const {
size_t size = min(t1->size_, t2->size_);
for (size_t i = 0; i < size; i++) {
if (t1->arr_[i] != t2->arr_[i]) {
return (t1->arr_[i] < t2->arr_[i]);
}
}
return t1->size_ < t2->size_;
}
};
private:
StackTrace(size_t size, size_t capacity)
: size_(size),
capacity_(capacity) {
}
~StackTrace() {}
size_t size_;
size_t capacity_;
uintptr_t arr_[];
};
// -------- Lock -------------------- {{{1
const char *kLockAllocCC = "kLockAllocCC";
class Lock {
public:
static Lock *Create(uintptr_t lock_addr) {
ScopedMallocCostCenter cc("LockLookup");
// Printf("Lock::Create: %p\n", lock_addr);
// Destroy(lock_addr);
// CHECK(Lookup(lock_addr) == NULL);
Lock *res = LookupOrCreate(lock_addr);
res->rd_held_ = 0;
res->wr_held_ = 0;
res->is_pure_happens_before_ = G_flags->pure_happens_before;
res->last_lock_site_ = NULL;
return res;
}
static void Destroy(uintptr_t lock_addr) {
// Printf("Lock::Destroy: %p\n", lock_addr);
// map_.erase(lock_addr);
}
static NOINLINE Lock *LookupOrCreate(uintptr_t lock_addr) {
ScopedMallocCostCenter cc("LockLookup");
Lock **lock = &(*map_)[lock_addr];
if (*lock == NULL) {
// Printf("Lock::LookupOrCreate: %p\n", lock_addr);
ScopedMallocCostCenter cc_lock("new Lock");
*lock = new Lock(lock_addr, map_->size());
}
return *lock;
}
static NOINLINE Lock *Lookup(uintptr_t lock_addr) {
ScopedMallocCostCenter cc("LockLookup");
Map::iterator it = map_->find(lock_addr);
if (it == map_->end()) return NULL;
return it->second;
}
int rd_held() const { return rd_held_; }
int wr_held() const { return wr_held_; }
uintptr_t lock_addr() const { return lock_addr_; }
LID lid() const { return lid_; }
bool is_pure_happens_before() const { return is_pure_happens_before_; }
// When a lock is pure happens-before, we need to create hb arcs
// between all Unlock/Lock pairs except RdUnlock/RdLock.
// For that purpose have two IDs on which we signal/wait.
// One id is the lock_addr itself, the second id is derived
// from lock_addr.
uintptr_t wr_signal_addr() const { return lock_addr(); }
uintptr_t rd_signal_addr() const { return lock_addr() + 1; }
void set_is_pure_happens_before(bool x) { is_pure_happens_before_ = x; }
void WrLock(TID tid, StackTrace *lock_site) {
CHECK(!rd_held_);
if (wr_held_ == 0) {
thread_holding_me_in_write_mode_ = tid;
} else {
CHECK(thread_holding_me_in_write_mode_ == tid);
}
wr_held_++;
StackTrace::Delete(last_lock_site_);
last_lock_site_ = lock_site;
}
void WrUnlock() {
CHECK(!rd_held_);
CHECK(wr_held_ > 0);
wr_held_--;
}
void RdLock(StackTrace *lock_site) {
CHECK(!wr_held_);
rd_held_++;
StackTrace::Delete(last_lock_site_);
last_lock_site_ = lock_site;
}
void RdUnlock() {
CHECK(!wr_held_);
CHECK(rd_held_);
rd_held_--;
}
void set_name(const char *name) { name_ = name; }
const char *name() const { return name_; }
string ToString() const {
string res;
char buff[100];
snprintf(buff, sizeof(buff), "L%d", lid_.raw());
// do we need to print the address?
// reinterpret_cast<void*>(lock_addr()));
res = buff;
if (name()) {
res += string(" ") + name();
}
return res;
}
static Lock *LIDtoLock(LID lid) {
// slow, but needed only for reports.
for (Map::iterator it = map_->begin(); it != map_->end(); ++it) {
Lock *l = it->second;
if (l->lid_ == lid) {
return l;
}
}
return NULL;
}
static string ToString(LID lid) {
Lock *lock = LIDtoLock(lid);
CHECK(lock);
return lock->ToString();
}
static void ReportLockWithOrWithoutContext(LID lid, bool with_context) {
if (!with_context) {
Report(" L%d\n", lid.raw());
return;
}
Lock *lock = LIDtoLock(lid);
CHECK(lock);
if (lock->last_lock_site_) {
Report(" %s (%p)\n%s",
lock->ToString().c_str(),
lock->lock_addr_,
lock->last_lock_site_->ToString().c_str());
} else {
Report(" %s. This lock was probably destroyed"
" w/o calling Unlock()\n", lock->ToString().c_str());
}
}
static void InitClassMembers() {
map_ = new Lock::Map;
}
private:
Lock(uintptr_t lock_addr, int32_t lid)
: lock_addr_(lock_addr),
lid_(lid),
rd_held_(0),
wr_held_(0),
is_pure_happens_before_(G_flags->pure_happens_before),
last_lock_site_(0),
name_(NULL) {
}
// Data members
uintptr_t lock_addr_;
LID lid_;
int rd_held_;
int wr_held_;
bool is_pure_happens_before_;
StackTrace *last_lock_site_;
const char *name_;
TID thread_holding_me_in_write_mode_;
// Static members
typedef map<uintptr_t, Lock*> Map;
static Map *map_;
};
Lock::Map *Lock::map_;
// Returns a string like "L123,L234".
static string SetOfLocksToString(const set<LID> &locks) {
string res;
for (set<LID>::const_iterator it = locks.begin();
it != locks.end(); ++it) {
LID lid = *it;
char buff[100];
snprintf(buff, sizeof(buff), "L%d", lid.raw());
if (it != locks.begin())
res += ", ";
res += buff;
}
return res;
}
// -------- FixedArray--------------- {{{1
template <typename T, size_t SizeLimit = 1024>
class FixedArray {
public:
explicit INLINE FixedArray(size_t array_size)
: size_(array_size),
array_((array_size <= SizeLimit
? alloc_space_
: new T[array_size])) { }
~FixedArray() {
if (array_ != alloc_space_) {
delete[] array_;
}
}
T* begin() { return array_; }
T& operator[](int i) { return array_[i]; }
private:
const size_t size_;
T* array_;
T alloc_space_[SizeLimit];
};
// -------- LockSet ----------------- {{{1
class LockSet {
public:
NOINLINE static LSID Add(LSID lsid, Lock *lock) {
ScopedMallocCostCenter cc("LockSetAdd");
LID lid = lock->lid();
if (lsid.IsEmpty()) {
// adding to an empty lock set
G_stats->ls_add_to_empty++;
return LSID(lid.raw());
}
int cache_res;
if (ls_add_cache_->Lookup(lsid.raw(), lid.raw(), &cache_res)) {
G_stats->ls_add_cache_hit++;
return LSID(cache_res);
}
LSID res;
if (lsid.IsSingleton()) {
LSSet set(lsid.GetSingleton(), lid);
G_stats->ls_add_to_singleton++;
res = ComputeId(set);
} else {
LSSet set(Get(lsid), lid);
G_stats->ls_add_to_multi++;
res = ComputeId(set);
}
ls_add_cache_->Insert(lsid.raw(), lid.raw(), res.raw());
return res;
}
// If lock is present in lsid, set new_lsid to (lsid \ lock) and return true.
// Otherwise set new_lsid to lsid and return false.
NOINLINE static bool Remove(LSID lsid, Lock *lock, LSID *new_lsid) {
*new_lsid = lsid;
if (lsid.IsEmpty()) return false;
LID lid = lock->lid();
if (lsid.IsSingleton()) {
// removing the only lock -> LSID(0)
if (lsid.GetSingleton() != lid) return false;
G_stats->ls_remove_from_singleton++;
*new_lsid = LSID(0);
return true;
}
int cache_res;
if (ls_rem_cache_->Lookup(lsid.raw(), lid.raw(), &cache_res)) {
G_stats->ls_rem_cache_hit++;
*new_lsid = LSID(cache_res);
return true;
}
LSSet &prev_set = Get(lsid);
if (!prev_set.has(lid)) return false;
LSSet set(prev_set, LSSet::REMOVE, lid);
CHECK(set.size() == prev_set.size() - 1);
G_stats->ls_remove_from_multi++;
LSID res = ComputeId(set);
ls_rem_cache_->Insert(lsid.raw(), lid.raw(), res.raw());
*new_lsid = res;
return true;
}
NOINLINE static bool IntersectionIsEmpty(LSID lsid1, LSID lsid2) {
// at least one empty
if (lsid1.IsEmpty() || lsid2.IsEmpty())
return true; // empty
// both singletons
if (lsid1.IsSingleton() && lsid2.IsSingleton()) {
return lsid1 != lsid2;
}
// first is singleton, second is not
if (lsid1.IsSingleton()) {
const LSSet &set2 = Get(lsid2);
return set2.has(LID(lsid1.raw())) == false;
}
// second is singleton, first is not
if (lsid2.IsSingleton()) {
const LSSet &set1 = Get(lsid1);
return set1.has(LID(lsid2.raw())) == false;
}
// LockSets are equal and not empty
if (lsid1 == lsid2)
return false;
// both are not singletons - slow path.
bool ret = true,
cache_hit = false;
DCHECK(lsid2.raw() < 0);
if (ls_intersection_cache_->Lookup(lsid1.raw(), -lsid2.raw(), &ret)) {
if (!DEBUG_MODE)
return ret;
cache_hit = true;
}
const LSSet &set1 = Get(lsid1);
const LSSet &set2 = Get(lsid2);
FixedArray<LID> intersection(min(set1.size(), set2.size()));
LID *end = set_intersection(set1.begin(), set1.end(),
set2.begin(), set2.end(),
intersection.begin());
DCHECK(!cache_hit || (ret == (end == intersection.begin())));
ret = (end == intersection.begin());
ls_intersection_cache_->Insert(lsid1.raw(), -lsid2.raw(), ret);
return ret;
}
static bool HasNonPhbLocks(LSID lsid) {
if (lsid.IsEmpty())
return false;
if (lsid.IsSingleton())
return !Lock::LIDtoLock(LID(lsid.raw()))->is_pure_happens_before();
LSSet &set = Get(lsid);
for (LSSet::const_iterator it = set.begin(); it != set.end(); ++it)
if (!Lock::LIDtoLock(*it)->is_pure_happens_before())
return true;
return false;
}
static string ToString(LSID lsid) {
if (lsid.IsEmpty()) {
return "{}";
} else if (lsid.IsSingleton()) {
return "{" + Lock::ToString(lsid.GetSingleton()) + "}";
}
const LSSet &set = Get(lsid);
string res = "{";
for (LSSet::const_iterator it = set.begin(); it != set.end(); ++it) {
if (it != set.begin()) res += ", ";
res += Lock::ToString(*it);
}
res += "}";
return res;
}
static void ReportLockSetWithContexts(LSID lsid,
set<LID> *locks_reported,
const char *descr) {
if (lsid.IsEmpty()) return;
Report("%s%s%s\n", c_green, descr, c_default);
if (lsid.IsSingleton()) {
LID lid = lsid.GetSingleton();
Lock::ReportLockWithOrWithoutContext(lid,
locks_reported->count(lid) == 0);
locks_reported->insert(lid);
} else {
const LSSet &set = Get(lsid);
for (LSSet::const_iterator it = set.begin(); it != set.end(); ++it) {
LID lid = *it;
Lock::ReportLockWithOrWithoutContext(lid,
locks_reported->count(lid) == 0);
locks_reported->insert(lid);
}
}
}
static void AddLocksToSet(LSID lsid, set<LID> *locks) {
if (lsid.IsEmpty()) return;
if (lsid.IsSingleton()) {
locks->insert(lsid.GetSingleton());
} else {
const LSSet &set = Get(lsid);
for (LSSet::const_iterator it = set.begin(); it != set.end(); ++it) {
locks->insert(*it);
}
}
}
static void InitClassMembers() {
map_ = new LockSet::Map;
vec_ = new LockSet::Vec;
ls_add_cache_ = new LSCache;
ls_rem_cache_ = new LSCache;
ls_rem_cache_ = new LSCache;
ls_intersection_cache_ = new LSIntersectionCache;
}
private:
// No instances are allowed.
LockSet() { }
typedef DenseMultimap<LID, 3> LSSet;
static LSSet &Get(LSID lsid) {
ScopedMallocCostCenter cc(__FUNCTION__);
int idx = -lsid.raw() - 1;
DCHECK(idx >= 0);
DCHECK(idx < static_cast<int>(vec_->size()));
return (*vec_)[idx];
}
static LSID ComputeId(const LSSet &set) {
CHECK(set.size() > 0);
if (set.size() == 1) {
// signleton lock set has lsid == lid.
return LSID(set.begin()->raw());
}
DCHECK(map_);
DCHECK(vec_);
// multiple locks.
ScopedMallocCostCenter cc("LockSet::ComputeId");
int32_t *id = &(*map_)[set];
if (*id == 0) {
vec_->push_back(set);
*id = map_->size();
if (set.size() == 2) G_stats->ls_size_2++;
else if (set.size() == 3) G_stats->ls_size_3++;
else if (set.size() == 4) G_stats->ls_size_4++;
else if (set.size() == 5) G_stats->ls_size_5++;
else G_stats->ls_size_other++;
if (*id >= 4096 && ((*id & (*id - 1)) == 0)) {
Report("INFO: %d LockSet IDs have been allocated "
"(2: %ld 3: %ld 4: %ld 5: %ld o: %ld)\n",
*id,
G_stats->ls_size_2, G_stats->ls_size_3,
G_stats->ls_size_4, G_stats->ls_size_5,
G_stats->ls_size_other
);
}
}
return LSID(-*id);
}
typedef map<LSSet, int32_t> Map;
static Map *map_;
static const char *kLockSetVecAllocCC;
typedef vector<LSSet> Vec;
static Vec *vec_;
// static const int kPrimeSizeOfLsCache = 307;
// static const int kPrimeSizeOfLsCache = 499;
static const int kPrimeSizeOfLsCache = 1021;
typedef IntPairToIntCache<kPrimeSizeOfLsCache> LSCache;
static LSCache *ls_add_cache_;
static LSCache *ls_rem_cache_;
static LSCache *ls_int_cache_;
typedef IntPairToBoolCache<kPrimeSizeOfLsCache> LSIntersectionCache;
static LSIntersectionCache *ls_intersection_cache_;
};
LockSet::Map *LockSet::map_;
LockSet::Vec *LockSet::vec_;
const char *LockSet::kLockSetVecAllocCC = "kLockSetVecAllocCC";
LockSet::LSCache *LockSet::ls_add_cache_;
LockSet::LSCache *LockSet::ls_rem_cache_;
LockSet::LSCache *LockSet::ls_int_cache_;
LockSet::LSIntersectionCache *LockSet::ls_intersection_cache_;
static string TwoLockSetsToString(LSID rd_lockset, LSID wr_lockset) {
string res;
if (rd_lockset == wr_lockset) {
res = "L";
res += LockSet::ToString(wr_lockset);
} else {
res = "WR-L";
res += LockSet::ToString(wr_lockset);
res += "/RD-L";
res += LockSet::ToString(rd_lockset);
}
return res;
}
// -------- VTS ------------------ {{{1
class VTS {
public:
static size_t MemoryRequiredForOneVts(size_t size) {
return sizeof(VTS) + size * sizeof(TS);
}
static size_t RoundUpSizeForEfficientUseOfFreeList(size_t size) {
if (size < 32) return size;
if (size < 64) return (size + 7) & ~7;
if (size < 128) return (size + 15) & ~15;
return (size + 31) & ~31;
}
static VTS *Create(size_t size) {
DCHECK(size > 0);
void *mem;
size_t rounded_size = RoundUpSizeForEfficientUseOfFreeList(size);
DCHECK(size <= rounded_size);
if (rounded_size <= kNumberOfFreeLists) {
// Small chunk, use FreeList.
ScopedMallocCostCenter cc("VTS::Create (from free list)");
mem = free_lists_[rounded_size]->Allocate();
G_stats->vts_create_small++;
} else {
// Large chunk, use new/delete instead of FreeList.
ScopedMallocCostCenter cc("VTS::Create (from new[])");
mem = new int8_t[MemoryRequiredForOneVts(size)];
G_stats->vts_create_big++;
}
VTS *res = new(mem) VTS(size);
G_stats->vts_total_create += size;
return res;
}
static void Unref(VTS *vts) {
if (!vts) return;
CHECK_GT(vts->ref_count_, 0);
if (AtomicDecrementRefcount(&vts->ref_count_) == 0) {
size_t size = vts->size_; // can't use vts->size().
size_t rounded_size = RoundUpSizeForEfficientUseOfFreeList(size);
if (rounded_size <= kNumberOfFreeLists) {
free_lists_[rounded_size]->Deallocate(vts);
G_stats->vts_delete_small++;
} else {
G_stats->vts_delete_big++;
delete vts;
}
G_stats->vts_total_delete += rounded_size;
}
}
static VTS *CreateSingleton(TID tid, int32_t clk = 1) {
VTS *res = Create(1);
res->arr_[0].tid = tid.raw();
res->arr_[0].clk = clk;
return res;
}
VTS *Clone() {
G_stats->vts_clone++;
AtomicIncrementRefcount(&ref_count_);
return this;
}
static VTS *CopyAndTick(const VTS *vts, TID id_to_tick) {
CHECK(vts->ref_count_);
VTS *res = Create(vts->size());
bool found = false;
for (size_t i = 0; i < res->size(); i++) {
res->arr_[i] = vts->arr_[i];
if (res->arr_[i].tid == id_to_tick.raw()) {
res->arr_[i].clk++;
found = true;
}
}
CHECK(found);
return res;
}
static VTS *Join(const VTS *vts_a, const VTS *vts_b) {
CHECK(vts_a->ref_count_);
CHECK(vts_b->ref_count_);
FixedArray<TS> result_ts(vts_a->size() + vts_b->size());
TS *t = result_ts.begin();
const TS *a = &vts_a->arr_[0];
const TS *b = &vts_b->arr_[0];
const TS *a_max = a + vts_a->size();
const TS *b_max = b + vts_b->size();
while (a < a_max && b < b_max) {
if (a->tid < b->tid) {
*t = *a;
a++;
t++;
} else if (a->tid > b->tid) {
*t = *b;
b++;
t++;
} else {
if (a->clk >= b->clk) {
*t = *a;
} else {
*t = *b;
}
a++;
b++;
t++;
}
}
while (a < a_max) {
*t = *a;
a++;
t++;
}
while (b < b_max) {
*t = *b;
b++;
t++;
}
VTS *res = VTS::Create(t - result_ts.begin());
for (size_t i = 0; i < res->size(); i++) {
res->arr_[i] = result_ts[i];
}
return res;
}
int32_t clk(TID tid) const {
// TODO(dvyukov): this function is sub-optimal,
// we only need thread's own clock.
for (size_t i = 0; i < size_; i++) {
if (arr_[i].tid == tid.raw()) {
return arr_[i].clk;
}
}
return 0;
}
static INLINE void FlushHBCache() {
hb_cache_->Flush();
}
static INLINE bool HappensBeforeCached(const VTS *vts_a, const VTS *vts_b) {
bool res = false;
if (hb_cache_->Lookup(vts_a->uniq_id_, vts_b->uniq_id_, &res)) {
G_stats->n_vts_hb_cached++;
DCHECK(res == HappensBefore(vts_a, vts_b));
return res;
}
res = HappensBefore(vts_a, vts_b);
hb_cache_->Insert(vts_a->uniq_id_, vts_b->uniq_id_, res);
return res;
}
// return true if vts_a happens-before vts_b.
static NOINLINE bool HappensBefore(const VTS *vts_a, const VTS *vts_b) {
CHECK(vts_a->ref_count_);
CHECK(vts_b->ref_count_);
G_stats->n_vts_hb++;
const TS *a = &vts_a->arr_[0];
const TS *b = &vts_b->arr_[0];
const TS *a_max = a + vts_a->size();
const TS *b_max = b + vts_b->size();
bool a_less_than_b = false;
while (a < a_max && b < b_max) {
if (a->tid < b->tid) {
// a->tid is not present in b.
return false;
} else if (a->tid > b->tid) {
// b->tid is not present in a.
a_less_than_b = true;
b++;
} else {
// this tid is present in both VTSs. Compare clocks.
if (a->clk > b->clk) return false;
if (a->clk < b->clk) a_less_than_b = true;
a++;
b++;
}
}
if (a < a_max) {
// Some tids are present in a and not in b
return false;
}
if (b < b_max) {
return true;
}
return a_less_than_b;
}
size_t size() const {
DCHECK(ref_count_);
return size_;
}
string ToString() const {
DCHECK(ref_count_);
string res = "[";
for (size_t i = 0; i < size(); i++) {
char buff[100];
snprintf(buff, sizeof(buff), "%d:%d;", arr_[i].tid, arr_[i].clk);
if (i) res += " ";
res += buff;
}
return res + "]";
}
void print(const char *name) const {
string str = ToString();
Printf("%s: %s\n", name, str.c_str());
}
static void TestHappensBefore() {
// TODO(kcc): need more tests here...
const char *test_vts[] = {
"[0:1;]",
"[0:4; 2:1;]",
"[0:4; 2:2; 4:1;]",
"[0:4; 3:2; 4:1;]",
"[0:4; 3:2; 4:2;]",
"[0:4; 3:3; 4:1;]",
NULL
};
for (int i = 0; test_vts[i]; i++) {
const VTS *vts1 = Parse(test_vts[i]);
for (int j = 0; test_vts[j]; j++) {
const VTS *vts2 = Parse(test_vts[j]);
bool hb = HappensBefore(vts1, vts2);
Printf("HB = %d\n %s\n %s\n", static_cast<int>(hb),
vts1->ToString().c_str(),
vts2->ToString().c_str());
delete vts2;
}
delete vts1;
}
}
static void Test() {
Printf("VTS::test();\n");
VTS *v1 = CreateSingleton(TID(0));
VTS *v2 = CreateSingleton(TID(1));
VTS *v3 = CreateSingleton(TID(2));
VTS *v4 = CreateSingleton(TID(3));
VTS *v12 = Join(v1, v2);
v12->print("v12");
VTS *v34 = Join(v3, v4);
v34->print("v34");
VTS *x1 = Parse("[0:4; 3:6; 4:2;]");
CHECK(x1);
x1->print("x1");
TestHappensBefore();
}
// Parse VTS string in the form "[0:4; 3:6; 4:2;]".
static VTS *Parse(const char *str) {
#if 1 // TODO(kcc): need sscanf in valgrind
return NULL;
#else
vector<TS> vec;
if (!str) return NULL;
if (str[0] != '[') return NULL;
str++;
int tid = 0, clk = 0;
int consumed = 0;
while (sscanf(str, "%d:%d;%n", &tid, &clk, &consumed) > 0) {
TS ts;
ts.tid = TID(tid);
ts.clk = clk;
vec.push_back(ts);
str += consumed;
// Printf("%d:%d\n", tid, clk);
}
if (*str != ']') return NULL;
VTS *res = Create(vec.size());
for (size_t i = 0; i < vec.size(); i++) {
res->arr_[i] = vec[i];
}
return res;
#endif
}
static void InitClassMembers() {
hb_cache_ = new HBCache;
free_lists_ = new FreeList *[kNumberOfFreeLists+1];
free_lists_[0] = 0;
for (size_t i = 1; i <= kNumberOfFreeLists; i++) {
free_lists_[i] = new FreeList(MemoryRequiredForOneVts(i),
(kNumberOfFreeLists * 4) / i);
}
}
int32_t uniq_id() const { return uniq_id_; }
private:
explicit VTS(size_t size)
: ref_count_(1),
size_(size) {
uniq_id_counter_++;
// If we've got overflow, we are in trouble, need to have 64-bits...
CHECK_GT(uniq_id_counter_, 0);
uniq_id_ = uniq_id_counter_;
}
~VTS() {}
struct TS {
int32_t tid;
int32_t clk;
};
// data members
int32_t ref_count_;
int32_t uniq_id_;
size_t size_;
TS arr_[]; // array of size_ elements.
// static data members
static int32_t uniq_id_counter_;
static const int kCacheSize = 4999; // Has to be prime.
typedef IntPairToBoolCache<kCacheSize> HBCache;
static HBCache *hb_cache_;
static const size_t kNumberOfFreeLists = 512; // Must be power of two.
// static const size_t kNumberOfFreeLists = 64; // Must be power of two.
static FreeList **free_lists_; // Array of kNumberOfFreeLists elements.
};
int32_t VTS::uniq_id_counter_;
VTS::HBCache *VTS::hb_cache_;
FreeList **VTS::free_lists_;
// This class is somewhat similar to VTS,
// but it's mutable, not reference counted and not sorted.
class VectorClock {
public:
VectorClock()
: size_(),
clock_()
{
}
void reset() {
free(clock_);
size_ = 0;
clock_ = NULL;
}
int32_t clock(TID tid) const {
for (size_t i = 0; i != size_; i += 1) {
if (clock_[i].tid == tid.raw()) {
return clock_[i].clk;
}
}
return 0;
}
void update(TID tid, int32_t clk) {
for (size_t i = 0; i != size_; i += 1) {
if (clock_[i].tid == tid.raw()) {
clock_[i].clk = clk;
return;
}
}
size_ += 1;
clock_ = (TS*)realloc(clock_, size_ * sizeof(TS));
clock_[size_ - 1].tid = tid.raw();
clock_[size_ - 1].clk = clk;
}
private:
struct TS {
int32_t tid;
int32_t clk;
};
size_t size_;
TS* clock_;
};
// -------- Mask -------------------- {{{1
// A bit mask (32-bits on 32-bit arch and 64-bits on 64-bit arch).
class Mask {
public:
static const uintptr_t kOne = 1;
static const uintptr_t kNBits = sizeof(uintptr_t) * 8;
static const uintptr_t kNBitsLog = kNBits == 32 ? 5 : 6;
Mask() : m_(0) {}
Mask(const Mask &m) : m_(m.m_) { }
explicit Mask(uintptr_t m) : m_(m) { }
INLINE bool Get(uintptr_t idx) const { return m_ & (kOne << idx); }
INLINE void Set(uintptr_t idx) { m_ |= kOne << idx; }
INLINE void Clear(uintptr_t idx) { m_ &= ~(kOne << idx); }
INLINE bool Empty() const {return m_ == 0; }
// Clear bits in range [a,b) and return old [a,b) range.
INLINE Mask ClearRangeAndReturnOld(uintptr_t a, uintptr_t b) {
DCHECK(a < b);
DCHECK(b <= kNBits);
uintptr_t res;
uintptr_t n_bits_in_mask = (b - a);
if (n_bits_in_mask == kNBits) {
res = m_;
m_ = 0;
} else {
uintptr_t t = (kOne << n_bits_in_mask);
uintptr_t mask = (t - 1) << a;
res = m_ & mask;
m_ &= ~mask;
}
return Mask(res);
}
INLINE void ClearRange(uintptr_t a, uintptr_t b) {
ClearRangeAndReturnOld(a, b);
}
INLINE void SetRange(uintptr_t a, uintptr_t b) {
DCHECK(a < b);
DCHECK(b <= kNBits);
uintptr_t n_bits_in_mask = (b - a);
if (n_bits_in_mask == kNBits) {
m_ = ~0;
} else {
uintptr_t t = (kOne << n_bits_in_mask);
uintptr_t mask = (t - 1) << a;
m_ |= mask;
}
}
INLINE uintptr_t GetRange(uintptr_t a, uintptr_t b) const {
// a bug was fixed here
DCHECK(a < b);
DCHECK(b <= kNBits);
uintptr_t n_bits_in_mask = (b - a);
if (n_bits_in_mask == kNBits) {
return m_;
} else {
uintptr_t t = (kOne << n_bits_in_mask);
uintptr_t mask = (t - 1) << a;
return m_ & mask;
}
}
// Get index of some set bit (asumes mask is non zero).
size_t GetSomeSetBit() {
DCHECK(m_);
size_t ret;
#ifdef __GNUC__
ret = __builtin_ctzl(m_);
#elif defined(_MSC_VER)
unsigned long index;
DCHECK(sizeof(uintptr_t) == 4);
_BitScanReverse(&index, m_);
ret = index;
#else
# error "Unsupported"
#endif
DCHECK(this->Get(ret));
return ret;
}
size_t PopCount() {
#ifdef VGO_linux
return __builtin_popcountl(m_);
#else
CHECK(0);
return 0;
#endif
}
void Subtract(Mask m) { m_ &= ~m.m_; }
void Union(Mask m) { m_ |= m.m_; }
static Mask Intersection(Mask m1, Mask m2) { return Mask(m1.m_ & m2.m_); }
void Clear() { m_ = 0; }
string ToString() const {
char buff[kNBits+1];
for (uintptr_t i = 0; i < kNBits; i++) {
buff[i] = Get(i) ? '1' : '0';
}
buff[kNBits] = 0;
return buff;
}
static void Test() {
Mask m;
m.Set(2);
Printf("%s\n", m.ToString().c_str());
m.ClearRange(0, kNBits);
Printf("%s\n", m.ToString().c_str());
}
private:
uintptr_t m_;
};
// -------- BitSet -------------------{{{1
// Poor man's sparse bit set.
class BitSet {
public:
// Add range [a,b). The range should be within one line (kNBitsLog).
void Add(uintptr_t a, uintptr_t b) {
uintptr_t line = a & ~(Mask::kNBits - 1);
DCHECK(a < b);
DCHECK(a - line < Mask::kNBits);
if (!(b - line <= Mask::kNBits)) {
Printf("XXXXX %p %p %p b-line=%ld size=%ld a-line=%ld\n", a, b, line,
b - line, b - a, a - line);
return;
}
DCHECK(b - line <= Mask::kNBits);
DCHECK(line == ((b - 1) & ~(Mask::kNBits - 1)));
Mask &mask= map_[line];
mask.SetRange(a - line, b - line);
}
bool empty() { return map_.empty(); }
size_t size() {
size_t res = 0;
for (Map::iterator it = map_.begin(); it != map_.end(); ++it) {
res += it->second.PopCount();
}
return res;
}
string ToString() {
char buff[100];
string res;
int lines = 0;
snprintf(buff, sizeof(buff), " %ld lines %ld bits:",
(long)map_.size(), (long)size());
res += buff;
for (Map::iterator it = map_.begin(); it != map_.end(); ++it) {
Mask mask = it->second;
snprintf(buff, sizeof(buff), " l%d (%ld):", lines++, (long)mask.PopCount());
res += buff;
uintptr_t line = it->first;
bool is_in = false;
for (size_t i = 0; i < Mask::kNBits; i++) {
uintptr_t addr = line + i;
if (mask.Get(i)) {
if (!is_in) {
snprintf(buff, sizeof(buff), " [%lx,", (long)addr);
res += buff;
is_in = true;
}
} else {
if (is_in) {
snprintf(buff, sizeof(buff), "%lx);", (long)addr);
res += buff;
is_in = false;
}
}
}
if (is_in) {
snprintf(buff, sizeof(buff), "%lx);", (long)(line + Mask::kNBits));
res += buff;
}
}
return res;
}
void Clear() { map_.clear(); }
private:
typedef map<uintptr_t, Mask> Map;
Map map_;
};
// -------- Segment -------------------{{{1
class Segment {
public:
// for debugging...
static bool ProfileSeg(SID sid) {
// return (sid.raw() % (1 << 14)) == 0;
return false;
}
// non-static methods
VTS *vts() const { return vts_; }
TID tid() const { return TID(tid_); }
LSID lsid(bool is_w) const { return lsid_[is_w]; }
uint32_t lock_era() const { return lock_era_; }
// static methods
static INLINE uintptr_t *embedded_stack_trace(SID sid) {
DCHECK(sid.valid());
DCHECK(kSizeOfHistoryStackTrace > 0);
size_t chunk_idx = (unsigned)sid.raw() / kChunkSizeForStacks;
size_t idx = (unsigned)sid.raw() % kChunkSizeForStacks;
DCHECK(chunk_idx < n_stack_chunks_);
DCHECK(all_stacks_[chunk_idx] != NULL);
return &all_stacks_[chunk_idx][idx * kSizeOfHistoryStackTrace];
}
static void ensure_space_for_stack_trace(SID sid) {
ScopedMallocCostCenter malloc_cc(__FUNCTION__);
DCHECK(sid.valid());
DCHECK(kSizeOfHistoryStackTrace > 0);
size_t chunk_idx = (unsigned)sid.raw() / kChunkSizeForStacks;
DCHECK(chunk_idx < n_stack_chunks_);
if (all_stacks_[chunk_idx])
return;
for (size_t i = 0; i <= chunk_idx; i++) {
if (all_stacks_[i]) continue;
all_stacks_[i] = new uintptr_t[
kChunkSizeForStacks * kSizeOfHistoryStackTrace];
// we don't clear this memory, it will be clreared later lazily.
// We also never delete it because it will be used until the very end.
}
}
static string StackTraceString(SID sid) {
DCHECK(kSizeOfHistoryStackTrace > 0);
return StackTrace::EmbeddedStackTraceToString(
embedded_stack_trace(sid), kSizeOfHistoryStackTrace);
}
// Allocate `n` fresh segments, put SIDs into `fresh_sids`.
static INLINE void AllocateFreshSegments(size_t n, SID *fresh_sids) {
ScopedMallocCostCenter malloc_cc(__FUNCTION__);
size_t i = 0;
size_t n_reusable = min(n, reusable_sids_->size());
// First, allocate from reusable_sids_.
for (; i < n_reusable; i++) {
G_stats->seg_reuse++;
DCHECK(!reusable_sids_->empty());
SID sid = reusable_sids_->back();
reusable_sids_->pop_back();
Segment *seg = GetInternal(sid);
DCHECK(!seg->seg_ref_count_);
DCHECK(!seg->vts());
DCHECK(!seg->tid().valid());
CHECK(sid.valid());
if (ProfileSeg(sid)) {
Printf("Segment: reused SID %d\n", sid.raw());
}
fresh_sids[i] = sid;
}
// allocate the rest from new sids.
for (; i < n; i++) {
G_stats->seg_create++;
CHECK(n_segments_ < kMaxSID);
Segment *seg = GetSegmentByIndex(n_segments_);
// This VTS may not be empty due to ForgetAllState().
VTS::Unref(seg->vts_);
seg->vts_ = 0;
seg->seg_ref_count_ = 0;
if (ProfileSeg(SID(n_segments_))) {
Printf("Segment: allocated SID %d\n", n_segments_);
}
SID sid = fresh_sids[i] = SID(n_segments_);
if (kSizeOfHistoryStackTrace > 0) {
ensure_space_for_stack_trace(sid);
}
n_segments_++;
}
}
// Initialize the contents of the given segment.
static INLINE void SetupFreshSid(SID sid, TID tid, VTS *vts,
LSID rd_lockset, LSID wr_lockset) {
DCHECK(vts);
DCHECK(tid.valid());
DCHECK(sid.valid());
Segment *seg = GetInternal(sid);
DCHECK(seg);
DCHECK(seg->seg_ref_count_ == 0);
seg->seg_ref_count_ = 0;
seg->tid_ = tid;
seg->lsid_[0] = rd_lockset;
seg->lsid_[1] = wr_lockset;
seg->vts_ = vts;
seg->lock_era_ = g_lock_era;
if (kSizeOfHistoryStackTrace) {
embedded_stack_trace(sid)[0] = 0;
}
}
static INLINE SID AddNewSegment(TID tid, VTS *vts,
LSID rd_lockset, LSID wr_lockset) {
ScopedMallocCostCenter malloc_cc("Segment::AddNewSegment()");
SID sid;
AllocateFreshSegments(1, &sid);
SetupFreshSid(sid, tid, vts, rd_lockset, wr_lockset);
return sid;
}
static bool Alive(SID sid) {
Segment *seg = GetInternal(sid);
return seg->vts() != NULL;
}
static void AssertLive(SID sid, int line) {
if (DEBUG_MODE) {
if (!(sid.raw() < INTERNAL_ANNOTATE_UNPROTECTED_READ(n_segments_))) {
Printf("Segment::AssertLive: failed on sid=%d n_segments = %dline=%d\n",
sid.raw(), n_segments_, line);
}
Segment *seg = GetInternal(sid);
if (!seg->vts()) {
Printf("Segment::AssertLive: failed on sid=%d line=%d\n",
sid.raw(), line);
}
DCHECK(seg->vts());
DCHECK(seg->tid().valid());
}
}
static INLINE Segment *Get(SID sid) {
AssertLive(sid, __LINE__);
Segment *res = GetInternal(sid);
DCHECK(res->vts());
DCHECK(res->tid().valid());
return res;
}
static INLINE void RecycleOneFreshSid(SID sid) {
Segment *seg = GetInternal(sid);
seg->tid_ = TID();
seg->vts_ = NULL;
reusable_sids_->push_back(sid);
if (ProfileSeg(sid)) {
Printf("Segment: recycled SID %d\n", sid.raw());
}
}
static bool RecycleOneSid(SID sid) {
ScopedMallocCostCenter malloc_cc("Segment::RecycleOneSid()");
Segment *seg = GetInternal(sid);
DCHECK(seg->seg_ref_count_ == 0);
DCHECK(sid.raw() < n_segments_);
if (!seg->vts()) return false; // Already recycled.
VTS::Unref(seg->vts_);
RecycleOneFreshSid(sid);
return true;
}
int32_t ref_count() const {
return INTERNAL_ANNOTATE_UNPROTECTED_READ(seg_ref_count_);
}
static void INLINE Ref(SID sid, const char *where) {
Segment *seg = GetInternal(sid);
if (ProfileSeg(sid)) {
Printf("SegRef : %d ref=%d %s; tid=%d\n", sid.raw(),
seg->seg_ref_count_, where, seg->tid().raw());
}
DCHECK(seg->seg_ref_count_ >= 0);
AtomicIncrementRefcount(&seg->seg_ref_count_);
}
static INLINE intptr_t UnrefNoRecycle(SID sid, const char *where) {
Segment *seg = GetInternal(sid);
if (ProfileSeg(sid)) {
Printf("SegUnref : %d ref=%d %s\n", sid.raw(), seg->seg_ref_count_, where);
}
DCHECK(seg->seg_ref_count_ > 0);
return AtomicDecrementRefcount(&seg->seg_ref_count_);
}
static void INLINE Unref(SID sid, const char *where) {
if (UnrefNoRecycle(sid, where) == 0) {
RecycleOneSid(sid);
}
}
static void ForgetAllState() {
n_segments_ = 1;
reusable_sids_->clear();
// vts_'es will be freed in AddNewSegment.
}
static string ToString(SID sid) {
char buff[100];
snprintf(buff, sizeof(buff), "T%d/S%d", Get(sid)->tid().raw(), sid.raw());
return buff;
}
static string ToStringTidOnly(SID sid) {
char buff[100];
snprintf(buff, sizeof(buff), "T%d", Get(sid)->tid().raw());
return buff;
}
static string ToStringWithLocks(SID sid) {
char buff[100];
Segment *seg = Get(sid);
snprintf(buff, sizeof(buff), "T%d/S%d ", seg->tid().raw(), sid.raw());
string res = buff;
res += TwoLockSetsToString(seg->lsid(false), seg->lsid(true));
return res;
}
static bool INLINE HappensBeforeOrSameThread(SID a, SID b) {
if (a == b) return true;
if (Get(a)->tid() == Get(b)->tid()) return true;
return HappensBefore(a, b);
}
static bool INLINE HappensBefore(SID a, SID b) {
DCHECK(a != b);
G_stats->n_seg_hb++;
bool res = false;
const Segment *seg_a = Get(a);
const Segment *seg_b = Get(b);
DCHECK(seg_a->tid() != seg_b->tid());
const VTS *vts_a = seg_a->vts();
const VTS *vts_b = seg_b->vts();
res = VTS::HappensBeforeCached(vts_a, vts_b);
#if 0
if (DEBUG_MODE) {
Printf("HB = %d\n %s\n %s\n", res,
vts_a->ToString().c_str(), vts_b->ToString().c_str());
}
#endif
return res;
}
static int32_t NumberOfSegments() { return n_segments_; }
static void ShowSegmentStats() {
Printf("Segment::ShowSegmentStats:\n");
Printf("n_segments_: %d\n", n_segments_);
Printf("reusable_sids_: %ld\n", reusable_sids_->size());
map<int, int> ref_to_freq_map;
for (int i = 1; i < n_segments_; i++) {
Segment *seg = GetInternal(SID(i));
int32_t refcount = seg->seg_ref_count_;
if (refcount > 10) refcount = 10;
ref_to_freq_map[refcount]++;
}
for (map<int, int>::iterator it = ref_to_freq_map.begin();
it != ref_to_freq_map.end(); ++it) {
Printf("ref %d => freq %d\n", it->first, it->second);
}
}
static void InitClassMembers() {
if (G_flags->keep_history == 0)
kSizeOfHistoryStackTrace = 0;
Report("INFO: Allocating %ldMb (%ld * %ldM) for Segments.\n",
(sizeof(Segment) * kMaxSID) >> 20,
sizeof(Segment), kMaxSID >> 20);
if (kSizeOfHistoryStackTrace) {
Report("INFO: Will allocate up to %ldMb for 'previous' stack traces.\n",
(kSizeOfHistoryStackTrace * sizeof(uintptr_t) * kMaxSID) >> 20);
}
all_segments_ = new Segment[kMaxSID];
// initialization all segments to 0.
memset(all_segments_, 0, kMaxSID * sizeof(Segment));
// initialize all_segments_[0] with garbage
memset(all_segments_, -1, sizeof(Segment));
if (kSizeOfHistoryStackTrace > 0) {
n_stack_chunks_ = kMaxSID / kChunkSizeForStacks;
if (n_stack_chunks_ * kChunkSizeForStacks < (size_t)kMaxSID)
n_stack_chunks_++;
all_stacks_ = new uintptr_t*[n_stack_chunks_];
memset(all_stacks_, 0, sizeof(uintptr_t*) * n_stack_chunks_);
}
n_segments_ = 1;
reusable_sids_ = new vector<SID>;
}
private:
static INLINE Segment *GetSegmentByIndex(int32_t index) {
return &all_segments_[index];
}
static INLINE Segment *GetInternal(SID sid) {
DCHECK(sid.valid());
DCHECK(sid.raw() < INTERNAL_ANNOTATE_UNPROTECTED_READ(n_segments_));
Segment *res = GetSegmentByIndex(sid.raw());
return res;
}
// Data members.
int32_t seg_ref_count_;
LSID lsid_[2];
TID tid_;
uint32_t lock_era_;
VTS *vts_;
// static class members.
// One large array of segments. The size is set by a command line (--max-sid)
// and never changes. Once we are out of vacant segments, we flush the state.
static Segment *all_segments_;
// We store stack traces separately because their size is unknown
// at compile time and because they are needed less often.
// The stacks are stored as an array of chunks, instead of one array,
// so that for small tests we do not require too much RAM.
// We don't use vector<> or another resizable array to avoid expensive
// resizing.
enum { kChunkSizeForStacks = DEBUG_MODE ? 512 : 1 * 1024 * 1024 };
static uintptr_t **all_stacks_;
static size_t n_stack_chunks_;
static int32_t n_segments_;
static vector<SID> *reusable_sids_;
};
Segment *Segment::all_segments_;
uintptr_t **Segment::all_stacks_;
size_t Segment::n_stack_chunks_;
int32_t Segment::n_segments_;
vector<SID> *Segment::reusable_sids_;
// -------- SegmentSet -------------- {{{1
class SegmentSet {
public:
static NOINLINE SSID AddSegmentToSS(SSID old_ssid, SID new_sid);
static NOINLINE SSID RemoveSegmentFromSS(SSID old_ssid, SID sid_to_remove);
static INLINE SSID AddSegmentToTupleSS(SSID ssid, SID new_sid);
static INLINE SSID RemoveSegmentFromTupleSS(SSID old_ssid, SID sid_to_remove);
SSID ComputeSSID() {
SSID res = map_->GetIdOrZero(this);
CHECK_NE(res.raw(), 0);
return res;
}
int ref_count() const { return ref_count_; }
static void AssertLive(SSID ssid, int line) {
DCHECK(ssid.valid());
if (DEBUG_MODE) {
if (ssid.IsSingleton()) {
Segment::AssertLive(ssid.GetSingleton(), line);
} else {
DCHECK(ssid.IsTuple());
int idx = -ssid.raw()-1;
DCHECK(idx < static_cast<int>(vec_->size()));
DCHECK(idx >= 0);
SegmentSet *res = (*vec_)[idx];
DCHECK(res);
DCHECK(res->ref_count_ >= 0);
res->Validate(line);
if (!res) {
Printf("SegmentSet::AssertLive failed at line %d (ssid=%d)\n",
line, ssid.raw());
DCHECK(0);
}
}
}
}
static SegmentSet *Get(SSID ssid) {
DCHECK(ssid.valid());
DCHECK(!ssid.IsSingleton());
int idx = -ssid.raw()-1;
ANNOTATE_IGNORE_READS_BEGIN();
DCHECK(idx < static_cast<int>(vec_->size()) && idx >= 0);
ANNOTATE_IGNORE_READS_END();
SegmentSet *res = (*vec_)[idx];
DCHECK(res);
DCHECK(res->size() >= 2);
return res;
}
void RecycleOneSegmentSet(SSID ssid) {
DCHECK(ref_count_ == 0);
DCHECK(ssid.valid());
DCHECK(!ssid.IsSingleton());
int idx = -ssid.raw()-1;
DCHECK(idx < static_cast<int>(vec_->size()) && idx >= 0);
CHECK((*vec_)[idx] == this);
// Printf("SegmentSet::RecycleOneSegmentSet: %d\n", ssid.raw());
//
// Recycle segments
for (int i = 0; i < kMaxSegmentSetSize; i++) {
SID sid = this->GetSID(i);
if (sid.raw() == 0) break;
Segment::Unref(sid, "SegmentSet::Recycle");
}
ref_count_ = -1;
map_->Erase(this);
ready_to_be_reused_->push_back(ssid);
G_stats->ss_recycle++;
}
static void INLINE Ref(SSID ssid, const char *where) {
AssertTILHeld(); // The reference counting logic below is not thread-safe
DCHECK(ssid.valid());
if (ssid.IsSingleton()) {
Segment::Ref(ssid.GetSingleton(), where);
} else {
SegmentSet *sset = Get(ssid);
// Printf("SSRef : %d ref=%d %s\n", ssid.raw(), sset->ref_count_, where);
DCHECK(sset->ref_count_ >= 0);
sset->ref_count_++;
}
}
static void INLINE Unref(SSID ssid, const char *where) {
AssertTILHeld(); // The reference counting logic below is not thread-safe
DCHECK(ssid.valid());
if (ssid.IsSingleton()) {
Segment::Unref(ssid.GetSingleton(), where);
} else {
SegmentSet *sset = Get(ssid);
// Printf("SSUnref : %d ref=%d %s\n", ssid.raw(), sset->ref_count_, where);
DCHECK(sset->ref_count_ > 0);
sset->ref_count_--;
if (sset->ref_count_ == 0) {
// We don't delete unused SSID straightaway due to performance reasons
// (to avoid flushing caches too often and because SSID may be reused
// again soon)
//
// Instead, we use two queues (deques):
// ready_to_be_recycled_ and ready_to_be_reused_.
// The algorithm is following:
// 1) When refcount_ becomes zero, we push the SSID into
// ready_to_be_recycled_.
// 2) When ready_to_be_recycled_ becomes too large, we call
// FlushRecycleQueue().
// In FlushRecycleQueue(), we pop the first half of
// ready_to_be_recycled_ and for each popped SSID we do
// * if "refcount_ > 0", do nothing (this SSID is in use again)
// * otherwise, we recycle this SSID (delete its VTS, etc) and push
// it into ready_to_be_reused_
// 3) When a new SegmentSet is about to be created, we re-use SSID from
// ready_to_be_reused_ (if available)
ready_to_be_recycled_->push_back(ssid);
if (UNLIKELY(ready_to_be_recycled_->size() >
2 * G_flags->segment_set_recycle_queue_size)) {
FlushRecycleQueue();
}
}
}
}
static void FlushRecycleQueue() {
while (ready_to_be_recycled_->size() >
G_flags->segment_set_recycle_queue_size) {
SSID rec_ssid = ready_to_be_recycled_->front();
ready_to_be_recycled_->pop_front();
int idx = -rec_ssid.raw()-1;
SegmentSet *rec_ss = (*vec_)[idx];
DCHECK(rec_ss);
DCHECK(rec_ss == Get(rec_ssid));
// We should check that this SSID haven't been referenced again.
if (rec_ss->ref_count_ == 0) {
rec_ss->RecycleOneSegmentSet(rec_ssid);
}
}
// SSIDs will be reused soon - need to flush some caches.
FlushCaches();
}
string ToString() const;
void Print() {
Printf("SS%d:%s\n", -ComputeSSID().raw(), ToString().c_str());
}
static string ToString(SSID ssid) {
CHECK(ssid.IsValidOrEmpty());
if (ssid.IsSingleton()) {
return "{" + Segment::ToStringTidOnly(SID(ssid.raw())) + "}";
} else if (ssid.IsEmpty()) {
return "{}";
} else {
AssertLive(ssid, __LINE__);
return Get(ssid)->ToString();
}
}
static string ToStringWithLocks(SSID ssid);
static void FlushCaches() {
add_segment_cache_->Flush();
remove_segment_cache_->Flush();
}
static void ForgetAllState() {
for (size_t i = 0; i < vec_->size(); i++) {
delete (*vec_)[i];
}
map_->Clear();
vec_->clear();
ready_to_be_reused_->clear();
ready_to_be_recycled_->clear();
FlushCaches();
}
static void Test();
static int32_t Size(SSID ssid) {
if (ssid.IsEmpty()) return 0;
if (ssid.IsSingleton()) return 1;
return Get(ssid)->size();
}
SID GetSID(int32_t i) const {
DCHECK(i >= 0 && i < kMaxSegmentSetSize);
DCHECK(i == 0 || sids_[i-1].raw() != 0);
return sids_[i];
}
void SetSID(int32_t i, SID sid) {
DCHECK(i >= 0 && i < kMaxSegmentSetSize);
DCHECK(i == 0 || sids_[i-1].raw() != 0);
sids_[i] = sid;
}
static SID GetSID(SSID ssid, int32_t i, int line) {
DCHECK(ssid.valid());
if (ssid.IsSingleton()) {
DCHECK(i == 0);
Segment::AssertLive(ssid.GetSingleton(), line);
return ssid.GetSingleton();
} else {
AssertLive(ssid, __LINE__);
SID sid = Get(ssid)->GetSID(i);
Segment::AssertLive(sid, line);
return sid;
}
}
static bool INLINE Contains(SSID ssid, SID seg) {
if (LIKELY(ssid.IsSingleton())) {
return ssid.GetSingleton() == seg;
} else if (LIKELY(ssid.IsEmpty())) {
return false;
}
SegmentSet *ss = Get(ssid);
for (int i = 0; i < kMaxSegmentSetSize; i++) {
SID sid = ss->GetSID(i);
if (sid.raw() == 0) break;
if (sid == seg)
return true;
}
return false;
}
static Segment *GetSegmentForNonSingleton(SSID ssid, int32_t i, int line) {
return Segment::Get(GetSID(ssid, i, line));
}
void NOINLINE Validate(int line) const;
static size_t NumberOfSegmentSets() { return vec_->size(); }
static void InitClassMembers() {
map_ = new Map;
vec_ = new vector<SegmentSet *>;
ready_to_be_recycled_ = new deque<SSID>;
ready_to_be_reused_ = new deque<SSID>;
add_segment_cache_ = new SsidSidToSidCache;
remove_segment_cache_ = new SsidSidToSidCache;
}
private:
SegmentSet() // Private CTOR
: ref_count_(0) {
// sids_ are filled with zeroes due to SID default CTOR.
if (DEBUG_MODE) {
for (int i = 0; i < kMaxSegmentSetSize; i++)
CHECK_EQ(sids_[i].raw(), 0);
}
}
int size() const {
for (int i = 0; i < kMaxSegmentSetSize; i++) {
if (sids_[i].raw() == 0) {
CHECK_GE(i, 2);
return i;
}
}
return kMaxSegmentSetSize;
}
static INLINE SSID AllocateAndCopy(SegmentSet *ss) {
DCHECK(ss->ref_count_ == 0);
DCHECK(sizeof(int32_t) == sizeof(SID));
SSID res_ssid;
SegmentSet *res_ss = 0;
if (!ready_to_be_reused_->empty()) {
res_ssid = ready_to_be_reused_->front();
ready_to_be_reused_->pop_front();
int idx = -res_ssid.raw()-1;
res_ss = (*vec_)[idx];
DCHECK(res_ss);
DCHECK(res_ss->ref_count_ == -1);
G_stats->ss_reuse++;
for (int i = 0; i < kMaxSegmentSetSize; i++) {
res_ss->sids_[i] = SID(0);
}
} else {
// create a new one
ScopedMallocCostCenter cc("SegmentSet::CreateNewSegmentSet");
G_stats->ss_create++;
res_ss = new SegmentSet;
vec_->push_back(res_ss);
res_ssid = SSID(-((int32_t)vec_->size()));
CHECK(res_ssid.valid());
}
DCHECK(res_ss);
res_ss->ref_count_ = 0;
for (int i = 0; i < kMaxSegmentSetSize; i++) {
SID sid = ss->GetSID(i);
if (sid.raw() == 0) break;
Segment::Ref(sid, "SegmentSet::FindExistingOrAlocateAndCopy");
res_ss->SetSID(i, sid);
}
DCHECK(res_ss == Get(res_ssid));
map_->Insert(res_ss, res_ssid);
return res_ssid;
}
static NOINLINE SSID FindExistingOrAlocateAndCopy(SegmentSet *ss) {
if (DEBUG_MODE) {
int size = ss->size();
if (size == 2) G_stats->ss_size_2++;
if (size == 3) G_stats->ss_size_3++;
if (size == 4) G_stats->ss_size_4++;
if (size > 4) G_stats->ss_size_other++;
}
// First, check if there is such set already.
SSID ssid = map_->GetIdOrZero(ss);
if (ssid.raw() != 0) { // Found.
AssertLive(ssid, __LINE__);
G_stats->ss_find++;
return ssid;
}
// If no such set, create one.
return AllocateAndCopy(ss);
}
static INLINE SSID DoubletonSSID(SID sid1, SID sid2) {
SegmentSet tmp;
tmp.SetSID(0, sid1);
tmp.SetSID(1, sid2);
return FindExistingOrAlocateAndCopy(&tmp);
}
// testing only
static SegmentSet *AddSegmentToTupleSS(SegmentSet *ss, SID new_sid) {
SSID ssid = AddSegmentToTupleSS(ss->ComputeSSID(), new_sid);
AssertLive(ssid, __LINE__);
return Get(ssid);
}
static SegmentSet *Doubleton(SID sid1, SID sid2) {
SSID ssid = DoubletonSSID(sid1, sid2);
AssertLive(ssid, __LINE__);
return Get(ssid);
}
// static data members
struct Less {
INLINE bool operator() (const SegmentSet *ss1,
const SegmentSet *ss2) const {
for (int i = 0; i < kMaxSegmentSetSize; i++) {
SID sid1 = ss1->sids_[i],
sid2 = ss2->sids_[i];
if (sid1 != sid2) return sid1 < sid2;
}
return false;
}
};
struct SSEq {
INLINE bool operator() (const SegmentSet *ss1,
const SegmentSet *ss2) const {
G_stats->sseq_calls++;
for (int i = 0; i < kMaxSegmentSetSize; i++) {
SID sid1 = ss1->sids_[i],
sid2 = ss2->sids_[i];
if (sid1 != sid2) return false;
}
return true;
}
};
struct SSHash {
INLINE size_t operator() (const SegmentSet *ss) const {
uintptr_t res = 0;
uint32_t* sids_array = (uint32_t*)ss->sids_;
// We must have even number of SIDs.
DCHECK((kMaxSegmentSetSize % 2) == 0);
G_stats->sshash_calls++;
// xor all SIDs together, half of them bswap-ed.
for (int i = 0; i < kMaxSegmentSetSize; i += 2) {
uintptr_t t1 = sids_array[i];
uintptr_t t2 = sids_array[i+1];
if (t2) t2 = tsan_bswap(t2);
res = res ^ t1 ^ t2;
}
return res;
}
};
struct SSTraits {
enum {
// These values are taken from the hash_compare defaults.
bucket_size = 4, // Must be greater than zero.
min_buckets = 8, // Must be power of 2.
};
INLINE size_t operator()(const SegmentSet *ss) const {
SSHash sshash;
return sshash(ss);
}
INLINE bool operator()(const SegmentSet *ss1, const SegmentSet *ss2) const {
Less less;
return less(ss1, ss2);
}
};
template <class MapType>
static SSID GetIdOrZeroFromMap(MapType *map, SegmentSet *ss) {
typename MapType::iterator it = map->find(ss);
if (it == map->end())
return SSID(0);
return it->second;
}
class Map {
public:
SSID GetIdOrZero(SegmentSet *ss) {
return GetIdOrZeroFromMap(&map_, ss);
}
void Insert(SegmentSet *ss, SSID id) {
map_[ss] = id;
}
void Erase(SegmentSet *ss) {
CHECK(map_.erase(ss));
}
void Clear() {
map_.clear();
}
private:
// TODO(timurrrr): consider making a custom hash_table.
#if defined(_MSC_VER)
typedef stdext::hash_map<SegmentSet*, SSID, SSTraits > MapType__;
#elif 1
typedef unordered_map<SegmentSet*, SSID, SSHash, SSEq > MapType__;
#else
// Old code, may be useful for debugging.
typedef map<SegmentSet*, SSID, Less > MapType__;
#endif
MapType__ map_;
};
// typedef map<SegmentSet*, SSID, Less> Map;
static Map *map_;
// TODO(kcc): use vector<SegmentSet> instead.
static vector<SegmentSet *> *vec_;
static deque<SSID> *ready_to_be_reused_;
static deque<SSID> *ready_to_be_recycled_;
typedef PairCache<SSID, SID, SSID, 1009, 1> SsidSidToSidCache;
static SsidSidToSidCache *add_segment_cache_;
static SsidSidToSidCache *remove_segment_cache_;
// sids_ contains up to kMaxSegmentSetSize SIDs.
// Contains zeros at the end if size < kMaxSegmentSetSize.
SID sids_[kMaxSegmentSetSize];
int32_t ref_count_;
};
SegmentSet::Map *SegmentSet::map_;
vector<SegmentSet *> *SegmentSet::vec_;
deque<SSID> *SegmentSet::ready_to_be_reused_;
deque<SSID> *SegmentSet::ready_to_be_recycled_;
SegmentSet::SsidSidToSidCache *SegmentSet::add_segment_cache_;
SegmentSet::SsidSidToSidCache *SegmentSet::remove_segment_cache_;
SSID SegmentSet::RemoveSegmentFromSS(SSID old_ssid, SID sid_to_remove) {
DCHECK(old_ssid.IsValidOrEmpty());
DCHECK(sid_to_remove.valid());
SSID res;
if (remove_segment_cache_->Lookup(old_ssid, sid_to_remove, &res)) {
return res;
}
if (old_ssid.IsEmpty()) {
res = old_ssid; // Nothing to remove.
} else if (LIKELY(old_ssid.IsSingleton())) {
SID sid = old_ssid.GetSingleton();
if (Segment::HappensBeforeOrSameThread(sid, sid_to_remove))
res = SSID(0); // Empty.
else
res = old_ssid;
} else {
res = RemoveSegmentFromTupleSS(old_ssid, sid_to_remove);
}
remove_segment_cache_->Insert(old_ssid, sid_to_remove, res);
return res;
}
// static
//
// This method returns a SSID of a SegmentSet containing "new_sid" and all those
// segments from "old_ssid" which do not happen-before "new_sid".
//
// For details, see
// http://code.google.com/p/data-race-test/wiki/ThreadSanitizerAlgorithm#State_machine
SSID SegmentSet::AddSegmentToSS(SSID old_ssid, SID new_sid) {
DCHECK(old_ssid.raw() == 0 || old_ssid.valid());
DCHECK(new_sid.valid());
Segment::AssertLive(new_sid, __LINE__);
SSID res;
// These two TIDs will only be used if old_ssid.IsSingleton() == true.
TID old_tid;
TID new_tid;
if (LIKELY(old_ssid.IsSingleton())) {
SID old_sid(old_ssid.raw());
DCHECK(old_sid.valid());
Segment::AssertLive(old_sid, __LINE__);
if (UNLIKELY(old_sid == new_sid)) {
// The new segment equals the old one - nothing has changed.
return old_ssid;
}
old_tid = Segment::Get(old_sid)->tid();
new_tid = Segment::Get(new_sid)->tid();
if (LIKELY(old_tid == new_tid)) {
// The new segment is in the same thread - just replace the SID.
return SSID(new_sid);
}
if (Segment::HappensBefore(old_sid, new_sid)) {
// The new segment is in another thread, but old segment
// happens before the new one - just replace the SID.
return SSID(new_sid);
}
DCHECK(!Segment::HappensBefore(new_sid, old_sid));
// The only other case is Signleton->Doubleton transition, see below.
} else if (LIKELY(old_ssid.IsEmpty())) {
return SSID(new_sid);
}
// Lookup the cache.
if (add_segment_cache_->Lookup(old_ssid, new_sid, &res)) {
SegmentSet::AssertLive(res, __LINE__);
return res;
}
if (LIKELY(old_ssid.IsSingleton())) {
// Signleton->Doubleton transition.
// These two TIDs were initialized before cache lookup (see above).
DCHECK(old_tid.valid());
DCHECK(new_tid.valid());
SID old_sid(old_ssid.raw());
DCHECK(old_sid.valid());
DCHECK(!Segment::HappensBefore(new_sid, old_sid));
DCHECK(!Segment::HappensBefore(old_sid, new_sid));
res = (old_tid < new_tid
? DoubletonSSID(old_sid, new_sid)
: DoubletonSSID(new_sid, old_sid));
SegmentSet::AssertLive(res, __LINE__);
} else {
res = AddSegmentToTupleSS(old_ssid, new_sid);
SegmentSet::AssertLive(res, __LINE__);
}
// Put the result into cache.
add_segment_cache_->Insert(old_ssid, new_sid, res);
return res;
}
SSID SegmentSet::RemoveSegmentFromTupleSS(SSID ssid, SID sid_to_remove) {
DCHECK(ssid.IsTuple());
DCHECK(ssid.valid());
AssertLive(ssid, __LINE__);
SegmentSet *ss = Get(ssid);
int32_t old_size = 0, new_size = 0;
SegmentSet tmp;
SID * tmp_sids = tmp.sids_;
CHECK(sizeof(int32_t) == sizeof(SID));
for (int i = 0; i < kMaxSegmentSetSize; i++, old_size++) {
SID sid = ss->GetSID(i);
if (sid.raw() == 0) break;
DCHECK(sid.valid());
Segment::AssertLive(sid, __LINE__);
if (Segment::HappensBeforeOrSameThread(sid, sid_to_remove))
continue; // Skip this segment from the result.
tmp_sids[new_size++] = sid;
}
if (new_size == old_size) return ssid;
if (new_size == 0) return SSID(0);
if (new_size == 1) return SSID(tmp_sids[0]);
if (DEBUG_MODE) tmp.Validate(__LINE__);
SSID res = FindExistingOrAlocateAndCopy(&tmp);
if (DEBUG_MODE) Get(res)->Validate(__LINE__);
return res;
}
// static
SSID SegmentSet::AddSegmentToTupleSS(SSID ssid, SID new_sid) {
DCHECK(ssid.IsTuple());
DCHECK(ssid.valid());
AssertLive(ssid, __LINE__);
SegmentSet *ss = Get(ssid);
Segment::AssertLive(new_sid, __LINE__);
const Segment *new_seg = Segment::Get(new_sid);
TID new_tid = new_seg->tid();
int32_t old_size = 0, new_size = 0;
SID tmp_sids[kMaxSegmentSetSize + 1];
CHECK(sizeof(int32_t) == sizeof(SID));
bool inserted_new_sid = false;
// traverse all SID in current ss. tids are ordered.
for (int i = 0; i < kMaxSegmentSetSize; i++, old_size++) {
SID sid = ss->GetSID(i);
if (sid.raw() == 0) break;
DCHECK(sid.valid());
Segment::AssertLive(sid, __LINE__);
const Segment *seg = Segment::Get(sid);
TID tid = seg->tid();
if (sid == new_sid) {
// we are trying to insert a sid which is already there.
// SS will not change.
return ssid;
}
if (tid == new_tid) {
if (seg->vts() == new_seg->vts() &&
seg->lsid(true) == new_seg->lsid(true) &&
seg->lsid(false) == new_seg->lsid(false)) {
// Optimization: if a segment with the same VTS and LS
// as in the current is already inside SS, don't modify the SS.
// Improves performance with --keep-history >= 1.
return ssid;
}
// we have another segment from the same thread => replace it.
tmp_sids[new_size++] = new_sid;
inserted_new_sid = true;
continue;
}
if (tid > new_tid && !inserted_new_sid) {
// there was no segment with this tid, put it now.
tmp_sids[new_size++] = new_sid;
inserted_new_sid = true;
}
if (!Segment::HappensBefore(sid, new_sid)) {
DCHECK(!Segment::HappensBefore(new_sid, sid));
tmp_sids[new_size++] = sid;
}
}
if (!inserted_new_sid) {
tmp_sids[new_size++] = new_sid;
}
CHECK_GT(new_size, 0);
if (new_size == 1) {
return SSID(new_sid.raw()); // Singleton.
}
if (new_size > kMaxSegmentSetSize) {
CHECK(new_size == kMaxSegmentSetSize + 1);
// we need to forget one segment. Which? The oldest one.
int seg_to_forget = 0;
Segment *oldest_segment = NULL;
for (int i = 0; i < new_size; i++) {
SID sid = tmp_sids[i];
if (sid == new_sid) continue;
Segment *s = Segment::Get(tmp_sids[i]);
if (oldest_segment == NULL ||
oldest_segment->vts()->uniq_id() > s->vts()->uniq_id()) {
oldest_segment = s;
seg_to_forget = i;
}
}
DCHECK(oldest_segment);
// Printf("seg_to_forget: %d T%d\n", tmp_sids[seg_to_forget].raw(),
// oldest_segment->tid().raw());
for (int i = seg_to_forget; i < new_size - 1; i++) {
tmp_sids[i] = tmp_sids[i+1];
}
new_size--;
}
CHECK(new_size <= kMaxSegmentSetSize);
SegmentSet tmp;
for (int i = 0; i < new_size; i++)
tmp.sids_[i] = tmp_sids[i]; // TODO(timurrrr): avoid copying?
if (DEBUG_MODE) tmp.Validate(__LINE__);
SSID res = FindExistingOrAlocateAndCopy(&tmp);
if (DEBUG_MODE) Get(res)->Validate(__LINE__);
return res;
}
void NOINLINE SegmentSet::Validate(int line) const {
// This is expensive!
int my_size = size();
for (int i = 0; i < my_size; i++) {
SID sid1 = GetSID(i);
CHECK(sid1.valid());
Segment::AssertLive(sid1, __LINE__);
for (int j = i + 1; j < my_size; j++) {
SID sid2 = GetSID(j);
CHECK(sid2.valid());
Segment::AssertLive(sid2, __LINE__);
bool hb1 = Segment::HappensBefore(sid1, sid2);
bool hb2 = Segment::HappensBefore(sid2, sid1);
if (hb1 || hb2) {
Printf("BAD at line %d: %d %d %s %s\n %s\n %s\n",
line, static_cast<int>(hb1), static_cast<int>(hb2),
Segment::ToString(sid1).c_str(),
Segment::ToString(sid2).c_str(),
Segment::Get(sid1)->vts()->ToString().c_str(),
Segment::Get(sid2)->vts()->ToString().c_str());
}
CHECK(!Segment::HappensBefore(GetSID(i), GetSID(j)));
CHECK(!Segment::HappensBefore(GetSID(j), GetSID(i)));
CHECK(Segment::Get(sid1)->tid() < Segment::Get(sid2)->tid());
}
}
for (int i = my_size; i < kMaxSegmentSetSize; i++) {
CHECK_EQ(sids_[i].raw(), 0);
}
}
string SegmentSet::ToStringWithLocks(SSID ssid) {
if (ssid.IsEmpty()) return "";
string res = "";
for (int i = 0; i < Size(ssid); i++) {
SID sid = GetSID(ssid, i, __LINE__);
if (i) res += ", ";
res += Segment::ToStringWithLocks(sid);
}
return res;
}
string SegmentSet::ToString() const {
Validate(__LINE__);
string res = "{";
for (int i = 0; i < size(); i++) {
SID sid = GetSID(i);
if (i) res += ", ";
CHECK(sid.valid());
Segment::AssertLive(sid, __LINE__);
res += Segment::ToStringTidOnly(sid).c_str();
}
res += "}";
return res;
}
// static
void SegmentSet::Test() {
LSID ls(0); // dummy
SID sid1 = Segment::AddNewSegment(TID(0), VTS::Parse("[0:2;]"), ls, ls);
SID sid2 = Segment::AddNewSegment(TID(1), VTS::Parse("[0:1; 1:1]"), ls, ls);
SID sid3 = Segment::AddNewSegment(TID(2), VTS::Parse("[0:1; 2:1]"), ls, ls);
SID sid4 = Segment::AddNewSegment(TID(3), VTS::Parse("[0:1; 3:1]"), ls, ls);
SID sid5 = Segment::AddNewSegment(TID(4), VTS::Parse("[0:3; 2:2; 3:2;]"),
ls, ls);
SID sid6 = Segment::AddNewSegment(TID(4), VTS::Parse("[0:3; 1:2; 2:2; 3:2;]"),
ls, ls);
// SS1:{T0/S1, T2/S3}
SegmentSet *d1 = SegmentSet::Doubleton(sid1, sid3);
d1->Print();
CHECK(SegmentSet::Doubleton(sid1, sid3) == d1);
// SS2:{T0/S1, T1/S2, T2/S3}
SegmentSet *d2 = SegmentSet::AddSegmentToTupleSS(d1, sid2);
CHECK(SegmentSet::AddSegmentToTupleSS(d1, sid2) == d2);
d2->Print();
// SS3:{T0/S1, T2/S3, T3/S4}
SegmentSet *d3 = SegmentSet::AddSegmentToTupleSS(d1, sid4);
CHECK(SegmentSet::AddSegmentToTupleSS(d1, sid4) == d3);
d3->Print();
// SS4:{T0/S1, T1/S2, T2/S3, T3/S4}
SegmentSet *d4 = SegmentSet::AddSegmentToTupleSS(d2, sid4);
CHECK(SegmentSet::AddSegmentToTupleSS(d2, sid4) == d4);
CHECK(SegmentSet::AddSegmentToTupleSS(d3, sid2) == d4);
d4->Print();
// SS5:{T1/S2, T4/S5}
SegmentSet *d5 = SegmentSet::AddSegmentToTupleSS(d4, sid5);
d5->Print();
SSID ssid6 = SegmentSet::AddSegmentToTupleSS(d4->ComputeSSID(), sid6);
CHECK(ssid6.IsSingleton());
Printf("%s\n", ToString(ssid6).c_str());
CHECK_EQ(sid6.raw(), 6);
CHECK_EQ(ssid6.raw(), 6);
}
// -------- Shadow Value ------------ {{{1
class ShadowValue {
public:
ShadowValue() {
if (DEBUG_MODE) {
rd_ssid_ = 0xDEADBEEF;
wr_ssid_ = 0xDEADBEEF;
}
}
void Clear() {
rd_ssid_ = 0;
wr_ssid_ = 0;
}
INLINE bool IsNew() const { return rd_ssid_ == 0 && wr_ssid_ == 0; }
// new experimental state machine.
SSID rd_ssid() const { return SSID(rd_ssid_); }
SSID wr_ssid() const { return SSID(wr_ssid_); }
INLINE void set(SSID rd_ssid, SSID wr_ssid) {
rd_ssid_ = rd_ssid.raw();
wr_ssid_ = wr_ssid.raw();
}
// comparison
INLINE bool operator == (const ShadowValue &sval) const {
return rd_ssid_ == sval.rd_ssid_ &&
wr_ssid_ == sval.wr_ssid_;
}
bool operator != (const ShadowValue &sval) const {
return !(*this == sval);
}
bool operator < (const ShadowValue &sval) const {
if (rd_ssid_ < sval.rd_ssid_) return true;
if (rd_ssid_ == sval.rd_ssid_ && wr_ssid_ < sval.wr_ssid_) return true;
return false;
}
void Ref(const char *where) {
if (!rd_ssid().IsEmpty()) {
DCHECK(rd_ssid().valid());
SegmentSet::Ref(rd_ssid(), where);
}
if (!wr_ssid().IsEmpty()) {
DCHECK(wr_ssid().valid());
SegmentSet::Ref(wr_ssid(), where);
}
}
void Unref(const char *where) {
if (!rd_ssid().IsEmpty()) {
DCHECK(rd_ssid().valid());
SegmentSet::Unref(rd_ssid(), where);
}
if (!wr_ssid().IsEmpty()) {
DCHECK(wr_ssid().valid());
SegmentSet::Unref(wr_ssid(), where);
}
}
string ToString() const {
char buff[1000];
if (IsNew()) {
return "{New}";
}
snprintf(buff, sizeof(buff), "R: %s; W: %s",
SegmentSet::ToStringWithLocks(rd_ssid()).c_str(),
SegmentSet::ToStringWithLocks(wr_ssid()).c_str());
return buff;
}
private:
int32_t rd_ssid_;
int32_t wr_ssid_;
};
// -------- CacheLine --------------- {{{1
// The CacheLine is a set of Mask::kNBits (32 or 64) Shadow Values.
// The shadow values in a cache line are grouped in subsets of 8 values.
// If a particular address of memory is always accessed by aligned 8-byte
// read/write instructions, only the shadow value correspoding to the
// first byte is set, the rest shadow values are not used.
// Ditto to aligned 4- and 2-byte accesses.
// If a memory was accessed as 8 bytes and then it was accesed as 4 bytes,
// (e.g. someone used a C union) we need to split the shadow value into two.
// If the memory was accessed as 4 bytes and is now accessed as 8 bytes,
// we need to try joining the shadow values.
//
// Hence the concept of granularity_mask (which is a string of 16 bits).
// 0000000000000000 -- no accesses were observed to these 8 bytes.
// 0000000000000001 -- all accesses were 8 bytes (aligned).
// 0000000000000110 -- all accesses were 4 bytes (aligned).
// 0000000001111000 -- all accesses were 2 bytes (aligned).
// 0111111110000000 -- all accesses were 1 byte.
// 0110000000100010 -- First 4 bytes were accessed by 4 byte insns,
// next 2 bytes by 2 byte insns, last 2 bytes by 1 byte insns.
INLINE bool GranularityIs8(uintptr_t off, uint16_t gr) {
return gr & 1;
}
INLINE bool GranularityIs4(uintptr_t off, uint16_t gr) {
uintptr_t off_within_8_bytes = (off >> 2) & 1; // 0 or 1.
return ((gr >> (1 + off_within_8_bytes)) & 1);
}
INLINE bool GranularityIs2(uintptr_t off, uint16_t gr) {
uintptr_t off_within_8_bytes = (off >> 1) & 3; // 0, 1, 2, or 3
return ((gr >> (3 + off_within_8_bytes)) & 1);
}
INLINE bool GranularityIs1(uintptr_t off, uint16_t gr) {
uintptr_t off_within_8_bytes = (off) & 7; // 0, ..., 7
return ((gr >> (7 + off_within_8_bytes)) & 1);
}
class CacheLine {
public:
static const uintptr_t kLineSizeBits = Mask::kNBitsLog; // Don't change this.
static const uintptr_t kLineSize = Mask::kNBits;
static CacheLine *CreateNewCacheLine(uintptr_t tag) {
ScopedMallocCostCenter cc("CreateNewCacheLine");
void *mem = free_list_->Allocate();
DCHECK(mem);
return new (mem) CacheLine(tag);
}
static void Delete(CacheLine *line) {
free_list_->Deallocate(line);
}
const Mask &has_shadow_value() const { return has_shadow_value_; }
Mask &traced() { return traced_; }
Mask &published() { return published_; }
Mask &racey() { return racey_; }
uintptr_t tag() { return tag_; }
void DebugTrace(uintptr_t off, const char *where_str, int where_int) {
(void)off;
(void)where_str;
(void)where_int;
#if 0
if (DEBUG_MODE && tag() == G_flags->trace_addr) {
uintptr_t off8 = off & ~7;
Printf("CacheLine %p, off=%ld off8=%ld gr=%d "
"has_sval: %d%d%d%d%d%d%d%d (%s:%d)\n",
tag(), off, off8,
granularity_[off/8],
has_shadow_value_.Get(off8 + 0),
has_shadow_value_.Get(off8 + 1),
has_shadow_value_.Get(off8 + 2),
has_shadow_value_.Get(off8 + 3),
has_shadow_value_.Get(off8 + 4),
has_shadow_value_.Get(off8 + 5),
has_shadow_value_.Get(off8 + 6),
has_shadow_value_.Get(off8 + 7),
where_str, where_int
);
}
#endif
}
// Add a new shadow value to a place where there was no shadow value before.
ShadowValue *AddNewSvalAtOffset(uintptr_t off) {
DebugTrace(off, __FUNCTION__, __LINE__);
CHECK(!has_shadow_value().Get(off));
has_shadow_value_.Set(off);
published_.Clear(off);
ShadowValue *res = GetValuePointer(off);
res->Clear();
DebugTrace(off, __FUNCTION__, __LINE__);
return res;
}
// Return true if this line has no useful information in it.
bool Empty() {
// The line has shadow values.
if (!has_shadow_value().Empty()) return false;
// If the line is traced, racey or published, we want to keep it.
if (!traced().Empty()) return false;
if (!racey().Empty()) return false;
if (!published().Empty()) return false;
return true;
}
INLINE Mask ClearRangeAndReturnOldUsed(uintptr_t from, uintptr_t to) {
traced_.ClearRange(from, to);
published_.ClearRange(from, to);
racey_.ClearRange(from, to);
for (uintptr_t x = (from + 7) / 8; x < to / 8; x++) {
granularity_[x] = 0;
}
return has_shadow_value_.ClearRangeAndReturnOld(from, to);
}
void Clear() {
has_shadow_value_.Clear();
traced_.Clear();
published_.Clear();
racey_.Clear();
for (size_t i = 0; i < TS_ARRAY_SIZE(granularity_); i++)
granularity_[i] = 0;
}
ShadowValue *GetValuePointer(uintptr_t offset) {
DCHECK(offset < kLineSize);
return &vals_[offset];
}
ShadowValue GetValue(uintptr_t offset) { return *GetValuePointer(offset); }
static uintptr_t ComputeOffset(uintptr_t a) {
return a & (kLineSize - 1);
}
static uintptr_t ComputeTag(uintptr_t a) {
return a & ~(kLineSize - 1);
}
static uintptr_t ComputeNextTag(uintptr_t a) {
return ComputeTag(a) + kLineSize;
}
uint16_t *granularity_mask(uintptr_t off) {
DCHECK(off < kLineSize);
return &granularity_[off / 8];
}
void Split_8_to_4(uintptr_t off) {
DebugTrace(off, __FUNCTION__, __LINE__);
uint16_t gr = *granularity_mask(off);
if (GranularityIs8(off, gr)) {
DCHECK(!GranularityIs4(off, gr));
DCHECK(!GranularityIs2(off, gr));
DCHECK(!GranularityIs1(off, gr));
uintptr_t off_8_aligned = off & ~7;
if (has_shadow_value_.Get(off_8_aligned)) {
ShadowValue sval = GetValue(off_8_aligned);
sval.Ref("Split_8_to_4");
DCHECK(!has_shadow_value_.Get(off_8_aligned + 4));
*AddNewSvalAtOffset(off_8_aligned + 4) = sval;
}
*granularity_mask(off) = gr = 3 << 1;
DCHECK(GranularityIs4(off, gr));
DebugTrace(off, __FUNCTION__, __LINE__);
}
}
void Split_4_to_2(uintptr_t off) {
DebugTrace(off, __FUNCTION__, __LINE__);
uint16_t gr = *granularity_mask(off);
if (GranularityIs4(off, gr)) {
DCHECK(!GranularityIs8(off, gr));
DCHECK(!GranularityIs2(off, gr));
DCHECK(!GranularityIs1(off, gr));
uint16_t off_4_aligned = off & ~3;
if (has_shadow_value_.Get(off_4_aligned)) {
ShadowValue sval = GetValue(off_4_aligned);
sval.Ref("Split_4_to_2");
DCHECK(!has_shadow_value_.Get(off_4_aligned + 2));
*AddNewSvalAtOffset(off_4_aligned + 2) = sval;
}
// Clear this 4-granularity bit.
uintptr_t off_within_8_bytes = (off >> 2) & 1; // 0 or 1.
gr &= ~(1 << (1 + off_within_8_bytes));
// Set two 2-granularity bits.
gr |= 3 << (3 + 2 * off_within_8_bytes);
*granularity_mask(off) = gr;
DebugTrace(off, __FUNCTION__, __LINE__);
}
}
void Split_2_to_1(uintptr_t off) {
DebugTrace(off, __FUNCTION__, __LINE__);
uint16_t gr = *granularity_mask(off);
if (GranularityIs2(off, gr)) {
DCHECK(!GranularityIs8(off, gr));
DCHECK(!GranularityIs4(off, gr));
DCHECK(!GranularityIs1(off, gr));
uint16_t off_2_aligned = off & ~1;
if (has_shadow_value_.Get(off_2_aligned)) {
ShadowValue sval = GetValue(off_2_aligned);
sval.Ref("Split_2_to_1");
DCHECK(!has_shadow_value_.Get(off_2_aligned + 1));
*AddNewSvalAtOffset(off_2_aligned + 1) = sval;
}
// Clear this 2-granularity bit.
uintptr_t off_within_8_bytes = (off >> 1) & 3; // 0, 1, 2, or 3
gr &= ~(1 << (3 + off_within_8_bytes));
// Set two 1-granularity bits.
gr |= 3 << (7 + 2 * off_within_8_bytes);
*granularity_mask(off) = gr;
DebugTrace(off, __FUNCTION__, __LINE__);
}
}
void Join_1_to_2(uintptr_t off) {
DebugTrace(off, __FUNCTION__, __LINE__);
DCHECK((off & 1) == 0);
uint16_t gr = *granularity_mask(off);
if (GranularityIs1(off, gr)) {
DCHECK(GranularityIs1(off + 1, gr));
if (has_shadow_value_.Get(off) && has_shadow_value_.Get(off + 1)) {
if (GetValue(off) == GetValue(off + 1)) {
ShadowValue *sval_p = GetValuePointer(off + 1);
sval_p->Unref("Join_1_to_2");
sval_p->Clear();
has_shadow_value_.Clear(off + 1);
uintptr_t off_within_8_bytes = (off >> 1) & 3; // 0, 1, 2, or 3
// Clear two 1-granularity bits.
gr &= ~(3 << (7 + 2 * off_within_8_bytes));
// Set one 2-granularity bit.
gr |= 1 << (3 + off_within_8_bytes);
*granularity_mask(off) = gr;
DebugTrace(off, __FUNCTION__, __LINE__);
}
}
}
}
void Join_2_to_4(uintptr_t off) {
DebugTrace(off, __FUNCTION__, __LINE__);
DCHECK((off & 3) == 0);
uint16_t gr = *granularity_mask(off);
if (GranularityIs2(off, gr) && GranularityIs2(off + 2, gr)) {
if (has_shadow_value_.Get(off) && has_shadow_value_.Get(off + 2)) {
if (GetValue(off) == GetValue(off + 2)) {
ShadowValue *sval_p = GetValuePointer(off + 2);
sval_p->Unref("Join_2_to_4");
sval_p->Clear();
has_shadow_value_.Clear(off + 2);
uintptr_t off_within_8_bytes = (off >> 2) & 1; // 0 or 1.
// Clear two 2-granularity bits.
gr &= ~(3 << (3 + 2 * off_within_8_bytes));
// Set one 4-granularity bit.
gr |= 1 << (1 + off_within_8_bytes);
*granularity_mask(off) = gr;
DebugTrace(off, __FUNCTION__, __LINE__);
}
}
}
}
void Join_4_to_8(uintptr_t off) {
DebugTrace(off, __FUNCTION__, __LINE__);
DCHECK((off & 7) == 0);
uint16_t gr = *granularity_mask(off);
if (GranularityIs4(off, gr) && GranularityIs4(off + 4, gr)) {
if (has_shadow_value_.Get(off) && has_shadow_value_.Get(off + 4)) {
if (GetValue(off) == GetValue(off + 4)) {
ShadowValue *sval_p = GetValuePointer(off + 4);
sval_p->Unref("Join_4_to_8");
sval_p->Clear();
has_shadow_value_.Clear(off + 4);
*granularity_mask(off) = 1;
DebugTrace(off, __FUNCTION__, __LINE__);
}
}
}
}
static void InitClassMembers() {
if (DEBUG_MODE) {
Printf("sizeof(CacheLine) = %ld\n", sizeof(CacheLine));
}
free_list_ = new FreeList(sizeof(CacheLine), 1024);
}
private:
explicit CacheLine(uintptr_t tag) {
tag_ = tag;
Clear();
}
~CacheLine() { }
uintptr_t tag_;
// data members
Mask has_shadow_value_;
Mask traced_;
Mask racey_;
Mask published_;
uint16_t granularity_[kLineSize / 8];
ShadowValue vals_[kLineSize];
// static data members.
static FreeList *free_list_;
};
FreeList *CacheLine::free_list_;
// If range [a,b) fits into one line, return that line's tag.
// Else range [a,b) is broken into these ranges:
// [a, line1_tag)
// [line1_tag, line2_tag)
// [line2_tag, b)
// and 0 is returned.
uintptr_t GetCacheLinesForRange(uintptr_t a, uintptr_t b,
uintptr_t *line1_tag, uintptr_t *line2_tag) {
uintptr_t a_tag = CacheLine::ComputeTag(a);
uintptr_t next_tag = CacheLine::ComputeNextTag(a);
if (b < next_tag) {
return a_tag;
}
*line1_tag = next_tag;
*line2_tag = CacheLine::ComputeTag(b);
return 0;
}
// -------- Cache ------------------ {{{1
class Cache {
public:
Cache() {
memset(lines_, 0, sizeof(lines_));
ANNOTATE_BENIGN_RACE_SIZED(lines_, sizeof(lines_),
"Cache::lines_ accessed without a lock");
}
INLINE static CacheLine *kLineIsLocked() {
return (CacheLine*)1;
}
INLINE static bool LineIsNullOrLocked(CacheLine *line) {
return (uintptr_t)line <= 1;
}
INLINE CacheLine *TidMagic(int32_t tid) {
return kLineIsLocked();
}
// Try to get a CacheLine for exclusive use.
// May return NULL or kLineIsLocked.
INLINE CacheLine *TryAcquireLine(TSanThread *thr, uintptr_t a, int call_site) {
uintptr_t cli = ComputeCacheLineIndexInCache(a);
CacheLine **addr = &lines_[cli];
CacheLine *res = (CacheLine*)AtomicExchange(
(uintptr_t*)addr, (uintptr_t)kLineIsLocked());
if (DEBUG_MODE && debug_cache) {
uintptr_t tag = CacheLine::ComputeTag(a);
if (res && res != kLineIsLocked())
Printf("TryAcquire %p empty=%d tag=%lx cli=%lx site=%d\n",
res, res->Empty(), res->tag(), cli, call_site);
else
Printf("TryAcquire tag=%lx cli=%d site=%d\n", tag, cli, call_site);
}
if (res) {
ANNOTATE_HAPPENS_AFTER((void*)cli);
}
return res;
}
INLINE CacheLine *AcquireLine(TSanThread *thr, uintptr_t a, int call_site) {
CacheLine *line = NULL;
int iter = 0;
const int max_iter = 1 << 30;
for (;;) {
line = TryAcquireLine(thr, a, call_site);
if (line != kLineIsLocked())
break;
iter++;
if ((iter % (1 << 6)) == 0) {
YIELD();
G_stats->try_acquire_line_spin++;
if (DEBUG_MODE && debug_cache && ((iter & (iter - 1)) == 0)) {
Printf("T%d %s a=%p iter=%d\n", raw_tid(thr), __FUNCTION__, a, iter);
}
} else {
for (int active_spin = 0; active_spin != 10; active_spin += 1) {
PROCESSOR_YIELD();
}
}
if (DEBUG_MODE && debug_cache && iter == max_iter) {
Printf("Failed to acquire a cache line: T%d a=%p site=%d\n",
raw_tid(thr), a, call_site);
CHECK(iter < max_iter);
}
}
DCHECK(lines_[ComputeCacheLineIndexInCache(a)] == TidMagic(raw_tid(thr)));
return line;
}
// Release a CacheLine from exclusive use.
INLINE void ReleaseLine(TSanThread *thr, uintptr_t a, CacheLine *line, int call_site) {
if (TS_SERIALIZED) return;
DCHECK(line != kLineIsLocked());
uintptr_t cli = ComputeCacheLineIndexInCache(a);
DCHECK(line == NULL ||
cli == ComputeCacheLineIndexInCache(line->tag()));
CacheLine **addr = &lines_[cli];
DCHECK(*addr == TidMagic(raw_tid(thr)));
ReleaseStore((uintptr_t*)addr, (uintptr_t)line);
ANNOTATE_HAPPENS_BEFORE((void*)cli);
if (DEBUG_MODE && debug_cache) {
uintptr_t tag = CacheLine::ComputeTag(a);
if (line)
Printf("Release %p empty=%d tag=%lx cli=%lx site=%d\n",
line, line->Empty(), line->tag(), cli, call_site);
else
Printf("Release tag=%lx cli=%d site=%d\n", tag, cli, call_site);
}
}
void AcquireAllLines(TSanThread *thr) {
CHECK(TS_SERIALIZED == 0);
for (size_t i = 0; i < (size_t)kNumLines; i++) {
uintptr_t tag = i << CacheLine::kLineSizeBits;
AcquireLine(thr, tag, __LINE__);
CHECK(lines_[i] == kLineIsLocked());
}
}
// Get a CacheLine. This operation should be performed under a lock
// (whatever that is), but other threads may be acquiring the same line
// concurrently w/o a lock.
// Every call to GetLine() which returns non-null line
// should be followed by a call to ReleaseLine().
INLINE CacheLine *GetLine(TSanThread *thr, uintptr_t a, bool create_new_if_need, int call_site) {
uintptr_t tag = CacheLine::ComputeTag(a);
DCHECK(tag <= a);
DCHECK(tag + CacheLine::kLineSize > a);
uintptr_t cli = ComputeCacheLineIndexInCache(a);
CacheLine *res = NULL;
CacheLine *line = NULL;
if (create_new_if_need == false && lines_[cli] == 0) {
// There is no such line in the cache, nor should it be in the storage.
// Check that the storage indeed does not have this line.
// Such DCHECK is racey if tsan is multi-threaded.
DCHECK(TS_SERIALIZED == 0 || storage_.count(tag) == 0);
return NULL;
}
if (TS_SERIALIZED) {
line = lines_[cli];
} else {
line = AcquireLine(thr, tag, call_site);
}
if (LIKELY(line && line->tag() == tag)) {
res = line;
} else {
res = WriteBackAndFetch(thr, line, tag, cli, create_new_if_need);
if (!res) {
ReleaseLine(thr, a, line, call_site);
}
}
if (DEBUG_MODE && debug_cache) {
if (res)
Printf("GetLine %p empty=%d tag=%lx\n", res, res->Empty(), res->tag());
else
Printf("GetLine res=NULL, line=%p tag=%lx cli=%lx\n", line, tag, cli);
}
return res;
}
INLINE CacheLine *GetLineOrCreateNew(TSanThread *thr, uintptr_t a, int call_site) {
return GetLine(thr, a, true, call_site);
}
INLINE CacheLine *GetLineIfExists(TSanThread *thr, uintptr_t a, int call_site) {
return GetLine(thr, a, false, call_site);
}
void ForgetAllState(TSanThread *thr) {
for (int i = 0; i < kNumLines; i++) {
if (TS_SERIALIZED == 0) CHECK(LineIsNullOrLocked(lines_[i]));
lines_[i] = NULL;
}
map<uintptr_t, Mask> racey_masks;
for (Map::iterator i = storage_.begin(); i != storage_.end(); ++i) {
CacheLine *line = i->second;
if (!line->racey().Empty()) {
racey_masks[line->tag()] = line->racey();
}
CacheLine::Delete(line);
}
storage_.clear();
// Restore the racey masks.
for (map<uintptr_t, Mask>::iterator it = racey_masks.begin();
it != racey_masks.end(); it++) {
CacheLine *line = GetLineOrCreateNew(thr, it->first, __LINE__);
line->racey() = it->second;
DCHECK(!line->racey().Empty());
ReleaseLine(thr, line->tag(), line, __LINE__);
}
}
void PrintStorageStats() {
if (!G_flags->show_stats) return;
set<ShadowValue> all_svals;
map<size_t, int> sizes;
for (Map::iterator it = storage_.begin(); it != storage_.end(); ++it) {
CacheLine *line = it->second;
// uintptr_t cli = ComputeCacheLineIndexInCache(line->tag());
//if (lines_[cli] == line) {
// this line is in cache -- ignore it.
// continue;
//}
set<ShadowValue> s;
for (uintptr_t i = 0; i < CacheLine::kLineSize; i++) {
if (line->has_shadow_value().Get(i)) {
ShadowValue sval = *(line->GetValuePointer(i));
s.insert(sval);
all_svals.insert(sval);
}
}
size_t size = s.size();
if (size > 10) size = 10;
sizes[size]++;
}
Printf("Storage sizes: %ld\n", storage_.size());
for (size_t size = 0; size <= CacheLine::kLineSize; size++) {
if (sizes[size]) {
Printf(" %ld => %d\n", size, sizes[size]);
}
}
Printf("Different svals: %ld\n", all_svals.size());
set <SSID> all_ssids;
for (set<ShadowValue>::iterator it = all_svals.begin(); it != all_svals.end(); ++it) {
ShadowValue sval = *it;
for (int i = 0; i < 2; i++) {
SSID ssid = i ? sval.rd_ssid() : sval.wr_ssid();
all_ssids.insert(ssid);
}
}
Printf("Different ssids: %ld\n", all_ssids.size());
set <SID> all_sids;
for (set<SSID>::iterator it = all_ssids.begin(); it != all_ssids.end(); ++it) {
int size = SegmentSet::Size(*it);
for (int i = 0; i < size; i++) {
SID sid = SegmentSet::GetSID(*it, i, __LINE__);
all_sids.insert(sid);
}
}
Printf("Different sids: %ld\n", all_sids.size());
for (int i = 1; i < Segment::NumberOfSegments(); i++) {
if (Segment::ProfileSeg(SID(i)) && all_sids.count(SID(i)) == 0) {
// Printf("Segment SID %d: missing in storage; ref=%d\n", i,
// Segment::Get(SID(i))->ref_count());
}
}
}
private:
INLINE uintptr_t ComputeCacheLineIndexInCache(uintptr_t addr) {
return (addr >> CacheLine::kLineSizeBits) & (kNumLines - 1);
}
NOINLINE CacheLine *WriteBackAndFetch(TSanThread *thr, CacheLine *old_line,
uintptr_t tag, uintptr_t cli,
bool create_new_if_need) {
ScopedMallocCostCenter cc("Cache::WriteBackAndFetch");
CacheLine *res;
size_t old_storage_size = storage_.size();
(void)old_storage_size;
CacheLine **line_for_this_tag = NULL;
if (create_new_if_need) {
line_for_this_tag = &storage_[tag];
} else {
Map::iterator it = storage_.find(tag);
if (it == storage_.end()) {
if (DEBUG_MODE && debug_cache) {
Printf("WriteBackAndFetch: old_line=%ld tag=%lx cli=%ld\n",
old_line, tag, cli);
}
return NULL;
}
line_for_this_tag = &(it->second);
}
CHECK(line_for_this_tag);
DCHECK(old_line != kLineIsLocked());
if (*line_for_this_tag == NULL) {
// creating a new cache line
CHECK(storage_.size() == old_storage_size + 1);
res = CacheLine::CreateNewCacheLine(tag);
if (DEBUG_MODE && debug_cache) {
Printf("%s %d new line %p cli=%lx\n", __FUNCTION__, __LINE__, res, cli);
}
*line_for_this_tag = res;
G_stats->cache_new_line++;
} else {
// taking an existing cache line from storage.
res = *line_for_this_tag;
if (DEBUG_MODE && debug_cache) {
Printf("%s %d exi line %p tag=%lx old=%p empty=%d cli=%lx\n",
__FUNCTION__, __LINE__, res, res->tag(), old_line,
res->Empty(), cli);
}
DCHECK(!res->Empty());
G_stats->cache_fetch++;
}
if (TS_SERIALIZED) {
lines_[cli] = res;
} else {
DCHECK(lines_[cli] == TidMagic(raw_tid(thr)));
}
if (old_line) {
if (DEBUG_MODE && debug_cache) {
Printf("%s %d old line %p empty=%d\n", __FUNCTION__, __LINE__,
old_line, old_line->Empty());
}
if (old_line->Empty()) {
storage_.erase(old_line->tag());
CacheLine::Delete(old_line);
G_stats->cache_delete_empty_line++;
} else {
if (debug_cache) {
DebugOnlyCheckCacheLineWhichWeReplace(old_line, res);
}
}
}
DCHECK(res->tag() == tag);
if (G_stats->cache_max_storage_size < storage_.size()) {
G_stats->cache_max_storage_size = storage_.size();
}
return res;
}
void DebugOnlyCheckCacheLineWhichWeReplace(CacheLine *old_line,
CacheLine *new_line) {
static int c = 0;
c++;
if ((c % 1024) == 1) {
set<int64_t> s;
for (uintptr_t i = 0; i < CacheLine::kLineSize; i++) {
if (old_line->has_shadow_value().Get(i)) {
int64_t sval = *reinterpret_cast<int64_t*>(
old_line->GetValuePointer(i));
s.insert(sval);
}
}
Printf("\n[%d] Cache Size=%ld %s different values: %ld\n", c,
storage_.size(), old_line->has_shadow_value().ToString().c_str(),
s.size());
Printf("new line: %p %p\n", new_line->tag(), new_line->tag()
+ CacheLine::kLineSize);
G_stats->PrintStatsForCache();
}
}
static const int kNumLines = 1 << (DEBUG_MODE ? 14 : 21);
CacheLine *lines_[kNumLines];
// tag => CacheLine
typedef unordered_map<uintptr_t, CacheLine*> Map;
Map storage_;
};
static Cache *G_cache;
// -------- Published range -------------------- {{{1
struct PublishInfo {
uintptr_t tag; // Tag of the cache line where the mem is published.
Mask mask; // The bits that are actually published.
VTS *vts; // The point where this range has been published.
};
typedef multimap<uintptr_t, PublishInfo> PublishInfoMap;
// Maps 'mem+size' to the PublishInfoMap{mem, size, vts}.
static PublishInfoMap *g_publish_info_map;
const int kDebugPublish = 0;
// Get a VTS where 'a' has been published,
// return NULL if 'a' was not published.
static const VTS *GetPublisherVTS(uintptr_t a) {
uintptr_t tag = CacheLine::ComputeTag(a);
uintptr_t off = CacheLine::ComputeOffset(a);
typedef PublishInfoMap::iterator Iter;
pair<Iter, Iter> eq_range = g_publish_info_map->equal_range(tag);
for (Iter it = eq_range.first; it != eq_range.second; ++it) {
PublishInfo &info = it->second;
DCHECK(info.tag == tag);
if (info.mask.Get(off)) {
G_stats->publish_get++;
// Printf("GetPublisherVTS: a=%p vts=%p\n", a, info.vts);
return info.vts;
}
}
Printf("GetPublisherVTS returned NULL: a=%p\n", a);
return NULL;
}
static bool CheckSanityOfPublishedMemory(uintptr_t tag, int line) {
if (!DEBUG_MODE) return true;
if (kDebugPublish)
Printf("CheckSanityOfPublishedMemory: line=%d\n", line);
typedef PublishInfoMap::iterator Iter;
pair<Iter, Iter> eq_range = g_publish_info_map->equal_range(tag);
Mask union_of_masks(0);
// iterate over all entries for this tag
for (Iter it = eq_range.first; it != eq_range.second; ++it) {
PublishInfo &info = it->second;
CHECK(info.tag == tag);
CHECK(it->first == tag);
CHECK(info.vts);
Mask mask(info.mask);
CHECK(!mask.Empty()); // Mask should not be empty..
// And should not intersect with other masks.
CHECK(Mask::Intersection(union_of_masks, mask).Empty());
union_of_masks.Union(mask);
}
return true;
}
// Clear the publish attribute for the bytes from 'line' that are set in 'mask'
static void ClearPublishedAttribute(CacheLine *line, Mask mask) {
CHECK(CheckSanityOfPublishedMemory(line->tag(), __LINE__));
typedef PublishInfoMap::iterator Iter;
bool deleted_some = true;
if (kDebugPublish)
Printf(" ClearPublishedAttribute: %p %s\n",
line->tag(), mask.ToString().c_str());
while (deleted_some) {
deleted_some = false;
pair<Iter, Iter> eq_range = g_publish_info_map->equal_range(line->tag());
for (Iter it = eq_range.first; it != eq_range.second; ++it) {
PublishInfo &info = it->second;
DCHECK(info.tag == line->tag());
if (kDebugPublish)
Printf("?ClearPublishedAttribute: %p %s\n", line->tag(),
info.mask.ToString().c_str());
info.mask.Subtract(mask);
if (kDebugPublish)
Printf("+ClearPublishedAttribute: %p %s\n", line->tag(),
info.mask.ToString().c_str());
G_stats->publish_clear++;
if (info.mask.Empty()) {
VTS::Unref(info.vts);
g_publish_info_map->erase(it);
deleted_some = true;
break;
}
}
}
CHECK(CheckSanityOfPublishedMemory(line->tag(), __LINE__));
}
// Publish range [a, b) in addr's CacheLine with vts.
static void PublishRangeInOneLine(TSanThread *thr, uintptr_t addr, uintptr_t a,
uintptr_t b, VTS *vts) {
ScopedMallocCostCenter cc("PublishRangeInOneLine");
DCHECK(b <= CacheLine::kLineSize);
DCHECK(a < b);
uintptr_t tag = CacheLine::ComputeTag(addr);
CHECK(CheckSanityOfPublishedMemory(tag, __LINE__));
CacheLine *line = G_cache->GetLineOrCreateNew(thr, tag, __LINE__);
if (1 || line->published().GetRange(a, b)) {
Mask mask(0);
mask.SetRange(a, b);
// TODO(timurrrr): add warning for re-publishing.
ClearPublishedAttribute(line, mask);
}
line->published().SetRange(a, b);
G_cache->ReleaseLine(thr, tag, line, __LINE__);
PublishInfo pub_info;
pub_info.tag = tag;
pub_info.mask.SetRange(a, b);
pub_info.vts = vts->Clone();
g_publish_info_map->insert(make_pair(tag, pub_info));
G_stats->publish_set++;
if (kDebugPublish)
Printf("PublishRange : [%p,%p) %p %s vts=%p\n",
a, b, tag, pub_info.mask.ToString().c_str(), vts);
CHECK(CheckSanityOfPublishedMemory(tag, __LINE__));
}
// Publish memory range [a, b).
static void PublishRange(TSanThread *thr, uintptr_t a, uintptr_t b, VTS *vts) {
CHECK(a);
CHECK(a < b);
if (kDebugPublish)
Printf("PublishRange : [%p,%p), size=%d, tag=%p\n",
a, b, (int)(b - a), CacheLine::ComputeTag(a));
uintptr_t line1_tag = 0, line2_tag = 0;
uintptr_t tag = GetCacheLinesForRange(a, b, &line1_tag, &line2_tag);
if (tag) {
PublishRangeInOneLine(thr, tag, a - tag, b - tag, vts);
return;
}
uintptr_t a_tag = CacheLine::ComputeTag(a);
PublishRangeInOneLine(thr, a, a - a_tag, CacheLine::kLineSize, vts);
for (uintptr_t tag_i = line1_tag; tag_i < line2_tag;
tag_i += CacheLine::kLineSize) {
PublishRangeInOneLine(thr, tag_i, 0, CacheLine::kLineSize, vts);
}
if (b > line2_tag) {
PublishRangeInOneLine(thr, line2_tag, 0, b - line2_tag, vts);
}
}
// -------- ThreadSanitizerReport -------------- {{{1
struct ThreadSanitizerReport {
// Types of reports.
enum ReportType {
DATA_RACE,
UNLOCK_FOREIGN,
UNLOCK_NONLOCKED,
INVALID_LOCK,
ATOMICITY_VIOLATION,
};
// Common fields.
ReportType type;
TID tid;
StackTrace *stack_trace;
const char *ReportName() const {
switch (type) {
case DATA_RACE: return "Race";
case UNLOCK_FOREIGN: return "UnlockForeign";
case UNLOCK_NONLOCKED: return "UnlockNonLocked";
case INVALID_LOCK: return "InvalidLock";
case ATOMICITY_VIOLATION: return "AtomicityViolation";
}
CHECK(0);
return NULL;
}
virtual ~ThreadSanitizerReport() {
StackTrace::Delete(stack_trace);
}
};
static bool ThreadSanitizerPrintReport(ThreadSanitizerReport *report);
// DATA_RACE.
struct ThreadSanitizerDataRaceReport : public ThreadSanitizerReport {
uintptr_t racey_addr;
string racey_addr_description;
uintptr_t last_access_size;
TID last_access_tid;
SID last_access_sid;
bool last_access_is_w;
LSID last_acces_lsid[2];
ShadowValue new_sval;
ShadowValue old_sval;
bool is_expected;
bool racey_addr_was_published;
};
// Report for bad unlock (UNLOCK_FOREIGN, UNLOCK_NONLOCKED).
struct ThreadSanitizerBadUnlockReport : public ThreadSanitizerReport {
LID lid;
};
// Report for invalid lock addresses (INVALID_LOCK).
struct ThreadSanitizerInvalidLockReport : public ThreadSanitizerReport {
uintptr_t lock_addr;
};
class AtomicityRegion;
struct ThreadSanitizerAtomicityViolationReport : public ThreadSanitizerReport {
AtomicityRegion *r1, *r2, *r3;
};
// -------- LockHistory ------------- {{{1
// For each thread we store a limited amount of history of locks and unlocks.
// If there is a race report (in hybrid mode) we try to guess a lock
// which might have been used to pass the ownership of the object between
// threads.
//
// Thread1: Thread2:
// obj->UpdateMe();
// mu.Lock();
// flag = true;
// mu.Unlock(); // (*)
// mu.Lock(); // (**)
// bool f = flag;
// mu.Unlock();
// if (f)
// obj->UpdateMeAgain();
//
// For this code a hybrid detector may report a false race.
// LockHistory will find the lock mu and report it.
struct LockHistory {
public:
// LockHistory which will track no more than `size` recent locks
// and the same amount of unlocks.
LockHistory(size_t size): size_(size) { }
// Record a Lock event.
void OnLock(LID lid) {
g_lock_era++;
Push(LockHistoryElement(lid, g_lock_era), &locks_);
}
// Record an Unlock event.
void OnUnlock(LID lid) {
g_lock_era++;
Push(LockHistoryElement(lid, g_lock_era), &unlocks_);
}
// Find locks such that:
// - A Lock happend in `l`.
// - An Unlock happened in `u`.
// - Lock's era is greater than Unlock's era.
// - Both eras are greater or equal than min_lock_era.
static bool Intersect(const LockHistory &l, const LockHistory &u,
int32_t min_lock_era, set<LID> *locks) {
const Queue &lq = l.locks_;
const Queue &uq = u.unlocks_;
for (size_t i = 0; i < lq.size(); i++) {
int32_t l_era = lq[i].lock_era;
if (l_era < min_lock_era) continue;
LID lid = lq[i].lid;
// We don't want to report pure happens-before locks since
// they already create h-b arcs.
if (Lock::LIDtoLock(lid)->is_pure_happens_before()) continue;
for (size_t j = 0; j < uq.size(); j++) {
int32_t u_era = uq[j].lock_era;
if (lid != uq[j].lid) continue;
// Report("LockHistory::Intersect: L%d %d %d %d\n", lid.raw(), min_lock_era, u_era, l_era);
if (u_era < min_lock_era) continue;
if (u_era > l_era) continue;
locks->insert(lid);
}
}
return !locks->empty();
}
void PrintLocks() const { Print(&locks_); }
void PrintUnlocks() const { Print(&unlocks_); }
private:
struct LockHistoryElement {
LID lid;
uint32_t lock_era;
LockHistoryElement(LID l, uint32_t era)
: lid(l),
lock_era(era) {
}
};
typedef deque<LockHistoryElement> Queue;
void Push(LockHistoryElement e, Queue *q) {
CHECK(q->size() <= size_);
if (q->size() == size_)
q->pop_front();
q->push_back(e);
}
void Print(const Queue *q) const {
set<LID> printed;
for (size_t i = 0; i < q->size(); i++) {
const LockHistoryElement &e = (*q)[i];
if (printed.count(e.lid)) continue;
Report("era %d: \n", e.lock_era);
Lock::ReportLockWithOrWithoutContext(e.lid, true);
printed.insert(e.lid);
}
}
Queue locks_;
Queue unlocks_;
size_t size_;
};
// -------- RecentSegmentsCache ------------- {{{1
// For each thread we store a limited amount of recent segments with
// the same VTS and LS as the current segment.
// When a thread enters a new basic block, we can sometimes reuse a
// recent segment if it is the same or not used anymore (see Search()).
//
// We need to flush the cache when current lockset changes or the current
// VTS changes or we do ForgetAllState.
// TODO(timurrrr): probably we can cache segments with different LSes and
// compare their LS with the current LS.
struct RecentSegmentsCache {
public:
RecentSegmentsCache(int cache_size) : cache_size_(cache_size) {}
~RecentSegmentsCache() { Clear(); }
void Clear() {
ShortenQueue(0);
}
void Push(SID sid) {
queue_.push_front(sid);
Segment::Ref(sid, "RecentSegmentsCache::ShortenQueue");
ShortenQueue(cache_size_);
}
void ForgetAllState() {
queue_.clear(); // Don't unref - the segments are already dead.
}
INLINE SID Search(CallStack *curr_stack,
SID curr_sid, /*OUT*/ bool *needs_refill) {
// TODO(timurrrr): we can probably move the matched segment to the head
// of the queue.
deque<SID>::iterator it = queue_.begin();
for (; it != queue_.end(); it++) {
SID sid = *it;
Segment::AssertLive(sid, __LINE__);
Segment *seg = Segment::Get(sid);
if (seg->ref_count() == 1 + (sid == curr_sid)) {
// The current segment is not used anywhere else,
// so just replace the stack trace in it.
// The refcount of an unused segment is equal to
// *) 1 if it is stored only in the cache,
// *) 2 if it is the current segment of the Thread.
*needs_refill = true;
return sid;
}
// Check three top entries of the call stack of the recent segment.
// If they match the current segment stack, don't create a new segment.
// This can probably lead to a little bit wrong stack traces in rare
// occasions but we don't really care that much.
if (kSizeOfHistoryStackTrace > 0) {
size_t n = curr_stack->size();
uintptr_t *emb_trace = Segment::embedded_stack_trace(sid);
if(*emb_trace && // This stack trace was filled
curr_stack->size() >= 3 &&
emb_trace[0] == (*curr_stack)[n-1] &&
emb_trace[1] == (*curr_stack)[n-2] &&
emb_trace[2] == (*curr_stack)[n-3]) {
*needs_refill = false;
return sid;
}
}
}
return SID();
}
private:
void ShortenQueue(size_t flush_to_length) {
while (queue_.size() > flush_to_length) {
SID sid = queue_.back();
Segment::Unref(sid, "RecentSegmentsCache::ShortenQueue");
queue_.pop_back();
}
}
deque<SID> queue_;
size_t cache_size_;
};
// -------- TraceInfo ------------------ {{{1
vector<TraceInfo*> *TraceInfo::g_all_traces;
TraceInfo *TraceInfo::NewTraceInfo(size_t n_mops, uintptr_t pc) {
ScopedMallocCostCenter cc("TraceInfo::NewTraceInfo");
size_t mem_size = (sizeof(TraceInfo) + (n_mops - 1) * sizeof(MopInfo));
uint8_t *mem = new uint8_t[mem_size];
memset(mem, 0xab, mem_size);
TraceInfo *res = new (mem) TraceInfo;
res->n_mops_ = n_mops;
res->pc_ = ThreadSanitizerWantToCreateSegmentsOnSblockEntry(pc) ? pc : 0;
res->counter_ = 0;
if (g_all_traces == NULL) {
g_all_traces = new vector<TraceInfo*>;
}
res->literace_storage = NULL;
if (G_flags->literace_sampling != 0) {
ScopedMallocCostCenter cc("TraceInfo::NewTraceInfo::LiteRaceStorage");
size_t index_of_this_trace = g_all_traces->size();
if ((index_of_this_trace % kLiteRaceStorageSize) == 0) {
res->literace_storage = (LiteRaceStorage*)
new LiteRaceCounters [kLiteRaceStorageSize * kLiteRaceNumTids];
memset(res->literace_storage, 0, sizeof(LiteRaceStorage));
} else {
CHECK(index_of_this_trace > 0);
res->literace_storage = (*g_all_traces)[index_of_this_trace - 1]->literace_storage;
CHECK(res->literace_storage);
}
res->storage_index = index_of_this_trace % kLiteRaceStorageSize;
}
g_all_traces->push_back(res);
return res;
}
void TraceInfo::PrintTraceProfile() {
if (!G_flags->trace_profile) return;
if (!g_all_traces) return;
int64_t total_counter = 0;
multimap<size_t, TraceInfo*> traces;
for (size_t i = 0; i < g_all_traces->size(); i++) {
TraceInfo *trace = (*g_all_traces)[i];
traces.insert(make_pair(trace->counter(), trace));
total_counter += trace->counter();
}
if (total_counter == 0) return;
Printf("TraceProfile: %ld traces, %lld hits\n",
g_all_traces->size(), total_counter);
int i = 0;
for (multimap<size_t, TraceInfo*>::reverse_iterator it = traces.rbegin();
it != traces.rend(); ++it, i++) {
TraceInfo *trace = it->second;
int64_t c = it->first;
int64_t permile = (c * 1000) / total_counter;
CHECK(trace->n_mops() > 0);
uintptr_t pc = trace->GetMop(0)->pc();
CHECK(pc);
if (permile == 0 || i >= 20) break;
Printf("TR=%p pc: %p %p c=%lld (%lld/1000) n_mops=%ld %s\n",
trace, trace->pc(), pc, c,
permile, trace->n_mops(),
PcToRtnNameAndFilePos(pc).c_str());
}
}
// -------- Atomicity --------------- {{{1
// An attempt to detect atomicity violations (aka high level races).
// Here we try to find a very restrictive pattern:
// Thread1 Thread2
// r1: {
// mu.Lock();
// code_r1();
// mu.Unlock();
// }
// r2: {
// mu.Lock();
// code_r2();
// mu.Unlock();
// }
// r3: {
// mu.Lock();
// code_r3();
// mu.Unlock();
// }
// We have 3 regions of code such that
// - two of them are in one thread and 3-rd in another thread.
// - all 3 regions have the same lockset,
// - the distance between r1 and r2 is small,
// - there is no h-b arc between r2 and r3,
// - r1 and r2 have different stack traces,
//
// In this situation we report a 'Suspected atomicity violation'.
//
// Current status:
// this code detects atomicity violations on our two motivating examples
// (--gtest_filter=*Atomicity* --gtest_also_run_disabled_tests) and does
// not overwhelm with false reports.
// However, this functionality is still raw and not tuned for performance.
// TS_ATOMICITY is on in debug mode or if we enabled it at the build time.
#ifndef TS_ATOMICITY
# define TS_ATOMICITY DEBUG_MODE
#endif
struct AtomicityRegion {
int lock_era;
TID tid;
VTS *vts;
StackTrace *stack_trace;
LSID lsid[2];
BitSet access_set[2];
bool used;
int n_mops_since_start;
void Print() {
Report("T%d era=%d nmss=%ld AtomicityRegion:\n rd: %s\n wr: %s\n %s\n%s",
tid.raw(),
lock_era,
n_mops_since_start,
access_set[0].ToString().c_str(),
access_set[1].ToString().c_str(),
TwoLockSetsToString(lsid[false], lsid[true]).c_str(),
stack_trace->ToString().c_str()
);
}
};
bool SimilarLockSetForAtomicity(AtomicityRegion *r1, AtomicityRegion *r2) {
// Compare only reader locksets (in case one region took reader locks)
return ((r1->lsid[0] == r2->lsid[0]));
}
static deque<AtomicityRegion *> *g_atomicity_regions;
static map<StackTrace *, int, StackTrace::Less> *reported_atomicity_stacks_;
const size_t kMaxAtomicityRegions = 8;
static void HandleAtomicityRegion(AtomicityRegion *atomicity_region) {
if (!g_atomicity_regions) {
g_atomicity_regions = new deque<AtomicityRegion*>;
reported_atomicity_stacks_ = new map<StackTrace *, int, StackTrace::Less>;
}
if (g_atomicity_regions->size() >= kMaxAtomicityRegions) {
AtomicityRegion *to_delete = g_atomicity_regions->back();
g_atomicity_regions->pop_back();
if (!to_delete->used) {
VTS::Unref(to_delete->vts);
StackTrace::Delete(to_delete->stack_trace);
delete to_delete;
}
}
g_atomicity_regions->push_front(atomicity_region);
size_t n = g_atomicity_regions->size();
if (0) {
for (size_t i = 0; i < n; i++) {
AtomicityRegion *r = (*g_atomicity_regions)[i];
r->Print();
}
}
AtomicityRegion *r3 = (*g_atomicity_regions)[0];
for (size_t i = 1; i < n; i++) {
AtomicityRegion *r2 = (*g_atomicity_regions)[i];
if (r2->tid != r3->tid &&
SimilarLockSetForAtomicity(r2, r3) &&
!VTS::HappensBeforeCached(r2->vts, r3->vts)) {
for (size_t j = i + 1; j < n; j++) {
AtomicityRegion *r1 = (*g_atomicity_regions)[j];
if (r1->tid != r2->tid) continue;
CHECK(r2->lock_era > r1->lock_era);
if (r2->lock_era - r1->lock_era > 2) break;
if (!SimilarLockSetForAtomicity(r1, r2)) continue;
if (StackTrace::Equals(r1->stack_trace, r2->stack_trace)) continue;
if (!(r1->access_set[1].empty() &&
!r2->access_set[1].empty() &&
!r3->access_set[1].empty())) continue;
CHECK(r1->n_mops_since_start <= r2->n_mops_since_start);
if (r2->n_mops_since_start - r1->n_mops_since_start > 5) continue;
if ((*reported_atomicity_stacks_)[r1->stack_trace] > 0) continue;
(*reported_atomicity_stacks_)[r1->stack_trace]++;
(*reported_atomicity_stacks_)[r2->stack_trace]++;
(*reported_atomicity_stacks_)[r3->stack_trace]++;
r1->used = r2->used = r3->used = true;
ThreadSanitizerAtomicityViolationReport *report =
new ThreadSanitizerAtomicityViolationReport;
report->type = ThreadSanitizerReport::ATOMICITY_VIOLATION;
report->tid = TID(0);
report->stack_trace = r1->stack_trace;
report->r1 = r1;
report->r2 = r2;
report->r3 = r3;
ThreadSanitizerPrintReport(report);
break;
}
}
}
}
// -------- TSanThread ------------------ {{{1
struct TSanThread {
public:
ThreadLocalStats stats;
TSanThread(TID tid, TID parent_tid, VTS *vts, StackTrace *creation_context,
CallStack *call_stack)
: is_running_(true),
tid_(tid),
sid_(0),
parent_tid_(parent_tid),
max_sp_(0),
min_sp_(0),
stack_size_for_ignore_(0),
fun_r_ignore_(0),
min_sp_for_ignore_(0),
n_mops_since_start_(0),
creation_context_(creation_context),
announced_(false),
rd_lockset_(0),
wr_lockset_(0),
expensive_bits_(0),
vts_at_exit_(NULL),
call_stack_(call_stack),
lock_history_(128),
recent_segments_cache_(G_flags->recent_segments_cache_size),
inside_atomic_op_(),
rand_state_((unsigned)(tid.raw() + (uintptr_t)vts
+ (uintptr_t)creation_context
+ (uintptr_t)call_stack)) {
NewSegmentWithoutUnrefingOld("TSanThread Creation", vts);
ignore_depth_[0] = ignore_depth_[1] = 0;
HandleRtnCall(0, 0, IGNORE_BELOW_RTN_UNKNOWN);
ignore_context_[0] = NULL;
ignore_context_[1] = NULL;
if (tid != TID(0) && parent_tid.valid()) {
CHECK(creation_context_);
}
// Add myself to the array of threads.
CHECK(tid.raw() < G_flags->max_n_threads);
CHECK(all_threads_[tid.raw()] == NULL);
n_threads_ = max(n_threads_, tid.raw() + 1);
all_threads_[tid.raw()] = this;
dead_sids_.reserve(kMaxNumDeadSids);
fresh_sids_.reserve(kMaxNumFreshSids);
ComputeExpensiveBits();
}
TID tid() const { return tid_; }
TID parent_tid() const { return parent_tid_; }
void increment_n_mops_since_start() {
n_mops_since_start_++;
}
// STACK
uintptr_t max_sp() const { return max_sp_; }
uintptr_t min_sp() const { return min_sp_; }
unsigned random() {
return tsan_prng(&rand_state_);
}
bool ShouldReportRaces() const {
return (inside_atomic_op_ == 0);
}
void SetStack(uintptr_t stack_min, uintptr_t stack_max) {
CHECK(stack_min < stack_max);
// Stay sane. Expect stack less than 64M.
CHECK(stack_max - stack_min <= 64 * 1024 * 1024);
min_sp_ = stack_min;
max_sp_ = stack_max;
if (G_flags->ignore_stack) {
min_sp_for_ignore_ = min_sp_;
stack_size_for_ignore_ = max_sp_ - min_sp_;
} else {
CHECK(min_sp_for_ignore_ == 0 &&
stack_size_for_ignore_ == 0);
}
}
bool MemoryIsInStack(uintptr_t a) {
return a >= min_sp_ && a <= max_sp_;
}
bool IgnoreMemoryIfInStack(uintptr_t a) {
return (a - min_sp_for_ignore_) < stack_size_for_ignore_;
}
bool Announce() {
if (announced_) return false;
announced_ = true;
if (tid_ == TID(0)) {
Report("INFO: T0 is program's main thread\n");
} else {
if (G_flags->announce_threads) {
Report("INFO: T%d has been created by T%d at this point: {{{\n%s}}}\n",
tid_.raw(), parent_tid_.raw(),
creation_context_->ToString().c_str());
TSanThread * parent = GetIfExists(parent_tid_);
CHECK(parent);
parent->Announce();
} else {
Report("INFO: T%d has been created by T%d. "
"Use --announce-threads to see the creation stack.\n",
tid_.raw(), parent_tid_.raw());
}
}
return true;
}
string ThreadName() const {
char buff[100];
snprintf(buff, sizeof(buff), "T%d", tid().raw());
string res = buff;
if (thread_name_.length() > 0) {
res += " (";
res += thread_name_;
res += ")";
}
return res;
}
bool is_running() const { return is_running_; }
INLINE void ComputeExpensiveBits() {
bool has_expensive_flags = G_flags->trace_level > 0 ||
G_flags->show_stats > 1 ||
G_flags->sample_events > 0;
expensive_bits_ =
(ignore_depth_[0] != 0) |
((ignore_depth_[1] != 0) << 1) |
((has_expensive_flags == true) << 2);
}
int expensive_bits() { return expensive_bits_; }
int ignore_reads() { return expensive_bits() & 1; }
int ignore_writes() { return (expensive_bits() >> 1) & 1; }
// ignore
INLINE void set_ignore_accesses(bool is_w, bool on) {
ignore_depth_[is_w] += on ? 1 : -1;
CHECK(ignore_depth_[is_w] >= 0);
ComputeExpensiveBits();
if (on && G_flags->save_ignore_context) {
StackTrace::Delete(ignore_context_[is_w]);
ignore_context_[is_w] = CreateStackTrace(0, 3);
}
}
INLINE void set_ignore_all_accesses(bool on) {
set_ignore_accesses(false, on);
set_ignore_accesses(true, on);
}
StackTrace *GetLastIgnoreContext(bool is_w) {
return ignore_context_[is_w];
}
SID sid() const {
return sid_;
}
Segment *segment() const {
CHECK(sid().valid());
Segment::AssertLive(sid(), __LINE__);
return Segment::Get(sid());
}
VTS *vts() const {
return segment()->vts();
}
void set_thread_name(const char *name) {
thread_name_ = string(name);
}
void HandleThreadEnd() {
CHECK(is_running_);
is_running_ = false;
CHECK(!vts_at_exit_);
vts_at_exit_ = vts()->Clone();
CHECK(vts_at_exit_);
FlushDeadSids();
ReleaseFreshSids();
call_stack_ = NULL;
}
// Return the TID of the joined child and it's vts
TID HandleThreadJoinAfter(VTS **vts_at_exit, TID joined_tid) {
CHECK(joined_tid.raw() > 0);
CHECK(GetIfExists(joined_tid) != NULL);
TSanThread* joined_thread = TSanThread::Get(joined_tid);
// Sometimes the joined thread is not truly dead yet.
// In that case we just take the current vts.
if (joined_thread->is_running_)
*vts_at_exit = joined_thread->vts()->Clone();
else
*vts_at_exit = joined_thread->vts_at_exit_;
if (*vts_at_exit == NULL) {
Printf("vts_at_exit==NULL; parent=%d, child=%d\n",
tid().raw(), joined_tid.raw());
}
CHECK(*vts_at_exit);
if (0)
Printf("T%d: vts_at_exit_: %s\n", joined_tid.raw(),
(*vts_at_exit)->ToString().c_str());
return joined_tid;
}
static int NumberOfThreads() {
return INTERNAL_ANNOTATE_UNPROTECTED_READ(n_threads_);
}
static TSanThread *GetIfExists(TID tid) {
if (tid.raw() < NumberOfThreads())
return Get(tid);
return NULL;
}
static TSanThread *Get(TID tid) {
DCHECK(tid.raw() < NumberOfThreads());
return all_threads_[tid.raw()];
}
void HandleAccessSet() {
BitSet *rd_set = lock_era_access_set(false);
BitSet *wr_set = lock_era_access_set(true);
if (rd_set->empty() && wr_set->empty()) return;
CHECK(G_flags->atomicity && !G_flags->pure_happens_before);
AtomicityRegion *atomicity_region = new AtomicityRegion;
atomicity_region->lock_era = g_lock_era;
atomicity_region->tid = tid();
atomicity_region->vts = vts()->Clone();
atomicity_region->lsid[0] = lsid(0);
atomicity_region->lsid[1] = lsid(1);
atomicity_region->access_set[0] = *rd_set;
atomicity_region->access_set[1] = *wr_set;
atomicity_region->stack_trace = CreateStackTrace();
atomicity_region->used = false;
atomicity_region->n_mops_since_start = this->n_mops_since_start_;
// atomicity_region->Print();
// Printf("----------- %s\n", __FUNCTION__);
// ReportStackTrace(0, 7);
HandleAtomicityRegion(atomicity_region);
}
// Locks
void HandleLock(uintptr_t lock_addr, bool is_w_lock) {
Lock *lock = Lock::LookupOrCreate(lock_addr);
if (debug_lock) {
Printf("T%d lid=%d %sLock %p; %s\n",
tid_.raw(), lock->lid().raw(),
is_w_lock ? "Wr" : "Rd",
lock_addr,
LockSet::ToString(lsid(is_w_lock)).c_str());
ReportStackTrace(0, 7);
}
// NOTE: we assume that all locks can be acquired recurively.
// No warning about recursive locking will be issued.
if (is_w_lock) {
// Recursive locks are properly handled because LockSet is in fact a
// multiset.
wr_lockset_ = LockSet::Add(wr_lockset_, lock);
rd_lockset_ = LockSet::Add(rd_lockset_, lock);
lock->WrLock(tid_, CreateStackTrace());
} else {
if (lock->wr_held()) {
ReportStackTrace();
}
rd_lockset_ = LockSet::Add(rd_lockset_, lock);
lock->RdLock(CreateStackTrace());
}
if (lock->is_pure_happens_before()) {
if (is_w_lock) {
HandleWait(lock->wr_signal_addr());
} else {
HandleWait(lock->rd_signal_addr());
}
}
if (G_flags->suggest_happens_before_arcs) {
lock_history_.OnLock(lock->lid());
}
NewSegmentForLockingEvent();
lock_era_access_set_[0].Clear();
lock_era_access_set_[1].Clear();
}
void HandleUnlock(uintptr_t lock_addr) {
HandleAccessSet();
Lock *lock = Lock::Lookup(lock_addr);
// If the lock is not found, report an error.
if (lock == NULL) {
ThreadSanitizerInvalidLockReport *report =
new ThreadSanitizerInvalidLockReport;
report->type = ThreadSanitizerReport::INVALID_LOCK;
report->tid = tid();
report->lock_addr = lock_addr;
report->stack_trace = CreateStackTrace();
ThreadSanitizerPrintReport(report);
return;
}
bool is_w_lock = lock->wr_held();
if (debug_lock) {
Printf("T%d lid=%d %sUnlock %p; %s\n",
tid_.raw(), lock->lid().raw(),
is_w_lock ? "Wr" : "Rd",
lock_addr,
LockSet::ToString(lsid(is_w_lock)).c_str());
ReportStackTrace(0, 7);
}
if (lock->is_pure_happens_before()) {
// reader unlock signals only to writer lock,
// writer unlock signals to both.
if (is_w_lock) {
HandleSignal(lock->rd_signal_addr());
}
HandleSignal(lock->wr_signal_addr());
}
if (!lock->wr_held() && !lock->rd_held()) {
ThreadSanitizerBadUnlockReport *report =
new ThreadSanitizerBadUnlockReport;
report->type = ThreadSanitizerReport::UNLOCK_NONLOCKED;
report->tid = tid();
report->lid = lock->lid();
report->stack_trace = CreateStackTrace();
ThreadSanitizerPrintReport(report);
return;
}
bool removed = false;
if (is_w_lock) {
lock->WrUnlock();
removed = LockSet::Remove(wr_lockset_, lock, &wr_lockset_)
&& LockSet::Remove(rd_lockset_, lock, &rd_lockset_);
} else {
lock->RdUnlock();
removed = LockSet::Remove(rd_lockset_, lock, &rd_lockset_);
}
if (!removed) {
ThreadSanitizerBadUnlockReport *report =
new ThreadSanitizerBadUnlockReport;
report->type = ThreadSanitizerReport::UNLOCK_FOREIGN;
report->tid = tid();
report->lid = lock->lid();
report->stack_trace = CreateStackTrace();
ThreadSanitizerPrintReport(report);
}
if (G_flags->suggest_happens_before_arcs) {
lock_history_.OnUnlock(lock->lid());
}
NewSegmentForLockingEvent();
lock_era_access_set_[0].Clear();
lock_era_access_set_[1].Clear();
}
// Handles memory access with race reports suppressed.
void HandleAtomicMop(uintptr_t a,
uintptr_t pc,
tsan_atomic_op op,
tsan_memory_order mo,
size_t size);
void HandleForgetSignaller(uintptr_t cv) {
SignallerMap::iterator it = signaller_map_->find(cv);
if (it != signaller_map_->end()) {
if (debug_happens_before) {
Printf("T%d: ForgetSignaller: %p:\n %s\n", tid_.raw(), cv,
(it->second.vts)->ToString().c_str());
if (G_flags->debug_level >= 1) {
ReportStackTrace();
}
}
VTS::Unref(it->second.vts);
signaller_map_->erase(it);
}
}
LSID lsid(bool is_w) {
return is_w ? wr_lockset_ : rd_lockset_;
}
const LockHistory &lock_history() { return lock_history_; }
// SIGNAL/WAIT events.
void HandleWait(uintptr_t cv) {
SignallerMap::iterator it = signaller_map_->find(cv);
if (it != signaller_map_->end()) {
const VTS *signaller_vts = it->second.vts;
NewSegmentForWait(signaller_vts);
}
if (debug_happens_before) {
Printf("T%d: Wait: %p:\n %s %s\n", tid_.raw(),
cv,
vts()->ToString().c_str(),
Segment::ToString(sid()).c_str());
if (G_flags->debug_level >= 1) {
ReportStackTrace();
}
}
}
void HandleSignal(uintptr_t cv) {
Signaller *signaller = &(*signaller_map_)[cv];
if (!signaller->vts) {
signaller->vts = vts()->Clone();
} else {
VTS *new_vts = VTS::Join(signaller->vts, vts());
VTS::Unref(signaller->vts);
signaller->vts = new_vts;
}
NewSegmentForSignal();
if (debug_happens_before) {
Printf("T%d: Signal: %p:\n %s %s\n %s\n", tid_.raw(), cv,
vts()->ToString().c_str(), Segment::ToString(sid()).c_str(),
(signaller->vts)->ToString().c_str());
if (G_flags->debug_level >= 1) {
ReportStackTrace();
}
}
}
void INLINE NewSegmentWithoutUnrefingOld(const char *call_site,
VTS *new_vts) {
DCHECK(new_vts);
SID new_sid = Segment::AddNewSegment(tid(), new_vts,
rd_lockset_, wr_lockset_);
SID old_sid = sid();
if (old_sid.raw() != 0 && new_vts != vts()) {
// Flush the cache if VTS changed - the VTS won't repeat.
recent_segments_cache_.Clear();
}
sid_ = new_sid;
Segment::Ref(new_sid, "TSanThread::NewSegmentWithoutUnrefingOld");
if (kSizeOfHistoryStackTrace > 0) {
FillEmbeddedStackTrace(Segment::embedded_stack_trace(sid()));
}
if (0)
Printf("2: %s T%d/S%d old_sid=%d NewSegment: %s\n", call_site,
tid().raw(), sid().raw(), old_sid.raw(),
vts()->ToString().c_str());
}
void INLINE NewSegment(const char *call_site, VTS *new_vts) {
SID old_sid = sid();
NewSegmentWithoutUnrefingOld(call_site, new_vts);
Segment::Unref(old_sid, "TSanThread::NewSegment");
}
void NewSegmentForLockingEvent() {
// Flush the cache since we can't reuse segments with different lockset.
recent_segments_cache_.Clear();
NewSegment(__FUNCTION__, vts()->Clone());
}
void NewSegmentForMallocEvent() {
// Flush the cache since we can't reuse segments with different lockset.
recent_segments_cache_.Clear();
NewSegment(__FUNCTION__, vts()->Clone());
}
void SetTopPc(uintptr_t pc) {
if (pc) {
DCHECK(!call_stack_->empty());
call_stack_->back() = pc;
}
}
void NOINLINE HandleSblockEnterSlowLocked() {
AssertTILHeld();
FlushStateIfOutOfSegments(this);
this->stats.history_creates_new_segment++;
VTS *new_vts = vts()->Clone();
NewSegment("HandleSblockEnter", new_vts);
recent_segments_cache_.Push(sid());
GetSomeFreshSids(); // fill the thread-local SID cache.
}
INLINE bool HandleSblockEnter(uintptr_t pc, bool allow_slow_path) {
DCHECK(G_flags->keep_history);
if (!pc) return true;
this->stats.events[SBLOCK_ENTER]++;
SetTopPc(pc);
bool refill_stack = false;
SID match = recent_segments_cache_.Search(call_stack_, sid(),
/*OUT*/&refill_stack);
DCHECK(kSizeOfHistoryStackTrace > 0);
if (match.valid()) {
// This part is 100% thread-local, no need for locking.
if (sid_ != match) {
Segment::Ref(match, "TSanThread::HandleSblockEnter");
this->AddDeadSid(sid_, "TSanThread::HandleSblockEnter");
sid_ = match;
}
if (refill_stack) {
this->stats.history_reuses_segment++;
FillEmbeddedStackTrace(Segment::embedded_stack_trace(sid()));
} else {
this->stats.history_uses_same_segment++;
}
} else if (fresh_sids_.size() > 0) {
// We have a fresh ready-to-use segment in thread local cache.
SID fresh_sid = fresh_sids_.back();
fresh_sids_.pop_back();
Segment::SetupFreshSid(fresh_sid, tid(), vts()->Clone(),
rd_lockset_, wr_lockset_);
this->AddDeadSid(sid_, "TSanThread::HandleSblockEnter-1");
Segment::Ref(fresh_sid, "TSanThread::HandleSblockEnter-1");
sid_ = fresh_sid;
recent_segments_cache_.Push(sid());
FillEmbeddedStackTrace(Segment::embedded_stack_trace(sid()));
this->stats.history_uses_preallocated_segment++;
} else {
if (!allow_slow_path) return false;
AssertTILHeld();
// No fresh SIDs available, have to grab a lock and get few.
HandleSblockEnterSlowLocked();
}
return true;
}
void NewSegmentForWait(const VTS *signaller_vts) {
const VTS *current_vts = vts();
if (0)
Printf("T%d NewSegmentForWait: \n %s\n %s\n", tid().raw(),
current_vts->ToString().c_str(),
signaller_vts->ToString().c_str());
// We don't want to create a happens-before arc if it will be redundant.
if (!VTS::HappensBeforeCached(signaller_vts, current_vts)) {
VTS *new_vts = VTS::Join(current_vts, signaller_vts);
NewSegment("NewSegmentForWait", new_vts);
}
DCHECK(VTS::HappensBeforeCached(signaller_vts, vts()));
}
void NewSegmentForSignal() {
VTS *cur_vts = vts();
VTS *new_vts = VTS::CopyAndTick(cur_vts, tid());
NewSegment("NewSegmentForSignal", new_vts);
}
// When creating a child thread, we need to know
// 1. where the thread was created (ctx)
// 2. What was the vector clock of the parent thread (vts).
struct ThreadCreateInfo {
StackTrace *ctx;
VTS *vts;
};
static void StopIgnoringAccessesInT0BecauseNewThreadStarted() {
AssertTILHeld();
if (g_so_far_only_one_thread) {
g_so_far_only_one_thread = false;
Get(TID(0))->set_ignore_all_accesses(false);
}
}
// This event comes before the child is created (e.g. just
// as we entered pthread_create).
void HandleThreadCreateBefore(TID parent_tid, uintptr_t pc) {
CHECK(parent_tid == tid());
StopIgnoringAccessesInT0BecauseNewThreadStarted();
// Store ctx and vts under TID(0).
ThreadCreateInfo info;
info.ctx = CreateStackTrace(pc);
info.vts = vts()->Clone();
CHECK(info.ctx && info.vts);
child_tid_to_create_info_[TID(0)] = info;
// Tick vts.
this->NewSegmentForSignal();
if (debug_thread) {
Printf("T%d: THR_CREATE_BEFORE\n", parent_tid.raw());
}
}
// This event comes when we are exiting the thread creation routine.
// It may appear before *or* after THR_START event, at least with PIN.
void HandleThreadCreateAfter(TID parent_tid, TID child_tid) {
CHECK(parent_tid == tid());
// Place the info under child_tid if we did not use it yet.
if (child_tid_to_create_info_.count(TID(0))){
child_tid_to_create_info_[child_tid] = child_tid_to_create_info_[TID(0)];
child_tid_to_create_info_.erase(TID(0));
}
if (debug_thread) {
Printf("T%d: THR_CREATE_AFTER %d\n", parent_tid.raw(), child_tid.raw());
}
}
void HandleChildThreadStart(TID child_tid, VTS **vts, StackTrace **ctx) {
TSanThread *parent = this;
ThreadCreateInfo info;
if (child_tid_to_create_info_.count(child_tid)) {
// We already seen THR_CREATE_AFTER, so the info is under child_tid.
info = child_tid_to_create_info_[child_tid];
child_tid_to_create_info_.erase(child_tid);
CHECK(info.ctx && info.vts);
} else if (child_tid_to_create_info_.count(TID(0))){
// We have not seen THR_CREATE_AFTER, but already seen THR_CREATE_BEFORE.
info = child_tid_to_create_info_[TID(0)];
child_tid_to_create_info_.erase(TID(0));
CHECK(info.ctx && info.vts);
} else {
// We have not seen THR_CREATE_BEFORE/THR_CREATE_AFTER.
// If the tool is single-threaded (valgrind) these events are redundant.
info.ctx = parent->CreateStackTrace();
info.vts = parent->vts()->Clone();
parent->NewSegmentForSignal();
}
*ctx = info.ctx;
VTS *singleton = VTS::CreateSingleton(child_tid);
*vts = VTS::Join(singleton, info.vts);
VTS::Unref(singleton);
VTS::Unref(info.vts);
if (debug_thread) {
Printf("T%d: THR_START parent: T%d : %s %s\n", child_tid.raw(),
parent->tid().raw(),
parent->vts()->ToString().c_str(),
(*vts)->ToString().c_str());
if (G_flags->announce_threads) {
Printf("%s\n", (*ctx)->ToString().c_str());
}
}
// Parent should have ticked its VTS so there should be no h-b.
DCHECK(!VTS::HappensBefore(parent->vts(), *vts));
}
// Support for Cyclic Barrier, e.g. pthread_barrier_t.
// We need to create (barrier_count-1)^2 h-b arcs between
// threads blocking on a barrier. We should not create any h-b arcs
// for two calls to barrier_wait if the barrier was reset between then.
struct CyclicBarrierInfo {
// The value given to barrier_init.
uint32_t barrier_count;
// How many times we may block on this barrier before resetting.
int32_t calls_before_reset;
// How many times we entered the 'wait-before' and 'wait-after' handlers.
int32_t n_wait_before, n_wait_after;
};
// The following situation is possible:
// - N threads blocked on a barrier.
// - All N threads reached the barrier and we started getting 'wait-after'
// events, but did not yet get all of them.
// - N threads blocked on the barrier again and we started getting
// 'wait-before' events from the next barrier epoch.
// - We continue getting 'wait-after' events from the previous epoch.
//
// We don't want to create h-b arcs between barrier events of different
// epochs, so we use 'barrier + (epoch % 4)' as an object on which we
// signal and wait (it is unlikely that more than 4 epochs are live at once.
enum { kNumberOfPossibleBarrierEpochsLiveAtOnce = 4 };
// Maps the barrier pointer to CyclicBarrierInfo.
typedef unordered_map<uintptr_t, CyclicBarrierInfo> CyclicBarrierMap;
CyclicBarrierInfo &GetCyclicBarrierInfo(uintptr_t barrier) {
if (cyclic_barrier_map_ == NULL) {
cyclic_barrier_map_ = new CyclicBarrierMap;
}
return (*cyclic_barrier_map_)[barrier];
}
void HandleBarrierInit(uintptr_t barrier, uint32_t n) {
CyclicBarrierInfo &info = GetCyclicBarrierInfo(barrier);
CHECK(n > 0);
memset(&info, 0, sizeof(CyclicBarrierInfo));
info.barrier_count = n;
}
void HandleBarrierWaitBefore(uintptr_t barrier) {
CyclicBarrierInfo &info = GetCyclicBarrierInfo(barrier);
CHECK(info.calls_before_reset >= 0);
int32_t epoch = info.n_wait_before / info.barrier_count;
epoch %= kNumberOfPossibleBarrierEpochsLiveAtOnce;
info.n_wait_before++;
if (info.calls_before_reset == 0) {
// We are blocking the first time after reset. Clear the VTS.
info.calls_before_reset = info.barrier_count;
Signaller &signaller = (*signaller_map_)[barrier + epoch];
VTS::Unref(signaller.vts);
signaller.vts = NULL;
if (debug_happens_before) {
Printf("T%d barrier %p (epoch %d) reset\n", tid().raw(),
barrier, epoch);
}
}
info.calls_before_reset--;
// Signal to all threads that blocked on this barrier.
if (debug_happens_before) {
Printf("T%d barrier %p (epoch %d) wait before\n", tid().raw(),
barrier, epoch);
}
HandleSignal(barrier + epoch);
}
void HandleBarrierWaitAfter(uintptr_t barrier) {
CyclicBarrierInfo &info = GetCyclicBarrierInfo(barrier);
int32_t epoch = info.n_wait_after / info.barrier_count;
epoch %= kNumberOfPossibleBarrierEpochsLiveAtOnce;
info.n_wait_after++;
if (debug_happens_before) {
Printf("T%d barrier %p (epoch %d) wait after\n", tid().raw(),
barrier, epoch);
}
HandleWait(barrier + epoch);
}
// Call stack -------------
void PopCallStack() {
CHECK(!call_stack_->empty());
call_stack_->pop_back();
}
void HandleRtnCall(uintptr_t call_pc, uintptr_t target_pc,
IGNORE_BELOW_RTN ignore_below) {
this->stats.events[RTN_CALL]++;
if (!call_stack_->empty() && call_pc) {
call_stack_->back() = call_pc;
}
call_stack_->push_back(target_pc);
bool ignore = false;
if (ignore_below == IGNORE_BELOW_RTN_UNKNOWN) {
if (ignore_below_cache_.Lookup(target_pc, &ignore) == false) {
ignore = ThreadSanitizerIgnoreAccessesBelowFunction(target_pc);
ignore_below_cache_.Insert(target_pc, ignore);
G_stats->ignore_below_cache_miss++;
} else {
// Just in case, check the result of caching.
DCHECK(ignore ==
ThreadSanitizerIgnoreAccessesBelowFunction(target_pc));
}
} else {
DCHECK(ignore_below == IGNORE_BELOW_RTN_YES ||
ignore_below == IGNORE_BELOW_RTN_NO);
ignore = ignore_below == IGNORE_BELOW_RTN_YES;
}
if (fun_r_ignore_) {
fun_r_ignore_++;
} else if (ignore) {
fun_r_ignore_ = 1;
set_ignore_all_accesses(true);
}
}
void HandleRtnExit() {
this->stats.events[RTN_EXIT]++;
if (!call_stack_->empty()) {
call_stack_->pop_back();
if (fun_r_ignore_) {
if (--fun_r_ignore_ == 0) {
set_ignore_all_accesses(false);
}
}
}
}
uintptr_t GetCallstackEntry(size_t offset_from_top) {
if (offset_from_top >= call_stack_->size()) return 0;
return (*call_stack_)[call_stack_->size() - offset_from_top - 1];
}
string CallStackRtnName(size_t offset_from_top = 0) {
if (call_stack_->size() <= offset_from_top)
return "";
uintptr_t pc = (*call_stack_)[call_stack_->size() - offset_from_top - 1];
return PcToRtnName(pc, false);
}
string CallStackToStringRtnOnly(int len) {
string res;
for (int i = 0; i < len; i++) {
if (i)
res += " ";
res += CallStackRtnName(i);
}
return res;
}
uintptr_t CallStackTopPc() {
if (call_stack_->empty())
return 0;
return call_stack_->back();
}
INLINE void FillEmbeddedStackTrace(uintptr_t *emb_trace) {
size_t size = min(call_stack_->size(), (size_t)kSizeOfHistoryStackTrace);
size_t idx = call_stack_->size() - 1;
uintptr_t *pcs = call_stack_->pcs();
for (size_t i = 0; i < size; i++, idx--) {
emb_trace[i] = pcs[idx];
}
if (size < (size_t) kSizeOfHistoryStackTrace) {
emb_trace[size] = 0;
}
}
INLINE void FillStackTrace(StackTrace *trace, size_t size) {
size_t idx = call_stack_->size() - 1;
uintptr_t *pcs = call_stack_->pcs();
for (size_t i = 0; i < size; i++, idx--) {
trace->Set(i, pcs[idx]);
}
}
INLINE StackTrace *CreateStackTrace(uintptr_t pc = 0,
int max_len = -1,
int capacity = 0) {
if (!call_stack_->empty() && pc) {
call_stack_->back() = pc;
}
if (max_len <= 0) {
max_len = G_flags->num_callers;
}
int size = call_stack_->size();
if (size > max_len)
size = max_len;
StackTrace *res = StackTrace::CreateNewEmptyStackTrace(size, capacity);
FillStackTrace(res, size);
return res;
}
void ReportStackTrace(uintptr_t pc = 0, int max_len = -1) {
StackTrace *trace = CreateStackTrace(pc, max_len);
Report("%s", trace->ToString().c_str());
StackTrace::Delete(trace);
}
static void ForgetAllState() {
// G_flags->debug_level = 2;
for (int i = 0; i < TSanThread::NumberOfThreads(); i++) {
TSanThread *thr = Get(TID(i));
thr->recent_segments_cache_.ForgetAllState();
thr->sid_ = SID(); // Reset the old SID so we don't try to read its VTS.
VTS *singleton_vts = VTS::CreateSingleton(TID(i), 2);
if (thr->is_running()) {
thr->NewSegmentWithoutUnrefingOld("ForgetAllState", singleton_vts);
}
for (map<TID, ThreadCreateInfo>::iterator j =
thr->child_tid_to_create_info_.begin();
j != thr->child_tid_to_create_info_.end(); ++j) {
ThreadCreateInfo &info = j->second;
VTS::Unref(info.vts);
// The parent's VTS should neither happen-before nor equal the child's.
info.vts = VTS::CreateSingleton(TID(i), 1);
}
if (thr->vts_at_exit_) {
VTS::Unref(thr->vts_at_exit_);
thr->vts_at_exit_ = singleton_vts->Clone();
}
thr->dead_sids_.clear();
thr->fresh_sids_.clear();
}
signaller_map_->ClearAndDeleteElements();
}
static void InitClassMembers() {
ScopedMallocCostCenter malloc_cc("InitClassMembers");
all_threads_ = new TSanThread*[G_flags->max_n_threads];
memset(all_threads_, 0, sizeof(TSanThread*) * G_flags->max_n_threads);
n_threads_ = 0;
signaller_map_ = new SignallerMap;
}
BitSet *lock_era_access_set(int is_w) {
return &lock_era_access_set_[is_w];
}
// --------- dead SIDs, fresh SIDs
// When running fast path w/o a lock we need to recycle SIDs to a thread-local
// pool. HasRoomForDeadSids and AddDeadSid may be called w/o a lock.
// FlushDeadSids should be called under a lock.
// When creating a new segment on SBLOCK_ENTER, we need to get a fresh SID
// from somewhere. We keep a pile of fresh ready-to-use SIDs in
// a thread-local array.
enum { kMaxNumDeadSids = 64,
kMaxNumFreshSids = 256, };
INLINE void AddDeadSid(SID sid, const char *where) {
if (TS_SERIALIZED) {
Segment::Unref(sid, where);
} else {
if (Segment::UnrefNoRecycle(sid, where) == 0) {
dead_sids_.push_back(sid);
}
}
}
INLINE void FlushDeadSids() {
if (TS_SERIALIZED) return;
size_t n = dead_sids_.size();
for (size_t i = 0; i < n; i++) {
SID sid = dead_sids_[i];
Segment::AssertLive(sid, __LINE__);
DCHECK(Segment::Get(sid)->ref_count() == 0);
Segment::RecycleOneSid(sid);
}
dead_sids_.clear();
}
INLINE bool HasRoomForDeadSids() const {
return TS_SERIALIZED ? false :
dead_sids_.size() < kMaxNumDeadSids - 2;
}
void GetSomeFreshSids() {
size_t cur_size = fresh_sids_.size();
DCHECK(cur_size <= kMaxNumFreshSids);
if (cur_size > kMaxNumFreshSids / 2) {
// We already have quite a few fresh SIDs, do nothing.
return;
}
DCHECK(fresh_sids_.capacity() >= kMaxNumFreshSids);
size_t n_requested_sids = kMaxNumFreshSids - cur_size;
fresh_sids_.resize(kMaxNumFreshSids);
Segment::AllocateFreshSegments(n_requested_sids, &fresh_sids_[cur_size]);
}
void ReleaseFreshSids() {
for (size_t i = 0; i < fresh_sids_.size(); i++) {
Segment::RecycleOneFreshSid(fresh_sids_[i]);
}
fresh_sids_.clear();
}
private:
bool is_running_;
string thread_name_;
TID tid_; // This thread's tid.
SID sid_; // Current segment ID.
TID parent_tid_; // Parent's tid.
bool thread_local_copy_of_g_has_expensive_flags_;
uintptr_t max_sp_;
uintptr_t min_sp_;
uintptr_t stack_size_for_ignore_;
uintptr_t fun_r_ignore_; // > 0 if we are inside a fun_r-ed function.
uintptr_t min_sp_for_ignore_;
uintptr_t n_mops_since_start_;
StackTrace *creation_context_;
bool announced_;
LSID rd_lockset_;
LSID wr_lockset_;
// These bits should be read in the hottest loop, so we combine them all
// together.
// bit 1 -- ignore reads.
// bit 2 -- ignore writes.
// bit 3 -- have expensive flags
int expensive_bits_;
int ignore_depth_[2];
StackTrace *ignore_context_[2];
VTS *vts_at_exit_;
CallStack *call_stack_;
vector<SID> dead_sids_;
vector<SID> fresh_sids_;
PtrToBoolCache<251> ignore_below_cache_;
LockHistory lock_history_;
BitSet lock_era_access_set_[2];
RecentSegmentsCache recent_segments_cache_;
map<TID, ThreadCreateInfo> child_tid_to_create_info_;
// This var is used to suppress race reports
// when handling atomic memory accesses.
// That is, an atomic memory access can't race with other accesses,
// however plain memory accesses can race with atomic memory accesses.
int inside_atomic_op_;
prng_t rand_state_;
struct Signaller {
VTS *vts;
};
class SignallerMap: public unordered_map<uintptr_t, Signaller> {
public:
void ClearAndDeleteElements() {
for (iterator it = begin(); it != end(); ++it) {
VTS::Unref(it->second.vts);
}
clear();
}
};
// All threads. The main thread has tid 0.
static TSanThread **all_threads_;
static int n_threads_;
// signaller address -> VTS
static SignallerMap *signaller_map_;
static CyclicBarrierMap *cyclic_barrier_map_;
};
INLINE static int32_t raw_tid(TSanThread *t) {
return t->tid().raw();
}
// TSanThread:: static members
TSanThread **TSanThread::all_threads_;
int TSanThread::n_threads_;
TSanThread::SignallerMap *TSanThread::signaller_map_;
TSanThread::CyclicBarrierMap *TSanThread::cyclic_barrier_map_;
// -------- TsanAtomicCore ------------------ {{{1
// Responsible for handling of atomic memory accesses.
class TsanAtomicCore {
public:
TsanAtomicCore();
void HandleWrite(TSanThread* thr,
uintptr_t a,
uint64_t v,
uint64_t prev,
bool is_acquire,
bool is_release,
bool is_rmw);
uint64_t HandleRead(TSanThread* thr,
uintptr_t a,
uint64_t v,
bool is_acquire);
void ClearMemoryState(uintptr_t a, uintptr_t b);
private:
// Represents one value in modification history
// of an atomic variable.
struct AtomicHistoryEntry {
// Actual value.
// (atomics of size more than uint64_t are not supported as of now)
uint64_t val;
// ID of a thread that did the modification.
TID tid;
// The thread's clock during the modification.
int32_t clk;
// Vector clock that is acquired by a thread
// that loads the value.
// Similar to Signaller::vts.
VTS* vts;
};
// Descriptor of an atomic variable.
struct Atomic {
// Number of stored entries in the modification order of the variable.
// This represents space-modelling preciseness trade-off.
// 4 values should be generally enough.
static int32_t const kHistSize = 4;
// Current position in the modification order.
int32_t hist_pos;
// Modification history organized as a circular buffer.
// That is, old values are discarded.
AtomicHistoryEntry hist [kHistSize];
// It's basically a tid->hist_pos map that tracks what threads
// had seen what values. It's required to meet the following requirement:
// even relaxed loads must not be reordered in a single thread.
VectorClock last_seen;
Atomic();
void reset(bool init = false);
};
typedef map<uintptr_t, Atomic> AtomicMap;
AtomicMap atomic_map_;
void AtomicFixHist(Atomic* atomic,
uint64_t prev);
TsanAtomicCore(TsanAtomicCore const&);
void operator=(TsanAtomicCore const&);
};
static TsanAtomicCore* g_atomicCore;
// -------- Clear Memory State ------------------ {{{1
static void INLINE UnrefSegmentsInMemoryRange(uintptr_t a, uintptr_t b,
Mask mask, CacheLine *line) {
while (!mask.Empty()) {
uintptr_t x = mask.GetSomeSetBit();
DCHECK(mask.Get(x));
mask.Clear(x);
line->GetValuePointer(x)->Unref("Detector::UnrefSegmentsInMemoryRange");
}
}
void INLINE ClearMemoryStateInOneLine(TSanThread *thr, uintptr_t addr,
uintptr_t beg, uintptr_t end) {
AssertTILHeld();
CacheLine *line = G_cache->GetLineIfExists(thr, addr, __LINE__);
// CacheLine *line = G_cache->GetLineOrCreateNew(addr, __LINE__);
if (line) {
DCHECK(beg < CacheLine::kLineSize);
DCHECK(end <= CacheLine::kLineSize);
DCHECK(beg < end);
Mask published = line->published();
if (UNLIKELY(!published.Empty())) {
Mask mask(published.GetRange(beg, end));
ClearPublishedAttribute(line, mask);
}
Mask old_used = line->ClearRangeAndReturnOldUsed(beg, end);
UnrefSegmentsInMemoryRange(beg, end, old_used, line);
G_cache->ReleaseLine(thr, addr, line, __LINE__);
}
}
// clear memory state for [a,b)
void NOINLINE ClearMemoryState(TSanThread *thr, uintptr_t a, uintptr_t b) {
if (a == b) return;
CHECK(a < b);
uintptr_t line1_tag = 0, line2_tag = 0;
uintptr_t single_line_tag = GetCacheLinesForRange(a, b,
&line1_tag, &line2_tag);
if (single_line_tag) {
ClearMemoryStateInOneLine(thr, a, a - single_line_tag,
b - single_line_tag);
return;
}
uintptr_t a_tag = CacheLine::ComputeTag(a);
ClearMemoryStateInOneLine(thr, a, a - a_tag, CacheLine::kLineSize);
for (uintptr_t tag_i = line1_tag; tag_i < line2_tag;
tag_i += CacheLine::kLineSize) {
ClearMemoryStateInOneLine(thr, tag_i, 0, CacheLine::kLineSize);
}
if (b > line2_tag) {
ClearMemoryStateInOneLine(thr, line2_tag, 0, b - line2_tag);
}
if (DEBUG_MODE && G_flags->debug_level >= 2) {
// Check that we've cleared it. Slow!
for (uintptr_t x = a; x < b; x++) {
uintptr_t off = CacheLine::ComputeOffset(x);
(void)off;
CacheLine *line = G_cache->GetLineOrCreateNew(thr, x, __LINE__);
CHECK(!line->has_shadow_value().Get(off));
G_cache->ReleaseLine(thr, x, line, __LINE__);
}
}
g_atomicCore->ClearMemoryState(a, b);
}
// -------- PCQ --------------------- {{{1
struct PCQ {
uintptr_t pcq_addr;
deque<VTS*> putters;
};
typedef map<uintptr_t, PCQ> PCQMap;
static PCQMap *g_pcq_map;
// -------- Heap info ---------------------- {{{1
#include "ts_heap_info.h"
// Information about heap memory.
struct HeapInfo {
uintptr_t ptr;
uintptr_t size;
SID sid;
HeapInfo() : ptr(0), size(0), sid(0) { }
Segment *seg() { return Segment::Get(sid); }
TID tid() { return seg()->tid(); }
string StackTraceString() { return Segment::StackTraceString(sid); }
};
static HeapMap<HeapInfo> *G_heap_map;
struct ThreadStackInfo {
uintptr_t ptr;
uintptr_t size;
ThreadStackInfo() : ptr(0), size(0) { }
};
static HeapMap<ThreadStackInfo> *G_thread_stack_map;
// -------- Forget all state -------- {{{1
// We need to forget all state and start over because we've
// run out of some resources (most likely, segment IDs).
static void ForgetAllStateAndStartOver(TSanThread *thr, const char *reason) {
// This is done under the main lock.
AssertTILHeld();
size_t start_time = g_last_flush_time = TimeInMilliSeconds();
Report("T%d INFO: %s. Flushing state.\n", raw_tid(thr), reason);
if (TS_SERIALIZED == 0) {
// We own the lock, but we also must acquire all cache lines
// so that the fast-path (unlocked) code does not execute while
// we are flushing.
G_cache->AcquireAllLines(thr);
}
if (0) {
Report("INFO: Thread Sanitizer will now forget all history.\n");
Report("INFO: This is experimental, and may fail!\n");
if (G_flags->keep_history > 0) {
Report("INFO: Consider re-running with --keep_history=0\n");
}
if (G_flags->show_stats) {
G_stats->PrintStats();
}
}
G_stats->n_forgets++;
Segment::ForgetAllState();
SegmentSet::ForgetAllState();
TSanThread::ForgetAllState();
VTS::FlushHBCache();
G_heap_map->Clear();
g_publish_info_map->clear();
for (PCQMap::iterator it = g_pcq_map->begin(); it != g_pcq_map->end(); ++it) {
PCQ &pcq = it->second;
for (deque<VTS*>::iterator it2 = pcq.putters.begin();
it2 != pcq.putters.end(); ++it2) {
VTS::Unref(*it2);
*it2 = VTS::CreateSingleton(TID(0), 1);
}
}
// Must be the last one to flush as it effectively releases the
// cach lines and enables fast path code to run in other threads.
G_cache->ForgetAllState(thr);
size_t stop_time = TimeInMilliSeconds();
if (DEBUG_MODE || (stop_time - start_time > 0)) {
Report("T%d INFO: Flush took %ld ms\n", raw_tid(thr),
stop_time - start_time);
}
}
static INLINE void FlushStateIfOutOfSegments(TSanThread *thr) {
if (Segment::NumberOfSegments() > kMaxSIDBeforeFlush) {
// too few sids left -- flush state.
if (DEBUG_MODE) {
G_cache->PrintStorageStats();
Segment::ShowSegmentStats();
}
ForgetAllStateAndStartOver(thr, "run out of segment IDs");
}
}
// -------- Expected Race ---------------------- {{{1
typedef HeapMap<ExpectedRace> ExpectedRacesMap;
static ExpectedRacesMap *G_expected_races_map;
static bool g_expecting_races;
static int g_found_races_since_EXPECT_RACE_BEGIN;
ExpectedRace* ThreadSanitizerFindExpectedRace(uintptr_t addr) {
return G_expected_races_map->GetInfo(addr);
}
// -------- Suppressions ----------------------- {{{1
static const char default_suppressions[] =
// TODO(kcc): as it gets bigger, move it into a separate object file.
"# We need to have some default suppressions, but we don't want to \n"
"# keep them in a separate text file, so we keep the in the code. \n"
#ifdef VGO_darwin
"{ \n"
" dyld tries to unlock an invalid mutex when adding/removing image. \n"
" ThreadSanitizer:InvalidLock \n"
" fun:pthread_mutex_unlock \n"
" fun:_dyld_register_func_for_*_image \n"
"} \n"
"{ \n"
" Benign reports in __NSOperationInternal when using workqueue threads \n"
" ThreadSanitizer:Race \n"
" fun:__+[__NSOperationInternal _observeValueForKeyPath:ofObject:changeKind:oldValue:newValue:indexes:context:]_block_invoke_*\n"
" fun:_dispatch_call_block_and_release \n"
"} \n"
"{ \n"
" Benign race in GCD when using workqueue threads. \n"
" ThreadSanitizer:Race \n"
" fun:____startOperations_block_invoke_* \n"
" ... \n"
" fun:_dispatch_call_block_and_release \n"
"} \n"
"{ \n"
" Benign race in NSOQSchedule when using workqueue threads. \n"
" ThreadSanitizer:Race \n"
" fun:__doStart* \n"
" ... \n"
" fun:_dispatch_call_block_and_release \n"
"} \n"
#endif
#ifndef _MSC_VER
"{ \n"
" False reports on std::string internals. See TSan issue #40. \n"
" ThreadSanitizer:Race \n"
" ... \n"
" fun:*~basic_string* \n"
"} \n"
"{ \n"
" False reports on std::string internals. See TSan issue #40. \n"
" ThreadSanitizer:Race \n"
" ... \n"
" fun:*basic_string*_M_destroy \n"
"} \n"
#else
"{ \n"
" False lock report inside ntdll.dll \n"
" ThreadSanitizer:InvalidLock \n"
" fun:* \n"
" obj:*ntdll.dll \n"
"} \n"
"{ \n"
" False report due to lack of debug symbols in ntdll.dll (a) \n"
" ThreadSanitizer:InvalidLock \n"
" fun:*SRWLock* \n"
"} \n"
"{ \n"
" False report due to lack of debug symbols in ntdll.dll (b) \n"
" ThreadSanitizer:UnlockForeign \n"
" fun:*SRWLock* \n"
"} \n"
"{ \n"
" False report due to lack of debug symbols in ntdll.dll (c) \n"
" ThreadSanitizer:UnlockNonLocked \n"
" fun:*SRWLock* \n"
"} \n"
"{ \n"
" False reports on std::string internals (2). See TSan issue #40. \n"
" ThreadSanitizer:Race \n"
" ... \n"
" fun:*basic_string*scalar deleting destructor* \n"
"} \n"
#endif
#ifdef TS_PIN
"{ \n"
" Suppression for issue 54 (PIN lacks support for IFUNC) \n"
" ThreadSanitizer:Race \n"
" ... \n"
" fun:*NegativeTests_Strlen::Worker* \n"
"} \n"
#endif
;
// -------- Report Storage --------------------- {{{1
class ReportStorage {
public:
ReportStorage()
: n_reports(0),
n_race_reports(0),
program_finished_(0),
unwind_cb_(0) {
if (G_flags->generate_suppressions) {
Report("INFO: generate_suppressions = true\n");
}
// Read default suppressions
int n = suppressions_.ReadFromString(default_suppressions);
if (n == -1) {
Report("Error reading default suppressions at line %d: %s\n",
suppressions_.GetErrorLineNo(),
suppressions_.GetErrorString().c_str());
exit(1);
}
// Read user-supplied suppressions.
for (size_t i = 0; i < G_flags->suppressions.size(); i++) {
const string &supp_path = G_flags->suppressions[i];
Report("INFO: reading suppressions file %s\n", supp_path.c_str());
int n = suppressions_.ReadFromString(ReadFileToString(supp_path, true));
if (n == -1) {
Report("Error at line %d: %s\n",
suppressions_.GetErrorLineNo(),
suppressions_.GetErrorString().c_str());
exit(1);
}
Report("INFO: %6d suppression(s) read from file %s\n",
n, supp_path.c_str());
}
}
bool NOINLINE AddReport(TSanThread *thr, uintptr_t pc, bool is_w, uintptr_t addr,
int size,
ShadowValue old_sval, ShadowValue new_sval,
bool is_published) {
{
// Check this isn't a "_ZNSs4_Rep20_S_empty_rep_storageE" report.
uintptr_t offset;
string symbol_descr;
if (GetNameAndOffsetOfGlobalObject(addr, &symbol_descr, &offset)) {
if (StringMatch("*empty_rep_storage*", symbol_descr))
return false;
if (StringMatch("_IO_stdfile_*_lock", symbol_descr))
return false;
if (StringMatch("_IO_*_stdout_", symbol_descr))
return false;
if (StringMatch("_IO_*_stderr_", symbol_descr))
return false;
}
}
bool is_expected = false;
ExpectedRace *expected_race = G_expected_races_map->GetInfo(addr);
if (debug_expected_races) {
Printf("Checking expected race for %lx; exp_race=%p\n",
addr, expected_race);
if (expected_race) {
Printf(" FOUND\n");
}
}
if (expected_race) {
if (G_flags->nacl_untrusted != expected_race->is_nacl_untrusted) {
Report("WARNING: this race is only expected in NaCl %strusted mode\n",
expected_race->is_nacl_untrusted ? "un" : "");
} else {
is_expected = true;
expected_race->count++;
}
}
if (g_expecting_races) {
is_expected = true;
g_found_races_since_EXPECT_RACE_BEGIN++;
}
if (is_expected && !G_flags->show_expected_races) return false;
StackTrace *stack_trace = thr->CreateStackTrace(pc);
if (unwind_cb_) {
int const maxcnt = 256;
uintptr_t cur_stack [maxcnt];
int cnt = unwind_cb_(cur_stack, maxcnt, pc);
if (cnt > 0 && cnt <= maxcnt) {
cnt = min<int>(cnt, stack_trace->capacity());
stack_trace->set_size(cnt);
for (int i = 0; i < cnt; i++)
stack_trace->Set(i, cur_stack[i]);
}
}
int n_reports_for_this_context = reported_stacks_[stack_trace]++;
if (n_reports_for_this_context > 0) {
// we already reported a race here.
StackTrace::Delete(stack_trace);
return false;
}
ThreadSanitizerDataRaceReport *race_report =
new ThreadSanitizerDataRaceReport;
race_report->type = ThreadSanitizerReport::DATA_RACE;
race_report->new_sval = new_sval;
race_report->old_sval = old_sval;
race_report->is_expected = is_expected;
race_report->last_access_is_w = is_w;
race_report->racey_addr = addr;
race_report->racey_addr_description = DescribeMemory(addr);
race_report->last_access_tid = thr->tid();
race_report->last_access_sid = thr->sid();
race_report->last_access_size = size;
race_report->stack_trace = stack_trace;
race_report->racey_addr_was_published = is_published;
race_report->last_acces_lsid[false] = thr->lsid(false);
race_report->last_acces_lsid[true] = thr->lsid(true);
Segment *seg = Segment::Get(thr->sid());
(void)seg;
CHECK(thr->lsid(false) == seg->lsid(false));
CHECK(thr->lsid(true) == seg->lsid(true));
return ThreadSanitizerPrintReport(race_report);
}
void AnnounceThreadsInSegmentSet(SSID ssid) {
if (ssid.IsEmpty()) return;
for (int s = 0; s < SegmentSet::Size(ssid); s++) {
Segment *seg = SegmentSet::GetSegmentForNonSingleton(ssid, s, __LINE__);
TSanThread::Get(seg->tid())->Announce();
}
}
void PrintConcurrentSegmentSet(SSID ssid, TID tid, SID sid,
LSID lsid, bool is_w,
const char *descr, set<LID> *locks,
set<SID>* concurrent_sids) {
if (ssid.IsEmpty()) return;
bool printed_header = false;
TSanThread *thr1 = TSanThread::Get(tid);
for (int s = 0; s < SegmentSet::Size(ssid); s++) {
SID concurrent_sid = SegmentSet::GetSID(ssid, s, __LINE__);
Segment *seg = Segment::Get(concurrent_sid);
if (Segment::HappensBeforeOrSameThread(concurrent_sid, sid)) continue;
if (!LockSet::IntersectionIsEmpty(lsid, seg->lsid(is_w))) continue;
if (concurrent_sids) {
concurrent_sids->insert(concurrent_sid);
}
TSanThread *thr2 = TSanThread::Get(seg->tid());
if (!printed_header) {
Report(" %sConcurrent %s happened at (OR AFTER) these points:%s\n",
c_magenta, descr, c_default);
printed_header = true;
}
Report(" %s (%s):\n",
thr2->ThreadName().c_str(),
TwoLockSetsToString(seg->lsid(false),
seg->lsid(true)).c_str());
if (G_flags->show_states) {
Report(" S%d\n", concurrent_sid.raw());
}
LockSet::AddLocksToSet(seg->lsid(false), locks);
LockSet::AddLocksToSet(seg->lsid(true), locks);
Report("%s", Segment::StackTraceString(concurrent_sid).c_str());
if (!G_flags->pure_happens_before &&
G_flags->suggest_happens_before_arcs) {
set<LID> message_locks;
// Report("Locks in T%d\n", thr1->tid().raw());
// thr1->lock_history().PrintLocks();
// Report("Unlocks in T%d\n", thr2->tid().raw());
// thr2->lock_history().PrintUnlocks();
if (LockHistory::Intersect(thr1->lock_history(), thr2->lock_history(),
seg->lock_era(), &message_locks)) {
Report(" Note: these locks were recently released by T%d"
" and later acquired by T%d: {%s}\n"
" See http://code.google.com/p/data-race-test/wiki/"
"PureHappensBeforeVsHybrid\n",
thr2->tid().raw(),
thr1->tid().raw(),
SetOfLocksToString(message_locks).c_str());
locks->insert(message_locks.begin(), message_locks.end());
}
}
}
}
void SetProgramFinished() {
CHECK(!program_finished_);
program_finished_ = true;
}
string RaceInfoString(uintptr_t pc, set<SID>& concurrent_sids) {
string s;
char buf[100];
snprintf(buf, 100, "Race verifier data: %p", (void*)pc);
s += buf;
for (set<SID>::iterator it = concurrent_sids.begin();
it != concurrent_sids.end(); ++it) {
// Take the first pc of the concurrent stack trace.
uintptr_t concurrent_pc = *Segment::embedded_stack_trace(*it);
snprintf(buf, 100, ",%p", (void*)concurrent_pc);
s += buf;
}
s += "\n";
return s;
}
void PrintRaceReport(ThreadSanitizerDataRaceReport *race) {
bool short_report = program_finished_;
if (!short_report) {
AnnounceThreadsInSegmentSet(race->new_sval.rd_ssid());
AnnounceThreadsInSegmentSet(race->new_sval.wr_ssid());
}
bool is_w = race->last_access_is_w;
TID tid = race->last_access_tid;
TSanThread *thr = TSanThread::Get(tid);
SID sid = race->last_access_sid;
LSID lsid = race->last_acces_lsid[is_w];
set<LID> all_locks;
n_race_reports++;
if (G_flags->html) {
Report("<b id=race%d>Race report #%d; </b>"
"<a href=\"#race%d\">Next;</a> "
"<a href=\"#race%d\">Prev;</a>\n",
n_race_reports, n_race_reports,
n_race_reports+1, n_race_reports-1);
}
// Note the {{{ and }}}. These are for vim folds.
Report("%sWARNING: %s data race during %s of size %d at %p: {{{%s\n",
c_red,
race->is_expected ? "Expected" : "Possible",
is_w ? "write" : "read",
race->last_access_size,
race->racey_addr,
c_default);
if (!short_report) {
LockSet::AddLocksToSet(race->last_acces_lsid[false], &all_locks);
LockSet::AddLocksToSet(race->last_acces_lsid[true], &all_locks);
Report(" %s (%s):\n",
thr->ThreadName().c_str(),
TwoLockSetsToString(race->last_acces_lsid[false],
race->last_acces_lsid[true]).c_str());
}
CHECK(race->stack_trace);
Report("%s", race->stack_trace->ToString().c_str());
if (short_report) {
Report(" See the full version of this report above.\n");
Report("}%s\n", "}}");
return;
}
// Report(" sid=%d; vts=%s\n", thr->sid().raw(),
// thr->vts()->ToString().c_str());
if (G_flags->show_states) {
Report(" old state: %s\n", race->old_sval.ToString().c_str());
Report(" new state: %s\n", race->new_sval.ToString().c_str());
}
set<SID> concurrent_sids;
if (G_flags->keep_history) {
PrintConcurrentSegmentSet(race->new_sval.wr_ssid(),
tid, sid, lsid, true, "write(s)", &all_locks,
&concurrent_sids);
if (is_w) {
PrintConcurrentSegmentSet(race->new_sval.rd_ssid(),
tid, sid, lsid, false, "read(s)", &all_locks,
&concurrent_sids);
}
} else {
Report(" %sAccess history is disabled. "
"Consider running with --keep-history=1 for better reports.%s\n",
c_cyan, c_default);
}
if (race->racey_addr_was_published) {
Report(" This memory was published\n");
}
if (race->racey_addr_description.size() > 0) {
Report("%s", race->racey_addr_description.c_str());
}
if (race->is_expected) {
ExpectedRace *expected_race =
G_expected_races_map->GetInfo(race->racey_addr);
if (expected_race) {
CHECK(expected_race->description);
Report(" Description: \"%s\"\n", expected_race->description);
}
}
set<LID> locks_reported;
if (!all_locks.empty()) {
Report(" %sLocks involved in this report "
"(reporting last lock sites):%s {%s}\n",
c_green, c_default,
SetOfLocksToString(all_locks).c_str());
for (set<LID>::iterator it = all_locks.begin();
it != all_locks.end(); ++it) {
LID lid = *it;
Lock::ReportLockWithOrWithoutContext(lid, true);
}
}
string raceInfoString = RaceInfoString(race->stack_trace->Get(0),
concurrent_sids);
Report(" %s", raceInfoString.c_str());
Report("}}}\n");
}
bool PrintReport(ThreadSanitizerReport *report) {
CHECK(report);
// Check if we have a suppression.
vector<string> funcs_mangled;
vector<string> funcs_demangled;
vector<string> objects;
CHECK(!g_race_verifier_active);
CHECK(report->stack_trace);
CHECK(report->stack_trace->size());
for (size_t i = 0; i < report->stack_trace->size(); i++) {
uintptr_t pc = report->stack_trace->Get(i);
string img, rtn, file;
int line;
PcToStrings(pc, false, &img, &rtn, &file, &line);
if (rtn == "(below main)" || rtn == "ThreadSanitizerStartThread")
break;
funcs_mangled.push_back(rtn);
funcs_demangled.push_back(NormalizeFunctionName(PcToRtnName(pc, true)));
objects.push_back(img);
if (rtn == "main")
break;
}
string suppression_name;
if (suppressions_.StackTraceSuppressed("ThreadSanitizer",
report->ReportName(),
funcs_mangled,
funcs_demangled,
objects,
&suppression_name)) {
used_suppressions_[suppression_name]++;
return false;
}
// Actually print it.
if (report->type == ThreadSanitizerReport::UNLOCK_FOREIGN) {
ThreadSanitizerBadUnlockReport *bad_unlock =
reinterpret_cast<ThreadSanitizerBadUnlockReport*>(report);
Report("WARNING: Lock %s was released by thread T%d"
" which did not acquire this lock: {{{\n%s}}}\n",
Lock::ToString(bad_unlock->lid).c_str(),
bad_unlock->tid.raw(),
bad_unlock->stack_trace->ToString().c_str());
} else if (report->type == ThreadSanitizerReport::UNLOCK_NONLOCKED) {
ThreadSanitizerBadUnlockReport *bad_unlock =
reinterpret_cast<ThreadSanitizerBadUnlockReport*>(report);
Report("WARNING: Unlocking a non-locked lock %s in thread T%d: "
"{{{\n%s}}}\n",
Lock::ToString(bad_unlock->lid).c_str(),
bad_unlock->tid.raw(),
bad_unlock->stack_trace->ToString().c_str());
} else if (report->type == ThreadSanitizerReport::INVALID_LOCK) {
ThreadSanitizerInvalidLockReport *invalid_lock =
reinterpret_cast<ThreadSanitizerInvalidLockReport*>(report);
Report("WARNING: accessing an invalid lock %p in thread T%d: "
"{{{\n%s}}}\n",
invalid_lock->lock_addr,
invalid_lock->tid.raw(),
invalid_lock->stack_trace->ToString().c_str());
} else if (report->type == ThreadSanitizerReport::ATOMICITY_VIOLATION) {
ThreadSanitizerAtomicityViolationReport *av =
reinterpret_cast<ThreadSanitizerAtomicityViolationReport*>(report);
Report("WARNING: Suspected atomicity violation {{{\n");
av->r1->Print();
av->r2->Print();
av->r3->Print();
Report("}}}\n");
} else {
CHECK(report->type == ThreadSanitizerReport::DATA_RACE);
ThreadSanitizerDataRaceReport *race =
reinterpret_cast<ThreadSanitizerDataRaceReport*>(report);
PrintRaceReport(race);
}
n_reports++;
SetNumberOfFoundErrors(n_reports);
if (!G_flags->summary_file.empty()) {
char buff[100];
snprintf(buff, sizeof(buff),
"ThreadSanitizer: %d warning(s) reported\n", n_reports);
// We overwrite the contents of this file with the new summary.
// We don't do that at the end because even if we crash later
// we will already have the summary.
OpenFileWriteStringAndClose(G_flags->summary_file, buff);
}
// Generate a suppression.
if (G_flags->generate_suppressions) {
string supp = "{\n";
supp += " <Put your suppression name here>\n";
supp += string(" ThreadSanitizer:") + report->ReportName() + "\n";
for (size_t i = 0; i < funcs_mangled.size(); i++) {
const string &func = funcs_demangled[i];
if (func.size() == 0 || func == "(no symbols") {
supp += " obj:" + objects[i] + "\n";
} else {
supp += " fun:" + funcs_demangled[i] + "\n";
}
if (StackTrace::CutStackBelowFunc(funcs_demangled[i])) {
break;
}
}
supp += "}";
Printf("------- suppression -------\n%s\n------- end suppression -------\n",
supp.c_str());
}
return true;
}
void PrintUsedSuppression() {
for (map<string, int>::iterator it = used_suppressions_.begin();
it != used_suppressions_.end(); ++it) {
Report("used_suppression: %d %s\n", it->second, it->first.c_str());
}
}
void PrintSummary() {
Report("ThreadSanitizer summary: reported %d warning(s) (%d race(s))\n",
n_reports, n_race_reports);
}
string DescribeMemory(uintptr_t a) {
const int kBufLen = 1023;
char buff[kBufLen+1];
// Is this stack?
for (int i = 0; i < TSanThread::NumberOfThreads(); i++) {
TSanThread *t = TSanThread::Get(TID(i));
if (!t || !t->is_running()) continue;
if (t->MemoryIsInStack(a)) {
snprintf(buff, sizeof(buff),
" %sLocation %p is %ld bytes inside T%d's stack [%p,%p]%s\n",
c_blue,
reinterpret_cast<void*>(a),
static_cast<long>(t->max_sp() - a),
i,
reinterpret_cast<void*>(t->min_sp()),
reinterpret_cast<void*>(t->max_sp()),
c_default
);
return buff;
}
}
HeapInfo *heap_info = G_heap_map->GetInfo(a);
if (heap_info) {
snprintf(buff, sizeof(buff),
" %sLocation %p is %ld bytes inside a block starting at %p"
" of size %ld allocated by T%d from heap:%s\n",
c_blue,
reinterpret_cast<void*>(a),
static_cast<long>(a - heap_info->ptr),
reinterpret_cast<void*>(heap_info->ptr),
static_cast<long>(heap_info->size),
heap_info->tid().raw(), c_default);
return string(buff) + heap_info->StackTraceString().c_str();
}
// Is it a global object?
uintptr_t offset;
string symbol_descr;
if (GetNameAndOffsetOfGlobalObject(a, &symbol_descr, &offset)) {
snprintf(buff, sizeof(buff),
" %sAddress %p is %d bytes inside data symbol \"",
c_blue, reinterpret_cast<void*>(a), static_cast<int>(offset));
return buff + symbol_descr + "\"" + c_default + "\n";
}
if (G_flags->debug_level >= 2) {
string res;
// Is this near stack?
for (int i = 0; i < TSanThread::NumberOfThreads(); i++) {
TSanThread *t = TSanThread::Get(TID(i));
const uintptr_t kMaxStackDiff = 1024 * 16;
uintptr_t diff1 = a - t->max_sp();
uintptr_t diff2 = t->min_sp() - a;
if (diff1 < kMaxStackDiff ||
diff2 < kMaxStackDiff ||
t->MemoryIsInStack(a)) {
uintptr_t diff = t->MemoryIsInStack(a) ? 0 :
(diff1 < kMaxStackDiff ? diff1 : diff2);
snprintf(buff, sizeof(buff),
" %sLocation %p is within %d bytes outside T%d's stack [%p,%p]%s\n",
c_blue,
reinterpret_cast<void*>(a),
static_cast<int>(diff),
i,
reinterpret_cast<void*>(t->min_sp()),
reinterpret_cast<void*>(t->max_sp()),
c_default
);
res += buff;
}
}
if (res.size() > 0) {
return res +
" This report _may_ indicate that valgrind incorrectly "
"computed the stack boundaries\n";
}
}
return "";
}
void SetUnwindCallback(ThreadSanitizerUnwindCallback cb) {
unwind_cb_ = cb;
}
private:
map<StackTrace *, int, StackTrace::Less> reported_stacks_;
int n_reports;
int n_race_reports;
bool program_finished_;
Suppressions suppressions_;
map<string, int> used_suppressions_;
ThreadSanitizerUnwindCallback unwind_cb_;
};
// -------- Event Sampling ---------------- {{{1
// This class samples (profiles) events.
// Instances of this class should all be static.
class EventSampler {
public:
// Sample one event
void Sample(TSanThread *thr, const char *event_name, bool need_locking) {
CHECK_NE(G_flags->sample_events, 0);
(counter_)++;
if ((counter_ & ((1 << G_flags->sample_events) - 1)) != 0)
return;
TIL til(ts_lock, 8, need_locking);
string pos = thr->CallStackToStringRtnOnly(G_flags->sample_events_depth);
(*samples_)[event_name][pos]++;
total_samples_++;
if (total_samples_ >= print_after_this_number_of_samples_) {
print_after_this_number_of_samples_ +=
print_after_this_number_of_samples_ / 2;
ShowSamples();
}
}
// Show existing samples
static void ShowSamples() {
if (G_flags->sample_events == 0) return;
Printf("ShowSamples: (all samples: %lld)\n", total_samples_);
for (SampleMapMap::iterator it1 = samples_->begin();
it1 != samples_->end(); ++it1) {
string name = it1->first;
SampleMap &m = it1->second;
int total = 0;
for (SampleMap::iterator it2 = m.begin(); it2 != m.end(); it2++) {
total += it2->second;
}
map<int, string> reverted_map;
for (SampleMap::iterator it2 = m.begin(); it2 != m.end(); it2++) {
int n_samples = it2->second;
if (n_samples * 1000 < total) continue;
reverted_map[n_samples] = it2->first;
}
Printf("%s: total samples %'d (~%'lld events)\n", name.c_str(),
total,
(int64_t)total << G_flags->sample_events);
for (map<int, string>::iterator it = reverted_map.begin();
it != reverted_map.end(); ++it) {
Printf("%s: %d samples (~%d%%) %s\n", name.c_str(), it->first,
(it->first * 100) / total, it->second.c_str());
}
Printf("\n");
}
}
static void InitClassMembers() {
samples_ = new SampleMapMap;
total_samples_ = 0;
print_after_this_number_of_samples_ = 1000;
}
private:
int counter_;
typedef map<string, int> SampleMap;
typedef map<string, SampleMap> SampleMapMap;
static SampleMapMap *samples_;
static int64_t total_samples_;
static int64_t print_after_this_number_of_samples_;
};
EventSampler::SampleMapMap *EventSampler::samples_;
int64_t EventSampler::total_samples_;
int64_t EventSampler::print_after_this_number_of_samples_;
// -------- Detector ---------------------- {{{1
// Collection of event handlers.
class Detector {
public:
void INLINE HandleTraceLoop(TSanThread *thr, uintptr_t pc,
MopInfo *mops,
uintptr_t *tleb, size_t n,
int expensive_bits, bool need_locking) {
bool has_expensive_flags = (expensive_bits & 4) != 0;
size_t i = 0;
uintptr_t sblock_pc = pc;
size_t n_locks = 0;
do {
uintptr_t addr = tleb[i];
if (addr == 0) continue; // This mop was not executed.
MopInfo *mop = &mops[i];
tleb[i] = 0; // we've consumed this mop, clear it.
DCHECK(mop->size() != 0);
DCHECK(mop->pc() != 0);
if ((expensive_bits & 1) && mop->is_write() == false) continue;
if ((expensive_bits & 2) && mop->is_write() == true) continue;
n_locks += HandleMemoryAccessInternal(thr, &sblock_pc, addr, mop,
has_expensive_flags,
need_locking);
} while (++i < n);
if (has_expensive_flags) {
const size_t mop_stat_size = TS_ARRAY_SIZE(thr->stats.mops_per_trace);
thr->stats.mops_per_trace[min(n, mop_stat_size - 1)]++;
const size_t stat_size = TS_ARRAY_SIZE(thr->stats.locks_per_trace);
thr->stats.locks_per_trace[min(n_locks, stat_size - 1)]++;
}
}
#ifdef _MSC_VER
NOINLINE
// With MSVC, INLINE would cause the compilation to be insanely slow.
#else
INLINE
#endif
void HandleTrace(TSanThread *thr, MopInfo *mops, size_t n, uintptr_t pc,
uintptr_t *tleb, bool need_locking) {
DCHECK(n);
// 0 bit - ignore reads, 1 bit -- ignore writes,
// 2 bit - has_expensive_flags.
int expensive_bits = thr->expensive_bits();
if (expensive_bits == 0) {
HandleTraceLoop(thr, pc, mops, tleb, n, 0, need_locking);
} else {
if ((expensive_bits & 3) == 3) {
// everything is ignored, just clear the tleb.
for (size_t i = 0; i < n; i++) tleb[i] = 0;
} else {
HandleTraceLoop(thr, pc, mops, tleb, n, expensive_bits, need_locking);
}
}
// At the end, the tleb must be cleared.
for (size_t i = 0; i < n; i++) DCHECK(tleb[i] == 0);
}
// Special case of a trace with just one mop and no sblock.
void INLINE HandleMemoryAccess(TSanThread *thr, uintptr_t pc,
uintptr_t addr, uintptr_t size,
bool is_w, bool need_locking) {
CHECK(size);
MopInfo mop(pc, size, is_w, false);
HandleTrace(thr, &mop, 1, 0/*no sblock*/, &addr, need_locking);
}
void ShowUnfreedHeap() {
// check if there is not deleted memory
// (for debugging free() interceptors, not for leak detection)
if (DEBUG_MODE && G_flags->debug_level >= 1) {
for (HeapMap<HeapInfo>::iterator it = G_heap_map->begin();
it != G_heap_map->end(); ++it) {
HeapInfo &info = it->second;
Printf("Not free()-ed memory: %p [%p, %p)\n%s\n",
info.size, info.ptr, info.ptr + info.size,
info.StackTraceString().c_str());
}
}
}
void FlushExpectedRaces(bool print_summary) {
// Report("ThreadSanitizerValgrind: done\n");
// check if we found all expected races (for unit tests only).
static int total_missing = 0;
int this_flush_missing = 0;
for (ExpectedRacesMap::iterator it = G_expected_races_map->begin();
it != G_expected_races_map->end(); ++it) {
ExpectedRace race = it->second;
if (debug_expected_races) {
Printf("Checking if expected race fired: %p\n", race.ptr);
}
if (race.count == 0 &&
!(g_race_verifier_active && !race.is_verifiable) &&
(G_flags->nacl_untrusted == race.is_nacl_untrusted)) {
++this_flush_missing;
Printf("Missing an expected race on %p: %s (annotated at %s)\n",
it->first,
race.description,
PcToRtnNameAndFilePos(race.pc).c_str());
}
}
if (this_flush_missing) {
int n_errs = GetNumberOfFoundErrors();
SetNumberOfFoundErrors(n_errs + this_flush_missing);
total_missing += this_flush_missing;
}
G_expected_races_map->Clear();
if (print_summary && total_missing > 0)
Report("WARNING: %d expected race(s) NOT detected!\n", total_missing);
}
void HandleProgramEnd() {
FlushExpectedRaces(true);
// ShowUnfreedHeap();
EventSampler::ShowSamples();
ShowStats();
TraceInfo::PrintTraceProfile();
ShowProcSelfStatus();
reports_.PrintUsedSuppression();
reports_.PrintSummary();
// Report("ThreadSanitizerValgrind: exiting\n");
}
void FlushIfOutOfMem(TSanThread *thr) {
static int max_vm_size;
static int soft_limit;
const int hard_limit = G_flags->max_mem_in_mb;
const int minimal_soft_limit = (hard_limit * 13) / 16;
const int print_info_limit = (hard_limit * 12) / 16;
CHECK(hard_limit > 0);
int vm_size_in_mb = GetVmSizeInMb();
if (max_vm_size < vm_size_in_mb) {
max_vm_size = vm_size_in_mb;
if (max_vm_size > print_info_limit) {
Report("INFO: ThreadSanitizer's VmSize: %dM\n", (int)max_vm_size);
}
}
if (soft_limit == 0) {
soft_limit = minimal_soft_limit;
}
if (vm_size_in_mb > soft_limit) {
ForgetAllStateAndStartOver(thr,
"ThreadSanitizer is running close to its memory limit");
soft_limit = vm_size_in_mb + 1;
}
}
// Force state flushing.
void FlushState(TID tid) {
ForgetAllStateAndStartOver(TSanThread::Get(tid),
"State flushing requested by client");
}
void FlushIfNeeded(TSanThread *thr) {
// Are we out of segment IDs?
#ifdef TS_VALGRIND // GetVmSizeInMb() works only with valgrind any way.
static int counter;
counter++; // ATTENTION: don't do this in multi-threaded code -- too slow.
CHECK(TS_SERIALIZED == 1);
// Are we out of memory?
if (G_flags->max_mem_in_mb > 0) {
const int kFreq = 1014 * 32;
if ((counter % kFreq) == 0) { // Don't do it too often.
// TODO(kcc): find a way to check memory limit more frequently.
TIL til(ts_lock, 7);
AssertTILHeld();
FlushIfOutOfMem(thr);
}
}
#if 0
if ((counter % (1024 * 1024 * 64)) == 0 ||
counter == (1024 * 1024)) {
// ShowStats();
EventSampler::ShowSamples();
TraceInfo::PrintTraceProfile();
}
#endif
#endif
#if 0 // do we still need it? Hope not..
size_t flush_period = G_flags->flush_period * 1000; // milliseconds.
if (flush_period && (counter % (1024 * 4)) == 0) {
size_t cur_time = TimeInMilliSeconds();
if (cur_time - g_last_flush_time > flush_period) {
TIL til(ts_lock, 7);
ForgetAllStateAndStartOver(
"Doing periodic flush (period is set by --flush_period=n_seconds)");
}
}
#endif
}
void HandleRtnCall(TID tid, uintptr_t call_pc, uintptr_t target_pc,
IGNORE_BELOW_RTN ignore_below) {
TSanThread *thr = TSanThread::Get(tid);
thr->HandleRtnCall(call_pc, target_pc, ignore_below);
FlushIfNeeded(thr);
}
void INLINE HandleOneEvent(Event *e) {
ScopedMallocCostCenter malloc_cc("HandleOneEvent");
DCHECK(e);
EventType type = e->type();
DCHECK(type != NOOP);
TSanThread *thr = NULL;
if (type != THR_START) {
thr = TSanThread::Get(TID(e->tid()));
DCHECK(thr);
thr->SetTopPc(e->pc());
thr->stats.events[type]++;
}
switch (type) {
case READ:
HandleMemoryAccess(thr, e->pc(), e->a(), e->info(), false, true);
return;
case WRITE:
HandleMemoryAccess(thr, e->pc(), e->a(), e->info(), true, true);
return;
case RTN_CALL:
HandleRtnCall(TID(e->tid()), e->pc(), e->a(),
IGNORE_BELOW_RTN_UNKNOWN);
return;
case RTN_EXIT:
thr->HandleRtnExit();
return;
default: break;
}
// Everything else is under a lock.
TIL til(ts_lock, 0);
AssertTILHeld();
if (UNLIKELY(type == THR_START)) {
HandleThreadStart(TID(e->tid()), TID(e->info()), (CallStack*)e->pc());
TSanThread::Get(TID(e->tid()))->stats.events[type]++;
return;
}
FlushStateIfOutOfSegments(thr);
// Since we have the lock, get some fresh SIDs.
thr->GetSomeFreshSids();
switch (type) {
case THR_START : CHECK(0); break;
break;
case SBLOCK_ENTER:
if (thr->ignore_reads() && thr->ignore_writes()) break;
thr->HandleSblockEnter(e->pc(), /*allow_slow_path=*/true);
break;
case THR_CREATE_BEFORE:
thr->HandleThreadCreateBefore(TID(e->tid()), e->pc());
break;
case THR_CREATE_AFTER:
thr->HandleThreadCreateAfter(TID(e->tid()), TID(e->info()));
break;
case THR_FIRST_INSN:
HandleThreadFirstInsn(TID(e->tid()));
break;
case THR_JOIN_AFTER : HandleThreadJoinAfter(e); break;
case THR_STACK_TOP : HandleThreadStackTop(e); break;
case THR_END : HandleThreadEnd(TID(e->tid())); break;
case MALLOC : HandleMalloc(e, false); break;
case FREE : HandleFree(e); break;
case MMAP : HandleMalloc(e, true); break; // same as MALLOC
case MUNMAP : HandleMunmap(e); break;
case WRITER_LOCK : thr->HandleLock(e->a(), true); break;
case READER_LOCK : thr->HandleLock(e->a(), false); break;
case UNLOCK : thr->HandleUnlock(e->a()); break;
case UNLOCK_OR_INIT : HandleUnlockOrInit(e); break;
case LOCK_CREATE:
case LOCK_DESTROY: HandleLockCreateOrDestroy(e); break;
case SIGNAL : thr->HandleSignal(e->a()); break;
case WAIT : thr->HandleWait(e->a()); break;
case CYCLIC_BARRIER_INIT:
thr->HandleBarrierInit(e->a(), e->info());
break;
case CYCLIC_BARRIER_WAIT_BEFORE :
thr->HandleBarrierWaitBefore(e->a());
break;
case CYCLIC_BARRIER_WAIT_AFTER :
thr->HandleBarrierWaitAfter(e->a());
break;
case PCQ_CREATE : HandlePcqCreate(e); break;
case PCQ_DESTROY : HandlePcqDestroy(e); break;
case PCQ_PUT : HandlePcqPut(e); break;
case PCQ_GET : HandlePcqGet(e); break;
case EXPECT_RACE :
HandleExpectRace(e->a(), (const char*)e->pc(), TID(e->tid()));
break;
case BENIGN_RACE :
HandleBenignRace(e->a(), e->info(),
(const char*)e->pc(), TID(e->tid()));
break;
case FLUSH_EXPECTED_RACES:
FlushExpectedRaces(false);
break;
case EXPECT_RACE_BEGIN:
CHECK(g_expecting_races == false);
g_expecting_races = true;
g_found_races_since_EXPECT_RACE_BEGIN = 0;
break;
case EXPECT_RACE_END:
CHECK(g_expecting_races == true);
g_expecting_races = false;
if (g_found_races_since_EXPECT_RACE_BEGIN == 0) {
int n_errs = GetNumberOfFoundErrors();
SetNumberOfFoundErrors(n_errs + 1);
Printf("WARNING: expected race not found.\n");
}
break;
case HB_LOCK : HandleHBLock(e); break;
case NON_HB_LOCK : HandleNonHBLock(e); break;
case IGNORE_READS_BEG: HandleIgnore(e, false, true); break;
case IGNORE_READS_END: HandleIgnore(e, false, false); break;
case IGNORE_WRITES_BEG: HandleIgnore(e, true, true); break;
case IGNORE_WRITES_END: HandleIgnore(e, true, false); break;
case SET_THREAD_NAME:
thr->set_thread_name((const char*)e->a());
break;
case SET_LOCK_NAME: {
uintptr_t lock_addr = e->a();
const char *name = reinterpret_cast<const char *>(e->info());
Lock *lock = Lock::LookupOrCreate(lock_addr);
lock->set_name(name);
}
break;
case PUBLISH_RANGE : HandlePublishRange(e); break;
case UNPUBLISH_RANGE :
Report("WARNING: ANNOTATE_UNPUBLISH_MEMORY_RANGE is deprecated\n");
break;
case TRACE_MEM : HandleTraceMem(e); break;
case STACK_TRACE : HandleStackTrace(e); break;
case NOOP : CHECK(0); break; // can't happen.
case VERBOSITY : e->Print(); G_flags->verbosity = e->info(); break;
case FLUSH_STATE : FlushState(TID(e->tid())); break;
default : CHECK(0); break;
}
}
private:
void ShowProcSelfStatus() {
if (G_flags->show_proc_self_status) {
string str = ReadFileToString("/proc/self/status", false);
if (!str.empty()) {
Printf("%s", str.c_str());
}
}
}
void ShowStats() {
if (G_flags->show_stats) {
G_stats->PrintStats();
G_cache->PrintStorageStats();
}
}
// PCQ_CREATE, PCQ_DESTROY, PCQ_PUT, PCQ_GET
void HandlePcqCreate(Event *e) {
if (G_flags->verbosity >= 2) {
e->Print();
}
PCQ pcq;
pcq.pcq_addr = e->a();
CHECK(!g_pcq_map->count(e->a()));
(*g_pcq_map)[e->a()] = pcq;
}
void HandlePcqDestroy(Event *e) {
if (G_flags->verbosity >= 2) {
e->Print();
}
CHECK(g_pcq_map->count(e->a()));
g_pcq_map->erase(e->a());
}
void HandlePcqPut(Event *e) {
if (G_flags->verbosity >= 2) {
e->Print();
}
PCQ &pcq = (*g_pcq_map)[e->a()];
CHECK(pcq.pcq_addr == e->a());
TSanThread *thread = TSanThread::Get(TID(e->tid()));
VTS *vts = thread->segment()->vts()->Clone();
pcq.putters.push_back(vts);
thread->NewSegmentForSignal();
}
void HandlePcqGet(Event *e) {
if (G_flags->verbosity >= 2) {
e->Print();
}
PCQ &pcq = (*g_pcq_map)[e->a()];
CHECK(pcq.pcq_addr == e->a());
CHECK(!pcq.putters.empty());
VTS *putter = pcq.putters.front();
pcq.putters.pop_front();
CHECK(putter);
TSanThread *thread = TSanThread::Get(TID(e->tid()));
thread->NewSegmentForWait(putter);
VTS::Unref(putter);
}
// PUBLISH_RANGE
void HandlePublishRange(Event *e) {
if (G_flags->verbosity >= 2) {
e->Print();
}
static int reported_deprecation;
reported_deprecation++;
if (reported_deprecation < 20) {
Report("WARNING: ANNOTATE_PUBLISH_MEMORY_RANGE is deprecated and will not"
" be supported in future versions of ThreadSanitizer.\n");
}
uintptr_t mem = e->a();
uintptr_t size = e->info();
TID tid(e->tid());
TSanThread *thread = TSanThread::Get(tid);
VTS *vts = thread->segment()->vts();
PublishRange(thread, mem, mem + size, vts);
thread->NewSegmentForSignal();
// Printf("Publish: [%p, %p)\n", mem, mem+size);
}
void HandleIgnore(Event *e, bool is_w, bool on) {
if (G_flags->verbosity >= 2) {
e->Print();
}
TSanThread *thread = TSanThread::Get(TID(e->tid()));
thread->set_ignore_accesses(is_w, on);
}
// BENIGN_RACE
void HandleBenignRace(uintptr_t ptr, uintptr_t size,
const char *descr, TID tid) {
TSanThread *thr = TSanThread::Get(tid);
if (debug_benign_races) {
Printf("T%d: BENIGN_RACE: ptr=%p size=%ld descr='%s'\n",
tid.raw(), ptr, size, descr);
}
// Simply set all 'racey' bits in the shadow state of [ptr, ptr+size).
for (uintptr_t p = ptr; p < ptr + size; p++) {
CacheLine *line = G_cache->GetLineOrCreateNew(thr, p, __LINE__);
CHECK(line);
line->racey().Set(CacheLine::ComputeOffset(p));
G_cache->ReleaseLine(thr, p, line, __LINE__);
}
}
// EXPECT_RACE
void HandleExpectRace(uintptr_t ptr, const char *descr, TID tid) {
ExpectedRace expected_race;
expected_race.ptr = ptr;
expected_race.size = 1;
expected_race.count = 0;
expected_race.is_verifiable = !descr ||
(string(descr).find("UNVERIFIABLE") == string::npos);
expected_race.is_nacl_untrusted = !descr ||
(string(descr).find("NACL_UNTRUSTED") != string::npos);
// copy descr (may not have strdup)
CHECK(descr);
size_t descr_len = strlen(descr);
char *d = new char [descr_len + 1];
memcpy(d, descr, descr_len);
d[descr_len] = 0;
expected_race.description = d;
TSanThread *thread = TSanThread::Get(tid);
expected_race.pc = thread->GetCallstackEntry(1);
G_expected_races_map->InsertInfo(ptr, expected_race);
// Flush 'racey' flag for the address
CacheLine *cache_line = G_cache->GetLineIfExists(thread, ptr, __LINE__);
if (cache_line != NULL) {
uintptr_t offset = CacheLine::ComputeOffset(ptr);
cache_line->racey().ClearRange(offset, offset + 1);
G_cache->ReleaseLine(thread, ptr, cache_line, __LINE__);
}
if (debug_expected_races) {
Printf("T%d: EXPECT_RACE: ptr=%p descr='%s'\n", tid.raw(), ptr, descr);
thread->ReportStackTrace(ptr);
int i = 0;
for (ExpectedRacesMap::iterator it = G_expected_races_map->begin();
it != G_expected_races_map->end(); ++it) {
ExpectedRace &x = it->second;
Printf(" [%d] %p [0x%lx]\n", i, &x, x.ptr);
i++;
}
}
}
void HandleStackTrace(Event *e) {
TSanThread *thread = TSanThread::Get(TID(e->tid()));
e->Print();
thread->ReportStackTrace();
}
// HB_LOCK
void HandleHBLock(Event *e) {
if (G_flags->verbosity >= 2) {
e->Print();
}
Lock *lock = Lock::LookupOrCreate(e->a());
CHECK(lock);
lock->set_is_pure_happens_before(true);
}
// NON_HB_LOCK
void HandleNonHBLock(Event *e) {
if (G_flags->verbosity >= 2) {
e->Print();
}
Lock *lock = Lock::LookupOrCreate(e->a());
CHECK(lock);
lock->set_is_pure_happens_before(false);
}
// UNLOCK_OR_INIT
// This is a hack to handle posix pthread_spin_unlock which is sometimes
// the same symbol as pthread_spin_init. We need to handle unlock as init
// if the lock was not seen before or if it is currently unlocked.
// TODO(kcc): is there a way to distinguish pthread_spin_init
// and pthread_spin_unlock?
void HandleUnlockOrInit(Event *e) {
TSanThread *thread = TSanThread::Get(TID(e->tid()));
if (G_flags->verbosity >= 2) {
e->Print();
thread->ReportStackTrace();
}
uintptr_t lock_addr = e->a();
Lock *lock = Lock::Lookup(lock_addr);
if (lock && lock->wr_held()) {
// We know this lock and it is locked. Just unlock it.
thread->HandleUnlock(lock_addr);
} else {
// Never seen this lock or it is currently unlocked. Init it.
Lock::Create(lock_addr);
}
}
void HandleLockCreateOrDestroy(Event *e) {
TSanThread *thread = TSanThread::Get(TID(e->tid()));
uintptr_t lock_addr = e->a();
if (debug_lock) {
e->Print();
}
if (e->type() == LOCK_CREATE) {
Lock::Create(lock_addr);
} else {
CHECK(e->type() == LOCK_DESTROY);
// A locked pthread_mutex_t can not be destroyed but other lock types can.
// When destroying a lock, we must unlock it.
// If there is a bug in a program when someone attempts to unlock
// a destoyed lock, we are likely to fail in an assert.
//
// We do not unlock-on-destroy after main() has exited.
// This is because global Mutex objects may be desctructed while threads
// holding them are still running. Urgh...
Lock *lock = Lock::Lookup(lock_addr);
// If the lock is not found, report an error.
if (lock == NULL) {
ThreadSanitizerInvalidLockReport *report =
new ThreadSanitizerInvalidLockReport;
report->type = ThreadSanitizerReport::INVALID_LOCK;
report->tid = TID(e->tid());
report->lock_addr = lock_addr;
report->stack_trace = thread->CreateStackTrace();
ThreadSanitizerPrintReport(report);
return;
}
if (lock->wr_held() || lock->rd_held()) {
if (G_flags->unlock_on_mutex_destroy && !g_has_exited_main) {
thread->HandleUnlock(lock_addr);
}
}
thread->HandleForgetSignaller(lock_addr);
Lock::Destroy(lock_addr);
}
}
void HandleTraceMem(Event *e) {
if (G_flags->trace_level == 0) return;
TID tid(e->tid());
TSanThread *thr = TSanThread::Get(TID(e->tid()));
uintptr_t a = e->a();
CacheLine *line = G_cache->GetLineOrCreateNew(thr, a, __LINE__);
uintptr_t offset = CacheLine::ComputeOffset(a);
line->traced().Set(offset);
G_cache->ReleaseLine(thr, a, line, __LINE__);
if (G_flags->verbosity >= 2) e->Print();
}
INLINE void RefAndUnrefTwoSegSetPairsIfDifferent(SSID new_ssid1,
SSID old_ssid1,
SSID new_ssid2,
SSID old_ssid2) {
bool recycle_1 = new_ssid1 != old_ssid1,
recycle_2 = new_ssid2 != old_ssid2;
if (recycle_1 && !new_ssid1.IsEmpty()) {
SegmentSet::Ref(new_ssid1, "RefAndUnrefTwoSegSetPairsIfDifferent");
}
if (recycle_2 && !new_ssid2.IsEmpty()) {
SegmentSet::Ref(new_ssid2, "RefAndUnrefTwoSegSetPairsIfDifferent");
}
if (recycle_1 && !old_ssid1.IsEmpty()) {
SegmentSet::Unref(old_ssid1, "RefAndUnrefTwoSegSetPairsIfDifferent");
}
if (recycle_2 && !old_ssid2.IsEmpty()) {
SegmentSet::Unref(old_ssid2, "RefAndUnrefTwoSegSetPairsIfDifferent");
}
}
// return true if the current pair of read/write segment sets
// describes a race.
bool NOINLINE CheckIfRace(SSID rd_ssid, SSID wr_ssid) {
int wr_ss_size = SegmentSet::Size(wr_ssid);
int rd_ss_size = SegmentSet::Size(rd_ssid);
DCHECK(wr_ss_size >= 2 || (wr_ss_size >= 1 && rd_ss_size >= 1));
// check all write-write pairs
for (int w1 = 0; w1 < wr_ss_size; w1++) {
SID w1_sid = SegmentSet::GetSID(wr_ssid, w1, __LINE__);
Segment *w1_seg = Segment::Get(w1_sid);
LSID w1_ls = w1_seg->lsid(true);
for (int w2 = w1 + 1; w2 < wr_ss_size; w2++) {
DCHECK(wr_ssid.IsTuple());
SegmentSet *ss = SegmentSet::Get(wr_ssid);
LSID w2_ls = Segment::Get(ss->GetSID(w2))->lsid(true);
if (LockSet::IntersectionIsEmpty(w1_ls, w2_ls)) {
return true;
} else {
// May happen only if the locks in the intersection are hybrid locks.
DCHECK(LockSet::HasNonPhbLocks(w1_ls) &&
LockSet::HasNonPhbLocks(w2_ls));
}
}
// check all write-read pairs
for (int r = 0; r < rd_ss_size; r++) {
SID r_sid = SegmentSet::GetSID(rd_ssid, r, __LINE__);
Segment *r_seg = Segment::Get(r_sid);
LSID r_ls = r_seg->lsid(false);
if (Segment::HappensBeforeOrSameThread(w1_sid, r_sid))
continue;
if (LockSet::IntersectionIsEmpty(w1_ls, r_ls)) {
return true;
} else {
// May happen only if the locks in the intersection are hybrid locks.
DCHECK(LockSet::HasNonPhbLocks(w1_ls) &&
LockSet::HasNonPhbLocks(r_ls));
}
}
}
return false;
}
// New experimental state machine.
// Set *res to the new state.
// Return true if the new state is race.
bool INLINE MemoryStateMachine(ShadowValue old_sval, TSanThread *thr,
bool is_w, ShadowValue *res) {
ShadowValue new_sval;
SID cur_sid = thr->sid();
DCHECK(cur_sid.valid());
if (UNLIKELY(old_sval.IsNew())) {
// We see this memory for the first time.
DCHECK(cur_sid.valid());
if (is_w) {
new_sval.set(SSID(0), SSID(cur_sid));
} else {
new_sval.set(SSID(cur_sid), SSID(0));
}
*res = new_sval;
return false;
}
SSID old_rd_ssid = old_sval.rd_ssid();
SSID old_wr_ssid = old_sval.wr_ssid();
SSID new_rd_ssid(0);
SSID new_wr_ssid(0);
if (is_w) {
new_rd_ssid = SegmentSet::RemoveSegmentFromSS(old_rd_ssid, cur_sid);
new_wr_ssid = SegmentSet::AddSegmentToSS(old_wr_ssid, cur_sid);
} else {
if (SegmentSet::Contains(old_wr_ssid, cur_sid)) {
// cur_sid is already in old_wr_ssid, no change to SSrd is required.
new_rd_ssid = old_rd_ssid;
} else {
new_rd_ssid = SegmentSet::AddSegmentToSS(old_rd_ssid, cur_sid);
}
new_wr_ssid = old_wr_ssid;
}
if (UNLIKELY(G_flags->sample_events > 0)) {
if (new_rd_ssid.IsTuple() || new_wr_ssid.IsTuple()) {
static EventSampler sampler;
sampler.Sample(thr, "HasTupleSS", false);
}
}
new_sval.set(new_rd_ssid, new_wr_ssid);
*res = new_sval;
if (new_sval == old_sval)
return false;
if (new_wr_ssid.IsTuple() ||
(!new_wr_ssid.IsEmpty() && !new_rd_ssid.IsEmpty())) {
return CheckIfRace(new_rd_ssid, new_wr_ssid);
}
return false;
}
// Fast path implementation for the case when we stay in the same thread.
// In this case we don't need to call HappensBefore(), deal with
// Tuple segment sets and check for race.
// If this function returns true, the ShadowValue *new_sval is updated
// in the same way as MemoryStateMachine() would have done it. Just faster.
INLINE bool MemoryStateMachineSameThread(bool is_w, ShadowValue old_sval,
TSanThread *thr,
ShadowValue *new_sval) {
#define MSM_STAT(i) do { if (DEBUG_MODE) \
thr->stats.msm_branch_count[i]++; } while ((void)0, 0)
SSID rd_ssid = old_sval.rd_ssid();
SSID wr_ssid = old_sval.wr_ssid();
SID cur_sid = thr->sid();
TID tid = thr->tid();
if (rd_ssid.IsEmpty()) {
if (wr_ssid.IsSingleton()) {
// *** CASE 01 ***: rd_ssid == 0, wr_ssid == singleton
SID wr_sid = wr_ssid.GetSingleton();
if (wr_sid == cur_sid) { // --- w/r: {0, cur} => {0, cur}
MSM_STAT(1);
// no op
return true;
}
if (tid == Segment::Get(wr_sid)->tid()) {
// same thread, but the segments are different.
DCHECK(cur_sid != wr_sid);
if (is_w) { // -------------- w: {0, wr} => {0, cur}
MSM_STAT(2);
new_sval->set(SSID(0), SSID(cur_sid));
thr->AddDeadSid(wr_sid, "FastPath01");
} else { // -------------- r: {0, wr} => {cur, wr}
MSM_STAT(3);
new_sval->set(SSID(cur_sid), wr_ssid);
}
Segment::Ref(cur_sid, "FastPath01");
return true;
}
} else if (wr_ssid.IsEmpty()) {
// *** CASE 00 ***: rd_ssid == 0, wr_ssid == 0
if (is_w) { // -------------- w: {0, 0} => {0, cur}
MSM_STAT(4);
new_sval->set(SSID(0), SSID(cur_sid));
} else { // -------------- r: {0, 0} => {cur, 0}
MSM_STAT(5);
new_sval->set(SSID(cur_sid), SSID(0));
}
Segment::Ref(cur_sid, "FastPath00");
return true;
}
} else if (rd_ssid.IsSingleton()) {
SID rd_sid = rd_ssid.GetSingleton();
if (wr_ssid.IsEmpty()) {
// *** CASE 10 ***: rd_ssid == singleton, wr_ssid == 0
if (rd_sid == cur_sid) {
// same segment.
if (is_w) { // -------------- w: {cur, 0} => {0, cur}
MSM_STAT(6);
new_sval->set(SSID(0), SSID(cur_sid));
} else { // -------------- r: {cur, 0} => {cur, 0}
MSM_STAT(7);
// no op
}
return true;
}
if (tid == Segment::Get(rd_sid)->tid()) {
// same thread, but the segments are different.
DCHECK(cur_sid != rd_sid);
if (is_w) { // -------------- w: {rd, 0} => {0, cur}
MSM_STAT(8);
new_sval->set(SSID(0), SSID(cur_sid));
} else { // -------------- r: {rd, 0} => {cur, 0}
MSM_STAT(9);
new_sval->set(SSID(cur_sid), SSID(0));
}
Segment::Ref(cur_sid, "FastPath10");
thr->AddDeadSid(rd_sid, "FastPath10");
return true;
}
} else if (wr_ssid.IsSingleton()){
// *** CASE 11 ***: rd_ssid == singleton, wr_ssid == singleton
DCHECK(rd_ssid.IsSingleton());
SID wr_sid = wr_ssid.GetSingleton();
DCHECK(wr_sid != rd_sid); // By definition of ShadowValue.
if (cur_sid == rd_sid) {
if (tid == Segment::Get(wr_sid)->tid()) {
if (is_w) { // -------------- w: {cur, wr} => {0, cur}
MSM_STAT(10);
new_sval->set(SSID(0), SSID(cur_sid));
thr->AddDeadSid(wr_sid, "FastPath11");
} else { // -------------- r: {cur, wr} => {cur, wr}
MSM_STAT(11);
// no op
}
return true;
}
} else if (cur_sid == wr_sid){
if (tid == Segment::Get(rd_sid)->tid()) {
if (is_w) { // -------------- w: {rd, cur} => {rd, cur}
MSM_STAT(12);
// no op
} else { // -------------- r: {rd, cur} => {0, cur}
MSM_STAT(13);
new_sval->set(SSID(0), SSID(cur_sid));
thr->AddDeadSid(rd_sid, "FastPath11");
}
return true;
}
} else if (tid == Segment::Get(rd_sid)->tid() &&
tid == Segment::Get(wr_sid)->tid()) {
if (is_w) { // -------------- w: {rd, wr} => {0, cur}
MSM_STAT(14);
new_sval->set(SSID(0), SSID(cur_sid));
thr->AddDeadSid(wr_sid, "FastPath11");
} else { // -------------- r: {rd, wr} => {cur, wr}
MSM_STAT(15);
new_sval->set(SSID(cur_sid), wr_ssid);
}
thr->AddDeadSid(rd_sid, "FastPath11");
Segment::Ref(cur_sid, "FastPath11");
return true;
}
}
}
MSM_STAT(0);
return false;
#undef MSM_STAT
}
// return false if we were not able to complete the task (fast_path_only).
INLINE bool HandleMemoryAccessHelper(bool is_w,
CacheLine *cache_line,
uintptr_t addr,
uintptr_t size,
uintptr_t pc,
TSanThread *thr,
bool fast_path_only) {
DCHECK((addr & (size - 1)) == 0); // size-aligned.
uintptr_t offset = CacheLine::ComputeOffset(addr);
ShadowValue old_sval;
ShadowValue *sval_p = NULL;
if (UNLIKELY(!cache_line->has_shadow_value().Get(offset))) {
sval_p = cache_line->AddNewSvalAtOffset(offset);
DCHECK(sval_p->IsNew());
} else {
sval_p = cache_line->GetValuePointer(offset);
}
old_sval = *sval_p;
bool res = false;
bool fast_path_ok = MemoryStateMachineSameThread(
is_w, old_sval, thr, sval_p);
if (fast_path_ok) {
res = true;
} else if (fast_path_only) {
res = false;
} else {
bool is_published = cache_line->published().Get(offset);
// We check only the first bit for publishing, oh well.
if (UNLIKELY(is_published)) {
const VTS *signaller_vts = GetPublisherVTS(addr);
CHECK(signaller_vts);
thr->NewSegmentForWait(signaller_vts);
}
bool is_race = MemoryStateMachine(old_sval, thr, is_w, sval_p);
// Check for race.
if (UNLIKELY(is_race)) {
if (thr->ShouldReportRaces()) {
if (G_flags->report_races && !cache_line->racey().Get(offset)) {
reports_.AddReport(thr, pc, is_w, addr, size,
old_sval, *sval_p, is_published);
}
cache_line->racey().SetRange(offset, offset + size);
}
}
// Ref/Unref segments
RefAndUnrefTwoSegSetPairsIfDifferent(sval_p->rd_ssid(),
old_sval.rd_ssid(),
sval_p->wr_ssid(),
old_sval.wr_ssid());
res = true;
}
if (DEBUG_MODE && !fast_path_only) {
// check that the SSIDs/SIDs in the new sval have sane ref counters.
CHECK(!sval_p->wr_ssid().IsEmpty() || !sval_p->rd_ssid().IsEmpty());
for (int i = 0; i < 2; i++) {
SSID ssid = i ? sval_p->rd_ssid() : sval_p->wr_ssid();
if (ssid.IsEmpty()) continue;
if (ssid.IsSingleton()) {
// singleton segment should have ref count > 0.
SID sid = ssid.GetSingleton();
Segment *seg = Segment::Get(sid);
(void)seg;
CHECK(seg->ref_count() > 0);
if (sid == thr->sid()) {
// if this is the current seg, ref count should be > 1.
CHECK(seg->ref_count() > 1);
}
} else {
SegmentSet *sset = SegmentSet::Get(ssid);
(void)sset;
CHECK(sset->ref_count() > 0);
}
}
}
return res;
}
// return false if we were not able to complete the task (fast_path_only).
INLINE bool HandleAccessGranularityAndExecuteHelper(
CacheLine *cache_line,
TSanThread *thr, uintptr_t addr, MopInfo *mop,
bool has_expensive_flags, bool fast_path_only) {
size_t size = mop->size();
uintptr_t pc = mop->pc();
bool is_w = mop->is_write();
uintptr_t a = addr;
uintptr_t b = 0;
uintptr_t off = CacheLine::ComputeOffset(a);
uint16_t *granularity_mask = cache_line->granularity_mask(off);
uint16_t gr = *granularity_mask;
// Can't do split/join on the fast path, bacause it involves segment set
// reference count manipulation that is not thread-safe.
if (size == 8 && (off & 7) == 0) {
if (!gr) {
*granularity_mask = gr = 1; // 0000000000000001
}
if (GranularityIs8(off, gr)) {
if (has_expensive_flags) thr->stats.n_fast_access8++;
cache_line->DebugTrace(off, __FUNCTION__, __LINE__);
goto one_call;
} else {
if (fast_path_only) return false;
if (has_expensive_flags) thr->stats.n_slow_access8++;
cache_line->Join_1_to_2(off);
cache_line->Join_1_to_2(off + 2);
cache_line->Join_1_to_2(off + 4);
cache_line->Join_1_to_2(off + 6);
cache_line->Join_2_to_4(off);
cache_line->Join_2_to_4(off + 4);
cache_line->Join_4_to_8(off);
goto slow_path;
}
} else if (size == 4 && (off & 3) == 0) {
if (!gr) {
*granularity_mask = gr = 3 << 1; // 0000000000000110
}
if (GranularityIs4(off, gr)) {
if (has_expensive_flags) thr->stats.n_fast_access4++;
cache_line->DebugTrace(off, __FUNCTION__, __LINE__);
goto one_call;
} else {
if (fast_path_only) return false;
if (has_expensive_flags) thr->stats.n_slow_access4++;
cache_line->Split_8_to_4(off);
cache_line->Join_1_to_2(off);
cache_line->Join_1_to_2(off + 2);
cache_line->Join_2_to_4(off);
goto slow_path;
}
} else if (size == 2 && (off & 1) == 0) {
if (!gr) {
*granularity_mask = gr = 15 << 3; // 0000000001111000
}
if (GranularityIs2(off, gr)) {
if (has_expensive_flags) thr->stats.n_fast_access2++;
cache_line->DebugTrace(off, __FUNCTION__, __LINE__);
goto one_call;
} else {
if (fast_path_only) return false;
if (has_expensive_flags) thr->stats.n_slow_access2++;
cache_line->Split_8_to_4(off);
cache_line->Split_4_to_2(off);
cache_line->Join_1_to_2(off);
goto slow_path;
}
} else if (size == 1) {
if (!gr) {
*granularity_mask = gr = 255 << 7; // 0111111110000000
}
if (GranularityIs1(off, gr)) {
if (has_expensive_flags) thr->stats.n_fast_access1++;
cache_line->DebugTrace(off, __FUNCTION__, __LINE__);
goto one_call;
} else {
if (fast_path_only) return false;
if (has_expensive_flags) thr->stats.n_slow_access1++;
cache_line->Split_8_to_4(off);
cache_line->Split_4_to_2(off);
cache_line->Split_2_to_1(off);
goto slow_path;
}
} else {
if (fast_path_only) return false;
if (has_expensive_flags) thr->stats.n_very_slow_access++;
// Very slow: size is not 1,2,4,8 or address is unaligned.
// Handle this access as a series of 1-byte accesses, but only
// inside the current cache line.
// TODO(kcc): do we want to handle the next cache line as well?
b = a + mop->size();
uintptr_t max_x = min(b, CacheLine::ComputeNextTag(a));
for (uintptr_t x = a; x < max_x; x++) {
off = CacheLine::ComputeOffset(x);
DCHECK(CacheLine::ComputeTag(x) == cache_line->tag());
uint16_t *granularity_mask = cache_line->granularity_mask(off);
if (!*granularity_mask) {
*granularity_mask = 1;
}
cache_line->DebugTrace(off, __FUNCTION__, __LINE__);
cache_line->Split_8_to_4(off);
cache_line->Split_4_to_2(off);
cache_line->Split_2_to_1(off);
if (!HandleMemoryAccessHelper(is_w, cache_line, x, 1, pc, thr, false))
return false;
}
return true;
}
slow_path:
if (fast_path_only) return false;
DCHECK(cache_line);
DCHECK(size == 1 || size == 2 || size == 4 || size == 8);
DCHECK((addr & (size - 1)) == 0); // size-aligned.
gr = *granularity_mask;
CHECK(gr);
// size is one of 1, 2, 4, 8; address is size-aligned, but the granularity
// is different.
b = a + mop->size();
for (uintptr_t x = a; x < b;) {
if (has_expensive_flags) thr->stats.n_access_slow_iter++;
off = CacheLine::ComputeOffset(x);
cache_line->DebugTrace(off, __FUNCTION__, __LINE__);
size_t s = 0;
// How many bytes are we going to access?
if (GranularityIs8(off, gr)) s = 8;
else if(GranularityIs4(off, gr)) s = 4;
else if(GranularityIs2(off, gr)) s = 2;
else s = 1;
if (!HandleMemoryAccessHelper(is_w, cache_line, x, s, pc, thr, false))
return false;
x += s;
}
return true;
one_call:
return HandleMemoryAccessHelper(is_w, cache_line, addr, size, pc,
thr, fast_path_only);
}
INLINE bool IsTraced(CacheLine *cache_line, uintptr_t addr,
bool has_expensive_flags) {
if (!has_expensive_flags) return false;
if (G_flags->trace_level == 0) return false;
DCHECK(cache_line);
uintptr_t off = CacheLine::ComputeOffset(addr);
if (cache_line->traced().Get(off)) {
return true;
} else if (addr == G_flags->trace_addr) {
return true;
}
return false;
}
void DoTrace(TSanThread *thr, uintptr_t addr, MopInfo *mop, bool need_locking) {
size_t size = mop->size();
uintptr_t pc = mop->pc();
TIL til(ts_lock, 1, need_locking);
for (uintptr_t x = addr; x < addr + size; x++) {
uintptr_t off = CacheLine::ComputeOffset(x);
CacheLine *cache_line = G_cache->GetLineOrCreateNew(thr,
x, __LINE__);
ShadowValue *sval_p = cache_line->GetValuePointer(off);
if (cache_line->has_shadow_value().Get(off) != 0) {
bool is_published = cache_line->published().Get(off);
Printf("TRACE: T%d/S%d %s[%d] addr=%p sval: %s%s; line=%p P=%s\n",
raw_tid(thr), thr->sid().raw(), mop->is_write() ? "wr" : "rd",
size, addr, sval_p->ToString().c_str(),
is_published ? " P" : "",
cache_line,
cache_line->published().Empty() ?
"0" : cache_line->published().ToString().c_str());
thr->ReportStackTrace(pc);
}
G_cache->ReleaseLine(thr, x, cache_line, __LINE__);
}
}
#if TS_SERIALIZED == 1
INLINE // TODO(kcc): this can also be made NOINLINE later.
#else
NOINLINE
#endif
void HandleMemoryAccessSlowLocked(TSanThread *thr,
uintptr_t addr,
MopInfo *mop,
bool has_expensive_flags,
bool need_locking) {
AssertTILHeld();
DCHECK(thr->lsid(false) == thr->segment()->lsid(false));
DCHECK(thr->lsid(true) == thr->segment()->lsid(true));
thr->FlushDeadSids();
if (TS_SERIALIZED == 0) {
// In serialized version this is the hotspot, so grab fresh SIDs
// only in non-serial variant.
thr->GetSomeFreshSids();
}
CacheLine *cache_line = G_cache->GetLineOrCreateNew(thr, addr, __LINE__);
HandleAccessGranularityAndExecuteHelper(cache_line, thr, addr,
mop, has_expensive_flags,
/*fast_path_only=*/false);
bool tracing = IsTraced(cache_line, addr, has_expensive_flags);
G_cache->ReleaseLine(thr, addr, cache_line, __LINE__);
cache_line = NULL; // just in case.
if (has_expensive_flags) {
if (tracing) {
DoTrace(thr, addr, mop, /*need_locking=*/false);
}
if (G_flags->sample_events > 0) {
const char *type = "SampleMemoryAccess";
static EventSampler sampler;
sampler.Sample(thr, type, false);
}
}
}
INLINE bool HandleMemoryAccessInternal(TSanThread *thr,
uintptr_t *sblock_pc,
uintptr_t addr,
MopInfo *mop,
bool has_expensive_flags,
bool need_locking) {
# define INC_STAT(stat) \
do { if (has_expensive_flags) (stat)++; } while ((void)0, 0)
if (TS_ATOMICITY && G_flags->atomicity) {
HandleMemoryAccessForAtomicityViolationDetector(thr, addr, mop);
return false;
}
DCHECK(mop->size() > 0);
DCHECK(thr->is_running());
DCHECK(!thr->ignore_reads() || !thr->ignore_writes());
// We do not check and ignore stack now.
// On unoptimized binaries this would give ~10% speedup if ignore_stack==true,
// but if --ignore_stack==false this would cost few extra insns.
// On optimized binaries ignoring stack gives nearly nothing.
// if (thr->IgnoreMemoryIfInStack(addr)) return;
CacheLine *cache_line = NULL;
INC_STAT(thr->stats.memory_access_sizes[mop->size() <= 16 ? mop->size() : 17 ]);
INC_STAT(thr->stats.events[mop->is_write() ? WRITE : READ]);
if (has_expensive_flags) {
thr->stats.access_to_first_1g += (addr >> 30) == 0;
thr->stats.access_to_first_2g += (addr >> 31) == 0;
thr->stats.access_to_first_4g += ((uint64_t)addr >> 32) == 0;
}
int locked_access_case = 0;
if (need_locking) {
// The fast (unlocked) path.
if (thr->HasRoomForDeadSids()) {
// Acquire a line w/o locks.
cache_line = G_cache->TryAcquireLine(thr, addr, __LINE__);
if (!Cache::LineIsNullOrLocked(cache_line)) {
// The line is not empty or locked -- check the tag.
if (cache_line->tag() == CacheLine::ComputeTag(addr)) {
// The line is ours and non-empty -- fire the fast path.
if (thr->HandleSblockEnter(*sblock_pc, /*allow_slow_path=*/false)) {
*sblock_pc = 0; // don't do SblockEnter any more.
bool res = HandleAccessGranularityAndExecuteHelper(
cache_line, thr, addr,
mop, has_expensive_flags,
/*fast_path_only=*/true);
bool traced = IsTraced(cache_line, addr, has_expensive_flags);
// release the line.
G_cache->ReleaseLine(thr, addr, cache_line, __LINE__);
if (res && has_expensive_flags && traced) {
DoTrace(thr, addr, mop, /*need_locking=*/true);
}
if (res) {
INC_STAT(thr->stats.unlocked_access_ok);
// fast path succeded, we are done.
return false;
} else {
locked_access_case = 1;
}
} else {
// we were not able to handle SblockEnter.
G_cache->ReleaseLine(thr, addr, cache_line, __LINE__);
locked_access_case = 2;
}
} else {
locked_access_case = 3;
// The line has a wrong tag.
G_cache->ReleaseLine(thr, addr, cache_line, __LINE__);
}
} else if (cache_line == NULL) {
locked_access_case = 4;
// We grabbed the cache slot but it is empty, release it.
G_cache->ReleaseLine(thr, addr, cache_line, __LINE__);
} else {
locked_access_case = 5;
}
} else {
locked_access_case = 6;
}
} else {
locked_access_case = 7;
}
if (need_locking) {
INC_STAT(thr->stats.locked_access[locked_access_case]);
}
// Everything below goes under a lock.
TIL til(ts_lock, 2, need_locking);
thr->HandleSblockEnter(*sblock_pc, /*allow_slow_path=*/true);
*sblock_pc = 0; // don't do SblockEnter any more.
HandleMemoryAccessSlowLocked(thr, addr, mop,
has_expensive_flags,
need_locking);
return true;
#undef INC_STAT
}
void HandleMemoryAccessForAtomicityViolationDetector(TSanThread *thr,
uintptr_t addr,
MopInfo *mop) {
CHECK(G_flags->atomicity);
TID tid = thr->tid();
if (thr->MemoryIsInStack(addr)) return;
LSID wr_lsid = thr->lsid(0);
LSID rd_lsid = thr->lsid(1);
if (wr_lsid.raw() == 0 && rd_lsid.raw() == 0) {
thr->increment_n_mops_since_start();
return;
}
// uint64_t combined_lsid = wr_lsid.raw();
// combined_lsid = (combined_lsid << 32) | rd_lsid.raw();
// if (combined_lsid == 0) return;
// Printf("Era=%d T%d %s a=%p pc=%p in_stack=%d %s\n", g_lock_era,
// tid.raw(), is_w ? "W" : "R", addr, pc, thr->MemoryIsInStack(addr),
// PcToRtnNameAndFilePos(pc).c_str());
BitSet *range_set = thr->lock_era_access_set(mop->is_write());
// Printf("era %d T%d access under lock pc=%p addr=%p size=%p w=%d\n",
// g_lock_era, tid.raw(), pc, addr, size, is_w);
range_set->Add(addr, addr + mop->size());
// Printf(" %s\n", range_set->ToString().c_str());
}
// MALLOC
void HandleMalloc(Event *e, bool is_mmap) {
ScopedMallocCostCenter cc("HandleMalloc");
TID tid(e->tid());
uintptr_t a = e->a();
uintptr_t size = e->info();
if (a == 0)
return;
#if defined(__GNUC__) && __WORDSIZE == 64
// If we are allocating a huge piece of memory,
// don't handle it because it is too slow.
// TODO(kcc): this is a workaround for NaCl. May need to fix it cleaner.
const uint64_t G84 = (1ULL << 32) * 21; // 84G.
if (size >= G84) {
return;
}
#endif
TSanThread *thr = TSanThread::Get(tid);
thr->NewSegmentForMallocEvent();
uintptr_t b = a + size;
CHECK(a <= b);
ClearMemoryState(thr, a, b);
// update heap_map
HeapInfo info;
info.ptr = a;
info.size = size;
info.sid = thr->sid();
Segment::Ref(info.sid, __FUNCTION__);
if (debug_malloc) {
Printf("T%d MALLOC: %p [%p %p) %s %s\n%s\n",
tid.raw(), size, a, a+size,
Segment::ToString(thr->sid()).c_str(),
thr->segment()->vts()->ToString().c_str(),
info.StackTraceString().c_str());
}
// CHECK(!G_heap_map->count(a)); // we may have two calls
// to AnnotateNewMemory.
G_heap_map->InsertInfo(a, info);
if (is_mmap) {
// Mmap may be used for thread stack, so we should keep the mmap info
// when state is flushing.
ThreadStackInfo ts_info;
ts_info.ptr = a;
ts_info.size = size;
G_thread_stack_map->InsertInfo(a, ts_info);
}
}
void ImitateWriteOnFree(TSanThread *thr, uintptr_t a, uintptr_t size, uintptr_t pc) {
// Handle the memory deletion as a write, but don't touch all
// the memory if there is too much of it, limit with the first 1K.
if (size && G_flags->free_is_write && !global_ignore) {
const uintptr_t kMaxWriteSizeOnFree = 2048;
uintptr_t write_size = min(kMaxWriteSizeOnFree, size);
uintptr_t step = sizeof(uintptr_t);
// We simulate 4- or 8-byte accesses to make analysis faster.
for (uintptr_t i = 0; i < write_size; i += step) {
uintptr_t this_size = write_size - i >= step ? step : write_size - i;
HandleMemoryAccess(thr, pc, a + i, this_size,
/*is_w=*/true, /*need_locking*/false);
}
}
}
// FREE
void HandleFree(Event *e) {
TID tid(e->tid());
TSanThread *thr = TSanThread::Get(tid);
uintptr_t a = e->a();
if (debug_free) {
e->Print();
thr->ReportStackTrace(e->pc());
}
if (a == 0)
return;
HeapInfo *info = G_heap_map->GetInfo(a);
if (!info || info->ptr != a)
return;
uintptr_t size = info->size;
uintptr_t pc = e->pc();
ImitateWriteOnFree(thr, a, size, pc);
// update G_heap_map
CHECK(info->ptr == a);
Segment::Unref(info->sid, __FUNCTION__);
ClearMemoryState(thr, a, a + size);
G_heap_map->EraseInfo(a);
// We imitate a Write event again, in case there will be use-after-free.
// We also need to create a new sblock so that the previous stack trace
// has free() in it.
if (G_flags->keep_history && G_flags->free_is_write) {
thr->HandleSblockEnter(pc, /*allow_slow_path*/true);
}
ImitateWriteOnFree(thr, a, size, pc);
}
void HandleMunmap(Event *e) {
// TODO(glider): at the moment we handle only munmap()s of single mmap()ed
// regions. The correct implementation should handle arbitrary munmap()s
// that may carve the existing mappings or split them into two parts.
// It should also be possible to munmap() several mappings at a time.
uintptr_t a = e->a();
if (a == 0)
return;
HeapInfo *h_info = G_heap_map->GetInfo(a);
uintptr_t size = e->info();
if (h_info && h_info->ptr == a && h_info->size == size) {
// TODO(glider): we may want to handle memory deletion and call
// Segment::Unref for all the unmapped memory.
Segment::Unref(h_info->sid, __FUNCTION__);
G_heap_map->EraseRange(a, a + size);
}
ThreadStackInfo *ts_info = G_thread_stack_map->GetInfo(a);
if (ts_info && ts_info->ptr == a && ts_info->size == size)
G_thread_stack_map->EraseRange(a, a + size);
}
void HandleThreadStart(TID child_tid, TID parent_tid, CallStack *call_stack) {
// Printf("HandleThreadStart: tid=%d parent_tid=%d pc=%lx pid=%d\n",
// child_tid.raw(), parent_tid.raw(), pc, getpid());
VTS *vts = NULL;
StackTrace *creation_context = NULL;
if (child_tid == TID(0)) {
// main thread, we are done.
vts = VTS::CreateSingleton(child_tid);
} else if (!parent_tid.valid()) {
TSanThread::StopIgnoringAccessesInT0BecauseNewThreadStarted();
Report("INFO: creating thread T%d w/o a parent\n", child_tid.raw());
vts = VTS::CreateSingleton(child_tid);
} else {
TSanThread::StopIgnoringAccessesInT0BecauseNewThreadStarted();
TSanThread *parent = TSanThread::Get(parent_tid);
CHECK(parent);
parent->HandleChildThreadStart(child_tid, &vts, &creation_context);
}
if (!call_stack) {
call_stack = new CallStack();
}
TSanThread *new_thread = new TSanThread(child_tid, parent_tid,
vts, creation_context, call_stack);
CHECK(new_thread == TSanThread::Get(child_tid));
if (child_tid == TID(0)) {
new_thread->set_ignore_all_accesses(true); // until a new thread comes.
}
}
// Executes before the first instruction of the thread but after the thread
// has been set up (e.g. the stack is in place).
void HandleThreadFirstInsn(TID tid) {
// TODO(kcc): get rid of this once we find out how to get the T0's stack.
if (tid == TID(0)) {
uintptr_t stack_min(0), stack_max(0);
GetThreadStack(tid.raw(), &stack_min, &stack_max);
TSanThread *thr = TSanThread::Get(tid);
thr->SetStack(stack_min, stack_max);
ClearMemoryState(thr, thr->min_sp(), thr->max_sp());
}
}
// THR_STACK_TOP
void HandleThreadStackTop(Event *e) {
TID tid(e->tid());
TSanThread *thr = TSanThread::Get(tid);
// Stack grows from bottom up.
uintptr_t sp = e->a();
uintptr_t sp_min = 0, sp_max = 0;
uintptr_t stack_size_if_known = e->info();
ThreadStackInfo *stack_info;
if (stack_size_if_known) {
sp_min = sp - stack_size_if_known;
sp_max = sp;
} else if (NULL != (stack_info = G_thread_stack_map->GetInfo(sp))) {
if (debug_thread) {
Printf("T%d %s: %p\n%s\n", e->tid(), __FUNCTION__, sp,
reports_.DescribeMemory(sp).c_str());
}
sp_min = stack_info->ptr;
sp_max = stack_info->ptr + stack_info->size;
}
if (debug_thread) {
Printf("T%d SP: %p [%p %p), size=%ldK\n",
e->tid(), sp, sp_min, sp_max, (sp_max - sp_min) >> 10);
}
if (sp_min < sp_max) {
CHECK((sp_max - sp_min) >= 8 * 1024); // stay sane.
CHECK((sp_max - sp_min) < 128 * 1024 * 1024); // stay sane.
ClearMemoryState(thr, sp_min, sp_max);
thr->SetStack(sp_min, sp_max);
}
}
// THR_END
void HandleThreadEnd(TID tid) {
TSanThread *thr = TSanThread::Get(tid);
// Add the thread-local stats to global stats.
G_stats->Add(thr->stats);
thr->stats.Clear();
// Printf("HandleThreadEnd: %d\n", tid.raw());
if (tid != TID(0)) {
TSanThread *child = TSanThread::Get(tid);
child->HandleThreadEnd();
if (debug_thread) {
Printf("T%d: THR_END : %s %s\n", tid.raw(),
Segment::ToString(child->sid()).c_str(),
child->vts()->ToString().c_str());
}
ClearMemoryState(thr, child->min_sp(), child->max_sp());
} else {
reports_.SetProgramFinished();
}
if (g_so_far_only_one_thread == false
&& (thr->ignore_reads() || thr->ignore_writes())) {
Report("WARNING: T%d ended while at least one 'ignore' bit is set: "
"ignore_wr=%d ignore_rd=%d\n", tid.raw(),
thr->ignore_reads(), thr->ignore_writes());
for (int i = 0; i < 2; i++) {
StackTrace *context = thr->GetLastIgnoreContext(i);
if (context) {
Report("Last ignore_%s call was here: \n%s\n", i ? "wr" : "rd",
context->ToString().c_str());
}
}
if (G_flags->save_ignore_context == false) {
Report("Rerun with --save_ignore_context to see where "
"IGNORE_END is missing\n");
}
}
ShowProcSelfStatus();
}
// THR_JOIN_AFTER
void HandleThreadJoinAfter(Event *e) {
TID tid(e->tid());
TSanThread *parent_thr = TSanThread::Get(tid);
VTS *vts_at_exit = NULL;
TID child_tid = parent_thr->HandleThreadJoinAfter(&vts_at_exit, TID(e->a()));
CHECK(vts_at_exit);
CHECK(parent_thr->sid().valid());
Segment::AssertLive(parent_thr->sid(), __LINE__);
parent_thr->NewSegmentForWait(vts_at_exit);
if (debug_thread) {
Printf("T%d: THR_JOIN_AFTER T%d : %s\n", tid.raw(),
child_tid.raw(), parent_thr->vts()->ToString().c_str());
}
}
public:
// TODO(kcc): merge this into Detector class. (?)
ReportStorage reports_;
void SetUnwindCallback(ThreadSanitizerUnwindCallback cb) {
reports_.SetUnwindCallback(cb);
}
};
static Detector *G_detector;
void TSanThread::HandleAtomicMop(uintptr_t a,
uintptr_t pc,
tsan_atomic_op op,
tsan_memory_order mo,
size_t size) {
if (op == tsan_atomic_op_fence)
return;
bool const is_store = (op != tsan_atomic_op_load);
CHECK(inside_atomic_op_ >= 0);
if (mo != tsan_memory_order_natomic)
inside_atomic_op_ += 1;
MopInfo mop (pc, size, is_store, true);
G_detector->HandleTrace(this, &mop, 1, pc, &a, false);
if (mo != tsan_memory_order_natomic)
inside_atomic_op_ -= 1;
CHECK(inside_atomic_op_ >= 0);
}
// -------- Flags ------------------------- {{{1
const char *usage_str =
"Usage:\n"
" %s [options] program_to_test [program's options]\n"
"See %s for details\n";
void ThreadSanitizerPrintUsage() {
Printf(usage_str, G_flags->tsan_program_name.c_str(),
G_flags->tsan_url.c_str());
}
static void ReportUnknownFlagAndExit(const string &str) {
Printf("Unknown flag or flag value: %s\n", str.c_str());
ThreadSanitizerPrintUsage();
exit(1);
}
// if arg and flag match, return true
// and set 'val' to the substring of arg after '='.
static bool FlagNameMatch(const string &arg, const string &flag, string *val) {
string f = string("--") + flag;
if (arg.size() < f.size()) return false;
for (size_t i = 0; i < f.size(); i++) {
// '-' must match '-'
// '_' may match '_' or '-'
if (f[i] == '_') {
if (arg[i] != '-' && arg[i] != '_') return false;
} else {
if (f[i] != arg[i]) return false;
}
}
if (arg.size() == f.size()) {
*val = "";
return true;
}
if (arg[f.size()] != '=') return false;
*val = arg.substr(f.size() + 1);
return true;
}
static int FindBoolFlag(const char *name, bool default_val,
vector<string> *args, bool *retval) {
int res = 0;
*retval = default_val;
bool cont = false;
do {
cont = false;
vector<string>::iterator it = args->begin();
for (; it != args->end(); ++it) {
string &str = *it;
string flag_value;
if (!FlagNameMatch(str, name, &flag_value)) continue;
if (flag_value == "") *retval = true;
else if (flag_value == "1") *retval = true;
else if (flag_value == "true") *retval = true;
else if (flag_value == "yes") *retval = true;
else if (flag_value == "0") *retval = false;
else if (flag_value == "false") *retval = false;
else if (flag_value == "no") *retval = false;
else
ReportUnknownFlagAndExit(str);
res++;
if (G_flags->verbosity >= 1) {
Printf("%40s => %s\n", name, *retval ? "true" : "false");
}
break;
}
if (it != args->end()) {
cont = true;
args->erase(it);
}
} while (cont);
return res;
}
static void FindIntFlag(const char *name, intptr_t default_val,
vector<string> *args, intptr_t *retval) {
*retval = default_val;
bool cont = false;
do {
cont = false;
vector<string>::iterator it = args->begin();
for (; it != args->end(); ++it) {
string &str = *it;
string flag_value;
if (!FlagNameMatch(str, name, &flag_value)) continue;
char *end_ptr;
const char *beg_ptr = flag_value.c_str();
intptr_t int_val = my_strtol(beg_ptr, &end_ptr, 0);
if (flag_value.empty() || beg_ptr + flag_value.size() != end_ptr)
ReportUnknownFlagAndExit(str);
*retval = int_val;
if (G_flags->verbosity >= 1) {
Printf("%40s => %ld\n", name, *retval);
}
break;
}
if (it != args->end()) {
cont = true;
args->erase(it);
}
} while (cont);
}
static void FindUIntFlag(const char *name, intptr_t default_val,
vector<string> *args, uintptr_t *retval) {
intptr_t signed_int;
FindIntFlag(name, default_val, args, &signed_int);
CHECK_GE(signed_int, 0);
*retval = signed_int;
}
void FindStringFlag(const char *name, vector<string> *args,
vector<string> *retval) {
bool cont = false;
do {
cont = false;
vector<string>::iterator it = args->begin();
for (; it != args->end(); ++it) {
string &str = *it;
string flag_value;
if (!FlagNameMatch(str, name, &flag_value)) continue;
retval->push_back(flag_value);
if (G_flags->verbosity >= 1) {
Printf("%40s => %s\n", name, flag_value.c_str());
}
break;
}
if (it != args->end()) {
cont = true;
args->erase(it);
}
} while (cont);
}
void FindStringFlag(const char *name, vector<string> *args,
string *retval) {
vector<string> tmp;
FindStringFlag(name, args, &tmp);
if (tmp.size() > 0) {
*retval = tmp.back();
}
}
static size_t GetMemoryLimitInMbFromProcSelfLimits() {
#ifdef VGO_linux
// Parse the memory limit section of /proc/self/limits.
string proc_self_limits = ReadFileToString("/proc/self/limits", false);
const char *max_addr_space = "Max address space";
size_t pos = proc_self_limits.find(max_addr_space);
if (pos == string::npos) return 0;
pos += strlen(max_addr_space);
while (proc_self_limits[pos] == ' ') pos++;
if (proc_self_limits[pos] == 'u')
return 0; // 'unlimited'.
char *end;
size_t result = my_strtol(proc_self_limits.c_str() + pos, &end, 0);
result >>= 20;
return result;
#else
return 0;
#endif
}
static size_t GetMemoryLimitInMb() {
size_t ret = -1; // Maximum possible value.
#if defined(VGO_linux) && __WORDSIZE == 32
// Valgrind doesn't support more than 3G per process on 32-bit Linux.
ret = 3 * 1024;
#endif
// Try /proc/self/limits.
size_t from_proc_self = GetMemoryLimitInMbFromProcSelfLimits();
if (from_proc_self && ret > from_proc_self) {
ret = from_proc_self;
}
// Try env.
const char *from_env_str =
(const char*)getenv("VALGRIND_MEMORY_LIMIT_IN_MB");
if (from_env_str) {
char *end;
size_t from_env_value = (size_t)my_strtol(from_env_str, &end, 0);
if (ret > from_env_value)
ret = from_env_value;
}
if (ret == (size_t)-1)
return 0;
return ret;
}
bool PhaseDebugIsOn(const char *phase_name) {
CHECK(G_flags);
for (size_t i = 0; i < G_flags->debug_phase.size(); i++) {
if (G_flags->debug_phase[i] == phase_name)
return true;
}
return false;
}
void ThreadSanitizerParseFlags(vector<string> *args) {
#ifdef TS_OFFLINE
string input_type_tmp;
FindStringFlag("input_type", args, &input_type_tmp);
if (input_type_tmp.size() > 0) {
G_flags->input_type = input_type_tmp;
} else {
G_flags->input_type = "str";
}
#endif
// Check this first.
FindIntFlag("v", 0, args, &G_flags->verbosity);
FindBoolFlag("ignore_stack", false, args, &G_flags->ignore_stack);
FindIntFlag("keep_history", 1, args, &G_flags->keep_history);
FindUIntFlag("segment_set_recycle_queue_size", DEBUG_MODE ? 10 : 10000, args,
&G_flags->segment_set_recycle_queue_size);
FindUIntFlag("recent_segments_cache_size", 10, args,
&G_flags->recent_segments_cache_size);
bool fast_mode = false;
FindBoolFlag("fast_mode", false, args, &fast_mode);
if (fast_mode) {
Printf("INFO: --fast-mode is deprecated\n");
}
bool ignore_in_dtor = false;
FindBoolFlag("ignore_in_dtor", false, args, &ignore_in_dtor);
if (ignore_in_dtor) {
Printf("INFO: --ignore-in-dtor is deprecated\n");
}
int has_phb = FindBoolFlag("pure_happens_before", true, args,
&G_flags->pure_happens_before);
bool hybrid = false;
int has_hyb = FindBoolFlag("hybrid", false, args, &hybrid);
if (has_hyb && has_phb) {
Printf("INFO: --hybrid and --pure-happens-before"
" is mutually exclusive; ignoring the --hybrid switch\n");
} else if (has_hyb && !has_phb) {
G_flags->pure_happens_before = !hybrid;
}
FindBoolFlag("show_expected_races", false, args,
&G_flags->show_expected_races);
FindBoolFlag("demangle", true, args, &G_flags->demangle);
FindBoolFlag("announce_threads", false, args, &G_flags->announce_threads);
FindBoolFlag("full_output", false, args, &G_flags->full_output);
FindBoolFlag("show_states", false, args, &G_flags->show_states);
FindBoolFlag("show_proc_self_status", false, args,
&G_flags->show_proc_self_status);
FindBoolFlag("show_valgrind_context", false, args,
&G_flags->show_valgrind_context);
FindBoolFlag("suggest_happens_before_arcs", true, args,
&G_flags->suggest_happens_before_arcs);
FindBoolFlag("show_pc", false, args, &G_flags->show_pc);
FindBoolFlag("full_stack_frames", false, args, &G_flags->full_stack_frames);
FindBoolFlag("free_is_write", true, args, &G_flags->free_is_write);
FindBoolFlag("exit_after_main", false, args, &G_flags->exit_after_main);
FindIntFlag("show_stats", 0, args, &G_flags->show_stats);
FindBoolFlag("trace_profile", false, args, &G_flags->trace_profile);
FindBoolFlag("color", false, args, &G_flags->color);
FindBoolFlag("html", false, args, &G_flags->html);
#ifdef TS_OFFLINE
bool show_pid_default = false;
#else
bool show_pid_default = true;
#endif
FindBoolFlag("show_pid", show_pid_default, args, &G_flags->show_pid);
FindBoolFlag("save_ignore_context", DEBUG_MODE ? true : false, args,
&G_flags->save_ignore_context);
FindIntFlag("dry_run", 0, args, &G_flags->dry_run);
FindBoolFlag("report_races", true, args, &G_flags->report_races);
FindIntFlag("locking_scheme", 1, args, &G_flags->locking_scheme);
FindBoolFlag("unlock_on_mutex_destroy", true, args,
&G_flags->unlock_on_mutex_destroy);
FindIntFlag("sample_events", 0, args, &G_flags->sample_events);
FindIntFlag("sample_events_depth", 2, args, &G_flags->sample_events_depth);
FindIntFlag("debug_level", 1, args, &G_flags->debug_level);
FindStringFlag("debug_phase", args, &G_flags->debug_phase);
FindIntFlag("trace_level", 0, args, &G_flags->trace_level);
FindIntFlag("literace_sampling", 0, args, &G_flags->literace_sampling);
FindIntFlag("sampling", 0, args, &G_flags->literace_sampling);
CHECK(G_flags->literace_sampling < 32);
CHECK(G_flags->literace_sampling >= 0);
FindBoolFlag("start_with_global_ignore_on", false, args,
&G_flags->start_with_global_ignore_on);
FindStringFlag("fullpath_after", args, &G_flags->file_prefix_to_cut);
FindStringFlag("file_prefix_to_cut", args, &G_flags->file_prefix_to_cut);
for (size_t i = 0; i < G_flags->file_prefix_to_cut.size(); i++) {
G_flags->file_prefix_to_cut[i] =
ConvertToPlatformIndependentPath(G_flags->file_prefix_to_cut[i]);
}
FindStringFlag("ignore", args, &G_flags->ignore);
FindStringFlag("whitelist", args, &G_flags->whitelist);
FindBoolFlag("ignore_unknown_pcs", false, args, &G_flags->ignore_unknown_pcs);
FindBoolFlag("thread_coverage", false, args, &G_flags->thread_coverage);
FindBoolFlag("atomicity", false, args, &G_flags->atomicity);
if (G_flags->atomicity) {
// When doing atomicity violation checking we should not
// create h-b arcs between Unlocks and Locks.
G_flags->pure_happens_before = false;
}
FindBoolFlag("call_coverage", false, args, &G_flags->call_coverage);
FindStringFlag("dump_events", args, &G_flags->dump_events);
FindBoolFlag("symbolize", true, args, &G_flags->symbolize);
FindIntFlag("trace_addr", 0, args,
reinterpret_cast<intptr_t*>(&G_flags->trace_addr));
FindIntFlag("max_mem_in_mb", 0, args, &G_flags->max_mem_in_mb);
FindBoolFlag("offline", false, args, &G_flags->offline);
FindBoolFlag("attach_mode", false, args, &G_flags->attach_mode);
if (G_flags->max_mem_in_mb == 0) {
G_flags->max_mem_in_mb = GetMemoryLimitInMb();
}
vector<string> summary_file_tmp;
FindStringFlag("summary_file", args, &summary_file_tmp);
if (summary_file_tmp.size() > 0) {
G_flags->summary_file = summary_file_tmp.back();
}
vector<string> log_file_tmp;
FindStringFlag("log_file", args, &log_file_tmp);
if (log_file_tmp.size() > 0) {
G_flags->log_file = log_file_tmp.back();
}
G_flags->tsan_program_name = "valgrind --tool=tsan";
FindStringFlag("tsan_program_name", args, &G_flags->tsan_program_name);
G_flags->tsan_url = "http://code.google.com/p/data-race-test";
FindStringFlag("tsan_url", args, &G_flags->tsan_url);
FindStringFlag("suppressions", args, &G_flags->suppressions);
FindBoolFlag("gen_suppressions", false, args,
&G_flags->generate_suppressions);
FindIntFlag("error_exitcode", 0, args, &G_flags->error_exitcode);
FindIntFlag("flush_period", 0, args, &G_flags->flush_period);
FindBoolFlag("trace_children", false, args, &G_flags->trace_children);
FindIntFlag("max_sid", kMaxSID, args, &G_flags->max_sid);
kMaxSID = G_flags->max_sid;
if (kMaxSID <= 100000) {
Printf("Error: max-sid should be at least 100000. Exiting\n");
exit(1);
}
FindIntFlag("max_sid_before_flush", (kMaxSID * 15) / 16, args,
&G_flags->max_sid_before_flush);
kMaxSIDBeforeFlush = G_flags->max_sid_before_flush;
FindIntFlag("num_callers_in_history", kSizeOfHistoryStackTrace, args,
&G_flags->num_callers_in_history);
kSizeOfHistoryStackTrace = G_flags->num_callers_in_history;
// Cut stack under the following default functions.
G_flags->cut_stack_below.push_back("TSanThread*ThreadBody*");
G_flags->cut_stack_below.push_back("ThreadSanitizerStartThread");
G_flags->cut_stack_below.push_back("start_thread");
G_flags->cut_stack_below.push_back("BaseThreadInitThunk");
FindStringFlag("cut_stack_below", args, &G_flags->cut_stack_below);
FindIntFlag("num_callers", 16, args, &G_flags->num_callers);
G_flags->max_n_threads = 100000;
if (G_flags->full_output) {
G_flags->announce_threads = true;
G_flags->show_pc = true;
G_flags->full_stack_frames = true;
G_flags->show_states = true;
G_flags->file_prefix_to_cut.clear();
}
FindIntFlag("race_verifier_sleep_ms", 100, args,
&G_flags->race_verifier_sleep_ms);
FindStringFlag("race_verifier", args, &G_flags->race_verifier);
FindStringFlag("race_verifier_extra", args, &G_flags->race_verifier_extra);
g_race_verifier_active =
!(G_flags->race_verifier.empty() && G_flags->race_verifier_extra.empty());
if (g_race_verifier_active) {
Printf("INFO: ThreadSanitizer running in Race Verifier mode.\n");
}
FindBoolFlag("nacl_untrusted", false, args, &G_flags->nacl_untrusted);
FindBoolFlag("threaded_analysis", false, args, &G_flags->threaded_analysis);
FindBoolFlag("sched_shake", false, args, &G_flags->sched_shake);
FindBoolFlag("api_ambush", false, args, &G_flags->api_ambush);
FindBoolFlag("enable_atomic", false, args, &G_flags->enable_atomic);
if (!args->empty()) {
ReportUnknownFlagAndExit(args->front());
}
debug_expected_races = PhaseDebugIsOn("expected_races");
debug_benign_races = PhaseDebugIsOn("benign_races");
debug_malloc = PhaseDebugIsOn("malloc");
debug_free = PhaseDebugIsOn("free");
debug_thread = PhaseDebugIsOn("thread");
debug_ignore = PhaseDebugIsOn("ignore");
debug_rtn = PhaseDebugIsOn("rtn");
debug_lock = PhaseDebugIsOn("lock");
debug_wrap = PhaseDebugIsOn("wrap");
debug_ins = PhaseDebugIsOn("ins");
debug_shadow_stack = PhaseDebugIsOn("shadow_stack");
debug_happens_before = PhaseDebugIsOn("happens_before");
debug_cache = PhaseDebugIsOn("cache");
debug_race_verifier = PhaseDebugIsOn("race_verifier");
debug_atomic = PhaseDebugIsOn("atomic");
}
// -------- ThreadSanitizer ------------------ {{{1
// Setup the list of functions/images/files to ignore.
static void SetupIgnore() {
g_ignore_lists = new IgnoreLists;
g_white_lists = new IgnoreLists;
// Add some major ignore entries so that tsan remains sane
// even w/o any ignore file. First - for all platforms.
g_ignore_lists->ignores.push_back(IgnoreFun("ThreadSanitizerStartThread"));
g_ignore_lists->ignores.push_back(IgnoreFun("exit"));
g_ignore_lists->ignores.push_back(IgnoreFun("longjmp"));
// Dangerous: recursively ignoring vfprintf hides races on printf arguments.
// See PrintfTests in unittest/racecheck_unittest.cc
// TODO(eugenis): Do something about this.
// http://code.google.com/p/data-race-test/issues/detail?id=53
g_ignore_lists->ignores_r.push_back(IgnoreFun("vfprintf"));
// do not create segments in our Replace_* functions
g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_memcpy"));
g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_memchr"));
g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strcpy"));
g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strchr"));
g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strchrnul"));
g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strrchr"));
g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strlen"));
g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strcmp"));
// Ignore everything in our own file.
g_ignore_lists->ignores.push_back(IgnoreFile("*ts_valgrind_intercepts.c"));
#ifndef _MSC_VER
// POSIX ignores
g_ignore_lists->ignores.push_back(IgnoreObj("*/libpthread*"));
g_ignore_lists->ignores.push_back(IgnoreObj("*/ld-2*.so"));
g_ignore_lists->ignores.push_back(IgnoreFun("pthread_create"));
g_ignore_lists->ignores.push_back(IgnoreFun("pthread_create@*"));
g_ignore_lists->ignores.push_back(IgnoreFun("pthread_create_WRK"));
g_ignore_lists->ignores.push_back(IgnoreFun("__cxa_*"));
g_ignore_lists->ignores.push_back(
IgnoreFun("*__gnu_cxx*__exchange_and_add*"));
g_ignore_lists->ignores.push_back(IgnoreFun("__lll_mutex_*"));
g_ignore_lists->ignores.push_back(IgnoreFun("__lll_*lock_*"));
g_ignore_lists->ignores.push_back(IgnoreFun("__fprintf_chk"));
g_ignore_lists->ignores.push_back(IgnoreFun("_IO_file_xsputn*"));
// fflush internals
g_ignore_lists->ignores.push_back(IgnoreFun("_IO_adjust_column"));
g_ignore_lists->ignores.push_back(IgnoreFun("_IO_flush_all_lockp"));
g_ignore_lists->ignores.push_back(IgnoreFun("__sigsetjmp"));
g_ignore_lists->ignores.push_back(IgnoreFun("__sigjmp_save"));
g_ignore_lists->ignores.push_back(IgnoreFun("_setjmp"));
g_ignore_lists->ignores.push_back(IgnoreFun("_longjmp_unwind"));
g_ignore_lists->ignores.push_back(IgnoreFun("__mktime_internal"));
// http://code.google.com/p/data-race-test/issues/detail?id=40
g_ignore_lists->ignores_r.push_back(IgnoreFun("_ZNSsD1Ev"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("gaih_inet"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("getaddrinfo"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("gethostbyname2_r"));
#ifdef VGO_darwin
// Mac-only ignores
g_ignore_lists->ignores.push_back(IgnoreObj("/usr/lib/dyld"));
g_ignore_lists->ignores.push_back(IgnoreObj("/usr/lib/libobjc.A.dylib"));
g_ignore_lists->ignores.push_back(IgnoreObj("*/libSystem.*.dylib"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("__CFDoExternRefOperation"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("_CFAutoreleasePoolPop"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("_CFAutoreleasePoolPush"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("OSAtomicAdd32"));
g_ignore_lists->ignores_r.push_back(IgnoreTriple("_dispatch_Block_copy",
"/usr/lib/libSystem.B.dylib", "*"));
// pthread_lib_{enter,exit} shouldn't give us any reports since they
// have IGNORE_ALL_ACCESSES_BEGIN/END but they do give the reports...
g_ignore_lists->ignores_r.push_back(IgnoreFun("pthread_lib_enter"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("pthread_lib_exit"));
#endif
#else
// Windows-only ignores
g_ignore_lists->ignores.push_back(IgnoreObj("*ole32.dll"));
g_ignore_lists->ignores.push_back(IgnoreObj("*OLEAUT32.dll"));
g_ignore_lists->ignores.push_back(IgnoreObj("*MSCTF.dll"));
g_ignore_lists->ignores.push_back(IgnoreObj("*ntdll.dll"));
g_ignore_lists->ignores.push_back(IgnoreObj("*mswsock.dll"));
g_ignore_lists->ignores.push_back(IgnoreObj("*WS2_32.dll"));
g_ignore_lists->ignores.push_back(IgnoreObj("*msvcrt.dll"));
g_ignore_lists->ignores.push_back(IgnoreObj("*kernel32.dll"));
g_ignore_lists->ignores.push_back(IgnoreObj("*ADVAPI32.DLL"));
g_ignore_lists->ignores.push_back(IgnoreFun("_EH_epilog3"));
g_ignore_lists->ignores.push_back(IgnoreFun("_EH_prolog3_catch"));
g_ignore_lists->ignores.push_back(IgnoreFun("unnamedImageEntryPoint"));
g_ignore_lists->ignores.push_back(IgnoreFun("_Mtxunlock"));
g_ignore_lists->ignores.push_back(IgnoreFun("IsNLSDefinedString"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("RtlDestroyQueryDebugBuffer"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("BCryptGenerateSymmetricKey"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("SHGetItemFromDataObject"));
// http://code.google.com/p/data-race-test/issues/detail?id=53
g_ignore_lists->ignores_r.push_back(IgnoreFun("_stbuf"));
g_ignore_lists->ignores_r.push_back(IgnoreFun("_getptd"));
// TODO(timurrrr): Add support for FLS (fiber-local-storage)
// http://code.google.com/p/data-race-test/issues/detail?id=55
g_ignore_lists->ignores_r.push_back(IgnoreFun("_freefls"));
#endif
#ifdef ANDROID
// Android does not have a libpthread; pthread_* functions live in libc.
// We have to ignore them one-by-one.
g_ignore_lists->ignores.push_back(IgnoreFun("pthread_*"));
g_ignore_lists->ignores.push_back(IgnoreFun("__init_tls"));
#endif
// Now read the ignore/whitelist files.
for (size_t i = 0; i < G_flags->ignore.size(); i++) {
string file_name = G_flags->ignore[i];
Report("INFO: Reading ignore file: %s\n", file_name.c_str());
string str = ReadFileToString(file_name, true);
ReadIgnoresFromString(str, g_ignore_lists);
}
for (size_t i = 0; i < G_flags->whitelist.size(); i++) {
string file_name = G_flags->whitelist[i];
Report("INFO: Reading whitelist file: %s\n", file_name.c_str());
string str = ReadFileToString(file_name, true);
ReadIgnoresFromString(str, g_white_lists);
}
}
void ThreadSanitizerSetUnwindCallback(ThreadSanitizerUnwindCallback cb) {
G_detector->SetUnwindCallback(cb);
}
void ThreadSanitizerNaclUntrustedRegion(uintptr_t mem_start, uintptr_t mem_end) {
g_nacl_mem_start = mem_start;
g_nacl_mem_end = mem_end;
}
bool AddrIsInNaclUntrustedRegion(uintptr_t addr) {
return addr >= g_nacl_mem_start && addr < g_nacl_mem_end;
}
bool ThreadSanitizerIgnoreForNacl(uintptr_t addr) {
// Ignore trusted addresses if tracing untrusted code, and ignore untrusted
// addresses otherwise.
return G_flags->nacl_untrusted != AddrIsInNaclUntrustedRegion(addr);
}
bool ThreadSanitizerWantToInstrumentSblock(uintptr_t pc) {
string img_name, rtn_name, file_name;
int line_no;
G_stats->pc_to_strings++;
PcToStrings(pc, false, &img_name, &rtn_name, &file_name, &line_no);
if (g_white_lists->ignores.size() > 0) {
bool in_white_list = TripleVectorMatchKnown(g_white_lists->ignores,
rtn_name, img_name, file_name);
if (in_white_list) {
if (debug_ignore) {
Report("INFO: Whitelisted rtn: %s\n", rtn_name.c_str());
}
} else {
return false;
}
}
if (G_flags->ignore_unknown_pcs && rtn_name == "(no symbols)") {
if (debug_ignore) {
Report("INFO: not instrumenting unknown function at %p\n", pc);
}
return false;
}
bool ignore = TripleVectorMatchKnown(g_ignore_lists->ignores,
rtn_name, img_name, file_name) ||
TripleVectorMatchKnown(g_ignore_lists->ignores_r,
rtn_name, img_name, file_name);
if (debug_ignore) {
Printf("%s: pc=%p file_name=%s img_name=%s rtn_name=%s ret=%d\n",
__FUNCTION__, pc, file_name.c_str(), img_name.c_str(),
rtn_name.c_str(), !ignore);
}
bool nacl_ignore = ThreadSanitizerIgnoreForNacl(pc);
return !(ignore || nacl_ignore);
}
bool ThreadSanitizerWantToCreateSegmentsOnSblockEntry(uintptr_t pc) {
string rtn_name;
rtn_name = PcToRtnName(pc, false);
if (G_flags->keep_history == 0)
return false;
return !(TripleVectorMatchKnown(g_ignore_lists->ignores_hist,
rtn_name, "", ""));
}
// Returns true if function at "pc" is marked as "fun_r" in the ignore file.
bool NOINLINE ThreadSanitizerIgnoreAccessesBelowFunction(uintptr_t pc) {
ScopedMallocCostCenter cc(__FUNCTION__);
typedef unordered_map<uintptr_t, bool> Cache;
static Cache *cache = NULL;
{
TIL ignore_below_lock(ts_ignore_below_lock, 18);
if (!cache)
cache = new Cache;
// Fast path - check if we already know the answer.
Cache::iterator i = cache->find(pc);
if (i != cache->end())
return i->second;
}
string rtn_name = PcToRtnName(pc, false);
bool ret =
TripleVectorMatchKnown(g_ignore_lists->ignores_r, rtn_name, "", "");
if (DEBUG_MODE) {
// Heavy test for NormalizeFunctionName: test on all possible inputs in
// debug mode. TODO(timurrrr): Remove when tested.
NormalizeFunctionName(PcToRtnName(pc, true));
}
// Grab the lock again
TIL ignore_below_lock(ts_ignore_below_lock, 19);
if (ret && debug_ignore) {
Report("INFO: ignoring all accesses below the function '%s' (%p)\n",
PcToRtnNameAndFilePos(pc).c_str(), pc);
}
return ((*cache)[pc] = ret);
}
// We intercept a user function with this name
// and answer the user query with a non-NULL string.
extern "C" const char *ThreadSanitizerQuery(const char *query) {
const char *ret = "0";
string str(query);
if (str == "pure_happens_before" && G_flags->pure_happens_before == true) {
ret = "1";
}
if (str == "hybrid_full" &&
G_flags->pure_happens_before == false) {
ret = "1";
}
if (str == "race_verifier" && g_race_verifier_active == true) {
ret = "1";
}
if (DEBUG_MODE && G_flags->debug_level >= 2) {
Printf("ThreadSanitizerQuery(\"%s\") = \"%s\"\n", query, ret);
}
if (str == "trace-level=0") {
Report("INFO: trace-level=0\n");
G_flags->trace_level = 0;
debug_happens_before = false;
}
if (str == "trace-level=1") {
Report("INFO: trace-level=1\n");
G_flags->trace_level = 1;
debug_happens_before = true;
}
return ret;
}
extern void ThreadSanitizerInit() {
ScopedMallocCostCenter cc("ThreadSanitizerInit");
ts_lock = new TSLock;
ts_ignore_below_lock = new TSLock;
g_so_far_only_one_thread = true;
ANNOTATE_BENIGN_RACE(&g_so_far_only_one_thread, "real benign race");
CHECK_EQ(sizeof(ShadowValue), 8);
CHECK(G_flags);
G_stats = new Stats;
SetupIgnore();
G_detector = new Detector;
G_cache = new Cache;
G_expected_races_map = new ExpectedRacesMap;
G_heap_map = new HeapMap<HeapInfo>;
G_thread_stack_map = new HeapMap<ThreadStackInfo>;
{
ScopedMallocCostCenter cc1("Segment::InitClassMembers");
Segment::InitClassMembers();
}
SegmentSet::InitClassMembers();
CacheLine::InitClassMembers();
TSanThread::InitClassMembers();
Lock::InitClassMembers();
LockSet::InitClassMembers();
EventSampler::InitClassMembers();
VTS::InitClassMembers();
// TODO(timurrrr): make sure *::InitClassMembers() are called only once for
// each class
g_publish_info_map = new PublishInfoMap;
g_stack_trace_free_list = new StackTraceFreeList;
g_pcq_map = new PCQMap;
g_atomicCore = new TsanAtomicCore();
if (G_flags->html) {
c_bold = "<font ><b>";
c_red = "<font color=red><b>";
c_green = "<font color=green><b>";
c_magenta = "<font color=magenta><b>";
c_cyan = "<font color=cyan><b>";
c_blue = "<font color=blue><b>";
c_yellow = "<font color=yellow><b>";
c_default = "</b></font>";
} else if (G_flags->color) {
// Enable ANSI colors.
c_bold = "\033[1m";
c_red = "\033[31m";
c_green = "\033[32m";
c_yellow = "\033[33m";
c_blue = "\033[34m";
c_magenta = "\033[35m";
c_cyan = "\033[36m";
c_default = "\033[0m";
}
if (G_flags->verbosity >= 1) {
Report("INFO: Started pid %d\n", getpid());
}
if (G_flags->start_with_global_ignore_on) {
global_ignore = true;
Report("INFO: STARTING WITH GLOBAL IGNORE ON\n");
}
ANNOTATE_BENIGN_RACE(&g_lock_era,
"g_lock_era may be incremented in a racey way");
}
extern void ThreadSanitizerFini() {
G_detector->HandleProgramEnd();
}
extern void ThreadSanitizerDumpAllStacks() {
// first, print running threads.
for (int i = 0; i < TSanThread::NumberOfThreads(); i++) {
TSanThread *t = TSanThread::Get(TID(i));
if (!t || !t->is_running()) continue;
Report("T%d\n", i);
t->ReportStackTrace();
}
// now print all dead threds.
for (int i = 0; i < TSanThread::NumberOfThreads(); i++) {
TSanThread *t = TSanThread::Get(TID(i));
if (!t || t->is_running()) continue;
Report("T%d (not running)\n", i);
t->ReportStackTrace();
}
}
extern void ThreadSanitizerHandleOneEvent(Event *e) {
// Lock is inside on some paths.
G_detector->HandleOneEvent(e);
}
TSanThread *ThreadSanitizerGetThreadByTid(int32_t tid) {
return TSanThread::Get(TID(tid));
}
extern NOINLINE void ThreadSanitizerHandleTrace(int32_t tid, TraceInfo *trace_info,
uintptr_t *tleb) {
ThreadSanitizerHandleTrace(TSanThread::Get(TID(tid)), trace_info, tleb);
}
extern NOINLINE void ThreadSanitizerHandleTrace(TSanThread *thr, TraceInfo *trace_info,
uintptr_t *tleb) {
DCHECK(thr);
// The lock is taken inside on the slow path.
G_detector->HandleTrace(thr,
trace_info->mops(),
trace_info->n_mops(),
trace_info->pc(),
tleb, /*need_locking=*/true);
}
extern NOINLINE void ThreadSanitizerHandleOneMemoryAccess(TSanThread *thr,
MopInfo mop,
uintptr_t addr) {
DCHECK(thr);
G_detector->HandleTrace(thr,
&mop,
1,
mop.create_sblock() ? mop.pc() : 0,
&addr, /*need_locking=*/true);
}
void NOINLINE ThreadSanitizerHandleRtnCall(int32_t tid, uintptr_t call_pc,
uintptr_t target_pc,
IGNORE_BELOW_RTN ignore_below) {
// This does locking on a cold path. Hot path in thread-local.
G_detector->HandleRtnCall(TID(tid), call_pc, target_pc, ignore_below);
if (G_flags->sample_events) {
static EventSampler sampler;
TSanThread *thr = TSanThread::Get(TID(tid));
sampler.Sample(thr, "RTN_CALL", true);
}
}
void NOINLINE ThreadSanitizerHandleRtnExit(int32_t tid) {
// This is a thread-local operation, no need for locking.
TSanThread::Get(TID(tid))->HandleRtnExit();
}
static bool ThreadSanitizerPrintReport(ThreadSanitizerReport *report) {
return G_detector->reports_.PrintReport(report);
}
// -------- TsanAtomicImplementation ------------------ {{{1
// Atomic operation handler.
// The idea of atomic handling is as simple as follows.
// * First, we handle it as normal memory access,
// however with race reporting suppressed. That is, we won't produce any
// race reports during atomic access, but we can produce race reports
// later during normal memory accesses that race with the access.
// * Then, we do the actual atomic memory access.
// It's executed in an atomic fashion, because there can be simultaneous
// atomic accesses from non-instrumented code (FUTEX_OP is a notable
// example).
// * Finally, we update simulated memory model state according to
// the access type and associated memory order as follows.
// For writes and RMWs we create a new entry in the modification order
// of the variable. For reads we scan the modification order starting
// from the latest entry and going back in time, during the scan we decide
// what entry the read returns. A separate VTS (happens-before edges)
// is associated with each entry in the modification order, so that a load
// acquires memory visibility from the exact release-sequence associated
// with the loaded value.
// For details of memory modelling refer to sections 1.10 and 29
// of C++0x standard:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf
uint64_t ThreadSanitizerHandleAtomicOp(int32_t tid,
uintptr_t pc,
tsan_atomic_op op,
tsan_memory_order mo,
tsan_memory_order fail_mo,
size_t size,
void volatile* a,
uint64_t v,
uint64_t cmp) {
if (G_flags->enable_atomic == false) {
uint64_t newv = 0;
uint64_t prev = 0;
return tsan_atomic_do_op(op, mo, fail_mo, size, a, v, cmp, &newv, &prev);
} else {
uint64_t rv = 0;
TSanThread* thr = TSanThread::Get(TID(tid));
// Just a verification of the parameters.
tsan_atomic_verify(op, mo, fail_mo, size, a);
{
TIL til(ts_lock, 0);
uint64_t newv = 0;
uint64_t prev = 0;
// Handle it as a plain mop. Race reports are temporally suppressed,though.
thr->HandleAtomicMop((uintptr_t)a, pc, op, mo, size);
// Do the actual atomic operation. It's executed in an atomic fashion,
// because there can be simultaneous atomic accesses
// from non-instrumented code.
rv = tsan_atomic_do_op(op, mo, fail_mo, size, a, v, cmp, &newv, &prev);
PrintfIf(debug_atomic, "rv=%llu, newv=%llu, prev=%llu\n",
(unsigned long long)rv,
(unsigned long long)newv,
(unsigned long long)prev);
if (op != tsan_atomic_op_fence) {
if (op == tsan_atomic_op_load) {
// For reads it replaces the return value with a random value
// from visible sequence of side-effects in the modification order
// of the variable.
rv = g_atomicCore->HandleRead(thr, (uintptr_t)a, rv,
tsan_atomic_is_acquire(mo));
} else if ((op == tsan_atomic_op_compare_exchange_weak
|| op == tsan_atomic_op_compare_exchange_strong)
&& cmp != rv) {
// Failed compare_exchange is handled as read, because, well,
// it's indeed just a read (at least logically).
g_atomicCore->HandleRead(thr, (uintptr_t)a, rv,
tsan_atomic_is_acquire(fail_mo));
} else {
// For writes and RMW operations it updates modification order
// of the atomic variable.
g_atomicCore->HandleWrite(thr, (uintptr_t)a, newv, prev,
tsan_atomic_is_acquire(mo),
tsan_atomic_is_release(mo),
tsan_atomic_is_rmw(op));
}
}
}
PrintfIf(debug_atomic, "ATOMIC: %s-%s %p (%llu,%llu)=%llu\n",
tsan_atomic_to_str(op),
tsan_atomic_to_str(mo),
a, (unsigned long long)v, (unsigned long long)cmp,
(unsigned long long)rv);
return rv;
}
}
TsanAtomicCore::TsanAtomicCore() {
}
void TsanAtomicCore::HandleWrite(TSanThread* thr,
uintptr_t a,
uint64_t v,
uint64_t prev,
bool const is_acquire,
bool const is_release,
bool const is_rmw) {
PrintfIf(debug_atomic, "HIST(%p): store acquire=%u, release=%u, rmw=%u\n",
(void*)a, is_acquire, is_release, is_rmw);
Atomic* atomic = &atomic_map_[a];
// Fix modification history if there were untracked accesses.
AtomicFixHist(atomic, prev);
AtomicHistoryEntry& hprv = atomic->hist
[(atomic->hist_pos - 1) % Atomic::kHistSize];
AtomicHistoryEntry& hist = atomic->hist
[atomic->hist_pos % Atomic::kHistSize];
// Fill in new entry in the modification history.
hist.val = v;
hist.tid = thr->tid();
hist.clk = thr->vts()->clk(thr->tid());
if (hist.vts != 0) {
VTS::Unref(hist.vts);
hist.vts = 0;
}
atomic->hist_pos += 1;
// Update VTS according to memory access type and memory ordering.
if (is_rmw) {
if (is_release) {
if (hprv.vts != 0) {
hist.vts = VTS::Join(hprv.vts, thr->vts());
} else {
hist.vts = thr->vts()->Clone();
}
} else if (hprv.vts != 0) {
hist.vts = hprv.vts->Clone();
}
if (is_acquire && hprv.vts != 0) {
thr->NewSegmentForWait(hprv.vts);
}
} else {
DCHECK(is_acquire == false);
if (is_release) {
hist.vts = thr->vts()->Clone();
}
}
// Update the thread's VTS if it's relese memory access.
if (is_release) {
thr->NewSegmentForSignal();
if (debug_happens_before) {
Printf("T%d: Signal: %p:\n %s %s\n %s\n",
thr->tid().raw(), a,
thr->vts()->ToString().c_str(),
Segment::ToString(thr->sid()).c_str(),
hist.vts->ToString().c_str());
if (G_flags->debug_level >= 1) {
thr->ReportStackTrace();
}
}
}
}
uint64_t TsanAtomicCore::HandleRead(TSanThread* thr,
uintptr_t a,
uint64_t v,
bool is_acquire) {
PrintfIf(debug_atomic, "HIST(%p): {\n", (void*)a);
Atomic* atomic = &atomic_map_[a];
// Fix modification history if there were untracked accesses.
AtomicFixHist(atomic, v);
AtomicHistoryEntry* hist0 = 0;
int32_t seen_seq = 0;
int32_t const seen_seq0 = atomic->last_seen.clock(thr->tid());
// Scan modification order of the variable from the latest entry
// back in time. For each side-effect (write) we determine as to
// whether we have to yield the value or we can go back in time further.
for (int32_t i = 0; i != Atomic::kHistSize; i += 1) {
int32_t const idx = (atomic->hist_pos - i - 1);
CHECK(idx >= 0);
AtomicHistoryEntry& hist = atomic->hist[idx % Atomic::kHistSize];
PrintfIf(debug_atomic, "HIST(%p): #%u (tid=%u, clk=%u,"
" val=%llu) vts=%u\n",
(void*)a, (unsigned)i, (unsigned)hist.tid.raw(),
(unsigned)hist.clk, (unsigned long long)hist.val,
(unsigned)thr->vts()->clk(hist.tid));
if (hist.tid.raw() == TID::kInvalidTID) {
// We hit an uninialized entry, that is, it's an access to an unitialized
// variable (potentially due to "race").
// Unfortunately, it should not happen as of now.
// TODO(dvyukov): how can we detect and report unitialized atomic reads?.
// .
hist0 = 0;
break;
} else if (i == Atomic::kHistSize - 1) {
// It's the last entry so we have to return it
// because we have to return something.
PrintfIf(debug_atomic, "HIST(%p): replaced: last\n", (void*)a);
hist0 = &hist;
break;
} else if (seen_seq0 >= idx) {
// The thread had already seen the entry so we have to return
// at least it.
PrintfIf(debug_atomic, "HIST(%p): replaced: stability\n", (void*)a);
hist0 = &hist;
break;
} else if (thr->vts()->clk(hist.tid) >= hist.clk) {
// The write happened-before the read, so we have to return it.
PrintfIf(debug_atomic, "HIST(%p): replaced: ordering\n", (void*)a);
hist0 = &hist;
break;
} else if (thr->random() % 2) {
// We are not obliged to return the entry but we can (and decided to do).
PrintfIf(debug_atomic, "HIST(%p): replaced: coherence\n", (void*)a);
seen_seq = idx;
hist0 = &hist;
break;
} else {
// Move on to the next (older) entry.
PrintfIf(debug_atomic, "HIST(%p): can be replaced but not\n", (void*)a);
}
}
if (hist0 != 0) {
v = hist0->val;
// Acquire mamory visibility is needed.
if (is_acquire) {
if (hist0->vts != 0) {
thr->NewSegmentForWait(hist0->vts);
}
if (debug_happens_before) {
Printf("T%d: Wait: %p:\n %s %s\n",
thr->tid().raw(), a,
thr->vts()->ToString().c_str(),
Segment::ToString(thr->sid()).c_str());
if (G_flags->debug_level >= 1) {
thr->ReportStackTrace();
}
}
}
if (seen_seq != 0) {
// Mark the entry as seen so we won't return any older entry later.
atomic->last_seen.update(thr->tid(), seen_seq);
}
} else {
CHECK("should never happen as of now" == 0);
PrintfIf(debug_atomic, "HIST(%p): UNITIALIZED LOAD\n", (void*)a);
v = thr->random();
}
PrintfIf(debug_atomic, "HIST(%p): } -> %llu\n",
(void*)a, (unsigned long long)v);
return v;
}
void TsanAtomicCore::ClearMemoryState(uintptr_t a, uintptr_t b) {
DCHECK(a <= b);
DCHECK(G_flags->enable_atomic || atomic_map_.empty());
AtomicMap::iterator begin (atomic_map_.lower_bound(a));
AtomicMap::iterator pos (begin);
for (; pos != atomic_map_.end() && pos->first <= b; ++pos) {
pos->second.reset();
}
atomic_map_.erase(begin, pos);
}
void TsanAtomicCore::AtomicFixHist(Atomic* atomic, uint64_t prev) {
AtomicHistoryEntry& hprv = atomic->hist
[(atomic->hist_pos - 1) % Atomic::kHistSize];
// In case we had missed an atomic access (that is, an access from
// non-instrumented code), reset whole history and initialize it
// with a single entry that happened "before world creation".
if (prev != hprv.val) {
PrintfIf(debug_atomic, "HIST RESET\n");
atomic->reset();
AtomicHistoryEntry& hist = atomic->hist
[atomic->hist_pos % Atomic::kHistSize];
hist.val = prev;
hist.tid = TID(0);
hist.clk = 0;
atomic->hist_pos += 1;
}
}
TsanAtomicCore::Atomic::Atomic() {
reset(true);
}
void TsanAtomicCore::Atomic::reset(bool init) {
hist_pos = sizeof(hist)/sizeof(hist[0]) + 1;
for (size_t i = 0; i != sizeof(hist)/sizeof(hist[0]); i += 1) {
hist[i].val = 0xBCEBC041;
hist[i].tid = TID(TID::kInvalidTID);
hist[i].clk = -1;
if (init == false && hist[i].vts != 0)
VTS::Unref(hist[i].vts);
hist[i].vts = 0;
}
last_seen.reset();
}
// -------- TODO -------------------------- {{{1
// - Support configurable aliases for function names (is it doable in valgrind)?
// - Correctly support atomic operations (not just ignore).
// - Handle INC as just one write
// - same for memset, etc
// - Implement correct handling of memory accesses with different sizes.
// - Do not create HB arcs between RdUnlock and RdLock
// - Compress cache lines
// - Optimize the case where a threads signals twice in a row on the same
// address.
// - Fix --ignore-in-dtor if --demangle=no.
// - Use cpplint (http://code.google.com/p/google-styleguide)
// - Get rid of annoying casts in printfs.
// - Compress stack traces (64-bit only. may save up to 36 bytes per segment).
// end. {{{1
// vim:shiftwidth=2:softtabstop=2:expandtab:tw=80