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