xref: /openbsd-src/gnu/llvm/compiler-rt/lib/scudo/standalone/tsd_shared.h (revision d89ec533011f513df1010f142a111086a0785f09)
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