diff --git a/compiler-rt/lib/tsan/rtl/CMakeLists.txt b/compiler-rt/lib/tsan/rtl/CMakeLists.txt index d7d84706bfd58..eb5f4a84fa359 100644 --- a/compiler-rt/lib/tsan/rtl/CMakeLists.txt +++ b/compiler-rt/lib/tsan/rtl/CMakeLists.txt @@ -49,6 +49,9 @@ set(TSAN_SOURCES tsan_symbolize.cpp tsan_sync.cpp tsan_vector_clock.cpp + rsan.cpp + rsan_report.cpp + rsan_stacktrace.cpp ) set(TSAN_CXX_SOURCES @@ -59,6 +62,10 @@ set(TSAN_PREINIT_SOURCES tsan_preinit.cpp ) +set_source_files_properties(tsan_interface_atomic.cpp PROPERTIES COMPILE_FLAGS -std=c++20) +set_source_files_properties(tsan_mman.cpp PROPERTIES COMPILE_FLAGS -std=c++20) +set_source_files_properties(tsan_rtl_mutex.cpp PROPERTIES COMPILE_FLAGS -std=c++20) + if(APPLE) list(APPEND TSAN_SOURCES tsan_interceptors_mac.cpp diff --git a/compiler-rt/lib/tsan/rtl/rsan.cpp b/compiler-rt/lib/tsan/rtl/rsan.cpp new file mode 100644 index 0000000000000..fb696eb277b98 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan.cpp @@ -0,0 +1,8 @@ +#include "rsan_vectorclock.hpp" +#include "rsan_robustnessmodel.hpp" +#include "rsan_instrument.hpp" +#include "rsan_map.hpp" +#include "rsan_arena.hpp" + +namespace Robustness{ +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_action.hpp b/compiler-rt/lib/tsan/rtl/rsan_action.hpp new file mode 100644 index 0000000000000..a066b4e6ea8fc --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_action.hpp @@ -0,0 +1,97 @@ +#pragma once +#include "rsan_defs.hpp" +namespace Robustness::Action{ + struct StoreAction{ + ThreadId tid; + Address addr; + int size; + }; + struct LoadAction{ + ThreadId tid; + Address addr; + int size; + }; + struct AtomicVerifyAction{ + ThreadId tid; + Address addr; + morder mo; + int size; + }; + struct AtomicVerifyStoreAction{ + ThreadId tid; + Address addr; + morder mo; + int size; + }; + struct AtomicLoadAction{ + ThreadId tid; + Address addr; + morder mo; + int size; + bool rmw; + DebugInfo dbg; + }; + struct AtomicStoreAction{ + ThreadId tid; + Address addr; + morder mo; + int size; + uint64_t oldValue; + uint64_t newValue; + DebugInfo dbg; + }; + struct AtomicRMWAction{ + ThreadId tid; + Address addr; + morder mo; + int size; + uint64_t oldValue; + uint64_t newValue; + DebugInfo dbg; + }; + struct AtomicCasAction{ + ThreadId tid; + Address addr; + morder mo; + int size; + uint64_t oldValue; + uint64_t newValue; + bool success; + DebugInfo dbg; + }; + struct FenceAction{ + ThreadId tid; + morder mo; + }; + struct TrackAction{ + ThreadId tid; + Address addr; + uint64_t value; + }; + struct WaitAction{ + ThreadId tid; + Address addr; + uint64_t value; + DebugInfo dbg; + }; + struct BcasAction{ + ThreadId tid; + Address addr; + uint64_t value; + DebugInfo dbg; + }; + struct ThreadCreate{ + ThreadId creator, createe; + }; + struct ThreadJoin{ + ThreadId absorber, absorbee; + }; + struct Free{ + ThreadId tid; + Address addr; + uptr size; + DebugInfo dbg; + }; +} + + diff --git a/compiler-rt/lib/tsan/rtl/rsan_arena.hpp b/compiler-rt/lib/tsan/rtl/rsan_arena.hpp new file mode 100644 index 0000000000000..95fe3c5229942 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_arena.hpp @@ -0,0 +1,45 @@ +#pragma once +#include "rsan_vector.h" +#include "rsan_defs.hpp" +#include "sanitizer_common/sanitizer_placement_new.h" + +namespace Robustness { + template< class T > + class Arena { + + //const FACTOR = 2; + static const u8 BASE = 8; + + u64 cv = 0; + u64 ci = 0; + + Vector> vs; + Arena(const Arena&) = delete; + + + public: + Arena() = default; + ~Arena() { + for (auto& v : vs) + v.clear(); + } + + T* allocate(){ + if (cv == vs.size()){ + vs.push_back(); + vs[cv].resize(BASE << (cv)); + ci = 0; + } + DCHECK_GT(vs.size(), cv); + DCHECK_GT(vs[cv].size(), ci); + auto ret = &vs[cv][ci++]; + DCHECK_GT(ret, 0); + if (ci >= vs[cv].size()){ + ++cv; + } + + new (ret) T(); + return ret; + } + }; +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_defs.hpp b/compiler-rt/lib/tsan/rtl/rsan_defs.hpp new file mode 100644 index 0000000000000..6b11897ce9a76 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_defs.hpp @@ -0,0 +1,96 @@ +#pragma once +#include "tsan_defs.h" +#include "tsan_rtl.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_allocator_internal.h" + +//class __tsan::ThreadState; + +namespace Robustness{ + using __tsan::s8; + using __tsan::u8; + using __tsan::s16; + using __tsan::u16; + using __tsan::s32; + using __tsan::u32; + using __tsan::s64; + using __tsan::u64; + using __tsan::uptr; + using __tsan::Epoch; + using __tsan::EpochInc; + using __tsan::EpochOverflow; + using __tsan::kEpochZero; + using __tsan::kEpochOver; + using __tsan::kEpochLast; + typedef __tsan::Epoch timestamp_t; + typedef s64 ssize_t; + typedef u64 uint64_t; + typedef s64 int64_t; + typedef __PTRDIFF_TYPE__ ptrdiff_t; + typedef __SIZE_TYPE__ size_t; + + typedef u8 uint8_t;; + + typedef u64 Address; + typedef u64 LocationId; + + typedef u32 ThreadId; + + using __tsan::InternalScopedString; + + using __tsan::flags; + + using __sanitizer::IsAligned; + + using __sanitizer::LowLevelAllocator; + using __sanitizer::InternalAlloc; + using __sanitizer::InternalFree; + using __sanitizer::internal_memcpy; + using __sanitizer::internal_memmove; + using __sanitizer::internal_memset; + using __sanitizer::RoundUpTo; + using __sanitizer::RoundUpToPowerOfTwo; + using __sanitizer::GetPageSizeCached; + using __sanitizer::MostSignificantSetBitIndex; + using __sanitizer::MmapOrDie; + using __sanitizer::UnmapOrDie; + using __sanitizer::Max; + using __sanitizer::Swap; + using __sanitizer::forward; + using __sanitizer::move; + + using __sanitizer::Printf; + using __sanitizer::Report; + + using __sanitizer::Lock; + using __sanitizer::Mutex; + + template + struct Pair{ + T1 first; + T2 second; + }; + template + auto pair(T1 fst, T2 snd){ + return Pair{fst, snd}; + } + + using __tsan::max; + using __tsan::min; + + enum class ViolationType{ + read, write, + }; + + struct DebugInfo { + __tsan::ThreadState* thr = nullptr; + uptr pc = 0xDEADBEEF; + }; + + template + inline constexpr bool always_false_v = false; + + inline bool isRobustness() { + return __tsan::flags()->enable_robustness; + } +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_dense_map.h b/compiler-rt/lib/tsan/rtl/rsan_dense_map.h new file mode 100644 index 0000000000000..57775613ed00b --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_dense_map.h @@ -0,0 +1,714 @@ +//===- sanitizer_dense_map.h - Dense probed hash table ----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is fork of llvm/ADT/DenseMap.h class with the following changes: +// * Use mmap to allocate. +// * No iterators. +// * Does not shrink. +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "rsan_defs.hpp" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_dense_map_info.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_type_traits.h" + +namespace Robustness { + namespace detail{ + using __sanitizer::detail::DenseMapPair; + using __sanitizer::detail::combineHashValue; + } // namespace detail + using __sanitizer::DenseMapInfo; + +template +class DenseMapBase { + public: + using size_type = unsigned; + using key_type = KeyT; + using mapped_type = ValueT; + using value_type = BucketT; + + WARN_UNUSED_RESULT bool empty() const { return getNumEntries() == 0; } + unsigned size() const { return getNumEntries(); } + + /// Grow the densemap so that it can contain at least \p NumEntries items + /// before resizing again. + void reserve(size_type NumEntries) { + auto NumBuckets = getMinBucketToReserveForEntries(NumEntries); + if (NumBuckets > getNumBuckets()) + grow(NumBuckets); + } + + void clear() { + if (getNumEntries() == 0 && getNumTombstones() == 0) + return; + + const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); + if (__sanitizer::is_trivially_destructible::value) { + // Use a simpler loop when values don't need destruction. + for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) + P->getFirst() = EmptyKey; + } else { + unsigned NumEntries = getNumEntries(); + for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { + if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey)) { + if (!KeyInfoT::isEqual(P->getFirst(), TombstoneKey)) { + P->getSecond().~ValueT(); + --NumEntries; + } + P->getFirst() = EmptyKey; + } + } + CHECK_EQ(NumEntries, 0); + } + setNumEntries(0); + setNumTombstones(0); + } + + /// Return 1 if the specified key is in the map, 0 otherwise. + size_type count(const KeyT &Key) const { + const BucketT *TheBucket; + return LookupBucketFor(Key, TheBucket) ? 1 : 0; + } + + value_type *find(const KeyT &Key) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return TheBucket; + return nullptr; + } + const value_type *find(const KeyT &Key) const { + const BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return TheBucket; + return nullptr; + } + bool contains(const KeyT &Key) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return true; + return false; + } + + /// Alternate version of find() which allows a different, and possibly + /// less expensive, key type. + /// The DenseMapInfo is responsible for supplying methods + /// getHashValue(LookupKeyT) and isEqual(LookupKeyT, KeyT) for each key + /// type used. + template + value_type *find_as(const LookupKeyT &Key) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return TheBucket; + return nullptr; + } + template + const value_type *find_as(const LookupKeyT &Key) const { + const BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return TheBucket; + return nullptr; + } + + /// lookup - Return the entry for the specified key, or a default + /// constructed value if no such entry exists. + ValueT lookup(const KeyT &Key) const { + const BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return TheBucket->getSecond(); + return ValueT(); + } + + // Inserts key,value pair into the map if the key isn't already in the map. + // If the key is already in the map, it returns false and doesn't update the + // value. + detail::DenseMapPair insert(const value_type &KV) { + return try_emplace(KV.first, KV.second); + } + + // Inserts key,value pair into the map if the key isn't already in the map. + // If the key is already in the map, it returns false and doesn't update the + // value. + detail::DenseMapPair insert(value_type &&KV) { + return try_emplace(__sanitizer::move(KV.first), + __sanitizer::move(KV.second)); + } + + // Inserts key,value pair into the map if the key isn't already in the map. + // The value is constructed in-place if the key is not in the map, otherwise + // it is not moved. + template + detail::DenseMapPair try_emplace(KeyT &&Key, + Ts &&...Args) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return {TheBucket, false}; // Already in map. + + // Otherwise, insert the new element. + TheBucket = InsertIntoBucket(TheBucket, __sanitizer::move(Key), + __sanitizer::forward(Args)...); + return {TheBucket, true}; + } + + // Inserts key,value pair into the map if the key isn't already in the map. + // The value is constructed in-place if the key is not in the map, otherwise + // it is not moved. + template + detail::DenseMapPair try_emplace(const KeyT &Key, + Ts &&...Args) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return {TheBucket, false}; // Already in map. + + // Otherwise, insert the new element. + TheBucket = + InsertIntoBucket(TheBucket, Key, __sanitizer::forward(Args)...); + return {TheBucket, true}; + } + + /// Alternate version of insert() which allows a different, and possibly + /// less expensive, key type. + /// The DenseMapInfo is responsible for supplying methods + /// getHashValue(LookupKeyT) and isEqual(LookupKeyT, KeyT) for each key + /// type used. + template + detail::DenseMapPair insert_as(value_type &&KV, + const LookupKeyT &Val) { + BucketT *TheBucket; + if (LookupBucketFor(Val, TheBucket)) + return {TheBucket, false}; // Already in map. + + // Otherwise, insert the new element. + TheBucket = + InsertIntoBucketWithLookup(TheBucket, __sanitizer::move(KV.first), + __sanitizer::move(KV.second), Val); + return {TheBucket, true}; + } + + bool erase(const KeyT &Val) { + BucketT *TheBucket; + if (!LookupBucketFor(Val, TheBucket)) + return false; // not in map. + + TheBucket->getSecond().~ValueT(); + TheBucket->getFirst() = getTombstoneKey(); + decrementNumEntries(); + incrementNumTombstones(); + return true; + } + + void erase(value_type *I) { + CHECK_NE(I, nullptr); + BucketT *TheBucket = &*I; + TheBucket->getSecond().~ValueT(); + TheBucket->getFirst() = getTombstoneKey(); + decrementNumEntries(); + incrementNumTombstones(); + } + + value_type &FindAndConstruct(const KeyT &Key) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return *TheBucket; + + return *InsertIntoBucket(TheBucket, Key); + } + + ValueT &operator[](const KeyT &Key) { return FindAndConstruct(Key).second; } + + value_type &FindAndConstruct(KeyT &&Key) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return *TheBucket; + + return *InsertIntoBucket(TheBucket, __sanitizer::move(Key)); + } + + ValueT &operator[](KeyT &&Key) { + return FindAndConstruct(__sanitizer::move(Key)).second; + } + + /// Iterate over active entries of the container. + /// + /// Function can return fast to stop the process. + template + void forEach(Fn fn) { + const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); + for (auto *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { + const KeyT K = P->getFirst(); + if (!KeyInfoT::isEqual(K, EmptyKey) && + !KeyInfoT::isEqual(K, TombstoneKey)) { + if (!fn(*P)) + return; + } + } + } + + template + void forEach(Fn fn) const { + const_cast(this)->forEach( + [&](const value_type &KV) { return fn(KV); }); + } + + protected: + DenseMapBase() = default; + + void destroyAll() { + if (getNumBuckets() == 0) // Nothing to do. + return; + + const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); + for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { + if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(P->getFirst(), TombstoneKey)) + P->getSecond().~ValueT(); + P->getFirst().~KeyT(); + } + } + + void initEmpty() { + setNumEntries(0); + setNumTombstones(0); + + CHECK_EQ((getNumBuckets() & (getNumBuckets() - 1)), 0); + const KeyT EmptyKey = getEmptyKey(); + for (BucketT *B = getBuckets(), *E = getBucketsEnd(); B != E; ++B) + ::new (&B->getFirst()) KeyT(EmptyKey); + } + + /// Returns the number of buckets to allocate to ensure that the DenseMap can + /// accommodate \p NumEntries without need to grow(). + unsigned getMinBucketToReserveForEntries(unsigned NumEntries) { + // Ensure that "NumEntries * 4 < NumBuckets * 3" + if (NumEntries == 0) + return 0; + // +1 is required because of the strict equality. + // For example if NumEntries is 48, we need to return 401. + return RoundUpToPowerOfTwo((NumEntries * 4 / 3 + 1) + /* NextPowerOf2 */ 1); + } + + void moveFromOldBuckets(BucketT *OldBucketsBegin, BucketT *OldBucketsEnd) { + initEmpty(); + + // Insert all the old elements. + const KeyT EmptyKey = getEmptyKey(); + const KeyT TombstoneKey = getTombstoneKey(); + for (BucketT *B = OldBucketsBegin, *E = OldBucketsEnd; B != E; ++B) { + if (!KeyInfoT::isEqual(B->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(B->getFirst(), TombstoneKey)) { + // Insert the key/value into the new table. + BucketT *DestBucket; + bool FoundVal = LookupBucketFor(B->getFirst(), DestBucket); + (void)FoundVal; // silence warning. + CHECK(!FoundVal); + DestBucket->getFirst() = __sanitizer::move(B->getFirst()); + ::new (&DestBucket->getSecond()) + ValueT(__sanitizer::move(B->getSecond())); + incrementNumEntries(); + + // Free the value. + B->getSecond().~ValueT(); + } + B->getFirst().~KeyT(); + } + } + + template + void copyFrom( + const DenseMapBase &other) { + CHECK_NE(&other, this); + CHECK_EQ(getNumBuckets(), other.getNumBuckets()); + + setNumEntries(other.getNumEntries()); + setNumTombstones(other.getNumTombstones()); + + if (__sanitizer::is_trivially_copyable::value && + __sanitizer::is_trivially_copyable::value) + internal_memcpy(reinterpret_cast(getBuckets()), + other.getBuckets(), getNumBuckets() * sizeof(BucketT)); + else + for (uptr i = 0; i < getNumBuckets(); ++i) { + ::new (&getBuckets()[i].getFirst()) + KeyT(other.getBuckets()[i].getFirst()); + if (!KeyInfoT::isEqual(getBuckets()[i].getFirst(), getEmptyKey()) && + !KeyInfoT::isEqual(getBuckets()[i].getFirst(), getTombstoneKey())) + ::new (&getBuckets()[i].getSecond()) + ValueT(other.getBuckets()[i].getSecond()); + } + } + + static unsigned getHashValue(const KeyT &Val) { + return KeyInfoT::getHashValue(Val); + } + + template + static unsigned getHashValue(const LookupKeyT &Val) { + return KeyInfoT::getHashValue(Val); + } + + static const KeyT getEmptyKey() { return KeyInfoT::getEmptyKey(); } + + static const KeyT getTombstoneKey() { return KeyInfoT::getTombstoneKey(); } + + private: + unsigned getNumEntries() const { + return static_cast(this)->getNumEntries(); + } + + void setNumEntries(unsigned Num) { + static_cast(this)->setNumEntries(Num); + } + + void incrementNumEntries() { setNumEntries(getNumEntries() + 1); } + + void decrementNumEntries() { setNumEntries(getNumEntries() - 1); } + + unsigned getNumTombstones() const { + return static_cast(this)->getNumTombstones(); + } + + void setNumTombstones(unsigned Num) { + static_cast(this)->setNumTombstones(Num); + } + + void incrementNumTombstones() { setNumTombstones(getNumTombstones() + 1); } + + void decrementNumTombstones() { setNumTombstones(getNumTombstones() - 1); } + + const BucketT *getBuckets() const { + return static_cast(this)->getBuckets(); + } + + BucketT *getBuckets() { return static_cast(this)->getBuckets(); } + + unsigned getNumBuckets() const { + return static_cast(this)->getNumBuckets(); + } + + BucketT *getBucketsEnd() { return getBuckets() + getNumBuckets(); } + + const BucketT *getBucketsEnd() const { + return getBuckets() + getNumBuckets(); + } + + void grow(unsigned AtLeast) { static_cast(this)->grow(AtLeast); } + + template + BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, + ValueArgs &&...Values) { + TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket); + + TheBucket->getFirst() = __sanitizer::forward(Key); + ::new (&TheBucket->getSecond()) + ValueT(__sanitizer::forward(Values)...); + return TheBucket; + } + + template + BucketT *InsertIntoBucketWithLookup(BucketT *TheBucket, KeyT &&Key, + ValueT &&Value, LookupKeyT &Lookup) { + TheBucket = InsertIntoBucketImpl(Key, Lookup, TheBucket); + + TheBucket->getFirst() = __sanitizer::move(Key); + ::new (&TheBucket->getSecond()) ValueT(__sanitizer::move(Value)); + return TheBucket; + } + + template + BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup, + BucketT *TheBucket) { + // If the load of the hash table is more than 3/4, or if fewer than 1/8 of + // the buckets are empty (meaning that many are filled with tombstones), + // grow the table. + // + // The later case is tricky. For example, if we had one empty bucket with + // tons of tombstones, failing lookups (e.g. for insertion) would have to + // probe almost the entire table until it found the empty bucket. If the + // table completely filled with tombstones, no lookup would ever succeed, + // causing infinite loops in lookup. + unsigned NewNumEntries = getNumEntries() + 1; + unsigned NumBuckets = getNumBuckets(); + if (UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) { + this->grow(NumBuckets * 2); + LookupBucketFor(Lookup, TheBucket); + NumBuckets = getNumBuckets(); + } else if (UNLIKELY(NumBuckets - (NewNumEntries + getNumTombstones()) <= + NumBuckets / 8)) { + this->grow(NumBuckets); + LookupBucketFor(Lookup, TheBucket); + } + CHECK(TheBucket); + + // Only update the state after we've grown our bucket space appropriately + // so that when growing buckets we have self-consistent entry count. + incrementNumEntries(); + + // If we are writing over a tombstone, remember this. + const KeyT EmptyKey = getEmptyKey(); + if (!KeyInfoT::isEqual(TheBucket->getFirst(), EmptyKey)) + decrementNumTombstones(); + + return TheBucket; + } + + /// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in + /// FoundBucket. If the bucket contains the key and a value, this returns + /// true, otherwise it returns a bucket with an empty marker or tombstone and + /// returns false. + template + bool LookupBucketFor(const LookupKeyT &Val, + const BucketT *&FoundBucket) const { + const BucketT *BucketsPtr = getBuckets(); + const unsigned NumBuckets = getNumBuckets(); + + if (NumBuckets == 0) { + FoundBucket = nullptr; + return false; + } + + // FoundTombstone - Keep track of whether we find a tombstone while probing. + const BucketT *FoundTombstone = nullptr; + const KeyT EmptyKey = getEmptyKey(); + const KeyT TombstoneKey = getTombstoneKey(); + CHECK(!KeyInfoT::isEqual(Val, EmptyKey)); + CHECK(!KeyInfoT::isEqual(Val, TombstoneKey)); + + unsigned BucketNo = getHashValue(Val) & (NumBuckets - 1); + unsigned ProbeAmt = 1; + while (true) { + const BucketT *ThisBucket = BucketsPtr + BucketNo; + // Found Val's bucket? If so, return it. + if (LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) { + FoundBucket = ThisBucket; + return true; + } + + // If we found an empty bucket, the key doesn't exist in the set. + // Insert it and return the default value. + if (LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) { + // If we've already seen a tombstone while probing, fill it in instead + // of the empty bucket we eventually probed to. + FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket; + return false; + } + + // If this is a tombstone, remember it. If Val ends up not in the map, we + // prefer to return it than something that would require more probing. + if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) && + !FoundTombstone) + FoundTombstone = ThisBucket; // Remember the first tombstone found. + + // Otherwise, it's a hash collision or a tombstone, continue quadratic + // probing. + BucketNo += ProbeAmt++; + BucketNo &= (NumBuckets - 1); + } + } + + template + bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) { + const BucketT *ConstFoundBucket; + bool Result = const_cast(this)->LookupBucketFor( + Val, ConstFoundBucket); + FoundBucket = const_cast(ConstFoundBucket); + return Result; + } + + public: + /// Return the approximate size (in bytes) of the actual map. + /// This is just the raw memory used by DenseMap. + /// If entries are pointers to objects, the size of the referenced objects + /// are not included. + uptr getMemorySize() const { + return RoundUpTo(getNumBuckets() * sizeof(BucketT), GetPageSizeCached()); + } +}; + +/// Equality comparison for DenseMap. +/// +/// Iterates over elements of LHS confirming that each (key, value) pair in LHS +/// is also in RHS, and that no additional pairs are in RHS. +/// Equivalent to N calls to RHS.find and N value comparisons. Amortized +/// complexity is linear, worst case is O(N^2) (if every hash collides). +template +bool operator==( + const DenseMapBase &LHS, + const DenseMapBase &RHS) { + if (LHS.size() != RHS.size()) + return false; + + bool R = true; + LHS.forEach( + [&](const typename DenseMapBase::value_type &KV) -> bool { + const auto *I = RHS.find(KV.first); + if (!I || I->second != KV.second) { + R = false; + return false; + } + return true; + }); + + return R; +} + +/// Inequality comparison for DenseMap. +/// +/// Equivalent to !(LHS == RHS). See operator== for performance notes. +template +bool operator!=( + const DenseMapBase &LHS, + const DenseMapBase &RHS) { + return !(LHS == RHS); +} + +template , + typename BucketT = detail::DenseMapPair> +class DenseMap : public DenseMapBase, + KeyT, ValueT, KeyInfoT, BucketT> { + friend class DenseMapBase; + + // Lift some types from the dependent base class into this class for + // simplicity of referring to them. + using BaseT = DenseMapBase; + + BucketT *Buckets = nullptr; + unsigned NumEntries = 0; + unsigned NumTombstones = 0; + unsigned NumBuckets = 0; + + public: + /// Create a DenseMap with an optional \p InitialReserve that guarantee that + /// this number of elements can be inserted in the map without grow() + explicit DenseMap(unsigned InitialReserve) { init(InitialReserve); } + constexpr DenseMap() = default; + + DenseMap(const DenseMap &other) : BaseT() { + init(0); + copyFrom(other); + } + + DenseMap(DenseMap &&other) : BaseT() { + init(0); + swap(other); + } + + ~DenseMap() { + this->destroyAll(); + deallocate_buffer(Buckets, sizeof(BucketT) * NumBuckets); + } + + void swap(DenseMap &RHS) { + Swap(Buckets, RHS.Buckets); + Swap(NumEntries, RHS.NumEntries); + Swap(NumTombstones, RHS.NumTombstones); + Swap(NumBuckets, RHS.NumBuckets); + } + + DenseMap &operator=(const DenseMap &other) { + if (&other != this) + copyFrom(other); + return *this; + } + + DenseMap &operator=(DenseMap &&other) { + this->destroyAll(); + deallocate_buffer(Buckets, sizeof(BucketT) * NumBuckets, alignof(BucketT)); + init(0); + swap(other); + return *this; + } + + void copyFrom(const DenseMap &other) { + this->destroyAll(); + deallocate_buffer(Buckets, sizeof(BucketT) * NumBuckets); + if (allocateBuckets(other.NumBuckets)) { + this->BaseT::copyFrom(other); + } else { + NumEntries = 0; + NumTombstones = 0; + } + } + + void init(unsigned InitNumEntries) { + auto InitBuckets = BaseT::getMinBucketToReserveForEntries(InitNumEntries); + if (allocateBuckets(InitBuckets)) { + this->BaseT::initEmpty(); + } else { + NumEntries = 0; + NumTombstones = 0; + } + } + + void grow(unsigned AtLeast) { + unsigned OldNumBuckets = NumBuckets; + BucketT *OldBuckets = Buckets; + + allocateBuckets(RoundUpToPowerOfTwo(Max(64, AtLeast))); + CHECK(Buckets); + if (!OldBuckets) { + this->BaseT::initEmpty(); + return; + } + + this->moveFromOldBuckets(OldBuckets, OldBuckets + OldNumBuckets); + + // Free the old table. + deallocate_buffer(OldBuckets, sizeof(BucketT) * OldNumBuckets); + } + + private: + unsigned getNumEntries() const { return NumEntries; } + + void setNumEntries(unsigned Num) { NumEntries = Num; } + + unsigned getNumTombstones() const { return NumTombstones; } + + void setNumTombstones(unsigned Num) { NumTombstones = Num; } + + BucketT *getBuckets() const { return Buckets; } + + unsigned getNumBuckets() const { return NumBuckets; } + + bool allocateBuckets(unsigned Num) { + NumBuckets = Num; + if (NumBuckets == 0) { + Buckets = nullptr; + return false; + } + + uptr Size = sizeof(BucketT) * NumBuckets; + if (Size * 2 <= GetPageSizeCached()) { + // We always allocate at least a page, so use entire space. + unsigned Log2 = MostSignificantSetBitIndex(GetPageSizeCached() / Size); + Size <<= Log2; + NumBuckets <<= Log2; + CHECK_EQ(Size, sizeof(BucketT) * NumBuckets); + CHECK_GT(Size * 2, GetPageSizeCached()); + } + Buckets = static_cast(allocate_buffer(Size)); + return true; + } + + static void *allocate_buffer(uptr Size) { + return MmapOrDie(RoundUpTo(Size, GetPageSizeCached()), "DenseMap"); + } + + static void deallocate_buffer(void *Ptr, uptr Size) { + UnmapOrDie(Ptr, RoundUpTo(Size, GetPageSizeCached())); + } +}; + +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_instrument.hpp b/compiler-rt/lib/tsan/rtl/rsan_instrument.hpp new file mode 100644 index 0000000000000..6e3bc6a5ac744 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_instrument.hpp @@ -0,0 +1,358 @@ +#pragma once +#include "rsan_robustnessmodel.hpp" +#include "rsan_map.hpp" +#include "rsan_defs.hpp" +#include "rsan_report.hpp" +#include "rsan_stacktrace.hpp" +#include "rsan_arena.hpp" +#include "rsan_lock.hpp" + +namespace Robustness{ + + static FakeMutex fakeMutex; + /*! + * Insrumentation + */ + template + //struct InstrumentationTemplate : Instrumentation{ + struct InstrumentationTemplate { + private: + Vsc vsc; //!< VSC tracking + I ins; //!< Memory Model tracking + int64_t violationsCount = 0; + + Mutex locksLock; // Global Robustness Lock + Arena locksAllocator; + + + template + using map = Robustness::Map; + + map locks; + + + //! Thread part + struct ThreadStruct{ + Vsc::Thread vsc; + typename I::Thread ins; + /*! Absorb another thread + * \param w Thread struct to absorb + */ + void absorb(const ThreadStruct &w){ + vsc.absorb(w.vsc); + ins.absorb(w.ins); + } + void resetKnowledge(const ThreadId &t){ + vsc.resetKnowledge(); + ins.resetKnowledge(t); + } + }; + //! Location Part + struct LocationStruct{ + Vsc::Location vsc; + typename I::Location ins; + LittleStackTrace lastWrite; + LittleStackTrace lastWriteU; + LocationId lid; + }; + //volatile LocationId locationCounter{1}; + u64 locationCounter{0}; + // Location 0 is reserved for SC fences + + Mutex structsLock; // Global Robustness Lock + Arena threadAllocator; + Arena locationAllocator; + + map threads; + map locations; + + /*! + * Get Location Struct for address + */ + inline auto& getLocationStruct(Address a) { + Lock lock(&structsLock); + if (auto it = locations.find(a); it != locations.end()){ + return *it->second; + } + auto w = locationAllocator.allocate(); + //w->lid = __atomic_fetch_add(&locationCounter, 1, __ATOMIC_SEQ_CST) ; + w->lid = ++locationCounter; + locations[a] = w; + return *w; + } + /*! + * Get Thread Struct for address + */ + inline auto& getThreadStruct(ThreadId tid) { + Lock lock(&structsLock); + if (auto it = threads.find(tid); it != threads.end()){ + return *it->second; + } + auto w = threadAllocator.allocate(); + threads[tid] = w; + return *w; + } + /*! + * Get Location Struct for address only if exists + */ + inline auto getMaybeLocationStruct(Address a) { + Lock lock(&structsLock); + auto t = locations.find(a); + return (t != locations.end() ? t->second : nullptr); + } + + + //! returns the number of violations + virtual int64_t getViolationsCount() /*override*/{ + return violationsCount; + } + + /*! + * Assert no read violation occurs + * + * \param t Thread Id + * \param l Address + * \param ts ThreadStruct + * \param ls Location Struct + */ + void assertReadViolation(ThreadId t, Address a, ThreadStruct &ts, LocationStruct &ls, DebugInfo dbg) { + auto l = ls.lid; + if (vsc.getLastTimeStamp(t, l, ts.vsc, ls.vsc) > ins.getLastTimeStamp(t, l, ts.ins, ls.ins)){ + reportViolation(t, a, l, dbg, ls.lastWrite); + } + } + /*! + * Assert no write violation occurs + * + * \param t Thread Id + * \param l Address + * \param ts ThreadStruct + * \param ls Location Struct + */ + void assertWriteViolation(ThreadId t, Address a, ThreadStruct &ts, LocationStruct &ls, DebugInfo dbg) { + auto l = ls.lid; + if (vsc.getLastTimeStampU(t, l, ts.vsc, ls.vsc) > ins.getLastTimeStampU(t, l, ts.ins, ls.ins)){ + reportViolation(t, a, l, dbg, ls.lastWriteU); + } + } + + void assertCasViolation(ThreadId t, Address a, ThreadStruct &ts, LocationStruct &ls, DebugInfo dbg, uint64_t val) { + // Weak CAS + assertReadViolation(t, a, ts, ls, dbg); + } + + void assertStrongCasViolation(ThreadId t, Address a, ThreadStruct &ts, LocationStruct &ls, DebugInfo dbg, uint64_t val) { + //auto l = ls.lid; + //if (vsc.getLastTimeStampUV(t, l, ts.vsc, ls.vsc, val) >= ins.getLastTimeStampU(t, l, ts.ins, ls.ins)){ + // reportViolation(t, a, l, dbg, ls.lastWriteU); + //} else if (vsc.getLastTimeStamp(t, l, ts.vsc, ls.vsc, val) >= ins.getLastTimeStamp(t, l, ts.ins, ls.ins)){ + // reportViolation(t, a, l, dbg, ls.lastWrite); + //} + } + + + public: + /*! + * Verify load statement for violation without updating + * + * \param t tid + * \param l address + */ + void verifyLoadStatement(ThreadId t, Address addr, morder , DebugInfo dbg) /*override*/{ + auto &ls = getLocationStruct(addr); + auto &ts = getThreadStruct(t); + assertReadViolation(t, addr, ts, ls, dbg); + } + /*! + * Verify store statement for violation without updating + * + * \param t tid + * \param l address + */ + void verifyStoreStatement(ThreadId t, Address addr, morder , DebugInfo dbg) /*override*/{ + auto &ls = getLocationStruct(addr); + auto &ts = getThreadStruct(t); + assertWriteViolation(t, addr, ts, ls, dbg); + } + + /*! + * Verify robustness and update load statement + * + * \param t tid + * \param l address + * \param mo memory order + */ + void updateLoadStatement(Action::AtomicLoadAction a) /*override*/{ + ThreadId t = a.tid; + Address addr = a.addr; + morder mo = a.mo; + auto &ls = getLocationStruct(addr); + auto &ts = getThreadStruct(t); + LocationId l = ls.lid; + assertReadViolation(t, addr, ts, ls, a.dbg); + + vsc.updateLoadStatement(t, l, ts.vsc, ls.vsc, mo); + ins.updateLoadStatement(t, l, ts.ins, ls.ins, mo); + } + + /*! + * Verify robustness and update store statement + * + * \param t tid + * \param l address + * \param mo memory order + */ + void updateStoreStatement(Action::AtomicStoreAction a) /*override*/{ + ThreadId t = a.tid; + Address addr = a.addr; + morder mo = a.mo; + uint64_t val = a.oldValue; + auto &ls = getLocationStruct(addr); + auto &ts = getThreadStruct(t); + LocationId l = ls.lid; + assertWriteViolation(t, addr, ts, ls, a.dbg); + + + vsc.updateStoreStatement(t, l, ts.vsc, ls.vsc, mo, val); + ins.updateStoreStatement(t, l, ts.ins, ls.ins, mo, val); + + + ObtainCurrentLine(a.dbg.thr, a.dbg.pc, &ls.lastWrite); + ObtainCurrentLine(a.dbg.thr, a.dbg.pc, &ls.lastWriteU); + } + + /*! + * Verify robustness and update RMW statement + * + * \param t tid + * \param l address + * \param mo memory order + */ + void updateRmwStatement(Action::AtomicRMWAction a) /*override*/{ + ThreadId t = a.tid; + Address addr = a.addr; + morder mo = a.mo; + uint64_t val = a.oldValue; + auto &ls = getLocationStruct(addr); + auto &ts = getThreadStruct(t); + LocationId l = ls.lid; + assertWriteViolation(t, addr, ts, ls, a.dbg); + + vsc.updateRmwStatement(t, l, ts.vsc, ls.vsc, mo, val); + ins.updateRmwStatement(t, l, ts.ins, ls.ins, mo, val); + + ObtainCurrentLine(a.dbg.thr, a.dbg.pc, &ls.lastWrite); + } + + void updateCasStatement(Action::AtomicCasAction a) /*override*/{ + ThreadId t = a.tid; + Address addr = a.addr; + morder mo = a.mo; + uint64_t expected = a.oldValue; + bool success = a.success; + auto &ls = getLocationStruct(addr); + auto &ts = getThreadStruct(t); + LocationId l = ls.lid; + assertCasViolation(t, addr, ts, ls, a.dbg, expected); + + if (success){ + vsc.updateRmwStatement(t, l, ts.vsc, ls.vsc, mo, expected); + ins.updateRmwStatement(t, l, ts.ins, ls.ins, mo, expected); + ObtainCurrentLine(a.dbg.thr, a.dbg.pc, &ls.lastWrite); + } else { + vsc.updateLoadStatement(t, l, ts.vsc, ls.vsc, mo); + ins.updateLoadStatement(t, l, ts.ins, ls.ins, mo); + } + + } + + /*! + * Update fence statement + * + * \param t tid + * \param mo memory order + */ + void updateFenceStatement(ThreadId t, morder mo) /*override*/{ + // HACK: This might break on architectures that use the address 0 + auto &ls = getLocationStruct(0); + auto &ts = getThreadStruct(t); + ins.updateFenceStatement(t, ts.ins, ls.ins, mo); + } + + /*! + * Absorb knowledge from thread (Join) + * + * \param _absorber The thread to gain the knowledge + * \param _absorbee The thread giving the knowldge + */ + void absorbThread(ThreadId _absorber, ThreadId _absorbee) /*override*/{ + auto &absorber = getThreadStruct(_absorber); + auto &absorbee = getThreadStruct(_absorbee); + absorber.absorb(absorbee); + } + + //! Initialize thread data structure + void initThread(ThreadId tid) { + ins.initThread(tid, getThreadStruct(tid).ins); + } + + /*! + * Clone knowledge of current thread to a new thread + * + * \param _src The thread creating the new thread + * \param _dst The newely created thread + */ + void cloneThread(ThreadId _src, ThreadId _dst) /*override*/{ + auto &dst = getThreadStruct(_dst); + auto &src = getThreadStruct(_src); + dst.resetKnowledge(_dst); + dst.absorb(src); + initThread(_dst); + } + + + /*! + * Free chunk of memory, removing knowledge by all relations and + * verifying the deletion doesn't violate anything + * + * \param t tid + * \param l Address + * \param size size from address + */ + void freeMemory(Action::Free w) /*override*/{ + auto &t = w.tid; + auto &addr = w.addr; + auto &size = w.size; + + auto &ts = getThreadStruct(t); + + + // We don't free the memory. We just mark the location as known to all. + for (auto a = addr; a lid, ls->vsc); + ins.freeLocation(ls->lid, ls->ins); + } + } + } + + Mutex* getLockForAddr(Address addr){ + if (!Robustness::isRobustness()) + return &fakeMutex; + Lock lock(&locksLock); + if (auto it = locks.find(addr); it != locks.end()){ + return it->second; + } + auto newLock = locksAllocator.allocate(); + locks[addr] = newLock; + return newLock; + } + + }; + + inline Robustness::InstrumentationTemplate ins; +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_lock.hpp b/compiler-rt/lib/tsan/rtl/rsan_lock.hpp new file mode 100644 index 0000000000000..864882ca016ed --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_lock.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "rsan_defs.hpp" + +//class __tsan::ThreadState; + +namespace Robustness{ +class SANITIZER_MUTEX FakeMutex : public Mutex { + public: + explicit constexpr FakeMutex(__tsan::MutexType type = __tsan::MutexUnchecked) + : Mutex(type) {} + + void Lock() SANITIZER_ACQUIRE() { } + + bool TryLock() SANITIZER_TRY_ACQUIRE(true) { return true; } + + void Unlock() SANITIZER_RELEASE() { } + + void ReadLock() SANITIZER_ACQUIRE_SHARED() { } + + void ReadUnlock() SANITIZER_RELEASE_SHARED() { } + + void CheckWriteLocked() const SANITIZER_CHECK_LOCKED() { } + + void CheckLocked() const SANITIZER_CHECK_LOCKED() {} + + void CheckReadLocked() const SANITIZER_CHECK_LOCKED() { } + + + //FakeMutex(LinkerInitialized) = delete; + FakeMutex(const FakeMutex &) = delete; + void operator=(const FakeMutex &) = delete; +}; +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_map.hpp b/compiler-rt/lib/tsan/rtl/rsan_map.hpp new file mode 100644 index 0000000000000..2bbb098c11ed1 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_map.hpp @@ -0,0 +1,88 @@ +#pragma once +#include "rsan_vector.h" + +namespace Robustness { +template< + class Key, + class T +> class Map { + + + Vector> v; + + u64 findLocationLinear(Key k, u64 start, u64 end){ + for (u64 i=start; i= k) return i; + return end; + } + + u64 find_(Key k, u64 first = 0){ + const auto len = v.size(); + size_t count = len - first; + while (count > 0) { + if (count <= 8) return findLocationLinear(k, first, count+first); + u64 step = count / 2, it = first + step; + u64 tkey = v[it].first; + if (tkey > k){ + count = step; + } else if (tkey < k){ + first = it + 1; + count -= step + 1; + } else { + return it; + } + } + return first; + } + + public: + + + template< class... Args > + Pair*, bool> try_emplace( const Key& k, Args&&... args ){ + auto i = find_(k); + if (i < v.size() && v[i].first == k){ + return pair(&v[i], false); + } else { + v.insert(i, pair(k, T(args...))); + return pair(&v[i], true); + } + } + + decltype(v.end()) find(const Key &k){ + auto i = find_(k); + if (i < v.size() && v[i].first == k) + return &v[i]; + else + return v.end(); + } + + + decltype(v.begin()) begin(){ + return v.begin(); + } + decltype(v.begin()) end(){ + return v.end(); + } + + bool contains(const Key &k){ + return find(k) != v.end(); + } + + void clear(){ + v.clear(); + } + + T& operator[]( Key&& key ){ + return this->try_emplace(key).first->second; + } + T& operator[]( const Key& key ){ + return this->try_emplace(key).first->second; + } + + auto size(){ + return v.size(); + } + +}; +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_memoryorder.hpp b/compiler-rt/lib/tsan/rtl/rsan_memoryorder.hpp new file mode 100644 index 0000000000000..5da25b7330145 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_memoryorder.hpp @@ -0,0 +1,65 @@ +#pragma once +#include "tsan_defs.h" +#include "tsan_interface.h" +namespace Robustness{ + using __tsan::morder; + using __tsan::mo_relaxed; + using __tsan::mo_consume; + using __tsan::mo_acquire; + using __tsan::mo_release; + using __tsan::mo_acq_rel; + using __tsan::mo_seq_cst; + //! Check if lhs is at least as strong as rhs. + /*! + * Check if memory order is at least as strong as another + * \param lhs memory order + * \param rhs memory order + * \return true if lhs is at least as powerful as rhs + */ + inline bool atLeast(__tsan::morder lhs, __tsan::morder rhs){ + using namespace std; + switch (rhs) { + case __tsan::mo_relaxed: + return true; + case __tsan::mo_consume: + case __tsan::mo_acquire: + switch (lhs) { + case __tsan::mo_relaxed: + case __tsan::mo_release: + return false; + case __tsan::mo_acq_rel: + case __tsan::mo_acquire: + case __tsan::mo_seq_cst: + return true; + case __tsan::mo_consume: + //assertm("Consume not supported", 0); + default: + //assertm("Unknown memory order value", 0); + // TODO: Remove bugs from here + return false; + } + case __tsan::mo_release: + switch (lhs) { + case __tsan::mo_relaxed: + case __tsan::mo_acquire: + case __tsan::mo_consume: + return false; + case __tsan::mo_acq_rel: + case __tsan::mo_release: + case __tsan::mo_seq_cst: + return true; + default: + // TODO: Remove bugs from here + //assertm("Unknown memory order value", 0); + return false; + } + case __tsan::mo_acq_rel: + return lhs == __tsan::mo_seq_cst || lhs == __tsan::mo_acq_rel; + case __tsan::mo_seq_cst: + return lhs == __tsan::mo_seq_cst; + } + //assertm(0, "Unhandeled atLeast for some memory order"); + __builtin_unreachable(); + } +} // namespace Robustness + diff --git a/compiler-rt/lib/tsan/rtl/rsan_report.cpp b/compiler-rt/lib/tsan/rtl/rsan_report.cpp new file mode 100644 index 0000000000000..064713e7472ef --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_report.cpp @@ -0,0 +1,72 @@ +#include "rsan_report.hpp" +#include "rsan_defs.hpp" +#include "sanitizer_common/sanitizer_stacktrace_printer.h" +namespace __tsan { + +void GetLineOfCode(InternalScopedString& res, const ReportStack *ent) { + if (ent == 0 || ent->frames == 0) { + res.Append("[failed to locate source]"); + return; + } + SymbolizedStack *frame = ent->frames; + for (int i = 0; frame && frame->info.address; frame = frame->next, i++) { + const char *formatString = "%S"; + // FIXME: Need to extract this... + StackTracePrinter::GetOrInit()->RenderFrame( + &res, formatString, i, frame->info.address, + &frame->info, common_flags()->symbolize_vs_style, + common_flags()->strip_path_prefix); + } +} + +ReportStack SymbolizeStack(StackTrace trace) { + if (trace.size == 0) + return ReportStack(); + SymbolizedStack *top = nullptr; + for (uptr si = 0; si < trace.size; si++) { + const uptr pc = trace.trace[si]; + uptr pc1 = pc; + // We obtain the return address, but we're interested in the previous + // instruction. + if ((pc & kExternalPCBit) == 0) + pc1 = StackTrace::GetPreviousInstructionPc(pc); + SymbolizedStack *ent = SymbolizeCode(pc1); + CHECK_NE(ent, 0); + SymbolizedStack *last = ent; + while (last->next) { + last->info.address = pc; // restore original pc for report + last = last->next; + } + last->info.address = pc; // restore original pc for report + last->next = top; + top = ent; + } + //StackStripMain(top); + + ReportStack stack; + stack.frames = top; + return stack; +} + +void getCurrentLine(InternalScopedString &ss, ThreadState *thr, uptr pc) { + //CheckedMutex::CheckNoLocks(); + ScopedIgnoreInterceptors ignore; + // + // We need to lock the slot during RestoreStack because it protects + // the slot journal. + //Lock slot_lock(&ctx->slots[static_cast(s[1].sid())].mtx); + //ThreadRegistryLock l0(&ctx->thread_registry); + //Lock slots_lock(&ctx->slot_mtx); + + VarSizeStackTrace trace; + ObtainCurrentLine(thr, pc, &trace); + auto stack = SymbolizeStack(trace); + GetLineOfCode(ss, &stack); +} + + +} //namespace __tsan + +namespace Robustness { +}// namespace Robustness + diff --git a/compiler-rt/lib/tsan/rtl/rsan_report.hpp b/compiler-rt/lib/tsan/rtl/rsan_report.hpp new file mode 100644 index 0000000000000..7163fc42f981d --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_report.hpp @@ -0,0 +1,85 @@ +#pragma once +#include "tsan_defs.h" +#include "tsan_rtl.h" +#include "tsan_symbolize.h" +#include "tsan_flags.h" +#include "rsan_defs.hpp" +#include "tsan_stack_trace.h" +#include "rsan_stacktrace.hpp" +namespace __tsan { + class ThreadState; + +void GetLineOfCode(InternalScopedString& res, const ReportStack *ent); +ReportStack SymbolizeStack(StackTrace trace); + +template +void ObtainCurrentLine(ThreadState *thr, uptr toppc, StackTraceTy *stack, + uptr *tag = nullptr) { + uptr size = thr->shadow_stack_pos - thr->shadow_stack; + uptr start = 0; + const auto kStackTraceMax = Robustness::kStackTraceMax; + if (size + !!toppc > kStackTraceMax) { + start = size + !!toppc - kStackTraceMax; + size = kStackTraceMax - !!toppc; + } + stack->Init(&thr->shadow_stack[start], size, toppc); + ExtractTagFromStack(stack, tag); +} +void getCurrentLine(InternalScopedString &s, ThreadState *thr, uptr pc); + +} //namespace __tsan + +namespace Robustness { + using __tsan::ObtainCurrentLine; + using __tsan::getCurrentLine; + + + +template void reportViolation(Robustness::ThreadId t, Robustness::Address a, Robustness::LocationId lid, const DebugInfo &dbg, const LittleStackTrace &prev = {}){ + InternalScopedString ss; + getCurrentLine(ss, dbg.thr, dbg.pc); + InternalScopedString prevs; + + auto oldstack = __tsan::SymbolizeStack(prev); + __tsan::GetLineOfCode(prevs, &oldstack); + + // TODO: Make the color codes easier to use + // TODO: Update print functions + if constexpr (V == Robustness::ViolationType::read || + V == Robustness::ViolationType::write){ + //++violationsCount; + const char *fmtString; +#define PRESTRING "\033[1;31mRobustness Violation: Tid: %u, Address: %llx (%d), Type: " +#define POSTSTRING " %d, Violation: %s, PrevAccess: %s\033[0m\n" + if constexpr (V == Robustness::ViolationType::read) + fmtString = PRESTRING "rd" POSTSTRING; + else + fmtString = PRESTRING "st" POSTSTRING; +#undef PRESTRING +#undef POSTRING + Printf(fmtString, t, a, lid, (int)V, ss.data(), prevs.data()); + } else + static_assert(Robustness::always_false_v, "Unknwon error type"); +} + +template void reportViolation(Robustness::ThreadId t, Robustness::Address a, const DebugInfo& dbg, const LittleStackTrace &prev, uint64_t val){ + InternalScopedString ss; + getCurrentLine(ss, dbg.thr, dbg.pc); + InternalScopedString prevs; + + auto oldstack = __tsan::SymbolizeStack(prev); + __tsan::GetLineOfCode(prevs, &oldstack); + + if constexpr (V == Robustness::ViolationType::read || + V == Robustness::ViolationType::write){ + const char *fmtString; + if constexpr (V == Robustness::ViolationType::read) + fmtString = "\033[1;31mRobustness Violation: Tid: %u, Address: %llx, Type: rd %d, Val: %llu Violation: %s, PrevAccess: %s\033[0m\n"; + else + fmtString = "\033[1;31mRobustness Violation: Tid: %u, Address: %llx, Type: st %d, Val: %llu Violation: %s, PrevAccess: %s\033[0m\n"; + Printf(fmtString, t, a, (int)V, val, ss.data(), prevs.data()); + } else + static_assert(Robustness::always_false_v, "Unknwon error type"); +} +} //namespace Robustness + diff --git a/compiler-rt/lib/tsan/rtl/rsan_robustnessmodel.hpp b/compiler-rt/lib/tsan/rtl/rsan_robustnessmodel.hpp new file mode 100644 index 0000000000000..d7bc02ff9c745 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_robustnessmodel.hpp @@ -0,0 +1,280 @@ +#pragma once +#include "rsan_memoryorder.hpp" +#include "rsan_vectorclock.hpp" +#include "rsan_defs.hpp" +#include "rsan_vector.h" +#include "rsan_map.hpp" +#include "rsan_action.hpp" + + +namespace Robustness { + template + using map = Robustness::Map; + + //! Track SC with VectorClocks + struct Vsc{ + //! Thread component + struct Thread{ + VectorClock v, vu; + //! Absorb other thread into self + void absorb(const Thread &t){ + v |= t.v; + vu |= t.vu; + } + void resetKnowledge(){ + v.reset(); + vu.reset(); + } + }; + //! Location component + struct Location{ + timestamp_t stamp = kEpochZero; + timestamp_t stampu = kEpochZero; + VectorClock m, w; + VectorClock mu, wu; + }; + + /*! + * Update load statement + * + * Memory order is ignored for SC + */ + void updateLoadStatement(ThreadId , LocationId , Thread &ts, Location &ls, morder){ + ls.m |= ts.v; + ts.v |= ls.w; + + ls.mu |= ts.vu; + ts.vu |= ls.wu; + } + + /*! + * Update store statement + * + * Memory order is ignored for SC + */ + void updateStoreStatement(ThreadId , LocationId a, Thread &ts, Location &ls, morder, u64 val){ + ls.m |= timestamp(a, EpochInc(ls.stamp)); + ts.v |= ls.m; + ls.w = ts.v; + ls.m = ts.v; + + ls.mu |= timestamp(a, EpochInc(ls.stampu)); + ts.vu |= ls.mu; + ls.wu = ts.vu; + ls.mu = ts.vu; + } + + /*! + * Update RMW statement + * + * Memory order is ignored for SC + */ + void updateRmwStatement(ThreadId t, LocationId a, Thread &ts, Location &ls, morder mo, u64 val){ + //return updateStoreStatement(t, a, ts, ls, mo); + ls.m |= timestamp(a, EpochInc(ls.stamp)); + ts.v |= ls.m; + ls.w = ts.v; + ls.m = ts.v; + + ts.vu |= ls.mu; + ls.wu = ts.vu; + ls.mu = ts.vu; + } + + //! Check if Thread knows of last write to \arg l + bool knowsLastWrite(ThreadId , LocationId l, Thread &ts, Location &ls) const{ + return ls.w[l] <= ts.v[l]; + } + timestamp_t getLastTimeStamp(ThreadId , LocationId l, Thread &ts, Location &ls) const{ + return ts.v[l].ts; + } + timestamp_t getLastTimeStampU(ThreadId , LocationId l, Thread &ts, Location &ls) const{ + return ts.vu[l].ts; + } + + //! Remove locations when freeing memory + void freeLocation(LocationId l, Location &ls){ + ls.w.reset(); + ls.m.reset(); + } + }; + + + //! Trace trace with RC20 semantics + struct VrlxNoFence{ + //! Thread component + struct Thread{ + VectorClock vc; + VectorClock vr; + VectorClock va; + VectorClock vcu; + VectorClock vru; + VectorClock vau; + + //! Absorb thread view into self + void absorb(const Thread &t){ + vc |= t.vc; + vr |= t.vr; + va |= t.va; + vcu |= t.vcu; + vru |= t.vru; + vau |= t.vau; + } + + void resetKnowledge(ThreadId t){ + vc.reset(); + vr.reset(); + va.reset(); + vcu.reset(); + vru.reset(); + vau.reset(); + } + }; + //! Location component + struct Location{ + timestamp_t writeStamp = kEpochZero, writeStampU = kEpochZero; + VectorClock w; + VectorClock wu; + }; + //! Initlialize thread + void initThread(ThreadId tid, Thread &ts){ + } + + + //! Update load statement + void updateLoadStatement(ThreadId , LocationId a, Thread &ts, Location &ls, morder mo){ + ts.va |= ls.w; + ts.vau |= ls.wu; + if (atLeast(mo, (mo_acquire))){ + ts.vc |= ls.w; + ts.vcu |= ls.wu; + } else { + ts.vc |= ls.w[a]; + ts.vcu |= ls.wu[a]; + } + } + + //! Update store statement + void updateStoreStatement(ThreadId t, LocationId a, Thread &ts, Location &ls, morder mo, uint64_t oldValue){ + const auto timestampV = timestamp(a, EpochInc(ls.writeStamp)); + const auto timestampVU = timestamp(a, EpochInc(ls.writeStampU)); + ls.w |= timestampV; + ls.wu |= timestampVU; + ts.va |= timestampV; + ts.vc |= timestampV; + ts.vau |= timestampVU; + ts.vcu |= timestampVU; + + + if (atLeast(mo, (mo_release))){ + ls.w = ts.vc; + ls.wu = ts.vcu; + } else { + ls.w = ts.vr; + ls.w |= timestampV; + ls.wu = ts.vru; + ls.wu |= timestampVU; + } + } + + //! Update RMW statement + void updateRmwStatement(ThreadId t, LocationId a, Thread &ts, Location &ls, morder mo, uint64_t oldValue){ + const auto timestampV = timestamp(a, EpochInc(ls.writeStamp)); + ls.w |= timestampV; + ts.va |= timestampV; + ts.vc |= timestampV; + + + ts.va |= ls.w; + ts.vau |= ls.wu; + if (atLeast(mo, (mo_acquire))){ + ts.vc |= ls.w; + ts.vcu |= ls.wu; + } else { + ts.vcu |= ls.wu[a]; + } + + if (atLeast(mo, (mo_release))){ + ls.w |= ts.vc; + ls.wu |= ts.vcu; + } else { + ls.w |= ts.vr; + ls.wu |= ts.vru; + } + } + + + Mutex SCLock; + /*! + * Update fence statement + * + * seq_cst fences are compiled to fence(acq); RMW(acq_rel); fence(rel); + */ + void updateFenceStatement(ThreadId t, Thread &ts, Location &ls, morder mo){ + if (mo == mo_seq_cst){ + updateFenceStatement(t, ts, ls, mo_acquire); + { + Lock instLock(&SCLock); + updateRmwStatement(t, LocationId(0), ts, ls, mo_acq_rel, 0); + } + updateFenceStatement(t, ts, ls, mo_release); + return; + } + if (atLeast(mo, (mo_acquire))){ + ts.vc = ts.va; + ts.vcu = ts.vau; + } + if (atLeast(mo, (mo_release))){ + ts.vr = ts.vc; + ts.vru = ts.vcu; + } + } + + auto getLastTimeStamp(ThreadId t, LocationId l, Thread &ts, Location &ls){ + return ts.vc[l].ts; + } + auto getLastTimeStampU(ThreadId t, LocationId l, Thread &ts, Location &ls){ + return ts.vcu[l].ts; + } + + + + //! Remove locations when freeing memory + void freeLocation(LocationId l, Location &ls){ + ls.w.reset(); + ls.wu.reset(); + } + }; + + + /// Instrumentation +class Instrumentation{ + public: + virtual void verifyLoadStatement(ThreadId t, LocationId l, morder mo) = 0; + virtual void verifyStoreStatement(ThreadId t, LocationId l, morder mo) = 0; + virtual void updateLoadStatement(Action::AtomicLoadAction a) = 0; + virtual void updateStoreStatement(Action::AtomicStoreAction a) = 0; + virtual void updateRmwStatement(Action::AtomicRMWAction a) = 0; + virtual void updateCasStatement(Action::AtomicCasAction a) = 0; + virtual void updateFenceStatement(ThreadId t, morder mo) = 0; + virtual void updateNALoad(ThreadId t, LocationId l) = 0; + virtual void updateNAStore(ThreadId t, LocationId l) = 0; + + virtual void absorbThread(ThreadId _absorber, ThreadId _absorbee) = 0; + virtual void cloneThread(ThreadId _src, ThreadId dst) = 0; + //void initThread(ThreadId tid); + //void removeThread(ThreadId t); + + virtual int64_t getViolationsCount() = 0; + virtual int64_t getRacesCount() = 0; + virtual void freeMemory(ThreadId t, LocationId l, ptrdiff_t size) = 0; + + virtual void trackAtomic(ThreadId t, LocationId l, uint64_t val) = 0; + virtual void waitAtomic(Action::WaitAction a) = 0; + virtual void bcasAtomic(Action::BcasAction a) = 0; + + virtual ~Instrumentation() = default; +}; + +} // namespace Robustness + diff --git a/compiler-rt/lib/tsan/rtl/rsan_stacktrace.cpp b/compiler-rt/lib/tsan/rtl/rsan_stacktrace.cpp new file mode 100644 index 0000000000000..6f6d20a4263a8 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_stacktrace.cpp @@ -0,0 +1,134 @@ +//===-- sanitizer_stacktrace.cpp ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file is shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +//===----------------------------------------------------------------------===// + +#include "rsan_stacktrace.hpp" + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_platform.h" +#include "sanitizer_common/sanitizer_ptrauth.h" + +namespace Robustness { + using __sanitizer::StackTrace; + using __sanitizer::uptr; + + +void LittleStackTrace::Init(const uptr *pcs, uptr cnt, uptr extra_top_pc) { + size = cnt + !!extra_top_pc; + CHECK_LE(size, kStackTraceMax); + internal_memcpy(trace_buffer, pcs, cnt * sizeof(trace_buffer[0])); + if (extra_top_pc) + trace_buffer[cnt] = extra_top_pc; + top_frame_bp = 0; +} + +// Sparc implementation is in its own file. +#if !defined(__sparc__) + +// In GCC on ARM bp points to saved lr, not fp, so we should check the next +// cell in stack to be a saved frame pointer. GetCanonicFrame returns the +// pointer to saved frame pointer in any case. +static inline uhwptr *GetCanonicFrame(uptr bp, + uptr stack_top, + uptr stack_bottom) { + CHECK_GT(stack_top, stack_bottom); +#ifdef __arm__ + if (!IsValidFrame(bp, stack_top, stack_bottom)) return 0; + uhwptr *bp_prev = (uhwptr *)bp; + if (IsValidFrame((uptr)bp_prev[0], stack_top, stack_bottom)) return bp_prev; + // The next frame pointer does not look right. This could be a GCC frame, step + // back by 1 word and try again. + if (IsValidFrame((uptr)bp_prev[-1], stack_top, stack_bottom)) + return bp_prev - 1; + // Nope, this does not look right either. This means the frame after next does + // not have a valid frame pointer, but we can still extract the caller PC. + // Unfortunately, there is no way to decide between GCC and LLVM frame + // layouts. Assume LLVM. + return bp_prev; +#else + return (uhwptr*)bp; +#endif +} + +void LittleStackTrace::UnwindFast(uptr pc, uptr bp, uptr stack_top, + uptr stack_bottom, u32 max_depth) { + // TODO(yln): add arg sanity check for stack_top/stack_bottom + CHECK_GE(max_depth, 2); + const uptr kPageSize = GetPageSizeCached(); + trace_buffer[0] = pc; + size = 1; + if (stack_top < 4096) return; // Sanity check for stack top. + uhwptr *frame = GetCanonicFrame(bp, stack_top, stack_bottom); + // Lowest possible address that makes sense as the next frame pointer. + // Goes up as we walk the stack. + uptr bottom = stack_bottom; + // Avoid infinite loop when frame == frame[0] by using frame > prev_frame. + while (IsValidFrame((uptr)frame, stack_top, bottom) && + IsAligned((uptr)frame, sizeof(*frame)) && + size < max_depth) { +#ifdef __powerpc__ + // PowerPC ABIs specify that the return address is saved at offset + // 16 of the *caller's* stack frame. Thus we must dereference the + // back chain to find the caller frame before extracting it. + uhwptr *caller_frame = (uhwptr*)frame[0]; + if (!IsValidFrame((uptr)caller_frame, stack_top, bottom) || + !IsAligned((uptr)caller_frame, sizeof(uhwptr))) + break; + uhwptr pc1 = caller_frame[2]; +#elif defined(__s390__) + uhwptr pc1 = frame[14]; +#elif defined(__loongarch__) || defined(__riscv) + // frame[-1] contains the return address + uhwptr pc1 = frame[-1]; +#else + uhwptr pc1 = STRIP_PAC_PC((void *)frame[1]); +#endif + // Let's assume that any pointer in the 0th page (i.e. <0x1000 on i386 and + // x86_64) is invalid and stop unwinding here. If we're adding support for + // a platform where this isn't true, we need to reconsider this check. + if (pc1 < kPageSize) + break; + if (pc1 != pc) { + trace_buffer[size++] = (uptr) pc1; + } + bottom = (uptr)frame; +#if defined(__loongarch__) || defined(__riscv) + // frame[-2] contain fp of the previous frame + uptr new_bp = (uptr)frame[-2]; +#else + uptr new_bp = (uptr)frame[0]; +#endif + frame = GetCanonicFrame(new_bp, stack_top, bottom); + } +} + +#endif // !defined(__sparc__) + +void LittleStackTrace::PopStackFrames(uptr count) { + CHECK_LT(count, size); + size -= count; + for (uptr i = 0; i < size; ++i) { + trace_buffer[i] = trace_buffer[i + count]; + } +} + +static uptr Distance(uptr a, uptr b) { return a < b ? b - a : a - b; } + +uptr LittleStackTrace::LocatePcInTrace(uptr pc) { + uptr best = 0; + for (uptr i = 1; i < size; ++i) { + if (Distance(trace[i], pc) < Distance(trace[best], pc)) best = i; + } + return best; +} + +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_stacktrace.hpp b/compiler-rt/lib/tsan/rtl/rsan_stacktrace.hpp new file mode 100644 index 0000000000000..9b06f2cb79e66 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_stacktrace.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "rsan_defs.hpp" + +namespace Robustness { + using __sanitizer::uhwptr; + +static const u32 kStackTraceMax = 4; + +// StackTrace that owns the buffer used to store the addresses. +struct LittleStackTrace : public __sanitizer::StackTrace { + uptr trace_buffer[kStackTraceMax] = {}; + uptr top_frame_bp; // Optional bp of a top frame. + + LittleStackTrace() : __sanitizer::StackTrace(trace_buffer, 0), top_frame_bp(0) {} + + void Init(const uptr *pcs, uptr cnt, uptr extra_top_pc = 0); + + // Get the stack trace with the given pc and bp. + // The pc will be in the position 0 of the resulting stack trace. + // The bp may refer to the current frame or to the caller's frame. + void Unwind(uptr pc, uptr bp, void *context, bool request_fast, + u32 max_depth = kStackTraceMax) { + top_frame_bp = (max_depth > 0) ? bp : 0; + // Small max_depth optimization + if (max_depth <= 1) { + if (max_depth == 1) + trace_buffer[0] = pc; + size = max_depth; + return; + } + UnwindImpl(pc, bp, context, request_fast, max_depth); + } + + void Unwind(u32 max_depth, uptr pc, uptr bp, void *context, uptr stack_top, + uptr stack_bottom, bool request_fast_unwind); + + void Reset() { + *static_cast(this) = StackTrace(trace_buffer, 0); + top_frame_bp = 0; + } + + LittleStackTrace(const LittleStackTrace &rhs) : StackTrace(trace, 0) { + trace = trace_buffer; + size = rhs.size; + for (auto i = 0u; i < kStackTraceMax; ++i) + trace_buffer[i] = rhs.trace_buffer[i]; + top_frame_bp = rhs.top_frame_bp; + } + //void operator=(const LittleStackTrace &rhs) : StackTrace(trace, 0) { + // trace = trace_buffer; + // size = rhs.size; + // for (auto i = 0u; i < kStackTraceMax; ++i) + // trace_buffer[i] = rhs.trace_buffer[i]; + // top_frame_bp = rhs.top_frame_bp; + //} + + private: + // Every runtime defines its own implementation of this method + void UnwindImpl(uptr pc, uptr bp, void *context, bool request_fast, + u32 max_depth); + + // UnwindFast/Slow have platform-specific implementations + void UnwindFast(uptr pc, uptr bp, uptr stack_top, uptr stack_bottom, + u32 max_depth); + void UnwindSlow(uptr pc, u32 max_depth); + void UnwindSlow(uptr pc, void *context, u32 max_depth); + + void PopStackFrames(uptr count); + uptr LocatePcInTrace(uptr pc); + + + friend class FastUnwindTest; +}; + +#if defined(__s390x__) +static const uptr kFrameSize = 160; +#elif defined(__s390__) +static const uptr kFrameSize = 96; +#else +static const uptr kFrameSize = 2 * sizeof(uhwptr); +#endif + +// Check if given pointer points into allocated stack area. +static inline bool IsValidFrame(uptr frame, uptr stack_top, uptr stack_bottom) { + return frame > stack_bottom && frame < stack_top - kFrameSize; +} + +} // namespace Robustness + diff --git a/compiler-rt/lib/tsan/rtl/rsan_vector.h b/compiler-rt/lib/tsan/rtl/rsan_vector.h new file mode 100644 index 0000000000000..8e2a0764193b2 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_vector.h @@ -0,0 +1,178 @@ +//===-- sanitizer_vector.h -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file is shared between sanitizers run-time libraries. +// +//===----------------------------------------------------------------------===// + +// Low-fat STL-like vector container. + +#pragma once + +#include "sanitizer_common/sanitizer_allocator_internal.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "rsan_defs.hpp" + +namespace Robustness { + +template +class Vector { + public: + Vector() : begin_(), end_(), last_() {} + + ~Vector() { + if (begin_) + InternalFree(begin_); + } + + void clear() { + if (begin_) + InternalFree(begin_); + begin_ = 0; + end_ = 0; + last_ = 0; + } + + uptr size() const { + return end_ - begin_; + } + + bool empty() const { + return end_ == begin_; + } + + T &operator[](uptr i) { + DCHECK_LT(i, end_ - begin_); + return begin_[i]; + } + + const T &operator[](uptr i) const { + DCHECK_LT(i, end_ - begin_); + return begin_[i]; + } + + T *push_back() { + EnsureSize(size() + 1); + T *p = &end_[-1]; + internal_memset(p, 0, sizeof(*p)); + return p; + } + + T *push_back(const T& v) { + EnsureSize(size() + 1); + T *p = &end_[-1]; + internal_memcpy(p, &v, sizeof(*p)); + return p; + } + + T *insert(u64 i, const T& v) { + DCHECK_LE(i, end_ - begin_); + EnsureSize(size() + 1); + auto start = begin_ + i; + internal_memmove(start+1, start, ((end_-1) - start) * sizeof(T)); + T *p = &begin_[i]; + internal_memcpy(p, &v, sizeof(*p)); + return p; + } + + void pop_back() { + DCHECK_GT(end_, begin_); + end_--; + } + + void resize(uptr size_) { + uptr old_size = size(); + if (size_ <= old_size) { + end_ = begin_ + size_; + return; + } + EnsureSize(size_); + if (size_ > old_size) + internal_memset(&begin_[old_size], 0, + sizeof(T) * (size_ - old_size)); + } + + void ensureSize(uptr size_){ + auto oldSize = size(); + EnsureSize(size_); + if (size_ > oldSize) + internal_memset(&begin_[oldSize], 0, + sizeof(T) * (size_ - oldSize)); + } + + Vector& operator=(const Vector &w){ + resize(w.size()); + internal_memcpy(begin_, w.begin_, w.size()* sizeof(T)); + return *this; + } + + T* begin() const{ + return begin_; + } + T* end() const{ + return end_; + } + const T* cbegin() const{ + return begin_; + } + const T* cend() const{ + return end_; + } + + void reserve(uptr size_){ + if (size_ <= (uptr)(last_ - begin_)) { + return; + } + uptr oldSize = end_ - begin_; + uptr cap0 = last_ - begin_; + uptr cap = cap0 * 5 / 4; // 25% growth + if (cap == 0) + cap = 16; + if (cap < size_) + cap = size_; + T *p = (T*)InternalAlloc(cap * sizeof(T)); + if (cap0) { + internal_memcpy(p, begin_, oldSize * sizeof(T)); + InternalFree(begin_); + } + begin_ = p; + end_ = begin_ + oldSize; + last_ = begin_ + cap; + } + + private: + T *begin_; + T *end_; + T *last_; + + void EnsureSize(uptr size_) { + if (size_ <= size()) + return; + if (size_ <= (uptr)(last_ - begin_)) { + end_ = begin_ + size_; + return; + } + uptr cap0 = last_ - begin_; + uptr cap = cap0 * 5 / 4; // 25% growth + if (cap == 0) + cap = 16; + if (cap < size_) + cap = size_; + T *p = (T*)InternalAlloc(cap * sizeof(T)); + if (cap0) { + internal_memcpy(p, begin_, cap0 * sizeof(T)); + InternalFree(begin_); + } + begin_ = p; + end_ = begin_ + size_; + last_ = begin_ + cap; + } + + //Vector(const Vector&); +}; +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/rsan_vectorclock.hpp b/compiler-rt/lib/tsan/rtl/rsan_vectorclock.hpp new file mode 100644 index 0000000000000..b34ec789080b1 --- /dev/null +++ b/compiler-rt/lib/tsan/rtl/rsan_vectorclock.hpp @@ -0,0 +1,115 @@ +#pragma once +#include "rsan_defs.hpp" +#include "rsan_vector.h" + +namespace Robustness{ + +template class MiniMapClock; + +/** + * Timestamp + */ +template +class Timestamp{ + public: + T key{}; + timestamp_t ts = kEpochZero; + + /// Check if the timestamp is newer than rhs + public: + bool contains(Timestamp rhs) const{ + return key == rhs.key && ts >= rhs.ts; + } + //auto operator<=>(const Timestamp&) const = default; +}; + template inline bool operator< (const Timestamp& lhs, const Timestamp& rhs) { return lhs.key < rhs.key ? true : lhs.key == rhs.key ? lhs.ts < rhs.ts : false; } + template inline bool operator==(const Timestamp& lhs, const Timestamp& rhs) { return lhs.key == rhs.key && lhs.ts == rhs.ts; } + template inline bool operator> (const Timestamp& lhs, const Timestamp& rhs) { return rhs < lhs; } + template inline bool operator<=(const Timestamp& lhs, const Timestamp& rhs) { return !(lhs > rhs); } + template inline bool operator>=(const Timestamp& lhs, const Timestamp& rhs) { return !(lhs < rhs); } + template inline bool operator!=(const Timestamp& lhs, const Timestamp& rhs) { return !(lhs == rhs); } + +template +auto timestamp(T key, timestamp_t ts){ + return Timestamp{key, ts}; +} + +/** + Vector Clock + **/ + +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +class VectorClock { + private: + Robustness::Vector impl; + + public: + /// Increment a timestamp t in the vector + auto inc(LocationId t){ + impl.ensureSize(t+1); + timestamp(t, EpochInc(impl[t])); + } + /// Reset the vector clock + void reset(){ + impl.clear(); + } + void receive(Timestamp ts){ + const auto loc = ts.key; + impl.ensureSize(loc+1); + impl[loc] = max(impl[loc], ts.ts); + } + + /** + Support + |= Union + **/ + VectorClock& operator|=(const VectorClock &rhs){ + auto S1 = impl.size(); + auto S2 = rhs.impl.size(); + impl.ensureSize(S2); + auto S = min(S1,S2); + uptr i = 0; + for (i = 0; i < S; ++i){ + impl[i] = max(impl[i], rhs.impl[i]); + } + for (i = S; i < S2; ++i){ + impl[i] = rhs.impl[i]; + } + return *this; + } + + + /** + |= - add a timestamp + **/ + auto& operator|=(const Timestamp &rhs){ + receive(rhs); + return *this; + } + bool contains(const VectorClock &rhs) const{ + auto S1 = impl.size(), S2 = rhs.impl.size(); + decltype(S1) i = 0; + for (; i < S1 && i < S2; ++i) + if (impl[i] < rhs.impl[i]) + return false; + for (; i < S2; ++i) + if (rhs.impl[i] > kEpochZero) + return false; + return true; + } + + auto operator[](LocationId t) const { + if (t < impl.size()) { + return timestamp(t, impl[t]); + } + return timestamp(t, kEpochZero); + } + + bool contains(const Timestamp &rhs) const{ + return operator[](rhs.key) >= rhs; + } +}; +} // namespace Robustness diff --git a/compiler-rt/lib/tsan/rtl/tsan_flags.inc b/compiler-rt/lib/tsan/rtl/tsan_flags.inc index 731d776cc893e..9da7a156eddc8 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_flags.inc +++ b/compiler-rt/lib/tsan/rtl/tsan_flags.inc @@ -16,6 +16,9 @@ // TSAN_FLAG(Type, Name, DefaultValue, Description) // See COMMON_FLAG in sanitizer_flags.inc for more details. +TSAN_FLAG(bool, enable_robustness, false, + "Enable robustness verification.") + TSAN_FLAG(bool, enable_annotations, true, "Enable dynamic annotations, otherwise they are no-ops.") // Suppress a race report if we've already output another race report diff --git a/compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp b/compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp index 527e5a9b4a8d8..2c4fe9a7b4b6b 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp @@ -25,6 +25,8 @@ #include "tsan_interface.h" #include "tsan_rtl.h" +#include "rsan_instrument.hpp" + using namespace __tsan; #if !SANITIZER_GO && __TSAN_HAS_INT128 @@ -226,9 +228,16 @@ namespace { template static T AtomicRMW(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { + Lock instLock(Robustness::ins.getLockForAddr((Robustness::Address(a)))); + Robustness::DebugInfo dbg = { .thr = thr, .pc = pc }; + auto oldValue = *a; MemoryAccess(thr, pc, (uptr)a, AccessSize(), kAccessWrite | kAccessAtomic); - if (LIKELY(mo == mo_relaxed)) - return F(a, v); + if (LIKELY(mo == mo_relaxed)) { + auto newValue = F(a, v); + if (Robustness::isRobustness()) + Robustness::ins.updateRmwStatement(Robustness::Action::AtomicRMWAction{.tid = thr->tid, .addr = (Robustness::Address(a)), .mo = mo, .size = AccessSize(), .oldValue = static_cast(oldValue), .newValue=static_cast(newValue), .dbg = move(dbg)}); + return newValue; + } SlotLocker locker(thr); { auto s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false); @@ -241,6 +250,8 @@ static T AtomicRMW(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { thr->clock.Acquire(s->clock); v = F(a, v); } + if (Robustness::isRobustness()) + Robustness::ins.updateRmwStatement(Robustness::Action::AtomicRMWAction{.tid = thr->tid, .addr = (Robustness::Address(a)), .mo = mo, .size = AccessSize(), .oldValue = static_cast(oldValue), .newValue=static_cast(v), .dbg = move(dbg)}); if (IsReleaseOrder(mo)) IncrementEpoch(thr); return v; @@ -262,6 +273,10 @@ struct OpLoad { template static T Atomic(ThreadState *thr, uptr pc, morder mo, const volatile T *a) { DCHECK(IsLoadOrder(mo)); + Lock instLock(Robustness::ins.getLockForAddr((Robustness::Address(a)))); + Robustness::DebugInfo dbg = { .thr = thr, .pc = pc }; + if (Robustness::isRobustness()) + Robustness::ins.updateLoadStatement(Robustness::Action::AtomicLoadAction{.tid = thr->tid, .addr = (Robustness::Address(a)), .mo = mo, .size = AccessSize(), .rmw = false, .dbg = move(dbg)}); // This fast-path is critical for performance. // Assume the access is atomic. if (!IsAcquireOrder(mo)) { @@ -303,6 +318,10 @@ struct OpStore { template static void Atomic(ThreadState *thr, uptr pc, morder mo, volatile T *a, T v) { DCHECK(IsStoreOrder(mo)); + Lock instLock(Robustness::ins.getLockForAddr((Robustness::Address(a)))); + Robustness::DebugInfo dbg = { .thr = thr, .pc = pc }; + if (Robustness::isRobustness()) + Robustness::ins.updateStoreStatement(Robustness::Action::AtomicStoreAction{.tid = thr->tid, .addr = (Robustness::Address(a)), .mo = mo, .size = AccessSize(), .oldValue = static_cast(*a), .newValue = static_cast(v), .dbg = move(dbg)}); MemoryAccess(thr, pc, (uptr)a, AccessSize(), kAccessWrite | kAccessAtomic); // This fast-path is critical for performance. @@ -438,39 +457,65 @@ struct OpCAS { // nor memory_order_acq_rel". LLVM (2021-05) fallbacks to Monotonic // (mo_relaxed) when those are used. DCHECK(IsLoadOrder(fmo)); + Lock instLock(Robustness::ins.getLockForAddr((Robustness::Address(a)))); + Robustness::DebugInfo dbg = { .thr = thr, .pc = pc }; MemoryAccess(thr, pc, (uptr)a, AccessSize(), kAccessWrite | kAccessAtomic); + + bool success; + bool release = IsReleaseOrder(mo); + T cc; + T pr; if (LIKELY(mo == mo_relaxed && fmo == mo_relaxed)) { - T cc = *c; - T pr = func_cas(a, cc, v); - if (pr == cc) - return true; + //T cc = *c; + //T pr = func_cas(a, cc, v); + cc = *c; + pr = func_cas(a, cc, v); + if (pr == cc) { + success = true; + goto cleanup; + // return true; + } *c = pr; + success = false; + goto cleanup; return false; } - SlotLocker locker(thr); - bool release = IsReleaseOrder(mo); - bool success; - { - auto s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false); - RWLock lock(&s->mtx, release); - T cc = *c; - T pr = func_cas(a, cc, v); - success = pr == cc; - if (!success) { - *c = pr; - mo = fmo; + { + SlotLocker locker(thr); + // bool release = IsReleaseOrder(mo); + { + auto *s = ctx->metamap.GetSyncOrCreate(thr, pc, (uptr)a, false); + RWLock lock(&s->mtx, release); + // T cc = *c; + // T pr = func_cas(a, cc, v); + cc = *c; + pr = func_cas(a, cc, v); + success = pr == cc; + if (!success) { + *c = pr; + mo = fmo; + } + if (success && IsAcqRelOrder(mo)) + thr->clock.ReleaseAcquire(&s->clock); + else if (success && IsReleaseOrder(mo)) + thr->clock.Release(&s->clock); + else if (IsAcquireOrder(mo)) + thr->clock.Acquire(s->clock); } - if (success && IsAcqRelOrder(mo)) - thr->clock.ReleaseAcquire(&s->clock); - else if (success && IsReleaseOrder(mo)) - thr->clock.Release(&s->clock); - else if (IsAcquireOrder(mo)) - thr->clock.Acquire(s->clock); - } - if (success && release) - IncrementEpoch(thr); + if (success && release) + IncrementEpoch(thr); + } + cleanup: + morder correctmo; + if (success){ + correctmo = mo; + } else { + correctmo = fmo; + } + if (Robustness::isRobustness()) + Robustness::ins.updateCasStatement(Robustness::Action::AtomicCasAction{.tid = thr->tid, .addr = (Robustness::Address(a)), .mo = correctmo, .size = AccessSize(), .oldValue = static_cast(cc), .newValue = static_cast(v), .success = success, .dbg = dbg}); return success; } @@ -488,6 +533,14 @@ struct OpFence { static void Atomic(ThreadState *thr, uptr pc, morder mo) { // FIXME(dvyukov): not implemented. + if (Robustness::isRobustness()) { + if (mo == mo_seq_cst){ + (void) Robustness::ins.getLockForAddr(Robustness::Address(0)); // Call for side effect + Robustness::ins.updateFenceStatement(thr->tid, mo); + } else { + Robustness::ins.updateFenceStatement(thr->tid, mo); + } + } __sync_synchronize(); } }; diff --git a/compiler-rt/lib/tsan/rtl/tsan_mman.cpp b/compiler-rt/lib/tsan/rtl/tsan_mman.cpp index 0ea83fb3b5982..29e531e08bdbc 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_mman.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_mman.cpp @@ -23,6 +23,8 @@ #include "tsan_report.h" #include "tsan_rtl.h" +#include "rsan_instrument.hpp" + namespace __tsan { struct MapUnmapCallback { @@ -276,13 +278,19 @@ void OnUserAlloc(ThreadState *thr, uptr pc, uptr p, uptr sz, bool write) { } void OnUserFree(ThreadState *thr, uptr pc, uptr p, bool write) { + Robustness::DebugInfo dbg = { .thr = thr, .pc = pc }; CHECK_NE(p, (void*)0); if (!thr->slot) { // Very early/late in thread lifetime, or during fork. UNUSED uptr sz = ctx->metamap.FreeBlock(thr->proc(), p, false); + if (Robustness::isRobustness()) + Robustness::ins.freeMemory(Robustness::Action::Free{.tid = thr->tid, .addr = p, .size = sz, .dbg = dbg}); DPrintf("#%d: free(0x%zx, %zu) (no slot)\n", thr->tid, p, sz); return; } + uptr size = ctx->metamap.FreeBlock(thr->proc(), p, true); + if (Robustness::isRobustness()) + Robustness::ins.freeMemory(Robustness::Action::Free{.tid = thr->tid, .addr = p, .size = size, .dbg = dbg}); SlotLocker locker(thr); uptr sz = ctx->metamap.FreeBlock(thr->proc(), p, true); DPrintf("#%d: free(0x%zx, %zu)\n", thr->tid, p, sz); diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp index 2a8aa1915c9ae..9145060e1d985 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp @@ -20,6 +20,8 @@ #include "tsan_symbolize.h" #include "tsan_platform.h" +#include "rsan_instrument.hpp" + namespace __tsan { void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r); @@ -156,6 +158,9 @@ void MutexPreLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { } void MutexPostLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz, int rec) { + Lock instLock(Robustness::ins.getLockForAddr((Robustness::LocationId) addr)); + Robustness::DebugInfo dbg = { .thr = thr, .pc = pc }; + // Note: We treat Mutex as atomic release/acquire var for robustness DPrintf("#%d: MutexPostLock %zx flag=0x%x rec=%d\n", thr->tid, addr, flagz, rec); if (flagz & MutexFlagRecursiveLock) @@ -204,6 +209,8 @@ void MutexPostLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz, int rec) { } } } + if (Robustness::isRobustness()) + Robustness::ins.updateLoadStatement(Robustness::Action::AtomicLoadAction{.tid = thr->tid, .addr = (Robustness::Address(addr)), .mo = mo_acquire, .rmw = false, .dbg = dbg}); if (report_double_lock) ReportMutexMisuse(thr, pc, ReportTypeMutexDoubleLock, addr, creation_stack_id); @@ -214,6 +221,8 @@ void MutexPostLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz, int rec) { } int MutexUnlock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { + Lock instLock(Robustness::ins.getLockForAddr((Robustness::LocationId) addr)); + Robustness::DebugInfo dbg = { .thr = thr, .pc = pc }; DPrintf("#%d: MutexUnlock %zx flagz=0x%x\n", thr->tid, addr, flagz); if (pc && IsAppMem(addr)) MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic); @@ -221,6 +230,8 @@ int MutexUnlock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { RecordMutexUnlock(thr, addr); bool report_bad_unlock = false; int rec = 0; + if (Robustness::isRobustness()) + Robustness::ins.updateStoreStatement(Robustness::Action::AtomicStoreAction{.tid = thr->tid, .addr = (Robustness::Address(addr)), .mo = mo_release, .oldValue = 0, .dbg = dbg}); { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true);