xref: /netbsd-src/external/gpl3/gcc.old/dist/libsanitizer/sanitizer_common/sanitizer_addrhashmap.h (revision e6c7e151de239c49d2e38720a061ed9d1fa99309)
1 //===-- sanitizer_addrhashmap.h ---------------------------------*- C++ -*-===//
2 //
3 // This file is distributed under the University of Illinois Open Source
4 // License. See LICENSE.TXT for details.
5 //
6 //===----------------------------------------------------------------------===//
7 //
8 // Concurrent uptr->T hashmap.
9 //
10 //===----------------------------------------------------------------------===//
11 
12 #ifndef SANITIZER_ADDRHASHMAP_H
13 #define SANITIZER_ADDRHASHMAP_H
14 
15 #include "sanitizer_common.h"
16 #include "sanitizer_mutex.h"
17 #include "sanitizer_atomic.h"
18 #include "sanitizer_allocator_internal.h"
19 
20 namespace __sanitizer {
21 
22 // Concurrent uptr->T hashmap.
23 // T must be a POD type, kSize is preferably a prime but can be any number.
24 // Usage example:
25 //
26 // typedef AddrHashMap<uptr, 11> Map;
27 // Map m;
28 // {
29 //   Map::Handle h(&m, addr);
30 //   use h.operator->() to access the data
31 //   if h.created() then the element was just created, and the current thread
32 //     has exclusive access to it
33 //   otherwise the current thread has only read access to the data
34 // }
35 // {
36 //   Map::Handle h(&m, addr, true);
37 //   this will remove the data from the map in Handle dtor
38 //   the current thread has exclusive access to the data
39 //   if !h.exists() then the element never existed
40 // }
41 template<typename T, uptr kSize>
42 class AddrHashMap {
43  private:
44   struct Cell {
45     atomic_uintptr_t addr;
46     T                val;
47   };
48 
49   struct AddBucket {
50     uptr cap;
51     uptr size;
52     Cell cells[1];  // variable len
53   };
54 
55   static const uptr kBucketSize = 3;
56 
57   struct Bucket {
58     RWMutex          mtx;
59     atomic_uintptr_t add;
60     Cell             cells[kBucketSize];
61   };
62 
63  public:
64   AddrHashMap();
65 
66   class Handle {
67    public:
68     Handle(AddrHashMap<T, kSize> *map, uptr addr);
69     Handle(AddrHashMap<T, kSize> *map, uptr addr, bool remove);
70     Handle(AddrHashMap<T, kSize> *map, uptr addr, bool remove, bool create);
71 
72     ~Handle();
73     T *operator->();
74     bool created() const;
75     bool exists() const;
76 
77    private:
78     friend AddrHashMap<T, kSize>;
79     AddrHashMap<T, kSize> *map_;
80     Bucket                *bucket_;
81     Cell                  *cell_;
82     uptr                   addr_;
83     uptr                   addidx_;
84     bool                   created_;
85     bool                   remove_;
86     bool                   create_;
87   };
88 
89  private:
90   friend class Handle;
91   Bucket *table_;
92 
93   void acquire(Handle *h);
94   void release(Handle *h);
95   uptr calcHash(uptr addr);
96 };
97 
98 template<typename T, uptr kSize>
99 AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr) {
100   map_ = map;
101   addr_ = addr;
102   remove_ = false;
103   create_ = true;
104   map_->acquire(this);
105 }
106 
107 template<typename T, uptr kSize>
108 AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr,
109     bool remove) {
110   map_ = map;
111   addr_ = addr;
112   remove_ = remove;
113   create_ = true;
114   map_->acquire(this);
115 }
116 
117 template<typename T, uptr kSize>
118 AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr,
119     bool remove, bool create) {
120   map_ = map;
121   addr_ = addr;
122   remove_ = remove;
123   create_ = create;
124   map_->acquire(this);
125 }
126 
127 template<typename T, uptr kSize>
128 AddrHashMap<T, kSize>::Handle::~Handle() {
129   map_->release(this);
130 }
131 
132 template <typename T, uptr kSize>
133 T *AddrHashMap<T, kSize>::Handle::operator->() {
134   return &cell_->val;
135 }
136 
137 template<typename T, uptr kSize>
138 bool AddrHashMap<T, kSize>::Handle::created() const {
139   return created_;
140 }
141 
142 template<typename T, uptr kSize>
143 bool AddrHashMap<T, kSize>::Handle::exists() const {
144   return cell_ != nullptr;
145 }
146 
147 template<typename T, uptr kSize>
148 AddrHashMap<T, kSize>::AddrHashMap() {
149   table_ = (Bucket*)MmapOrDie(kSize * sizeof(table_[0]), "AddrHashMap");
150 }
151 
152 template<typename T, uptr kSize>
153 void AddrHashMap<T, kSize>::acquire(Handle *h) {
154   uptr addr = h->addr_;
155   uptr hash = calcHash(addr);
156   Bucket *b = &table_[hash];
157 
158   h->created_ = false;
159   h->addidx_ = -1U;
160   h->bucket_ = b;
161   h->cell_ = nullptr;
162 
163   // If we want to remove the element, we need exclusive access to the bucket,
164   // so skip the lock-free phase.
165   if (h->remove_)
166     goto locked;
167 
168  retry:
169   // First try to find an existing element w/o read mutex.
170   CHECK(!h->remove_);
171   // Check the embed cells.
172   for (uptr i = 0; i < kBucketSize; i++) {
173     Cell *c = &b->cells[i];
174     uptr addr1 = atomic_load(&c->addr, memory_order_acquire);
175     if (addr1 == addr) {
176       h->cell_ = c;
177       return;
178     }
179   }
180 
181   // Check the add cells with read lock.
182   if (atomic_load(&b->add, memory_order_relaxed)) {
183     b->mtx.ReadLock();
184     AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);
185     for (uptr i = 0; i < add->size; i++) {
186       Cell *c = &add->cells[i];
187       uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
188       if (addr1 == addr) {
189         h->addidx_ = i;
190         h->cell_ = c;
191         return;
192       }
193     }
194     b->mtx.ReadUnlock();
195   }
196 
197  locked:
198   // Re-check existence under write lock.
199   // Embed cells.
200   b->mtx.Lock();
201   for (uptr i = 0; i < kBucketSize; i++) {
202     Cell *c = &b->cells[i];
203     uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
204     if (addr1 == addr) {
205       if (h->remove_) {
206         h->cell_ = c;
207         return;
208       }
209       b->mtx.Unlock();
210       goto retry;
211     }
212   }
213 
214   // Add cells.
215   AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);
216   if (add) {
217     for (uptr i = 0; i < add->size; i++) {
218       Cell *c = &add->cells[i];
219       uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
220       if (addr1 == addr) {
221         if (h->remove_) {
222           h->addidx_ = i;
223           h->cell_ = c;
224           return;
225         }
226         b->mtx.Unlock();
227         goto retry;
228       }
229     }
230   }
231 
232   // The element does not exist, no need to create it if we want to remove.
233   if (h->remove_ || !h->create_) {
234     b->mtx.Unlock();
235     return;
236   }
237 
238   // Now try to create it under the mutex.
239   h->created_ = true;
240   // See if we have a free embed cell.
241   for (uptr i = 0; i < kBucketSize; i++) {
242     Cell *c = &b->cells[i];
243     uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
244     if (addr1 == 0) {
245       h->cell_ = c;
246       return;
247     }
248   }
249 
250   // Store in the add cells.
251   if (!add) {
252     // Allocate a new add array.
253     const uptr kInitSize = 64;
254     add = (AddBucket*)InternalAlloc(kInitSize);
255     internal_memset(add, 0, kInitSize);
256     add->cap = (kInitSize - sizeof(*add)) / sizeof(add->cells[0]) + 1;
257     add->size = 0;
258     atomic_store(&b->add, (uptr)add, memory_order_relaxed);
259   }
260   if (add->size == add->cap) {
261     // Grow existing add array.
262     uptr oldsize = sizeof(*add) + (add->cap - 1) * sizeof(add->cells[0]);
263     uptr newsize = oldsize * 2;
264     AddBucket *add1 = (AddBucket*)InternalAlloc(newsize);
265     internal_memset(add1, 0, newsize);
266     add1->cap = (newsize - sizeof(*add)) / sizeof(add->cells[0]) + 1;
267     add1->size = add->size;
268     internal_memcpy(add1->cells, add->cells, add->size * sizeof(add->cells[0]));
269     InternalFree(add);
270     atomic_store(&b->add, (uptr)add1, memory_order_relaxed);
271     add = add1;
272   }
273   // Store.
274   uptr i = add->size++;
275   Cell *c = &add->cells[i];
276   CHECK_EQ(atomic_load(&c->addr, memory_order_relaxed), 0);
277   h->addidx_ = i;
278   h->cell_ = c;
279 }
280 
281 template<typename T, uptr kSize>
282 void AddrHashMap<T, kSize>::release(Handle *h) {
283   if (!h->cell_)
284     return;
285   Bucket *b = h->bucket_;
286   Cell *c = h->cell_;
287   uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
288   if (h->created_) {
289     // Denote completion of insertion.
290     CHECK_EQ(addr1, 0);
291     // After the following store, the element becomes available
292     // for lock-free reads.
293     atomic_store(&c->addr, h->addr_, memory_order_release);
294     b->mtx.Unlock();
295   } else if (h->remove_) {
296     // Denote that the cell is empty now.
297     CHECK_EQ(addr1, h->addr_);
298     atomic_store(&c->addr, 0, memory_order_release);
299     // See if we need to compact the bucket.
300     AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);
301     if (h->addidx_ == -1U) {
302       // Removed from embed array, move an add element into the freed cell.
303       if (add && add->size != 0) {
304         uptr last = --add->size;
305         Cell *c1 = &add->cells[last];
306         c->val = c1->val;
307         uptr addr1 = atomic_load(&c1->addr, memory_order_relaxed);
308         atomic_store(&c->addr, addr1, memory_order_release);
309         atomic_store(&c1->addr, 0, memory_order_release);
310       }
311     } else {
312       // Removed from add array, compact it.
313       uptr last = --add->size;
314       Cell *c1 = &add->cells[last];
315       if (c != c1) {
316         *c = *c1;
317         atomic_store(&c1->addr, 0, memory_order_relaxed);
318       }
319     }
320     if (add && add->size == 0) {
321       // FIXME(dvyukov): free add?
322     }
323     b->mtx.Unlock();
324   } else {
325     CHECK_EQ(addr1, h->addr_);
326     if (h->addidx_ != -1U)
327       b->mtx.ReadUnlock();
328   }
329 }
330 
331 template<typename T, uptr kSize>
332 uptr AddrHashMap<T, kSize>::calcHash(uptr addr) {
333   addr += addr << 10;
334   addr ^= addr >> 6;
335   return addr % kSize;
336 }
337 
338 } // namespace __sanitizer
339 
340 #endif // SANITIZER_ADDRHASHMAP_H
341