13cab2bb3Spatrick //===-- tsd_shared.h --------------------------------------------*- C++ -*-===// 23cab2bb3Spatrick // 33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information. 53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 63cab2bb3Spatrick // 73cab2bb3Spatrick //===----------------------------------------------------------------------===// 83cab2bb3Spatrick 93cab2bb3Spatrick #ifndef SCUDO_TSD_SHARED_H_ 103cab2bb3Spatrick #define SCUDO_TSD_SHARED_H_ 113cab2bb3Spatrick 123cab2bb3Spatrick #include "tsd.h" 133cab2bb3Spatrick 14*d89ec533Spatrick #if SCUDO_HAS_PLATFORM_TLS_SLOT 15*d89ec533Spatrick // This is a platform-provided header that needs to be on the include path when 16*d89ec533Spatrick // Scudo is compiled. It must declare a function with the prototype: 17*d89ec533Spatrick // uintptr_t *getPlatformAllocatorTlsSlot() 18*d89ec533Spatrick // that returns the address of a thread-local word of storage reserved for 19*d89ec533Spatrick // Scudo, that must be zero-initialized in newly created threads. 20*d89ec533Spatrick #include "scudo_platform_tls_slot.h" 21*d89ec533Spatrick #endif 22*d89ec533Spatrick 233cab2bb3Spatrick namespace scudo { 243cab2bb3Spatrick 25*d89ec533Spatrick template <class Allocator, u32 TSDsArraySize, u32 DefaultTSDCount> 26*d89ec533Spatrick struct TSDRegistrySharedT { initTSDRegistrySharedT27*d89ec533Spatrick void init(Allocator *Instance) { 28*d89ec533Spatrick DCHECK(!Initialized); 29*d89ec533Spatrick Instance->init(); 30*d89ec533Spatrick for (u32 I = 0; I < TSDsArraySize; I++) 31*d89ec533Spatrick TSDs[I].init(Instance); 321f9cb04fSpatrick const u32 NumberOfCPUs = getNumberOfCPUs(); 33*d89ec533Spatrick setNumberOfTSDs((NumberOfCPUs == 0) ? DefaultTSDCount 34*d89ec533Spatrick : Min(NumberOfCPUs, DefaultTSDCount)); 353cab2bb3Spatrick Initialized = true; 363cab2bb3Spatrick } 37*d89ec533Spatrick initOnceMaybeTSDRegistrySharedT38*d89ec533Spatrick void initOnceMaybe(Allocator *Instance) { 39*d89ec533Spatrick ScopedLock L(Mutex); 40*d89ec533Spatrick if (LIKELY(Initialized)) 41*d89ec533Spatrick return; 42*d89ec533Spatrick init(Instance); // Sets Initialized. 433cab2bb3Spatrick } 443cab2bb3Spatrick unmapTestOnlyTSDRegistrySharedT45*d89ec533Spatrick void unmapTestOnly(Allocator *Instance) { 46*d89ec533Spatrick for (u32 I = 0; I < TSDsArraySize; I++) { 47*d89ec533Spatrick TSDs[I].commitBack(Instance); 48*d89ec533Spatrick TSDs[I] = {}; 49*d89ec533Spatrick } 503cab2bb3Spatrick setCurrentTSD(nullptr); 51*d89ec533Spatrick Initialized = false; 523cab2bb3Spatrick } 533cab2bb3Spatrick initThreadMaybeTSDRegistrySharedT543cab2bb3Spatrick ALWAYS_INLINE void initThreadMaybe(Allocator *Instance, 553cab2bb3Spatrick UNUSED bool MinimalInit) { 563cab2bb3Spatrick if (LIKELY(getCurrentTSD())) 573cab2bb3Spatrick return; 583cab2bb3Spatrick initThread(Instance); 593cab2bb3Spatrick } 603cab2bb3Spatrick getTSDAndLockTSDRegistrySharedT613cab2bb3Spatrick ALWAYS_INLINE TSD<Allocator> *getTSDAndLock(bool *UnlockRequired) { 623cab2bb3Spatrick TSD<Allocator> *TSD = getCurrentTSD(); 633cab2bb3Spatrick DCHECK(TSD); 643cab2bb3Spatrick *UnlockRequired = true; 653cab2bb3Spatrick // Try to lock the currently associated context. 663cab2bb3Spatrick if (TSD->tryLock()) 673cab2bb3Spatrick return TSD; 683cab2bb3Spatrick // If that fails, go down the slow path. 69*d89ec533Spatrick if (TSDsArraySize == 1U) { 70*d89ec533Spatrick // Only 1 TSD, not need to go any further. 71*d89ec533Spatrick // The compiler will optimize this one way or the other. 72*d89ec533Spatrick TSD->lock(); 73*d89ec533Spatrick return TSD; 74*d89ec533Spatrick } 753cab2bb3Spatrick return getTSDAndLockSlow(TSD); 763cab2bb3Spatrick } 773cab2bb3Spatrick disableTSDRegistrySharedT783cab2bb3Spatrick void disable() { 793cab2bb3Spatrick Mutex.lock(); 80*d89ec533Spatrick for (u32 I = 0; I < TSDsArraySize; I++) 813cab2bb3Spatrick TSDs[I].lock(); 823cab2bb3Spatrick } 833cab2bb3Spatrick enableTSDRegistrySharedT843cab2bb3Spatrick void enable() { 85*d89ec533Spatrick for (s32 I = static_cast<s32>(TSDsArraySize - 1); I >= 0; I--) 863cab2bb3Spatrick TSDs[I].unlock(); 873cab2bb3Spatrick Mutex.unlock(); 883cab2bb3Spatrick } 893cab2bb3Spatrick setOptionTSDRegistrySharedT90*d89ec533Spatrick bool setOption(Option O, sptr Value) { 91*d89ec533Spatrick if (O == Option::MaxTSDsCount) 92*d89ec533Spatrick return setNumberOfTSDs(static_cast<u32>(Value)); 93*d89ec533Spatrick if (O == Option::ThreadDisableMemInit) 94*d89ec533Spatrick setDisableMemInit(Value); 95*d89ec533Spatrick // Not supported by the TSD Registry, but not an error either. 96*d89ec533Spatrick return true; 97*d89ec533Spatrick } 98*d89ec533Spatrick getDisableMemInitTSDRegistrySharedT99*d89ec533Spatrick bool getDisableMemInit() const { return *getTlsPtr() & 1; } 100*d89ec533Spatrick 1013cab2bb3Spatrick private: getTlsPtrTSDRegistrySharedT102*d89ec533Spatrick ALWAYS_INLINE uptr *getTlsPtr() const { 103*d89ec533Spatrick #if SCUDO_HAS_PLATFORM_TLS_SLOT 104*d89ec533Spatrick return reinterpret_cast<uptr *>(getPlatformAllocatorTlsSlot()); 1053cab2bb3Spatrick #else 106*d89ec533Spatrick static thread_local uptr ThreadTSD; 107*d89ec533Spatrick return &ThreadTSD; 1083cab2bb3Spatrick #endif 1093cab2bb3Spatrick } 1103cab2bb3Spatrick 111*d89ec533Spatrick static_assert(alignof(TSD<Allocator>) >= 2, ""); 112*d89ec533Spatrick setCurrentTSDTSDRegistrySharedT113*d89ec533Spatrick ALWAYS_INLINE void setCurrentTSD(TSD<Allocator> *CurrentTSD) { 114*d89ec533Spatrick *getTlsPtr() &= 1; 115*d89ec533Spatrick *getTlsPtr() |= reinterpret_cast<uptr>(CurrentTSD); 116*d89ec533Spatrick } 117*d89ec533Spatrick getCurrentTSDTSDRegistrySharedT1183cab2bb3Spatrick ALWAYS_INLINE TSD<Allocator> *getCurrentTSD() { 119*d89ec533Spatrick return reinterpret_cast<TSD<Allocator> *>(*getTlsPtr() & ~1ULL); 1203cab2bb3Spatrick } 1213cab2bb3Spatrick setNumberOfTSDsTSDRegistrySharedT122*d89ec533Spatrick bool setNumberOfTSDs(u32 N) { 123*d89ec533Spatrick ScopedLock L(MutexTSDs); 124*d89ec533Spatrick if (N < NumberOfTSDs) 125*d89ec533Spatrick return false; 126*d89ec533Spatrick if (N > TSDsArraySize) 127*d89ec533Spatrick N = TSDsArraySize; 128*d89ec533Spatrick NumberOfTSDs = N; 129*d89ec533Spatrick NumberOfCoPrimes = 0; 130*d89ec533Spatrick // Compute all the coprimes of NumberOfTSDs. This will be used to walk the 131*d89ec533Spatrick // array of TSDs in a random order. For details, see: 132*d89ec533Spatrick // https://lemire.me/blog/2017/09/18/visiting-all-values-in-an-array-exactly-once-in-random-order/ 133*d89ec533Spatrick for (u32 I = 0; I < N; I++) { 134*d89ec533Spatrick u32 A = I + 1; 135*d89ec533Spatrick u32 B = N; 136*d89ec533Spatrick // Find the GCD between I + 1 and N. If 1, they are coprimes. 137*d89ec533Spatrick while (B != 0) { 138*d89ec533Spatrick const u32 T = A; 139*d89ec533Spatrick A = B; 140*d89ec533Spatrick B = T % B; 141*d89ec533Spatrick } 142*d89ec533Spatrick if (A == 1) 143*d89ec533Spatrick CoPrimes[NumberOfCoPrimes++] = I + 1; 144*d89ec533Spatrick } 145*d89ec533Spatrick return true; 146*d89ec533Spatrick } 147*d89ec533Spatrick setDisableMemInitTSDRegistrySharedT148*d89ec533Spatrick void setDisableMemInit(bool B) { 149*d89ec533Spatrick *getTlsPtr() &= ~1ULL; 150*d89ec533Spatrick *getTlsPtr() |= B; 1513cab2bb3Spatrick } 1523cab2bb3Spatrick initThreadTSDRegistrySharedT1533cab2bb3Spatrick NOINLINE void initThread(Allocator *Instance) { 1543cab2bb3Spatrick initOnceMaybe(Instance); 1553cab2bb3Spatrick // Initial context assignment is done in a plain round-robin fashion. 1563cab2bb3Spatrick const u32 Index = atomic_fetch_add(&CurrentIndex, 1U, memory_order_relaxed); 1573cab2bb3Spatrick setCurrentTSD(&TSDs[Index % NumberOfTSDs]); 1583cab2bb3Spatrick Instance->callPostInitCallback(); 1593cab2bb3Spatrick } 1603cab2bb3Spatrick getTSDAndLockSlowTSDRegistrySharedT1613cab2bb3Spatrick NOINLINE TSD<Allocator> *getTSDAndLockSlow(TSD<Allocator> *CurrentTSD) { 1623cab2bb3Spatrick // Use the Precedence of the current TSD as our random seed. Since we are 1633cab2bb3Spatrick // in the slow path, it means that tryLock failed, and as a result it's 1643cab2bb3Spatrick // very likely that said Precedence is non-zero. 1653cab2bb3Spatrick const u32 R = static_cast<u32>(CurrentTSD->getPrecedence()); 166*d89ec533Spatrick u32 N, Inc; 167*d89ec533Spatrick { 168*d89ec533Spatrick ScopedLock L(MutexTSDs); 169*d89ec533Spatrick N = NumberOfTSDs; 170*d89ec533Spatrick DCHECK_NE(NumberOfCoPrimes, 0U); 171*d89ec533Spatrick Inc = CoPrimes[R % NumberOfCoPrimes]; 172*d89ec533Spatrick } 173*d89ec533Spatrick if (N > 1U) { 174*d89ec533Spatrick u32 Index = R % N; 1753cab2bb3Spatrick uptr LowestPrecedence = UINTPTR_MAX; 1763cab2bb3Spatrick TSD<Allocator> *CandidateTSD = nullptr; 1773cab2bb3Spatrick // Go randomly through at most 4 contexts and find a candidate. 178*d89ec533Spatrick for (u32 I = 0; I < Min(4U, N); I++) { 1793cab2bb3Spatrick if (TSDs[Index].tryLock()) { 1803cab2bb3Spatrick setCurrentTSD(&TSDs[Index]); 1813cab2bb3Spatrick return &TSDs[Index]; 1823cab2bb3Spatrick } 1833cab2bb3Spatrick const uptr Precedence = TSDs[Index].getPrecedence(); 1843cab2bb3Spatrick // A 0 precedence here means another thread just locked this TSD. 1853cab2bb3Spatrick if (Precedence && Precedence < LowestPrecedence) { 1863cab2bb3Spatrick CandidateTSD = &TSDs[Index]; 1873cab2bb3Spatrick LowestPrecedence = Precedence; 1883cab2bb3Spatrick } 1893cab2bb3Spatrick Index += Inc; 190*d89ec533Spatrick if (Index >= N) 191*d89ec533Spatrick Index -= N; 1923cab2bb3Spatrick } 1933cab2bb3Spatrick if (CandidateTSD) { 1943cab2bb3Spatrick CandidateTSD->lock(); 1953cab2bb3Spatrick setCurrentTSD(CandidateTSD); 1963cab2bb3Spatrick return CandidateTSD; 1973cab2bb3Spatrick } 1983cab2bb3Spatrick } 1993cab2bb3Spatrick // Last resort, stick with the current one. 2003cab2bb3Spatrick CurrentTSD->lock(); 2013cab2bb3Spatrick return CurrentTSD; 2023cab2bb3Spatrick } 2033cab2bb3Spatrick 204*d89ec533Spatrick atomic_u32 CurrentIndex = {}; 205*d89ec533Spatrick u32 NumberOfTSDs = 0; 206*d89ec533Spatrick u32 NumberOfCoPrimes = 0; 207*d89ec533Spatrick u32 CoPrimes[TSDsArraySize] = {}; 208*d89ec533Spatrick bool Initialized = false; 2093cab2bb3Spatrick HybridMutex Mutex; 210*d89ec533Spatrick HybridMutex MutexTSDs; 211*d89ec533Spatrick TSD<Allocator> TSDs[TSDsArraySize]; 2123cab2bb3Spatrick }; 2133cab2bb3Spatrick 2143cab2bb3Spatrick } // namespace scudo 2153cab2bb3Spatrick 2163cab2bb3Spatrick #endif // SCUDO_TSD_SHARED_H_ 217