xref: /freebsd/contrib/llvm-project/compiler-rt/lib/tsan/rtl/tsan_sync.cpp (revision 2e4311906d8c8dc7a7c726345268253bca6d4acc)
1 //===-- tsan_sync.cpp -----------------------------------------------------===//
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 // This file is a part of ThreadSanitizer (TSan), a race detector.
10 //
11 //===----------------------------------------------------------------------===//
12 #include "sanitizer_common/sanitizer_placement_new.h"
13 #include "tsan_sync.h"
14 #include "tsan_rtl.h"
15 #include "tsan_mman.h"
16 
17 namespace __tsan {
18 
19 void DDMutexInit(ThreadState *thr, uptr pc, SyncVar *s);
20 
21 SyncVar::SyncVar() : mtx(MutexTypeSyncVar) { Reset(0); }
22 
23 void SyncVar::Init(ThreadState *thr, uptr pc, uptr addr, u64 uid) {
24   this->addr = addr;
25   this->uid = uid;
26   this->next = 0;
27 
28   creation_stack_id = 0;
29   if (!SANITIZER_GO)  // Go does not use them
30     creation_stack_id = CurrentStackId(thr, pc);
31   if (common_flags()->detect_deadlocks)
32     DDMutexInit(thr, pc, this);
33 }
34 
35 void SyncVar::Reset(Processor *proc) {
36   uid = 0;
37   creation_stack_id = 0;
38   owner_tid = kInvalidTid;
39   last_lock = 0;
40   recursion = 0;
41   atomic_store_relaxed(&flags, 0);
42 
43   if (proc == 0) {
44     CHECK_EQ(clock.size(), 0);
45     CHECK_EQ(read_clock.size(), 0);
46   } else {
47     clock.Reset(&proc->clock_cache);
48     read_clock.Reset(&proc->clock_cache);
49   }
50 }
51 
52 MetaMap::MetaMap()
53     : block_alloc_(LINKER_INITIALIZED, "heap block allocator"),
54       sync_alloc_(LINKER_INITIALIZED, "sync allocator") {
55   atomic_store(&uid_gen_, 0, memory_order_relaxed);
56 }
57 
58 void MetaMap::AllocBlock(ThreadState *thr, uptr pc, uptr p, uptr sz) {
59   u32 idx = block_alloc_.Alloc(&thr->proc()->block_cache);
60   MBlock *b = block_alloc_.Map(idx);
61   b->siz = sz;
62   b->tag = 0;
63   b->tid = thr->tid;
64   b->stk = CurrentStackId(thr, pc);
65   u32 *meta = MemToMeta(p);
66   DCHECK_EQ(*meta, 0);
67   *meta = idx | kFlagBlock;
68 }
69 
70 uptr MetaMap::FreeBlock(Processor *proc, uptr p) {
71   MBlock* b = GetBlock(p);
72   if (b == 0)
73     return 0;
74   uptr sz = RoundUpTo(b->siz, kMetaShadowCell);
75   FreeRange(proc, p, sz);
76   return sz;
77 }
78 
79 bool MetaMap::FreeRange(Processor *proc, uptr p, uptr sz) {
80   bool has_something = false;
81   u32 *meta = MemToMeta(p);
82   u32 *end = MemToMeta(p + sz);
83   if (end == meta)
84     end++;
85   for (; meta < end; meta++) {
86     u32 idx = *meta;
87     if (idx == 0) {
88       // Note: don't write to meta in this case -- the block can be huge.
89       continue;
90     }
91     *meta = 0;
92     has_something = true;
93     while (idx != 0) {
94       if (idx & kFlagBlock) {
95         block_alloc_.Free(&proc->block_cache, idx & ~kFlagMask);
96         break;
97       } else if (idx & kFlagSync) {
98         DCHECK(idx & kFlagSync);
99         SyncVar *s = sync_alloc_.Map(idx & ~kFlagMask);
100         u32 next = s->next;
101         s->Reset(proc);
102         sync_alloc_.Free(&proc->sync_cache, idx & ~kFlagMask);
103         idx = next;
104       } else {
105         CHECK(0);
106       }
107     }
108   }
109   return has_something;
110 }
111 
112 // ResetRange removes all meta objects from the range.
113 // It is called for large mmap-ed regions. The function is best-effort wrt
114 // freeing of meta objects, because we don't want to page in the whole range
115 // which can be huge. The function probes pages one-by-one until it finds a page
116 // without meta objects, at this point it stops freeing meta objects. Because
117 // thread stacks grow top-down, we do the same starting from end as well.
118 void MetaMap::ResetRange(Processor *proc, uptr p, uptr sz) {
119   if (SANITIZER_GO) {
120     // UnmapOrDie/MmapFixedNoReserve does not work on Windows,
121     // so we do the optimization only for C/C++.
122     FreeRange(proc, p, sz);
123     return;
124   }
125   const uptr kMetaRatio = kMetaShadowCell / kMetaShadowSize;
126   const uptr kPageSize = GetPageSizeCached() * kMetaRatio;
127   if (sz <= 4 * kPageSize) {
128     // If the range is small, just do the normal free procedure.
129     FreeRange(proc, p, sz);
130     return;
131   }
132   // First, round both ends of the range to page size.
133   uptr diff = RoundUp(p, kPageSize) - p;
134   if (diff != 0) {
135     FreeRange(proc, p, diff);
136     p += diff;
137     sz -= diff;
138   }
139   diff = p + sz - RoundDown(p + sz, kPageSize);
140   if (diff != 0) {
141     FreeRange(proc, p + sz - diff, diff);
142     sz -= diff;
143   }
144   // Now we must have a non-empty page-aligned range.
145   CHECK_GT(sz, 0);
146   CHECK_EQ(p, RoundUp(p, kPageSize));
147   CHECK_EQ(sz, RoundUp(sz, kPageSize));
148   const uptr p0 = p;
149   const uptr sz0 = sz;
150   // Probe start of the range.
151   for (uptr checked = 0; sz > 0; checked += kPageSize) {
152     bool has_something = FreeRange(proc, p, kPageSize);
153     p += kPageSize;
154     sz -= kPageSize;
155     if (!has_something && checked > (128 << 10))
156       break;
157   }
158   // Probe end of the range.
159   for (uptr checked = 0; sz > 0; checked += kPageSize) {
160     bool has_something = FreeRange(proc, p + sz - kPageSize, kPageSize);
161     sz -= kPageSize;
162     // Stacks grow down, so sync object are most likely at the end of the region
163     // (if it is a stack). The very end of the stack is TLS and tsan increases
164     // TLS by at least 256K, so check at least 512K.
165     if (!has_something && checked > (512 << 10))
166       break;
167   }
168   // Finally, page out the whole range (including the parts that we've just
169   // freed). Note: we can't simply madvise, because we need to leave a zeroed
170   // range (otherwise __tsan_java_move can crash if it encounters a left-over
171   // meta objects in java heap).
172   uptr metap = (uptr)MemToMeta(p0);
173   uptr metasz = sz0 / kMetaRatio;
174   UnmapOrDie((void*)metap, metasz);
175   if (!MmapFixedSuperNoReserve(metap, metasz))
176     Die();
177 }
178 
179 MBlock* MetaMap::GetBlock(uptr p) {
180   u32 *meta = MemToMeta(p);
181   u32 idx = *meta;
182   for (;;) {
183     if (idx == 0)
184       return 0;
185     if (idx & kFlagBlock)
186       return block_alloc_.Map(idx & ~kFlagMask);
187     DCHECK(idx & kFlagSync);
188     SyncVar * s = sync_alloc_.Map(idx & ~kFlagMask);
189     idx = s->next;
190   }
191 }
192 
193 SyncVar* MetaMap::GetOrCreateAndLock(ThreadState *thr, uptr pc,
194                               uptr addr, bool write_lock) {
195   return GetAndLock(thr, pc, addr, write_lock, true);
196 }
197 
198 SyncVar* MetaMap::GetIfExistsAndLock(uptr addr, bool write_lock) {
199   return GetAndLock(0, 0, addr, write_lock, false);
200 }
201 
202 SyncVar *MetaMap::GetAndLock(ThreadState *thr, uptr pc, uptr addr, bool write_lock,
203                              bool create) NO_THREAD_SAFETY_ANALYSIS {
204   u32 *meta = MemToMeta(addr);
205   u32 idx0 = *meta;
206   u32 myidx = 0;
207   SyncVar *mys = 0;
208   for (;;) {
209     u32 idx = idx0;
210     for (;;) {
211       if (idx == 0)
212         break;
213       if (idx & kFlagBlock)
214         break;
215       DCHECK(idx & kFlagSync);
216       SyncVar * s = sync_alloc_.Map(idx & ~kFlagMask);
217       if (s->addr == addr) {
218         if (myidx != 0) {
219           mys->Reset(thr->proc());
220           sync_alloc_.Free(&thr->proc()->sync_cache, myidx);
221         }
222         if (write_lock)
223           s->mtx.Lock();
224         else
225           s->mtx.ReadLock();
226         return s;
227       }
228       idx = s->next;
229     }
230     if (!create)
231       return 0;
232     if (*meta != idx0) {
233       idx0 = *meta;
234       continue;
235     }
236 
237     if (myidx == 0) {
238       const u64 uid = atomic_fetch_add(&uid_gen_, 1, memory_order_relaxed);
239       myidx = sync_alloc_.Alloc(&thr->proc()->sync_cache);
240       mys = sync_alloc_.Map(myidx);
241       mys->Init(thr, pc, addr, uid);
242     }
243     mys->next = idx0;
244     if (atomic_compare_exchange_strong((atomic_uint32_t*)meta, &idx0,
245         myidx | kFlagSync, memory_order_release)) {
246       if (write_lock)
247         mys->mtx.Lock();
248       else
249         mys->mtx.ReadLock();
250       return mys;
251     }
252   }
253 }
254 
255 void MetaMap::MoveMemory(uptr src, uptr dst, uptr sz) {
256   // src and dst can overlap,
257   // there are no concurrent accesses to the regions (e.g. stop-the-world).
258   CHECK_NE(src, dst);
259   CHECK_NE(sz, 0);
260   uptr diff = dst - src;
261   u32 *src_meta = MemToMeta(src);
262   u32 *dst_meta = MemToMeta(dst);
263   u32 *src_meta_end = MemToMeta(src + sz);
264   uptr inc = 1;
265   if (dst > src) {
266     src_meta = MemToMeta(src + sz) - 1;
267     dst_meta = MemToMeta(dst + sz) - 1;
268     src_meta_end = MemToMeta(src) - 1;
269     inc = -1;
270   }
271   for (; src_meta != src_meta_end; src_meta += inc, dst_meta += inc) {
272     CHECK_EQ(*dst_meta, 0);
273     u32 idx = *src_meta;
274     *src_meta = 0;
275     *dst_meta = idx;
276     // Patch the addresses in sync objects.
277     while (idx != 0) {
278       if (idx & kFlagBlock)
279         break;
280       CHECK(idx & kFlagSync);
281       SyncVar *s = sync_alloc_.Map(idx & ~kFlagMask);
282       s->addr += diff;
283       idx = s->next;
284     }
285   }
286 }
287 
288 void MetaMap::OnProcIdle(Processor *proc) {
289   block_alloc_.FlushCache(&proc->block_cache);
290   sync_alloc_.FlushCache(&proc->sync_cache);
291 }
292 
293 }  // namespace __tsan
294