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     T &operator*();
75     const T &operator*() const;
76     bool created() const;
77     bool exists() const;
78 
79    private:
80     friend AddrHashMap<T, kSize>;
81     AddrHashMap<T, kSize> *map_;
82     Bucket                *bucket_;
83     Cell                  *cell_;
84     uptr                   addr_;
85     uptr                   addidx_;
86     bool                   created_;
87     bool                   remove_;
88     bool                   create_;
89   };
90 
91  private:
92   friend class Handle;
93   Bucket *table_;
94 
95   void acquire(Handle *h);
96   void release(Handle *h);
97   uptr calcHash(uptr addr);
98 };
99 
100 template<typename T, uptr kSize>
Handle(AddrHashMap<T,kSize> * map,uptr addr)101 AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr) {
102   map_ = map;
103   addr_ = addr;
104   remove_ = false;
105   create_ = true;
106   map_->acquire(this);
107 }
108 
109 template<typename T, uptr kSize>
Handle(AddrHashMap<T,kSize> * map,uptr addr,bool remove)110 AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr,
111     bool remove) {
112   map_ = map;
113   addr_ = addr;
114   remove_ = remove;
115   create_ = true;
116   map_->acquire(this);
117 }
118 
119 template<typename T, uptr kSize>
Handle(AddrHashMap<T,kSize> * map,uptr addr,bool remove,bool create)120 AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr,
121     bool remove, bool create) {
122   map_ = map;
123   addr_ = addr;
124   remove_ = remove;
125   create_ = create;
126   map_->acquire(this);
127 }
128 
129 template<typename T, uptr kSize>
~Handle()130 AddrHashMap<T, kSize>::Handle::~Handle() {
131   map_->release(this);
132 }
133 
134 template <typename T, uptr kSize>
135 T *AddrHashMap<T, kSize>::Handle::operator->() {
136   return &cell_->val;
137 }
138 
139 template <typename T, uptr kSize>
140 const T &AddrHashMap<T, kSize>::Handle::operator*() const {
141   return cell_->val;
142 }
143 
144 template <typename T, uptr kSize>
145 T &AddrHashMap<T, kSize>::Handle::operator*() {
146   return cell_->val;
147 }
148 
149 template<typename T, uptr kSize>
created()150 bool AddrHashMap<T, kSize>::Handle::created() const {
151   return created_;
152 }
153 
154 template<typename T, uptr kSize>
exists()155 bool AddrHashMap<T, kSize>::Handle::exists() const {
156   return cell_ != nullptr;
157 }
158 
159 template<typename T, uptr kSize>
AddrHashMap()160 AddrHashMap<T, kSize>::AddrHashMap() {
161   table_ = (Bucket*)MmapOrDie(kSize * sizeof(table_[0]), "AddrHashMap");
162 }
163 
164 template<typename T, uptr kSize>
acquire(Handle * h)165 void AddrHashMap<T, kSize>::acquire(Handle *h) {
166   uptr addr = h->addr_;
167   uptr hash = calcHash(addr);
168   Bucket *b = &table_[hash];
169 
170   h->created_ = false;
171   h->addidx_ = -1U;
172   h->bucket_ = b;
173   h->cell_ = nullptr;
174 
175   // If we want to remove the element, we need exclusive access to the bucket,
176   // so skip the lock-free phase.
177   if (h->remove_)
178     goto locked;
179 
180  retry:
181   // First try to find an existing element w/o read mutex.
182   CHECK(!h->remove_);
183   // Check the embed cells.
184   for (uptr i = 0; i < kBucketSize; i++) {
185     Cell *c = &b->cells[i];
186     uptr addr1 = atomic_load(&c->addr, memory_order_acquire);
187     if (addr1 == addr) {
188       h->cell_ = c;
189       return;
190     }
191   }
192 
193   // Check the add cells with read lock.
194   if (atomic_load(&b->add, memory_order_relaxed)) {
195     b->mtx.ReadLock();
196     AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);
197     for (uptr i = 0; i < add->size; i++) {
198       Cell *c = &add->cells[i];
199       uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
200       if (addr1 == addr) {
201         h->addidx_ = i;
202         h->cell_ = c;
203         return;
204       }
205     }
206     b->mtx.ReadUnlock();
207   }
208 
209  locked:
210   // Re-check existence under write lock.
211   // Embed cells.
212   b->mtx.Lock();
213   for (uptr i = 0; i < kBucketSize; i++) {
214     Cell *c = &b->cells[i];
215     uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
216     if (addr1 == addr) {
217       if (h->remove_) {
218         h->cell_ = c;
219         return;
220       }
221       b->mtx.Unlock();
222       goto retry;
223     }
224   }
225 
226   // Add cells.
227   AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);
228   if (add) {
229     for (uptr i = 0; i < add->size; i++) {
230       Cell *c = &add->cells[i];
231       uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
232       if (addr1 == addr) {
233         if (h->remove_) {
234           h->addidx_ = i;
235           h->cell_ = c;
236           return;
237         }
238         b->mtx.Unlock();
239         goto retry;
240       }
241     }
242   }
243 
244   // The element does not exist, no need to create it if we want to remove.
245   if (h->remove_ || !h->create_) {
246     b->mtx.Unlock();
247     return;
248   }
249 
250   // Now try to create it under the mutex.
251   h->created_ = true;
252   // See if we have a free embed cell.
253   for (uptr i = 0; i < kBucketSize; i++) {
254     Cell *c = &b->cells[i];
255     uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
256     if (addr1 == 0) {
257       h->cell_ = c;
258       return;
259     }
260   }
261 
262   // Store in the add cells.
263   if (!add) {
264     // Allocate a new add array.
265     const uptr kInitSize = 64;
266     add = (AddBucket*)InternalAlloc(kInitSize);
267     internal_memset(add, 0, kInitSize);
268     add->cap = (kInitSize - sizeof(*add)) / sizeof(add->cells[0]) + 1;
269     add->size = 0;
270     atomic_store(&b->add, (uptr)add, memory_order_relaxed);
271   }
272   if (add->size == add->cap) {
273     // Grow existing add array.
274     uptr oldsize = sizeof(*add) + (add->cap - 1) * sizeof(add->cells[0]);
275     uptr newsize = oldsize * 2;
276     AddBucket *add1 = (AddBucket*)InternalAlloc(newsize);
277     internal_memset(add1, 0, newsize);
278     add1->cap = (newsize - sizeof(*add)) / sizeof(add->cells[0]) + 1;
279     add1->size = add->size;
280     internal_memcpy(add1->cells, add->cells, add->size * sizeof(add->cells[0]));
281     InternalFree(add);
282     atomic_store(&b->add, (uptr)add1, memory_order_relaxed);
283     add = add1;
284   }
285   // Store.
286   uptr i = add->size++;
287   Cell *c = &add->cells[i];
288   CHECK_EQ(atomic_load(&c->addr, memory_order_relaxed), 0);
289   h->addidx_ = i;
290   h->cell_ = c;
291 }
292 
293 template<typename T, uptr kSize>
release(Handle * h)294 void AddrHashMap<T, kSize>::release(Handle *h) {
295   if (!h->cell_)
296     return;
297   Bucket *b = h->bucket_;
298   Cell *c = h->cell_;
299   uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
300   if (h->created_) {
301     // Denote completion of insertion.
302     CHECK_EQ(addr1, 0);
303     // After the following store, the element becomes available
304     // for lock-free reads.
305     atomic_store(&c->addr, h->addr_, memory_order_release);
306     b->mtx.Unlock();
307   } else if (h->remove_) {
308     // Denote that the cell is empty now.
309     CHECK_EQ(addr1, h->addr_);
310     atomic_store(&c->addr, 0, memory_order_release);
311     // See if we need to compact the bucket.
312     AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);
313     if (h->addidx_ == -1U) {
314       // Removed from embed array, move an add element into the freed cell.
315       if (add && add->size != 0) {
316         uptr last = --add->size;
317         Cell *c1 = &add->cells[last];
318         c->val = c1->val;
319         uptr addr1 = atomic_load(&c1->addr, memory_order_relaxed);
320         atomic_store(&c->addr, addr1, memory_order_release);
321         atomic_store(&c1->addr, 0, memory_order_release);
322       }
323     } else {
324       // Removed from add array, compact it.
325       uptr last = --add->size;
326       Cell *c1 = &add->cells[last];
327       if (c != c1) {
328         *c = *c1;
329         atomic_store(&c1->addr, 0, memory_order_relaxed);
330       }
331     }
332     if (add && add->size == 0) {
333       // FIXME(dvyukov): free add?
334     }
335     b->mtx.Unlock();
336   } else {
337     CHECK_EQ(addr1, h->addr_);
338     if (h->addidx_ != -1U)
339       b->mtx.ReadUnlock();
340   }
341 }
342 
343 template<typename T, uptr kSize>
calcHash(uptr addr)344 uptr AddrHashMap<T, kSize>::calcHash(uptr addr) {
345   addr += addr << 10;
346   addr ^= addr >> 6;
347   return addr % kSize;
348 }
349 
350 } // namespace __sanitizer
351 
352 #endif // SANITIZER_ADDRHASHMAP_H
353