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