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 #if SCUDO_HAS_PLATFORM_TLS_SLOT 15 // This is a platform-provided header that needs to be on the include path when 16 // Scudo is compiled. It must declare a function with the prototype: 17 // uintptr_t *getPlatformAllocatorTlsSlot() 18 // that returns the address of a thread-local word of storage reserved for 19 // Scudo, that must be zero-initialized in newly created threads. 20 #include "scudo_platform_tls_slot.h" 21 #endif 22 23 namespace scudo { 24 25 template <class Allocator, u32 TSDsArraySize, u32 DefaultTSDCount> 26 struct TSDRegistrySharedT { 27 void initLinkerInitialized(Allocator *Instance) { 28 Instance->initLinkerInitialized(); 29 for (u32 I = 0; I < TSDsArraySize; I++) 30 TSDs[I].initLinkerInitialized(Instance); 31 const u32 NumberOfCPUs = getNumberOfCPUs(); 32 setNumberOfTSDs((NumberOfCPUs == 0) ? DefaultTSDCount 33 : Min(NumberOfCPUs, DefaultTSDCount)); 34 Initialized = true; 35 } 36 void init(Allocator *Instance) { 37 memset(this, 0, sizeof(*this)); 38 initLinkerInitialized(Instance); 39 } 40 41 void unmapTestOnly() { setCurrentTSD(nullptr); } 42 43 ALWAYS_INLINE void initThreadMaybe(Allocator *Instance, 44 UNUSED bool MinimalInit) { 45 if (LIKELY(getCurrentTSD())) 46 return; 47 initThread(Instance); 48 } 49 50 ALWAYS_INLINE TSD<Allocator> *getTSDAndLock(bool *UnlockRequired) { 51 TSD<Allocator> *TSD = getCurrentTSD(); 52 DCHECK(TSD); 53 *UnlockRequired = true; 54 // Try to lock the currently associated context. 55 if (TSD->tryLock()) 56 return TSD; 57 // If that fails, go down the slow path. 58 if (TSDsArraySize == 1U) { 59 // Only 1 TSD, not need to go any further. 60 // The compiler will optimize this one way or the other. 61 TSD->lock(); 62 return TSD; 63 } 64 return getTSDAndLockSlow(TSD); 65 } 66 67 void disable() { 68 Mutex.lock(); 69 for (u32 I = 0; I < TSDsArraySize; I++) 70 TSDs[I].lock(); 71 } 72 73 void enable() { 74 for (s32 I = static_cast<s32>(TSDsArraySize - 1); I >= 0; I--) 75 TSDs[I].unlock(); 76 Mutex.unlock(); 77 } 78 79 bool setOption(Option O, sptr Value) { 80 if (O == Option::MaxTSDsCount) 81 return setNumberOfTSDs(static_cast<u32>(Value)); 82 if (O == Option::ThreadDisableMemInit) 83 setDisableMemInit(Value); 84 // Not supported by the TSD Registry, but not an error either. 85 return true; 86 } 87 88 bool getDisableMemInit() const { return *getTlsPtr() & 1; } 89 90 private: 91 ALWAYS_INLINE uptr *getTlsPtr() const { 92 #if SCUDO_HAS_PLATFORM_TLS_SLOT 93 return reinterpret_cast<uptr *>(getPlatformAllocatorTlsSlot()); 94 #else 95 static thread_local uptr ThreadTSD; 96 return &ThreadTSD; 97 #endif 98 } 99 100 static_assert(alignof(TSD<Allocator>) >= 2, ""); 101 102 ALWAYS_INLINE void setCurrentTSD(TSD<Allocator> *CurrentTSD) { 103 *getTlsPtr() &= 1; 104 *getTlsPtr() |= reinterpret_cast<uptr>(CurrentTSD); 105 } 106 107 ALWAYS_INLINE TSD<Allocator> *getCurrentTSD() { 108 return reinterpret_cast<TSD<Allocator> *>(*getTlsPtr() & ~1ULL); 109 } 110 111 bool setNumberOfTSDs(u32 N) { 112 ScopedLock L(MutexTSDs); 113 if (N < NumberOfTSDs) 114 return false; 115 if (N > TSDsArraySize) 116 N = TSDsArraySize; 117 NumberOfTSDs = N; 118 NumberOfCoPrimes = 0; 119 // Compute all the coprimes of NumberOfTSDs. This will be used to walk the 120 // array of TSDs in a random order. For details, see: 121 // https://lemire.me/blog/2017/09/18/visiting-all-values-in-an-array-exactly-once-in-random-order/ 122 for (u32 I = 0; I < N; I++) { 123 u32 A = I + 1; 124 u32 B = N; 125 // Find the GCD between I + 1 and N. If 1, they are coprimes. 126 while (B != 0) { 127 const u32 T = A; 128 A = B; 129 B = T % B; 130 } 131 if (A == 1) 132 CoPrimes[NumberOfCoPrimes++] = I + 1; 133 } 134 return true; 135 } 136 137 void setDisableMemInit(bool B) { 138 *getTlsPtr() &= ~1ULL; 139 *getTlsPtr() |= B; 140 } 141 142 void initOnceMaybe(Allocator *Instance) { 143 ScopedLock L(Mutex); 144 if (LIKELY(Initialized)) 145 return; 146 initLinkerInitialized(Instance); // Sets Initialized. 147 } 148 149 NOINLINE void initThread(Allocator *Instance) { 150 initOnceMaybe(Instance); 151 // Initial context assignment is done in a plain round-robin fashion. 152 const u32 Index = atomic_fetch_add(&CurrentIndex, 1U, memory_order_relaxed); 153 setCurrentTSD(&TSDs[Index % NumberOfTSDs]); 154 Instance->callPostInitCallback(); 155 } 156 157 NOINLINE TSD<Allocator> *getTSDAndLockSlow(TSD<Allocator> *CurrentTSD) { 158 // Use the Precedence of the current TSD as our random seed. Since we are 159 // in the slow path, it means that tryLock failed, and as a result it's 160 // very likely that said Precedence is non-zero. 161 const u32 R = static_cast<u32>(CurrentTSD->getPrecedence()); 162 u32 N, Inc; 163 { 164 ScopedLock L(MutexTSDs); 165 N = NumberOfTSDs; 166 DCHECK_NE(NumberOfCoPrimes, 0U); 167 Inc = CoPrimes[R % NumberOfCoPrimes]; 168 } 169 if (N > 1U) { 170 u32 Index = R % N; 171 uptr LowestPrecedence = UINTPTR_MAX; 172 TSD<Allocator> *CandidateTSD = nullptr; 173 // Go randomly through at most 4 contexts and find a candidate. 174 for (u32 I = 0; I < Min(4U, N); I++) { 175 if (TSDs[Index].tryLock()) { 176 setCurrentTSD(&TSDs[Index]); 177 return &TSDs[Index]; 178 } 179 const uptr Precedence = TSDs[Index].getPrecedence(); 180 // A 0 precedence here means another thread just locked this TSD. 181 if (Precedence && Precedence < LowestPrecedence) { 182 CandidateTSD = &TSDs[Index]; 183 LowestPrecedence = Precedence; 184 } 185 Index += Inc; 186 if (Index >= N) 187 Index -= N; 188 } 189 if (CandidateTSD) { 190 CandidateTSD->lock(); 191 setCurrentTSD(CandidateTSD); 192 return CandidateTSD; 193 } 194 } 195 // Last resort, stick with the current one. 196 CurrentTSD->lock(); 197 return CurrentTSD; 198 } 199 200 atomic_u32 CurrentIndex; 201 u32 NumberOfTSDs; 202 u32 NumberOfCoPrimes; 203 u32 CoPrimes[TSDsArraySize]; 204 bool Initialized; 205 HybridMutex Mutex; 206 HybridMutex MutexTSDs; 207 TSD<Allocator> TSDs[TSDsArraySize]; 208 }; 209 210 } // namespace scudo 211 212 #endif // SCUDO_TSD_SHARED_H_ 213