xref: /freebsd/contrib/llvm-project/compiler-rt/lib/scudo/standalone/tsd_shared.h (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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