1 //===-- tsd_shared.h --------------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #ifndef SCUDO_TSD_SHARED_H_ 10 #define SCUDO_TSD_SHARED_H_ 11 12 #include "tsd.h" 13 14 #include "string_utils.h" 15 16 #if SCUDO_HAS_PLATFORM_TLS_SLOT 17 // This is a platform-provided header that needs to be on the include path when 18 // Scudo is compiled. It must declare a function with the prototype: 19 // uintptr_t *getPlatformAllocatorTlsSlot() 20 // that returns the address of a thread-local word of storage reserved for 21 // Scudo, that must be zero-initialized in newly created threads. 22 #include "scudo_platform_tls_slot.h" 23 #endif 24 25 namespace scudo { 26 27 template <class Allocator, u32 TSDsArraySize, u32 DefaultTSDCount> 28 struct TSDRegistrySharedT { 29 using ThisT = TSDRegistrySharedT<Allocator, TSDsArraySize, DefaultTSDCount>; 30 31 struct ScopedTSD { ScopedTSDTSDRegistrySharedT::ScopedTSD32 ALWAYS_INLINE ScopedTSD(ThisT &TSDRegistry) { 33 CurrentTSD = TSDRegistry.getTSDAndLock(); 34 DCHECK_NE(CurrentTSD, nullptr); 35 } 36 ~ScopedTSDTSDRegistrySharedT::ScopedTSD37 ~ScopedTSD() { CurrentTSD->unlock(); } 38 39 TSD<Allocator> &operator*() { return *CurrentTSD; } 40 41 TSD<Allocator> *operator->() { 42 CurrentTSD->assertLocked(/*BypassCheck=*/false); 43 return CurrentTSD; 44 } 45 46 private: 47 TSD<Allocator> *CurrentTSD; 48 }; 49 initTSDRegistrySharedT50 void init(Allocator *Instance) REQUIRES(Mutex) { 51 DCHECK(!Initialized); 52 Instance->init(); 53 for (u32 I = 0; I < TSDsArraySize; I++) 54 TSDs[I].init(Instance); 55 const u32 NumberOfCPUs = getNumberOfCPUs(); 56 setNumberOfTSDs((NumberOfCPUs == 0) ? DefaultTSDCount 57 : Min(NumberOfCPUs, DefaultTSDCount)); 58 Initialized = true; 59 } 60 initOnceMaybeTSDRegistrySharedT61 void initOnceMaybe(Allocator *Instance) EXCLUDES(Mutex) { 62 ScopedLock L(Mutex); 63 if (LIKELY(Initialized)) 64 return; 65 init(Instance); // Sets Initialized. 66 } 67 unmapTestOnlyTSDRegistrySharedT68 void unmapTestOnly(Allocator *Instance) EXCLUDES(Mutex) { 69 for (u32 I = 0; I < TSDsArraySize; I++) { 70 TSDs[I].commitBack(Instance); 71 TSDs[I] = {}; 72 } 73 setCurrentTSD(nullptr); 74 ScopedLock L(Mutex); 75 Initialized = false; 76 } 77 drainCachesTSDRegistrySharedT78 void drainCaches(Allocator *Instance) { 79 ScopedLock L(MutexTSDs); 80 for (uptr I = 0; I < NumberOfTSDs; ++I) { 81 TSDs[I].lock(); 82 Instance->drainCache(&TSDs[I]); 83 TSDs[I].unlock(); 84 } 85 } 86 initThreadMaybeTSDRegistrySharedT87 ALWAYS_INLINE void initThreadMaybe(Allocator *Instance, 88 UNUSED bool MinimalInit) { 89 if (LIKELY(getCurrentTSD())) 90 return; 91 initThread(Instance); 92 } 93 disableTSDRegistrySharedT94 void disable() NO_THREAD_SAFETY_ANALYSIS { 95 Mutex.lock(); 96 for (u32 I = 0; I < TSDsArraySize; I++) 97 TSDs[I].lock(); 98 } 99 enableTSDRegistrySharedT100 void enable() NO_THREAD_SAFETY_ANALYSIS { 101 for (s32 I = static_cast<s32>(TSDsArraySize - 1); I >= 0; I--) 102 TSDs[I].unlock(); 103 Mutex.unlock(); 104 } 105 setOptionTSDRegistrySharedT106 bool setOption(Option O, sptr Value) { 107 if (O == Option::MaxTSDsCount) 108 return setNumberOfTSDs(static_cast<u32>(Value)); 109 if (O == Option::ThreadDisableMemInit) 110 setDisableMemInit(Value); 111 // Not supported by the TSD Registry, but not an error either. 112 return true; 113 } 114 getDisableMemInitTSDRegistrySharedT115 bool getDisableMemInit() const { return *getTlsPtr() & 1; } 116 getStatsTSDRegistrySharedT117 void getStats(ScopedString *Str) EXCLUDES(MutexTSDs) { 118 ScopedLock L(MutexTSDs); 119 120 Str->append("Stats: SharedTSDs: %u available; total %u\n", NumberOfTSDs, 121 TSDsArraySize); 122 for (uptr I = 0; I < NumberOfTSDs; ++I) { 123 TSDs[I].lock(); 124 // Theoretically, we want to mark TSD::lock()/TSD::unlock() with proper 125 // thread annotations. However, given the TSD is only locked on shared 126 // path, do the assertion in a separate path to avoid confusing the 127 // analyzer. 128 TSDs[I].assertLocked(/*BypassCheck=*/true); 129 Str->append(" Shared TSD[%zu]:\n", I); 130 TSDs[I].getCache().getStats(Str); 131 TSDs[I].unlock(); 132 } 133 } 134 135 private: getTSDAndLockTSDRegistrySharedT136 ALWAYS_INLINE TSD<Allocator> *getTSDAndLock() NO_THREAD_SAFETY_ANALYSIS { 137 TSD<Allocator> *TSD = getCurrentTSD(); 138 DCHECK(TSD); 139 // Try to lock the currently associated context. 140 if (TSD->tryLock()) 141 return TSD; 142 // If that fails, go down the slow path. 143 if (TSDsArraySize == 1U) { 144 // Only 1 TSD, not need to go any further. 145 // The compiler will optimize this one way or the other. 146 TSD->lock(); 147 return TSD; 148 } 149 return getTSDAndLockSlow(TSD); 150 } 151 getTlsPtrTSDRegistrySharedT152 ALWAYS_INLINE uptr *getTlsPtr() const { 153 #if SCUDO_HAS_PLATFORM_TLS_SLOT 154 return reinterpret_cast<uptr *>(getPlatformAllocatorTlsSlot()); 155 #else 156 static thread_local uptr ThreadTSD; 157 return &ThreadTSD; 158 #endif 159 } 160 161 static_assert(alignof(TSD<Allocator>) >= 2, ""); 162 setCurrentTSDTSDRegistrySharedT163 ALWAYS_INLINE void setCurrentTSD(TSD<Allocator> *CurrentTSD) { 164 *getTlsPtr() &= 1; 165 *getTlsPtr() |= reinterpret_cast<uptr>(CurrentTSD); 166 } 167 getCurrentTSDTSDRegistrySharedT168 ALWAYS_INLINE TSD<Allocator> *getCurrentTSD() { 169 return reinterpret_cast<TSD<Allocator> *>(*getTlsPtr() & ~1ULL); 170 } 171 setNumberOfTSDsTSDRegistrySharedT172 bool setNumberOfTSDs(u32 N) EXCLUDES(MutexTSDs) { 173 ScopedLock L(MutexTSDs); 174 if (N < NumberOfTSDs) 175 return false; 176 if (N > TSDsArraySize) 177 N = TSDsArraySize; 178 NumberOfTSDs = N; 179 NumberOfCoPrimes = 0; 180 // Compute all the coprimes of NumberOfTSDs. This will be used to walk the 181 // array of TSDs in a random order. For details, see: 182 // https://lemire.me/blog/2017/09/18/visiting-all-values-in-an-array-exactly-once-in-random-order/ 183 for (u32 I = 0; I < N; I++) { 184 u32 A = I + 1; 185 u32 B = N; 186 // Find the GCD between I + 1 and N. If 1, they are coprimes. 187 while (B != 0) { 188 const u32 T = A; 189 A = B; 190 B = T % B; 191 } 192 if (A == 1) 193 CoPrimes[NumberOfCoPrimes++] = I + 1; 194 } 195 return true; 196 } 197 setDisableMemInitTSDRegistrySharedT198 void setDisableMemInit(bool B) { 199 *getTlsPtr() &= ~1ULL; 200 *getTlsPtr() |= B; 201 } 202 initThreadTSDRegistrySharedT203 NOINLINE void initThread(Allocator *Instance) NO_THREAD_SAFETY_ANALYSIS { 204 initOnceMaybe(Instance); 205 // Initial context assignment is done in a plain round-robin fashion. 206 const u32 Index = atomic_fetch_add(&CurrentIndex, 1U, memory_order_relaxed); 207 setCurrentTSD(&TSDs[Index % NumberOfTSDs]); 208 Instance->callPostInitCallback(); 209 } 210 211 // TSDs is an array of locks which is not supported for marking thread-safety 212 // capability. getTSDAndLockSlowTSDRegistrySharedT213 NOINLINE TSD<Allocator> *getTSDAndLockSlow(TSD<Allocator> *CurrentTSD) 214 EXCLUDES(MutexTSDs) { 215 // Use the Precedence of the current TSD as our random seed. Since we are 216 // in the slow path, it means that tryLock failed, and as a result it's 217 // very likely that said Precedence is non-zero. 218 const u32 R = static_cast<u32>(CurrentTSD->getPrecedence()); 219 u32 N, Inc; 220 { 221 ScopedLock L(MutexTSDs); 222 N = NumberOfTSDs; 223 DCHECK_NE(NumberOfCoPrimes, 0U); 224 Inc = CoPrimes[R % NumberOfCoPrimes]; 225 } 226 if (N > 1U) { 227 u32 Index = R % N; 228 uptr LowestPrecedence = UINTPTR_MAX; 229 TSD<Allocator> *CandidateTSD = nullptr; 230 // Go randomly through at most 4 contexts and find a candidate. 231 for (u32 I = 0; I < Min(4U, N); I++) { 232 if (TSDs[Index].tryLock()) { 233 setCurrentTSD(&TSDs[Index]); 234 return &TSDs[Index]; 235 } 236 const uptr Precedence = TSDs[Index].getPrecedence(); 237 // A 0 precedence here means another thread just locked this TSD. 238 if (Precedence && Precedence < LowestPrecedence) { 239 CandidateTSD = &TSDs[Index]; 240 LowestPrecedence = Precedence; 241 } 242 Index += Inc; 243 if (Index >= N) 244 Index -= N; 245 } 246 if (CandidateTSD) { 247 CandidateTSD->lock(); 248 setCurrentTSD(CandidateTSD); 249 return CandidateTSD; 250 } 251 } 252 // Last resort, stick with the current one. 253 CurrentTSD->lock(); 254 return CurrentTSD; 255 } 256 257 atomic_u32 CurrentIndex = {}; 258 u32 NumberOfTSDs GUARDED_BY(MutexTSDs) = 0; 259 u32 NumberOfCoPrimes GUARDED_BY(MutexTSDs) = 0; 260 u32 CoPrimes[TSDsArraySize] GUARDED_BY(MutexTSDs) = {}; 261 bool Initialized GUARDED_BY(Mutex) = false; 262 HybridMutex Mutex; 263 HybridMutex MutexTSDs; 264 TSD<Allocator> TSDs[TSDsArraySize]; 265 }; 266 267 } // namespace scudo 268 269 #endif // SCUDO_TSD_SHARED_H_ 270