10b57cec5SDimitry Andric //===-- tsd_shared.h --------------------------------------------*- C++ -*-===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric 90b57cec5SDimitry Andric #ifndef SCUDO_TSD_SHARED_H_ 100b57cec5SDimitry Andric #define SCUDO_TSD_SHARED_H_ 110b57cec5SDimitry Andric 120b57cec5SDimitry Andric #include "tsd.h" 130b57cec5SDimitry Andric 1406c3fb27SDimitry Andric #include "string_utils.h" 1506c3fb27SDimitry Andric 16e8d8bef9SDimitry Andric #if SCUDO_HAS_PLATFORM_TLS_SLOT 17e8d8bef9SDimitry Andric // This is a platform-provided header that needs to be on the include path when 18e8d8bef9SDimitry Andric // Scudo is compiled. It must declare a function with the prototype: 19e8d8bef9SDimitry Andric // uintptr_t *getPlatformAllocatorTlsSlot() 20e8d8bef9SDimitry Andric // that returns the address of a thread-local word of storage reserved for 21e8d8bef9SDimitry Andric // Scudo, that must be zero-initialized in newly created threads. 22e8d8bef9SDimitry Andric #include "scudo_platform_tls_slot.h" 23e8d8bef9SDimitry Andric #endif 24e8d8bef9SDimitry Andric 250b57cec5SDimitry Andric namespace scudo { 260b57cec5SDimitry Andric 27e8d8bef9SDimitry Andric template <class Allocator, u32 TSDsArraySize, u32 DefaultTSDCount> 28e8d8bef9SDimitry Andric struct TSDRegistrySharedT { 29*0fca6ea1SDimitry Andric using ThisT = TSDRegistrySharedT<Allocator, TSDsArraySize, DefaultTSDCount>; 30*0fca6ea1SDimitry Andric 31*0fca6ea1SDimitry Andric struct ScopedTSD { ScopedTSDTSDRegistrySharedT::ScopedTSD32*0fca6ea1SDimitry Andric ALWAYS_INLINE ScopedTSD(ThisT &TSDRegistry) { 33*0fca6ea1SDimitry Andric CurrentTSD = TSDRegistry.getTSDAndLock(); 34*0fca6ea1SDimitry Andric DCHECK_NE(CurrentTSD, nullptr); 35*0fca6ea1SDimitry Andric } 36*0fca6ea1SDimitry Andric ~ScopedTSDTSDRegistrySharedT::ScopedTSD37*0fca6ea1SDimitry Andric ~ScopedTSD() { CurrentTSD->unlock(); } 38*0fca6ea1SDimitry Andric 39*0fca6ea1SDimitry Andric TSD<Allocator> &operator*() { return *CurrentTSD; } 40*0fca6ea1SDimitry Andric 41*0fca6ea1SDimitry Andric TSD<Allocator> *operator->() { 42*0fca6ea1SDimitry Andric CurrentTSD->assertLocked(/*BypassCheck=*/false); 43*0fca6ea1SDimitry Andric return CurrentTSD; 44*0fca6ea1SDimitry Andric } 45*0fca6ea1SDimitry Andric 46*0fca6ea1SDimitry Andric private: 47*0fca6ea1SDimitry Andric TSD<Allocator> *CurrentTSD; 48*0fca6ea1SDimitry Andric }; 49*0fca6ea1SDimitry Andric initTSDRegistrySharedT5006c3fb27SDimitry Andric void init(Allocator *Instance) REQUIRES(Mutex) { 51fe6060f1SDimitry Andric DCHECK(!Initialized); 52fe6060f1SDimitry Andric Instance->init(); 53e8d8bef9SDimitry Andric for (u32 I = 0; I < TSDsArraySize; I++) 54fe6060f1SDimitry Andric TSDs[I].init(Instance); 55e8d8bef9SDimitry Andric const u32 NumberOfCPUs = getNumberOfCPUs(); 56e8d8bef9SDimitry Andric setNumberOfTSDs((NumberOfCPUs == 0) ? DefaultTSDCount 57e8d8bef9SDimitry Andric : Min(NumberOfCPUs, DefaultTSDCount)); 580b57cec5SDimitry Andric Initialized = true; 590b57cec5SDimitry Andric } 60fe6060f1SDimitry Andric initOnceMaybeTSDRegistrySharedT6106c3fb27SDimitry Andric void initOnceMaybe(Allocator *Instance) EXCLUDES(Mutex) { 62fe6060f1SDimitry Andric ScopedLock L(Mutex); 63fe6060f1SDimitry Andric if (LIKELY(Initialized)) 64fe6060f1SDimitry Andric return; 65fe6060f1SDimitry Andric init(Instance); // Sets Initialized. 660b57cec5SDimitry Andric } 670b57cec5SDimitry Andric unmapTestOnlyTSDRegistrySharedT6806c3fb27SDimitry Andric void unmapTestOnly(Allocator *Instance) EXCLUDES(Mutex) { 69fe6060f1SDimitry Andric for (u32 I = 0; I < TSDsArraySize; I++) { 70fe6060f1SDimitry Andric TSDs[I].commitBack(Instance); 71fe6060f1SDimitry Andric TSDs[I] = {}; 72fe6060f1SDimitry Andric } 73fe6060f1SDimitry Andric setCurrentTSD(nullptr); 7406c3fb27SDimitry Andric ScopedLock L(Mutex); 75fe6060f1SDimitry Andric Initialized = false; 76fe6060f1SDimitry Andric } 770b57cec5SDimitry Andric drainCachesTSDRegistrySharedT7806c3fb27SDimitry Andric void drainCaches(Allocator *Instance) { 7906c3fb27SDimitry Andric ScopedLock L(MutexTSDs); 8006c3fb27SDimitry Andric for (uptr I = 0; I < NumberOfTSDs; ++I) { 8106c3fb27SDimitry Andric TSDs[I].lock(); 8206c3fb27SDimitry Andric Instance->drainCache(&TSDs[I]); 8306c3fb27SDimitry Andric TSDs[I].unlock(); 8406c3fb27SDimitry Andric } 8506c3fb27SDimitry Andric } 8606c3fb27SDimitry Andric initThreadMaybeTSDRegistrySharedT870b57cec5SDimitry Andric ALWAYS_INLINE void initThreadMaybe(Allocator *Instance, 880b57cec5SDimitry Andric UNUSED bool MinimalInit) { 890b57cec5SDimitry Andric if (LIKELY(getCurrentTSD())) 900b57cec5SDimitry Andric return; 910b57cec5SDimitry Andric initThread(Instance); 920b57cec5SDimitry Andric } 930b57cec5SDimitry Andric disableTSDRegistrySharedT9406c3fb27SDimitry Andric void disable() NO_THREAD_SAFETY_ANALYSIS { 95480093f4SDimitry Andric Mutex.lock(); 96e8d8bef9SDimitry Andric for (u32 I = 0; I < TSDsArraySize; I++) 97480093f4SDimitry Andric TSDs[I].lock(); 98480093f4SDimitry Andric } 99480093f4SDimitry Andric enableTSDRegistrySharedT10006c3fb27SDimitry Andric void enable() NO_THREAD_SAFETY_ANALYSIS { 101e8d8bef9SDimitry Andric for (s32 I = static_cast<s32>(TSDsArraySize - 1); I >= 0; I--) 102480093f4SDimitry Andric TSDs[I].unlock(); 103480093f4SDimitry Andric Mutex.unlock(); 104480093f4SDimitry Andric } 105480093f4SDimitry Andric setOptionTSDRegistrySharedT106e8d8bef9SDimitry Andric bool setOption(Option O, sptr Value) { 107e8d8bef9SDimitry Andric if (O == Option::MaxTSDsCount) 108e8d8bef9SDimitry Andric return setNumberOfTSDs(static_cast<u32>(Value)); 109e8d8bef9SDimitry Andric if (O == Option::ThreadDisableMemInit) 110e8d8bef9SDimitry Andric setDisableMemInit(Value); 111e8d8bef9SDimitry Andric // Not supported by the TSD Registry, but not an error either. 112e8d8bef9SDimitry Andric return true; 113e8d8bef9SDimitry Andric } 114e8d8bef9SDimitry Andric getDisableMemInitTSDRegistrySharedT115e8d8bef9SDimitry Andric bool getDisableMemInit() const { return *getTlsPtr() & 1; } 116e8d8bef9SDimitry Andric getStatsTSDRegistrySharedT11706c3fb27SDimitry Andric void getStats(ScopedString *Str) EXCLUDES(MutexTSDs) { 11806c3fb27SDimitry Andric ScopedLock L(MutexTSDs); 11906c3fb27SDimitry Andric 12006c3fb27SDimitry Andric Str->append("Stats: SharedTSDs: %u available; total %u\n", NumberOfTSDs, 12106c3fb27SDimitry Andric TSDsArraySize); 12206c3fb27SDimitry Andric for (uptr I = 0; I < NumberOfTSDs; ++I) { 12306c3fb27SDimitry Andric TSDs[I].lock(); 1245f757f3fSDimitry Andric // Theoretically, we want to mark TSD::lock()/TSD::unlock() with proper 1255f757f3fSDimitry Andric // thread annotations. However, given the TSD is only locked on shared 1265f757f3fSDimitry Andric // path, do the assertion in a separate path to avoid confusing the 1275f757f3fSDimitry Andric // analyzer. 1285f757f3fSDimitry Andric TSDs[I].assertLocked(/*BypassCheck=*/true); 12906c3fb27SDimitry Andric Str->append(" Shared TSD[%zu]:\n", I); 13006c3fb27SDimitry Andric TSDs[I].getCache().getStats(Str); 13106c3fb27SDimitry Andric TSDs[I].unlock(); 13206c3fb27SDimitry Andric } 13306c3fb27SDimitry Andric } 13406c3fb27SDimitry Andric 1350b57cec5SDimitry Andric private: getTSDAndLockTSDRegistrySharedT136*0fca6ea1SDimitry Andric ALWAYS_INLINE TSD<Allocator> *getTSDAndLock() NO_THREAD_SAFETY_ANALYSIS { 137*0fca6ea1SDimitry Andric TSD<Allocator> *TSD = getCurrentTSD(); 138*0fca6ea1SDimitry Andric DCHECK(TSD); 139*0fca6ea1SDimitry Andric // Try to lock the currently associated context. 140*0fca6ea1SDimitry Andric if (TSD->tryLock()) 141*0fca6ea1SDimitry Andric return TSD; 142*0fca6ea1SDimitry Andric // If that fails, go down the slow path. 143*0fca6ea1SDimitry Andric if (TSDsArraySize == 1U) { 144*0fca6ea1SDimitry Andric // Only 1 TSD, not need to go any further. 145*0fca6ea1SDimitry Andric // The compiler will optimize this one way or the other. 146*0fca6ea1SDimitry Andric TSD->lock(); 147*0fca6ea1SDimitry Andric return TSD; 148*0fca6ea1SDimitry Andric } 149*0fca6ea1SDimitry Andric return getTSDAndLockSlow(TSD); 150*0fca6ea1SDimitry Andric } 151*0fca6ea1SDimitry Andric getTlsPtrTSDRegistrySharedT152e8d8bef9SDimitry Andric ALWAYS_INLINE uptr *getTlsPtr() const { 153e8d8bef9SDimitry Andric #if SCUDO_HAS_PLATFORM_TLS_SLOT 154e8d8bef9SDimitry Andric return reinterpret_cast<uptr *>(getPlatformAllocatorTlsSlot()); 1550b57cec5SDimitry Andric #else 156e8d8bef9SDimitry Andric static thread_local uptr ThreadTSD; 157e8d8bef9SDimitry Andric return &ThreadTSD; 1580b57cec5SDimitry Andric #endif 1590b57cec5SDimitry Andric } 1600b57cec5SDimitry Andric 161e8d8bef9SDimitry Andric static_assert(alignof(TSD<Allocator>) >= 2, ""); 162e8d8bef9SDimitry Andric setCurrentTSDTSDRegistrySharedT163e8d8bef9SDimitry Andric ALWAYS_INLINE void setCurrentTSD(TSD<Allocator> *CurrentTSD) { 164e8d8bef9SDimitry Andric *getTlsPtr() &= 1; 165e8d8bef9SDimitry Andric *getTlsPtr() |= reinterpret_cast<uptr>(CurrentTSD); 166e8d8bef9SDimitry Andric } 167e8d8bef9SDimitry Andric getCurrentTSDTSDRegistrySharedT1680b57cec5SDimitry Andric ALWAYS_INLINE TSD<Allocator> *getCurrentTSD() { 169e8d8bef9SDimitry Andric return reinterpret_cast<TSD<Allocator> *>(*getTlsPtr() & ~1ULL); 170e8d8bef9SDimitry Andric } 171e8d8bef9SDimitry Andric setNumberOfTSDsTSDRegistrySharedT17206c3fb27SDimitry Andric bool setNumberOfTSDs(u32 N) EXCLUDES(MutexTSDs) { 173e8d8bef9SDimitry Andric ScopedLock L(MutexTSDs); 174e8d8bef9SDimitry Andric if (N < NumberOfTSDs) 175e8d8bef9SDimitry Andric return false; 176e8d8bef9SDimitry Andric if (N > TSDsArraySize) 177e8d8bef9SDimitry Andric N = TSDsArraySize; 178e8d8bef9SDimitry Andric NumberOfTSDs = N; 179e8d8bef9SDimitry Andric NumberOfCoPrimes = 0; 180e8d8bef9SDimitry Andric // Compute all the coprimes of NumberOfTSDs. This will be used to walk the 181e8d8bef9SDimitry Andric // array of TSDs in a random order. For details, see: 182e8d8bef9SDimitry Andric // https://lemire.me/blog/2017/09/18/visiting-all-values-in-an-array-exactly-once-in-random-order/ 183e8d8bef9SDimitry Andric for (u32 I = 0; I < N; I++) { 184e8d8bef9SDimitry Andric u32 A = I + 1; 185e8d8bef9SDimitry Andric u32 B = N; 186e8d8bef9SDimitry Andric // Find the GCD between I + 1 and N. If 1, they are coprimes. 187e8d8bef9SDimitry Andric while (B != 0) { 188e8d8bef9SDimitry Andric const u32 T = A; 189e8d8bef9SDimitry Andric A = B; 190e8d8bef9SDimitry Andric B = T % B; 191e8d8bef9SDimitry Andric } 192e8d8bef9SDimitry Andric if (A == 1) 193e8d8bef9SDimitry Andric CoPrimes[NumberOfCoPrimes++] = I + 1; 194e8d8bef9SDimitry Andric } 195e8d8bef9SDimitry Andric return true; 196e8d8bef9SDimitry Andric } 197e8d8bef9SDimitry Andric setDisableMemInitTSDRegistrySharedT198e8d8bef9SDimitry Andric void setDisableMemInit(bool B) { 199e8d8bef9SDimitry Andric *getTlsPtr() &= ~1ULL; 200e8d8bef9SDimitry Andric *getTlsPtr() |= B; 2010b57cec5SDimitry Andric } 2020b57cec5SDimitry Andric initThreadTSDRegistrySharedT20306c3fb27SDimitry Andric NOINLINE void initThread(Allocator *Instance) NO_THREAD_SAFETY_ANALYSIS { 2040b57cec5SDimitry Andric initOnceMaybe(Instance); 2050b57cec5SDimitry Andric // Initial context assignment is done in a plain round-robin fashion. 2060b57cec5SDimitry Andric const u32 Index = atomic_fetch_add(&CurrentIndex, 1U, memory_order_relaxed); 2070b57cec5SDimitry Andric setCurrentTSD(&TSDs[Index % NumberOfTSDs]); 208480093f4SDimitry Andric Instance->callPostInitCallback(); 2090b57cec5SDimitry Andric } 2100b57cec5SDimitry Andric 21106c3fb27SDimitry Andric // TSDs is an array of locks which is not supported for marking thread-safety 21206c3fb27SDimitry Andric // capability. getTSDAndLockSlowTSDRegistrySharedT21306c3fb27SDimitry Andric NOINLINE TSD<Allocator> *getTSDAndLockSlow(TSD<Allocator> *CurrentTSD) 21406c3fb27SDimitry Andric EXCLUDES(MutexTSDs) { 2150b57cec5SDimitry Andric // Use the Precedence of the current TSD as our random seed. Since we are 2160b57cec5SDimitry Andric // in the slow path, it means that tryLock failed, and as a result it's 2170b57cec5SDimitry Andric // very likely that said Precedence is non-zero. 21868d75effSDimitry Andric const u32 R = static_cast<u32>(CurrentTSD->getPrecedence()); 219e8d8bef9SDimitry Andric u32 N, Inc; 220e8d8bef9SDimitry Andric { 221e8d8bef9SDimitry Andric ScopedLock L(MutexTSDs); 222e8d8bef9SDimitry Andric N = NumberOfTSDs; 223e8d8bef9SDimitry Andric DCHECK_NE(NumberOfCoPrimes, 0U); 224e8d8bef9SDimitry Andric Inc = CoPrimes[R % NumberOfCoPrimes]; 225e8d8bef9SDimitry Andric } 226e8d8bef9SDimitry Andric if (N > 1U) { 227e8d8bef9SDimitry Andric u32 Index = R % N; 2280b57cec5SDimitry Andric uptr LowestPrecedence = UINTPTR_MAX; 2290b57cec5SDimitry Andric TSD<Allocator> *CandidateTSD = nullptr; 2300b57cec5SDimitry Andric // Go randomly through at most 4 contexts and find a candidate. 231e8d8bef9SDimitry Andric for (u32 I = 0; I < Min(4U, N); I++) { 2320b57cec5SDimitry Andric if (TSDs[Index].tryLock()) { 2330b57cec5SDimitry Andric setCurrentTSD(&TSDs[Index]); 2340b57cec5SDimitry Andric return &TSDs[Index]; 2350b57cec5SDimitry Andric } 2360b57cec5SDimitry Andric const uptr Precedence = TSDs[Index].getPrecedence(); 2370b57cec5SDimitry Andric // A 0 precedence here means another thread just locked this TSD. 2380b57cec5SDimitry Andric if (Precedence && Precedence < LowestPrecedence) { 2390b57cec5SDimitry Andric CandidateTSD = &TSDs[Index]; 2400b57cec5SDimitry Andric LowestPrecedence = Precedence; 2410b57cec5SDimitry Andric } 2420b57cec5SDimitry Andric Index += Inc; 243e8d8bef9SDimitry Andric if (Index >= N) 244e8d8bef9SDimitry Andric Index -= N; 2450b57cec5SDimitry Andric } 2460b57cec5SDimitry Andric if (CandidateTSD) { 2470b57cec5SDimitry Andric CandidateTSD->lock(); 2480b57cec5SDimitry Andric setCurrentTSD(CandidateTSD); 2490b57cec5SDimitry Andric return CandidateTSD; 2500b57cec5SDimitry Andric } 2510b57cec5SDimitry Andric } 2520b57cec5SDimitry Andric // Last resort, stick with the current one. 2530b57cec5SDimitry Andric CurrentTSD->lock(); 2540b57cec5SDimitry Andric return CurrentTSD; 2550b57cec5SDimitry Andric } 2560b57cec5SDimitry Andric 257fe6060f1SDimitry Andric atomic_u32 CurrentIndex = {}; 25806c3fb27SDimitry Andric u32 NumberOfTSDs GUARDED_BY(MutexTSDs) = 0; 25906c3fb27SDimitry Andric u32 NumberOfCoPrimes GUARDED_BY(MutexTSDs) = 0; 26006c3fb27SDimitry Andric u32 CoPrimes[TSDsArraySize] GUARDED_BY(MutexTSDs) = {}; 26106c3fb27SDimitry Andric bool Initialized GUARDED_BY(Mutex) = false; 2620b57cec5SDimitry Andric HybridMutex Mutex; 263e8d8bef9SDimitry Andric HybridMutex MutexTSDs; 264e8d8bef9SDimitry Andric TSD<Allocator> TSDs[TSDsArraySize]; 2650b57cec5SDimitry Andric }; 2660b57cec5SDimitry Andric 2670b57cec5SDimitry Andric } // namespace scudo 2680b57cec5SDimitry Andric 2690b57cec5SDimitry Andric #endif // SCUDO_TSD_SHARED_H_ 270