|  | //===-- tsd_shared.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 | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #ifndef SCUDO_TSD_SHARED_H_ | 
|  | #define SCUDO_TSD_SHARED_H_ | 
|  |  | 
|  | #include "tsd.h" | 
|  |  | 
|  | #include "string_utils.h" | 
|  |  | 
|  | #if SCUDO_HAS_PLATFORM_TLS_SLOT | 
|  | // This is a platform-provided header that needs to be on the include path when | 
|  | // Scudo is compiled. It must declare a function with the prototype: | 
|  | //   uintptr_t *getPlatformAllocatorTlsSlot() | 
|  | // that returns the address of a thread-local word of storage reserved for | 
|  | // Scudo, that must be zero-initialized in newly created threads. | 
|  | #include "scudo_platform_tls_slot.h" | 
|  | #endif | 
|  |  | 
|  | namespace scudo { | 
|  |  | 
|  | template <class Allocator, u32 TSDsArraySize, u32 DefaultTSDCount> | 
|  | struct TSDRegistrySharedT { | 
|  | using ThisT = TSDRegistrySharedT<Allocator, TSDsArraySize, DefaultTSDCount>; | 
|  |  | 
|  | struct ScopedTSD { | 
|  | ALWAYS_INLINE ScopedTSD(ThisT &TSDRegistry) { | 
|  | CurrentTSD = TSDRegistry.getTSDAndLock(); | 
|  | DCHECK_NE(CurrentTSD, nullptr); | 
|  | } | 
|  |  | 
|  | ~ScopedTSD() { CurrentTSD->unlock(); } | 
|  |  | 
|  | TSD<Allocator> &operator*() { return *CurrentTSD; } | 
|  |  | 
|  | TSD<Allocator> *operator->() { | 
|  | CurrentTSD->assertLocked(/*BypassCheck=*/false); | 
|  | return CurrentTSD; | 
|  | } | 
|  |  | 
|  | private: | 
|  | TSD<Allocator> *CurrentTSD; | 
|  | }; | 
|  |  | 
|  | void init(Allocator *Instance) REQUIRES(Mutex) { | 
|  | DCHECK(!Initialized); | 
|  | Instance->init(); | 
|  | for (u32 I = 0; I < TSDsArraySize; I++) | 
|  | TSDs[I].init(Instance); | 
|  | const u32 NumberOfCPUs = getNumberOfCPUs(); | 
|  | setNumberOfTSDs((NumberOfCPUs == 0) ? DefaultTSDCount | 
|  | : Min(NumberOfCPUs, DefaultTSDCount)); | 
|  | Initialized = true; | 
|  | } | 
|  |  | 
|  | void initOnceMaybe(Allocator *Instance) EXCLUDES(Mutex) { | 
|  | ScopedLock L(Mutex); | 
|  | if (LIKELY(Initialized)) | 
|  | return; | 
|  | init(Instance); // Sets Initialized. | 
|  | } | 
|  |  | 
|  | void unmapTestOnly(Allocator *Instance) EXCLUDES(Mutex) { | 
|  | for (u32 I = 0; I < TSDsArraySize; I++) { | 
|  | TSDs[I].commitBack(Instance); | 
|  | TSDs[I] = {}; | 
|  | } | 
|  | setCurrentTSD(nullptr); | 
|  | ScopedLock L(Mutex); | 
|  | Initialized = false; | 
|  | } | 
|  |  | 
|  | void drainCaches(Allocator *Instance) { | 
|  | ScopedLock L(MutexTSDs); | 
|  | for (uptr I = 0; I < NumberOfTSDs; ++I) { | 
|  | TSDs[I].lock(); | 
|  | Instance->drainCache(&TSDs[I]); | 
|  | TSDs[I].unlock(); | 
|  | } | 
|  | } | 
|  |  | 
|  | ALWAYS_INLINE void initThreadMaybe(Allocator *Instance, | 
|  | UNUSED bool MinimalInit) { | 
|  | if (LIKELY(getCurrentTSD())) | 
|  | return; | 
|  | initThread(Instance); | 
|  | } | 
|  |  | 
|  | void disable() NO_THREAD_SAFETY_ANALYSIS { | 
|  | Mutex.lock(); | 
|  | for (u32 I = 0; I < TSDsArraySize; I++) | 
|  | TSDs[I].lock(); | 
|  | } | 
|  |  | 
|  | void enable() NO_THREAD_SAFETY_ANALYSIS { | 
|  | for (s32 I = static_cast<s32>(TSDsArraySize - 1); I >= 0; I--) | 
|  | TSDs[I].unlock(); | 
|  | Mutex.unlock(); | 
|  | } | 
|  |  | 
|  | bool setOption(Option O, sptr Value) { | 
|  | if (O == Option::MaxTSDsCount) | 
|  | return setNumberOfTSDs(static_cast<u32>(Value)); | 
|  | if (O == Option::ThreadDisableMemInit) | 
|  | setDisableMemInit(Value); | 
|  | // Not supported by the TSD Registry, but not an error either. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool getDisableMemInit() const { return *getTlsPtr() & 1; } | 
|  |  | 
|  | void getStats(ScopedString *Str) EXCLUDES(MutexTSDs) { | 
|  | ScopedLock L(MutexTSDs); | 
|  |  | 
|  | Str->append("Stats: SharedTSDs: %u available; total %u\n", NumberOfTSDs, | 
|  | TSDsArraySize); | 
|  | for (uptr I = 0; I < NumberOfTSDs; ++I) { | 
|  | TSDs[I].lock(); | 
|  | // Theoretically, we want to mark TSD::lock()/TSD::unlock() with proper | 
|  | // thread annotations. However, given the TSD is only locked on shared | 
|  | // path, do the assertion in a separate path to avoid confusing the | 
|  | // analyzer. | 
|  | TSDs[I].assertLocked(/*BypassCheck=*/true); | 
|  | Str->append("  Shared TSD[%zu]:\n", I); | 
|  | TSDs[I].getCache().getStats(Str); | 
|  | TSDs[I].unlock(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | ALWAYS_INLINE TSD<Allocator> *getTSDAndLock() NO_THREAD_SAFETY_ANALYSIS { | 
|  | TSD<Allocator> *TSD = getCurrentTSD(); | 
|  | DCHECK(TSD); | 
|  | // Try to lock the currently associated context. | 
|  | if (TSD->tryLock()) | 
|  | return TSD; | 
|  | // If that fails, go down the slow path. | 
|  | if (TSDsArraySize == 1U) { | 
|  | // Only 1 TSD, not need to go any further. | 
|  | // The compiler will optimize this one way or the other. | 
|  | TSD->lock(); | 
|  | return TSD; | 
|  | } | 
|  | return getTSDAndLockSlow(TSD); | 
|  | } | 
|  |  | 
|  | ALWAYS_INLINE uptr *getTlsPtr() const { | 
|  | #if SCUDO_HAS_PLATFORM_TLS_SLOT | 
|  | return reinterpret_cast<uptr *>(getPlatformAllocatorTlsSlot()); | 
|  | #else | 
|  | static thread_local uptr ThreadTSD; | 
|  | return &ThreadTSD; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static_assert(alignof(TSD<Allocator>) >= 2, ""); | 
|  |  | 
|  | ALWAYS_INLINE void setCurrentTSD(TSD<Allocator> *CurrentTSD) { | 
|  | *getTlsPtr() &= 1; | 
|  | *getTlsPtr() |= reinterpret_cast<uptr>(CurrentTSD); | 
|  | } | 
|  |  | 
|  | ALWAYS_INLINE TSD<Allocator> *getCurrentTSD() { | 
|  | return reinterpret_cast<TSD<Allocator> *>(*getTlsPtr() & ~1ULL); | 
|  | } | 
|  |  | 
|  | bool setNumberOfTSDs(u32 N) EXCLUDES(MutexTSDs) { | 
|  | ScopedLock L(MutexTSDs); | 
|  | if (N < NumberOfTSDs) | 
|  | return false; | 
|  | if (N > TSDsArraySize) | 
|  | N = TSDsArraySize; | 
|  | NumberOfTSDs = N; | 
|  | NumberOfCoPrimes = 0; | 
|  | // Compute all the coprimes of NumberOfTSDs. This will be used to walk the | 
|  | // array of TSDs in a random order. For details, see: | 
|  | // https://lemire.me/blog/2017/09/18/visiting-all-values-in-an-array-exactly-once-in-random-order/ | 
|  | for (u32 I = 0; I < N; I++) { | 
|  | u32 A = I + 1; | 
|  | u32 B = N; | 
|  | // Find the GCD between I + 1 and N. If 1, they are coprimes. | 
|  | while (B != 0) { | 
|  | const u32 T = A; | 
|  | A = B; | 
|  | B = T % B; | 
|  | } | 
|  | if (A == 1) | 
|  | CoPrimes[NumberOfCoPrimes++] = I + 1; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void setDisableMemInit(bool B) { | 
|  | *getTlsPtr() &= ~1ULL; | 
|  | *getTlsPtr() |= B; | 
|  | } | 
|  |  | 
|  | NOINLINE void initThread(Allocator *Instance) NO_THREAD_SAFETY_ANALYSIS { | 
|  | initOnceMaybe(Instance); | 
|  | // Initial context assignment is done in a plain round-robin fashion. | 
|  | const u32 Index = atomic_fetch_add(&CurrentIndex, 1U, memory_order_relaxed); | 
|  | setCurrentTSD(&TSDs[Index % NumberOfTSDs]); | 
|  | Instance->callPostInitCallback(); | 
|  | } | 
|  |  | 
|  | // TSDs is an array of locks which is not supported for marking thread-safety | 
|  | // capability. | 
|  | NOINLINE TSD<Allocator> *getTSDAndLockSlow(TSD<Allocator> *CurrentTSD) | 
|  | EXCLUDES(MutexTSDs) { | 
|  | // Use the Precedence of the current TSD as our random seed. Since we are | 
|  | // in the slow path, it means that tryLock failed, and as a result it's | 
|  | // very likely that said Precedence is non-zero. | 
|  | const u32 R = static_cast<u32>(CurrentTSD->getPrecedence()); | 
|  | u32 N, Inc; | 
|  | { | 
|  | ScopedLock L(MutexTSDs); | 
|  | N = NumberOfTSDs; | 
|  | DCHECK_NE(NumberOfCoPrimes, 0U); | 
|  | Inc = CoPrimes[R % NumberOfCoPrimes]; | 
|  | } | 
|  | if (N > 1U) { | 
|  | u32 Index = R % N; | 
|  | uptr LowestPrecedence = UINTPTR_MAX; | 
|  | TSD<Allocator> *CandidateTSD = nullptr; | 
|  | // Go randomly through at most 4 contexts and find a candidate. | 
|  | for (u32 I = 0; I < Min(4U, N); I++) { | 
|  | if (TSDs[Index].tryLock()) { | 
|  | setCurrentTSD(&TSDs[Index]); | 
|  | return &TSDs[Index]; | 
|  | } | 
|  | const uptr Precedence = TSDs[Index].getPrecedence(); | 
|  | // A 0 precedence here means another thread just locked this TSD. | 
|  | if (Precedence && Precedence < LowestPrecedence) { | 
|  | CandidateTSD = &TSDs[Index]; | 
|  | LowestPrecedence = Precedence; | 
|  | } | 
|  | Index += Inc; | 
|  | if (Index >= N) | 
|  | Index -= N; | 
|  | } | 
|  | if (CandidateTSD) { | 
|  | CandidateTSD->lock(); | 
|  | setCurrentTSD(CandidateTSD); | 
|  | return CandidateTSD; | 
|  | } | 
|  | } | 
|  | // Last resort, stick with the current one. | 
|  | CurrentTSD->lock(); | 
|  | return CurrentTSD; | 
|  | } | 
|  |  | 
|  | atomic_u32 CurrentIndex = {}; | 
|  | u32 NumberOfTSDs GUARDED_BY(MutexTSDs) = 0; | 
|  | u32 NumberOfCoPrimes GUARDED_BY(MutexTSDs) = 0; | 
|  | u32 CoPrimes[TSDsArraySize] GUARDED_BY(MutexTSDs) = {}; | 
|  | bool Initialized GUARDED_BY(Mutex) = false; | 
|  | HybridMutex Mutex; | 
|  | HybridMutex MutexTSDs; | 
|  | TSD<Allocator> TSDs[TSDsArraySize]; | 
|  | }; | 
|  |  | 
|  | } // namespace scudo | 
|  |  | 
|  | #endif // SCUDO_TSD_SHARED_H_ |