tsd_shared.h 7.8 KB

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