xref: /openbsd-src/gnu/llvm/compiler-rt/lib/sanitizer_common/sanitizer_addrhashmap.h (revision 810390e339a5425391477d5d41c78d7cab2424ac)
13cab2bb3Spatrick //===-- sanitizer_addrhashmap.h ---------------------------------*- C++ -*-===//
23cab2bb3Spatrick //
33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information.
53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63cab2bb3Spatrick //
73cab2bb3Spatrick //===----------------------------------------------------------------------===//
83cab2bb3Spatrick //
93cab2bb3Spatrick // Concurrent uptr->T hashmap.
103cab2bb3Spatrick //
113cab2bb3Spatrick //===----------------------------------------------------------------------===//
123cab2bb3Spatrick 
133cab2bb3Spatrick #ifndef SANITIZER_ADDRHASHMAP_H
143cab2bb3Spatrick #define SANITIZER_ADDRHASHMAP_H
153cab2bb3Spatrick 
163cab2bb3Spatrick #include "sanitizer_common.h"
173cab2bb3Spatrick #include "sanitizer_mutex.h"
183cab2bb3Spatrick #include "sanitizer_atomic.h"
193cab2bb3Spatrick #include "sanitizer_allocator_internal.h"
203cab2bb3Spatrick 
213cab2bb3Spatrick namespace __sanitizer {
223cab2bb3Spatrick 
233cab2bb3Spatrick // Concurrent uptr->T hashmap.
243cab2bb3Spatrick // T must be a POD type, kSize is preferably a prime but can be any number.
253cab2bb3Spatrick // Usage example:
263cab2bb3Spatrick //
273cab2bb3Spatrick // typedef AddrHashMap<uptr, 11> Map;
283cab2bb3Spatrick // Map m;
293cab2bb3Spatrick // {
303cab2bb3Spatrick //   Map::Handle h(&m, addr);
313cab2bb3Spatrick //   use h.operator->() to access the data
323cab2bb3Spatrick //   if h.created() then the element was just created, and the current thread
333cab2bb3Spatrick //     has exclusive access to it
343cab2bb3Spatrick //   otherwise the current thread has only read access to the data
353cab2bb3Spatrick // }
363cab2bb3Spatrick // {
373cab2bb3Spatrick //   Map::Handle h(&m, addr, true);
383cab2bb3Spatrick //   this will remove the data from the map in Handle dtor
393cab2bb3Spatrick //   the current thread has exclusive access to the data
403cab2bb3Spatrick //   if !h.exists() then the element never existed
413cab2bb3Spatrick // }
42*810390e3Srobert // {
43*810390e3Srobert //   Map::Handle h(&m, addr, false, true);
44*810390e3Srobert //   this will create a new element or return a handle to an existing element
45*810390e3Srobert //   if !h.created() this thread does *not* have exclusive access to the data
46*810390e3Srobert // }
473cab2bb3Spatrick template<typename T, uptr kSize>
483cab2bb3Spatrick class AddrHashMap {
493cab2bb3Spatrick  private:
503cab2bb3Spatrick   struct Cell {
513cab2bb3Spatrick     atomic_uintptr_t addr;
523cab2bb3Spatrick     T                val;
533cab2bb3Spatrick   };
543cab2bb3Spatrick 
553cab2bb3Spatrick   struct AddBucket {
563cab2bb3Spatrick     uptr cap;
573cab2bb3Spatrick     uptr size;
583cab2bb3Spatrick     Cell cells[1];  // variable len
593cab2bb3Spatrick   };
603cab2bb3Spatrick 
613cab2bb3Spatrick   static const uptr kBucketSize = 3;
623cab2bb3Spatrick 
633cab2bb3Spatrick   struct Bucket {
64*810390e3Srobert     Mutex mtx;
653cab2bb3Spatrick     atomic_uintptr_t add;
663cab2bb3Spatrick     Cell             cells[kBucketSize];
673cab2bb3Spatrick   };
683cab2bb3Spatrick 
693cab2bb3Spatrick  public:
703cab2bb3Spatrick   AddrHashMap();
713cab2bb3Spatrick 
723cab2bb3Spatrick   class Handle {
733cab2bb3Spatrick    public:
743cab2bb3Spatrick     Handle(AddrHashMap<T, kSize> *map, uptr addr);
753cab2bb3Spatrick     Handle(AddrHashMap<T, kSize> *map, uptr addr, bool remove);
763cab2bb3Spatrick     Handle(AddrHashMap<T, kSize> *map, uptr addr, bool remove, bool create);
773cab2bb3Spatrick 
783cab2bb3Spatrick     ~Handle();
793cab2bb3Spatrick     T *operator->();
803cab2bb3Spatrick     T &operator*();
813cab2bb3Spatrick     const T &operator*() const;
823cab2bb3Spatrick     bool created() const;
833cab2bb3Spatrick     bool exists() const;
843cab2bb3Spatrick 
853cab2bb3Spatrick    private:
863cab2bb3Spatrick     friend AddrHashMap<T, kSize>;
873cab2bb3Spatrick     AddrHashMap<T, kSize> *map_;
883cab2bb3Spatrick     Bucket                *bucket_;
893cab2bb3Spatrick     Cell                  *cell_;
903cab2bb3Spatrick     uptr                   addr_;
913cab2bb3Spatrick     uptr                   addidx_;
923cab2bb3Spatrick     bool                   created_;
933cab2bb3Spatrick     bool                   remove_;
943cab2bb3Spatrick     bool                   create_;
953cab2bb3Spatrick   };
963cab2bb3Spatrick 
97*810390e3Srobert   typedef void (*ForEachCallback)(const uptr key, const T &val, void *arg);
98*810390e3Srobert   // ForEach acquires a lock on each bucket while iterating over
99*810390e3Srobert   // elements. Note that this only ensures that the structure of the hashmap is
100*810390e3Srobert   // unchanged, there may be a data race to the element itself.
101*810390e3Srobert   void ForEach(ForEachCallback cb, void *arg);
102*810390e3Srobert 
1033cab2bb3Spatrick  private:
1043cab2bb3Spatrick   friend class Handle;
1053cab2bb3Spatrick   Bucket *table_;
1063cab2bb3Spatrick 
1073cab2bb3Spatrick   void acquire(Handle *h);
1083cab2bb3Spatrick   void release(Handle *h);
1093cab2bb3Spatrick   uptr calcHash(uptr addr);
1103cab2bb3Spatrick };
1113cab2bb3Spatrick 
1123cab2bb3Spatrick template <typename T, uptr kSize>
ForEach(ForEachCallback cb,void * arg)113*810390e3Srobert void AddrHashMap<T, kSize>::ForEach(ForEachCallback cb, void *arg) {
114*810390e3Srobert   for (uptr n = 0; n < kSize; n++) {
115*810390e3Srobert     Bucket *bucket = &table_[n];
116*810390e3Srobert 
117*810390e3Srobert     ReadLock lock(&bucket->mtx);
118*810390e3Srobert 
119*810390e3Srobert     for (uptr i = 0; i < kBucketSize; i++) {
120*810390e3Srobert       Cell *c = &bucket->cells[i];
121*810390e3Srobert       uptr addr1 = atomic_load(&c->addr, memory_order_acquire);
122*810390e3Srobert       if (addr1 != 0)
123*810390e3Srobert         cb(addr1, c->val, arg);
124*810390e3Srobert     }
125*810390e3Srobert 
126*810390e3Srobert     // Iterate over any additional cells.
127*810390e3Srobert     if (AddBucket *add =
128*810390e3Srobert             (AddBucket *)atomic_load(&bucket->add, memory_order_acquire)) {
129*810390e3Srobert       for (uptr i = 0; i < add->size; i++) {
130*810390e3Srobert         Cell *c = &add->cells[i];
131*810390e3Srobert         uptr addr1 = atomic_load(&c->addr, memory_order_acquire);
132*810390e3Srobert         if (addr1 != 0)
133*810390e3Srobert           cb(addr1, c->val, arg);
134*810390e3Srobert       }
135*810390e3Srobert     }
136*810390e3Srobert   }
137*810390e3Srobert }
138*810390e3Srobert 
139*810390e3Srobert template<typename T, uptr kSize>
Handle(AddrHashMap<T,kSize> * map,uptr addr)1403cab2bb3Spatrick AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr) {
1413cab2bb3Spatrick   map_ = map;
1423cab2bb3Spatrick   addr_ = addr;
1433cab2bb3Spatrick   remove_ = false;
1443cab2bb3Spatrick   create_ = true;
1453cab2bb3Spatrick   map_->acquire(this);
1463cab2bb3Spatrick }
1473cab2bb3Spatrick 
1483cab2bb3Spatrick template<typename T, uptr kSize>
Handle(AddrHashMap<T,kSize> * map,uptr addr,bool remove)1493cab2bb3Spatrick AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr,
1503cab2bb3Spatrick     bool remove) {
1513cab2bb3Spatrick   map_ = map;
1523cab2bb3Spatrick   addr_ = addr;
1533cab2bb3Spatrick   remove_ = remove;
1543cab2bb3Spatrick   create_ = true;
1553cab2bb3Spatrick   map_->acquire(this);
1563cab2bb3Spatrick }
1573cab2bb3Spatrick 
1583cab2bb3Spatrick template<typename T, uptr kSize>
Handle(AddrHashMap<T,kSize> * map,uptr addr,bool remove,bool create)1593cab2bb3Spatrick AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr,
1603cab2bb3Spatrick     bool remove, bool create) {
1613cab2bb3Spatrick   map_ = map;
1623cab2bb3Spatrick   addr_ = addr;
1633cab2bb3Spatrick   remove_ = remove;
1643cab2bb3Spatrick   create_ = create;
1653cab2bb3Spatrick   map_->acquire(this);
1663cab2bb3Spatrick }
1673cab2bb3Spatrick 
1683cab2bb3Spatrick template<typename T, uptr kSize>
~Handle()1693cab2bb3Spatrick AddrHashMap<T, kSize>::Handle::~Handle() {
1703cab2bb3Spatrick   map_->release(this);
1713cab2bb3Spatrick }
1723cab2bb3Spatrick 
1733cab2bb3Spatrick template <typename T, uptr kSize>
1743cab2bb3Spatrick T *AddrHashMap<T, kSize>::Handle::operator->() {
1753cab2bb3Spatrick   return &cell_->val;
1763cab2bb3Spatrick }
1773cab2bb3Spatrick 
1783cab2bb3Spatrick template <typename T, uptr kSize>
1793cab2bb3Spatrick const T &AddrHashMap<T, kSize>::Handle::operator*() const {
1803cab2bb3Spatrick   return cell_->val;
1813cab2bb3Spatrick }
1823cab2bb3Spatrick 
1833cab2bb3Spatrick template <typename T, uptr kSize>
1843cab2bb3Spatrick T &AddrHashMap<T, kSize>::Handle::operator*() {
1853cab2bb3Spatrick   return cell_->val;
1863cab2bb3Spatrick }
1873cab2bb3Spatrick 
1883cab2bb3Spatrick template<typename T, uptr kSize>
created()1893cab2bb3Spatrick bool AddrHashMap<T, kSize>::Handle::created() const {
1903cab2bb3Spatrick   return created_;
1913cab2bb3Spatrick }
1923cab2bb3Spatrick 
1933cab2bb3Spatrick template<typename T, uptr kSize>
exists()1943cab2bb3Spatrick bool AddrHashMap<T, kSize>::Handle::exists() const {
1953cab2bb3Spatrick   return cell_ != nullptr;
1963cab2bb3Spatrick }
1973cab2bb3Spatrick 
1983cab2bb3Spatrick template<typename T, uptr kSize>
AddrHashMap()1993cab2bb3Spatrick AddrHashMap<T, kSize>::AddrHashMap() {
2003cab2bb3Spatrick   table_ = (Bucket*)MmapOrDie(kSize * sizeof(table_[0]), "AddrHashMap");
2013cab2bb3Spatrick }
2023cab2bb3Spatrick 
2033cab2bb3Spatrick template <typename T, uptr kSize>
acquire(Handle * h)204*810390e3Srobert void AddrHashMap<T, kSize>::acquire(Handle *h)
205*810390e3Srobert     SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
2063cab2bb3Spatrick   uptr addr = h->addr_;
2073cab2bb3Spatrick   uptr hash = calcHash(addr);
2083cab2bb3Spatrick   Bucket *b = &table_[hash];
2093cab2bb3Spatrick 
2103cab2bb3Spatrick   h->created_ = false;
2113cab2bb3Spatrick   h->addidx_ = -1U;
2123cab2bb3Spatrick   h->bucket_ = b;
2133cab2bb3Spatrick   h->cell_ = nullptr;
2143cab2bb3Spatrick 
2153cab2bb3Spatrick   // If we want to remove the element, we need exclusive access to the bucket,
2163cab2bb3Spatrick   // so skip the lock-free phase.
2173cab2bb3Spatrick   if (h->remove_)
2183cab2bb3Spatrick     goto locked;
2193cab2bb3Spatrick 
2203cab2bb3Spatrick  retry:
2213cab2bb3Spatrick   // First try to find an existing element w/o read mutex.
2223cab2bb3Spatrick   CHECK(!h->remove_);
2233cab2bb3Spatrick   // Check the embed cells.
2243cab2bb3Spatrick   for (uptr i = 0; i < kBucketSize; i++) {
2253cab2bb3Spatrick     Cell *c = &b->cells[i];
2263cab2bb3Spatrick     uptr addr1 = atomic_load(&c->addr, memory_order_acquire);
2273cab2bb3Spatrick     if (addr1 == addr) {
2283cab2bb3Spatrick       h->cell_ = c;
2293cab2bb3Spatrick       return;
2303cab2bb3Spatrick     }
2313cab2bb3Spatrick   }
2323cab2bb3Spatrick 
2333cab2bb3Spatrick   // Check the add cells with read lock.
2343cab2bb3Spatrick   if (atomic_load(&b->add, memory_order_relaxed)) {
2353cab2bb3Spatrick     b->mtx.ReadLock();
2363cab2bb3Spatrick     AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);
2373cab2bb3Spatrick     for (uptr i = 0; i < add->size; i++) {
2383cab2bb3Spatrick       Cell *c = &add->cells[i];
2393cab2bb3Spatrick       uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
2403cab2bb3Spatrick       if (addr1 == addr) {
2413cab2bb3Spatrick         h->addidx_ = i;
2423cab2bb3Spatrick         h->cell_ = c;
2433cab2bb3Spatrick         return;
2443cab2bb3Spatrick       }
2453cab2bb3Spatrick     }
2463cab2bb3Spatrick     b->mtx.ReadUnlock();
2473cab2bb3Spatrick   }
2483cab2bb3Spatrick 
2493cab2bb3Spatrick  locked:
2503cab2bb3Spatrick   // Re-check existence under write lock.
2513cab2bb3Spatrick   // Embed cells.
2523cab2bb3Spatrick   b->mtx.Lock();
2533cab2bb3Spatrick   for (uptr i = 0; i < kBucketSize; i++) {
2543cab2bb3Spatrick     Cell *c = &b->cells[i];
2553cab2bb3Spatrick     uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
2563cab2bb3Spatrick     if (addr1 == addr) {
2573cab2bb3Spatrick       if (h->remove_) {
2583cab2bb3Spatrick         h->cell_ = c;
2593cab2bb3Spatrick         return;
2603cab2bb3Spatrick       }
2613cab2bb3Spatrick       b->mtx.Unlock();
2623cab2bb3Spatrick       goto retry;
2633cab2bb3Spatrick     }
2643cab2bb3Spatrick   }
2653cab2bb3Spatrick 
2663cab2bb3Spatrick   // Add cells.
2673cab2bb3Spatrick   AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);
2683cab2bb3Spatrick   if (add) {
2693cab2bb3Spatrick     for (uptr i = 0; i < add->size; i++) {
2703cab2bb3Spatrick       Cell *c = &add->cells[i];
2713cab2bb3Spatrick       uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
2723cab2bb3Spatrick       if (addr1 == addr) {
2733cab2bb3Spatrick         if (h->remove_) {
2743cab2bb3Spatrick           h->addidx_ = i;
2753cab2bb3Spatrick           h->cell_ = c;
2763cab2bb3Spatrick           return;
2773cab2bb3Spatrick         }
2783cab2bb3Spatrick         b->mtx.Unlock();
2793cab2bb3Spatrick         goto retry;
2803cab2bb3Spatrick       }
2813cab2bb3Spatrick     }
2823cab2bb3Spatrick   }
2833cab2bb3Spatrick 
2843cab2bb3Spatrick   // The element does not exist, no need to create it if we want to remove.
2853cab2bb3Spatrick   if (h->remove_ || !h->create_) {
2863cab2bb3Spatrick     b->mtx.Unlock();
2873cab2bb3Spatrick     return;
2883cab2bb3Spatrick   }
2893cab2bb3Spatrick 
2903cab2bb3Spatrick   // Now try to create it under the mutex.
2913cab2bb3Spatrick   h->created_ = true;
2923cab2bb3Spatrick   // See if we have a free embed cell.
2933cab2bb3Spatrick   for (uptr i = 0; i < kBucketSize; i++) {
2943cab2bb3Spatrick     Cell *c = &b->cells[i];
2953cab2bb3Spatrick     uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
2963cab2bb3Spatrick     if (addr1 == 0) {
2973cab2bb3Spatrick       h->cell_ = c;
2983cab2bb3Spatrick       return;
2993cab2bb3Spatrick     }
3003cab2bb3Spatrick   }
3013cab2bb3Spatrick 
3023cab2bb3Spatrick   // Store in the add cells.
3033cab2bb3Spatrick   if (!add) {
3043cab2bb3Spatrick     // Allocate a new add array.
3053cab2bb3Spatrick     const uptr kInitSize = 64;
3063cab2bb3Spatrick     add = (AddBucket*)InternalAlloc(kInitSize);
3073cab2bb3Spatrick     internal_memset(add, 0, kInitSize);
3083cab2bb3Spatrick     add->cap = (kInitSize - sizeof(*add)) / sizeof(add->cells[0]) + 1;
3093cab2bb3Spatrick     add->size = 0;
3103cab2bb3Spatrick     atomic_store(&b->add, (uptr)add, memory_order_relaxed);
3113cab2bb3Spatrick   }
3123cab2bb3Spatrick   if (add->size == add->cap) {
3133cab2bb3Spatrick     // Grow existing add array.
3143cab2bb3Spatrick     uptr oldsize = sizeof(*add) + (add->cap - 1) * sizeof(add->cells[0]);
3153cab2bb3Spatrick     uptr newsize = oldsize * 2;
3163cab2bb3Spatrick     AddBucket *add1 = (AddBucket*)InternalAlloc(newsize);
3173cab2bb3Spatrick     internal_memset(add1, 0, newsize);
3183cab2bb3Spatrick     add1->cap = (newsize - sizeof(*add)) / sizeof(add->cells[0]) + 1;
3193cab2bb3Spatrick     add1->size = add->size;
3203cab2bb3Spatrick     internal_memcpy(add1->cells, add->cells, add->size * sizeof(add->cells[0]));
3213cab2bb3Spatrick     InternalFree(add);
3223cab2bb3Spatrick     atomic_store(&b->add, (uptr)add1, memory_order_relaxed);
3233cab2bb3Spatrick     add = add1;
3243cab2bb3Spatrick   }
3253cab2bb3Spatrick   // Store.
3263cab2bb3Spatrick   uptr i = add->size++;
3273cab2bb3Spatrick   Cell *c = &add->cells[i];
3283cab2bb3Spatrick   CHECK_EQ(atomic_load(&c->addr, memory_order_relaxed), 0);
3293cab2bb3Spatrick   h->addidx_ = i;
3303cab2bb3Spatrick   h->cell_ = c;
3313cab2bb3Spatrick  }
3323cab2bb3Spatrick 
3333cab2bb3Spatrick  template <typename T, uptr kSize>
release(Handle * h)334*810390e3Srobert  void AddrHashMap<T, kSize>::release(Handle *h)
335*810390e3Srobert      SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
3363cab2bb3Spatrick    if (!h->cell_)
3373cab2bb3Spatrick      return;
3383cab2bb3Spatrick    Bucket *b = h->bucket_;
3393cab2bb3Spatrick    Cell *c = h->cell_;
3403cab2bb3Spatrick    uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);
3413cab2bb3Spatrick    if (h->created_) {
3423cab2bb3Spatrick      // Denote completion of insertion.
3433cab2bb3Spatrick      CHECK_EQ(addr1, 0);
3443cab2bb3Spatrick      // After the following store, the element becomes available
3453cab2bb3Spatrick      // for lock-free reads.
3463cab2bb3Spatrick      atomic_store(&c->addr, h->addr_, memory_order_release);
3473cab2bb3Spatrick      b->mtx.Unlock();
3483cab2bb3Spatrick    } else if (h->remove_) {
3493cab2bb3Spatrick      // Denote that the cell is empty now.
3503cab2bb3Spatrick      CHECK_EQ(addr1, h->addr_);
3513cab2bb3Spatrick      atomic_store(&c->addr, 0, memory_order_release);
3523cab2bb3Spatrick      // See if we need to compact the bucket.
3533cab2bb3Spatrick      AddBucket *add = (AddBucket *)atomic_load(&b->add, memory_order_relaxed);
3543cab2bb3Spatrick      if (h->addidx_ == -1U) {
3553cab2bb3Spatrick        // Removed from embed array, move an add element into the freed cell.
3563cab2bb3Spatrick        if (add && add->size != 0) {
3573cab2bb3Spatrick          uptr last = --add->size;
3583cab2bb3Spatrick          Cell *c1 = &add->cells[last];
3593cab2bb3Spatrick          c->val = c1->val;
3603cab2bb3Spatrick          uptr addr1 = atomic_load(&c1->addr, memory_order_relaxed);
3613cab2bb3Spatrick          atomic_store(&c->addr, addr1, memory_order_release);
3623cab2bb3Spatrick          atomic_store(&c1->addr, 0, memory_order_release);
3633cab2bb3Spatrick        }
3643cab2bb3Spatrick      } else {
3653cab2bb3Spatrick        // Removed from add array, compact it.
3663cab2bb3Spatrick        uptr last = --add->size;
3673cab2bb3Spatrick        Cell *c1 = &add->cells[last];
3683cab2bb3Spatrick        if (c != c1) {
3693cab2bb3Spatrick          *c = *c1;
3703cab2bb3Spatrick          atomic_store(&c1->addr, 0, memory_order_relaxed);
3713cab2bb3Spatrick        }
3723cab2bb3Spatrick      }
3733cab2bb3Spatrick      if (add && add->size == 0) {
3743cab2bb3Spatrick        // FIXME(dvyukov): free add?
3753cab2bb3Spatrick      }
3763cab2bb3Spatrick      b->mtx.Unlock();
3773cab2bb3Spatrick    } else {
3783cab2bb3Spatrick      CHECK_EQ(addr1, h->addr_);
3793cab2bb3Spatrick      if (h->addidx_ != -1U)
3803cab2bb3Spatrick        b->mtx.ReadUnlock();
3813cab2bb3Spatrick    }
3823cab2bb3Spatrick  }
3833cab2bb3Spatrick 
3843cab2bb3Spatrick template<typename T, uptr kSize>
calcHash(uptr addr)3853cab2bb3Spatrick uptr AddrHashMap<T, kSize>::calcHash(uptr addr) {
3863cab2bb3Spatrick   addr += addr << 10;
3873cab2bb3Spatrick   addr ^= addr >> 6;
3883cab2bb3Spatrick   return addr % kSize;
3893cab2bb3Spatrick }
3903cab2bb3Spatrick 
3913cab2bb3Spatrick } // namespace __sanitizer
3923cab2bb3Spatrick 
3933cab2bb3Spatrick #endif // SANITIZER_ADDRHASHMAP_H
394