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