Path: blob/main/contrib/llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_addrhashmap.h
35233 views
//===-- sanitizer_addrhashmap.h ---------------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// Concurrent uptr->T hashmap.9//10//===----------------------------------------------------------------------===//1112#ifndef SANITIZER_ADDRHASHMAP_H13#define SANITIZER_ADDRHASHMAP_H1415#include "sanitizer_common.h"16#include "sanitizer_mutex.h"17#include "sanitizer_atomic.h"18#include "sanitizer_allocator_internal.h"1920namespace __sanitizer {2122// 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 data31// if h.created() then the element was just created, and the current thread32// has exclusive access to it33// otherwise the current thread has only read access to the data34// }35// {36// Map::Handle h(&m, addr, true);37// this will remove the data from the map in Handle dtor38// the current thread has exclusive access to the data39// if !h.exists() then the element never existed40// }41// {42// Map::Handle h(&m, addr, false, true);43// this will create a new element or return a handle to an existing element44// if !h.created() this thread does *not* have exclusive access to the data45// }46template<typename T, uptr kSize>47class AddrHashMap {48private:49struct Cell {50atomic_uintptr_t addr;51T val;52};5354struct AddBucket {55uptr cap;56uptr size;57Cell cells[1]; // variable len58};5960static const uptr kBucketSize = 3;6162struct Bucket {63Mutex mtx;64atomic_uintptr_t add;65Cell cells[kBucketSize];66};6768public:69AddrHashMap();7071class Handle {72public:73Handle(AddrHashMap<T, kSize> *map, uptr addr);74Handle(AddrHashMap<T, kSize> *map, uptr addr, bool remove);75Handle(AddrHashMap<T, kSize> *map, uptr addr, bool remove, bool create);7677~Handle();78T *operator->();79T &operator*();80const T &operator*() const;81bool created() const;82bool exists() const;8384private:85friend AddrHashMap<T, kSize>;86AddrHashMap<T, kSize> *map_;87Bucket *bucket_;88Cell *cell_;89uptr addr_;90uptr addidx_;91bool created_;92bool remove_;93bool create_;94};9596typedef void (*ForEachCallback)(const uptr key, const T &val, void *arg);97// ForEach acquires a lock on each bucket while iterating over98// elements. Note that this only ensures that the structure of the hashmap is99// unchanged, there may be a data race to the element itself.100void ForEach(ForEachCallback cb, void *arg);101102private:103friend class Handle;104Bucket *table_;105106void acquire(Handle *h);107void release(Handle *h);108uptr calcHash(uptr addr);109};110111template <typename T, uptr kSize>112void AddrHashMap<T, kSize>::ForEach(ForEachCallback cb, void *arg) {113for (uptr n = 0; n < kSize; n++) {114Bucket *bucket = &table_[n];115116ReadLock lock(&bucket->mtx);117118for (uptr i = 0; i < kBucketSize; i++) {119Cell *c = &bucket->cells[i];120uptr addr1 = atomic_load(&c->addr, memory_order_acquire);121if (addr1 != 0)122cb(addr1, c->val, arg);123}124125// Iterate over any additional cells.126if (AddBucket *add =127(AddBucket *)atomic_load(&bucket->add, memory_order_acquire)) {128for (uptr i = 0; i < add->size; i++) {129Cell *c = &add->cells[i];130uptr addr1 = atomic_load(&c->addr, memory_order_acquire);131if (addr1 != 0)132cb(addr1, c->val, arg);133}134}135}136}137138template<typename T, uptr kSize>139AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr) {140map_ = map;141addr_ = addr;142remove_ = false;143create_ = true;144map_->acquire(this);145}146147template<typename T, uptr kSize>148AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr,149bool remove) {150map_ = map;151addr_ = addr;152remove_ = remove;153create_ = true;154map_->acquire(this);155}156157template<typename T, uptr kSize>158AddrHashMap<T, kSize>::Handle::Handle(AddrHashMap<T, kSize> *map, uptr addr,159bool remove, bool create) {160map_ = map;161addr_ = addr;162remove_ = remove;163create_ = create;164map_->acquire(this);165}166167template<typename T, uptr kSize>168AddrHashMap<T, kSize>::Handle::~Handle() {169map_->release(this);170}171172template <typename T, uptr kSize>173T *AddrHashMap<T, kSize>::Handle::operator->() {174return &cell_->val;175}176177template <typename T, uptr kSize>178const T &AddrHashMap<T, kSize>::Handle::operator*() const {179return cell_->val;180}181182template <typename T, uptr kSize>183T &AddrHashMap<T, kSize>::Handle::operator*() {184return cell_->val;185}186187template<typename T, uptr kSize>188bool AddrHashMap<T, kSize>::Handle::created() const {189return created_;190}191192template<typename T, uptr kSize>193bool AddrHashMap<T, kSize>::Handle::exists() const {194return cell_ != nullptr;195}196197template<typename T, uptr kSize>198AddrHashMap<T, kSize>::AddrHashMap() {199table_ = (Bucket*)MmapOrDie(kSize * sizeof(table_[0]), "AddrHashMap");200}201202template <typename T, uptr kSize>203void AddrHashMap<T, kSize>::acquire(Handle *h)204SANITIZER_NO_THREAD_SAFETY_ANALYSIS {205uptr addr = h->addr_;206uptr hash = calcHash(addr);207Bucket *b = &table_[hash];208209h->created_ = false;210h->addidx_ = -1U;211h->bucket_ = b;212h->cell_ = nullptr;213214// If we want to remove the element, we need exclusive access to the bucket,215// so skip the lock-free phase.216if (h->remove_)217goto locked;218219retry:220// First try to find an existing element w/o read mutex.221CHECK(!h->remove_);222// Check the embed cells.223for (uptr i = 0; i < kBucketSize; i++) {224Cell *c = &b->cells[i];225uptr addr1 = atomic_load(&c->addr, memory_order_acquire);226if (addr1 == addr) {227h->cell_ = c;228return;229}230}231232// Check the add cells with read lock.233if (atomic_load(&b->add, memory_order_relaxed)) {234b->mtx.ReadLock();235AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);236for (uptr i = 0; i < add->size; i++) {237Cell *c = &add->cells[i];238uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);239if (addr1 == addr) {240h->addidx_ = i;241h->cell_ = c;242return;243}244}245b->mtx.ReadUnlock();246}247248locked:249// Re-check existence under write lock.250// Embed cells.251b->mtx.Lock();252for (uptr i = 0; i < kBucketSize; i++) {253Cell *c = &b->cells[i];254uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);255if (addr1 == addr) {256if (h->remove_) {257h->cell_ = c;258return;259}260b->mtx.Unlock();261goto retry;262}263}264265// Add cells.266AddBucket *add = (AddBucket*)atomic_load(&b->add, memory_order_relaxed);267if (add) {268for (uptr i = 0; i < add->size; i++) {269Cell *c = &add->cells[i];270uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);271if (addr1 == addr) {272if (h->remove_) {273h->addidx_ = i;274h->cell_ = c;275return;276}277b->mtx.Unlock();278goto retry;279}280}281}282283// The element does not exist, no need to create it if we want to remove.284if (h->remove_ || !h->create_) {285b->mtx.Unlock();286return;287}288289// Now try to create it under the mutex.290h->created_ = true;291// See if we have a free embed cell.292for (uptr i = 0; i < kBucketSize; i++) {293Cell *c = &b->cells[i];294uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);295if (addr1 == 0) {296h->cell_ = c;297return;298}299}300301// Store in the add cells.302if (!add) {303// Allocate a new add array.304const uptr kInitSize = 64;305add = (AddBucket*)InternalAlloc(kInitSize);306internal_memset(add, 0, kInitSize);307add->cap = (kInitSize - sizeof(*add)) / sizeof(add->cells[0]) + 1;308add->size = 0;309atomic_store(&b->add, (uptr)add, memory_order_relaxed);310}311if (add->size == add->cap) {312// Grow existing add array.313uptr oldsize = sizeof(*add) + (add->cap - 1) * sizeof(add->cells[0]);314uptr newsize = oldsize * 2;315AddBucket *add1 = (AddBucket*)InternalAlloc(newsize);316internal_memset(add1, 0, newsize);317add1->cap = (newsize - sizeof(*add)) / sizeof(add->cells[0]) + 1;318add1->size = add->size;319internal_memcpy(add1->cells, add->cells, add->size * sizeof(add->cells[0]));320InternalFree(add);321atomic_store(&b->add, (uptr)add1, memory_order_relaxed);322add = add1;323}324// Store.325uptr i = add->size++;326Cell *c = &add->cells[i];327CHECK_EQ(atomic_load(&c->addr, memory_order_relaxed), 0);328h->addidx_ = i;329h->cell_ = c;330}331332template <typename T, uptr kSize>333void AddrHashMap<T, kSize>::release(Handle *h)334SANITIZER_NO_THREAD_SAFETY_ANALYSIS {335if (!h->cell_)336return;337Bucket *b = h->bucket_;338Cell *c = h->cell_;339uptr addr1 = atomic_load(&c->addr, memory_order_relaxed);340if (h->created_) {341// Denote completion of insertion.342CHECK_EQ(addr1, 0);343// After the following store, the element becomes available344// for lock-free reads.345atomic_store(&c->addr, h->addr_, memory_order_release);346b->mtx.Unlock();347} else if (h->remove_) {348// Denote that the cell is empty now.349CHECK_EQ(addr1, h->addr_);350atomic_store(&c->addr, 0, memory_order_release);351// See if we need to compact the bucket.352AddBucket *add = (AddBucket *)atomic_load(&b->add, memory_order_relaxed);353if (h->addidx_ == -1U) {354// Removed from embed array, move an add element into the freed cell.355if (add && add->size != 0) {356uptr last = --add->size;357Cell *c1 = &add->cells[last];358c->val = c1->val;359uptr addr1 = atomic_load(&c1->addr, memory_order_relaxed);360atomic_store(&c->addr, addr1, memory_order_release);361atomic_store(&c1->addr, 0, memory_order_release);362}363} else {364// Removed from add array, compact it.365uptr last = --add->size;366Cell *c1 = &add->cells[last];367if (c != c1) {368*c = *c1;369atomic_store(&c1->addr, 0, memory_order_relaxed);370}371}372if (add && add->size == 0) {373// FIXME(dvyukov): free add?374}375b->mtx.Unlock();376} else {377CHECK_EQ(addr1, h->addr_);378if (h->addidx_ != -1U)379b->mtx.ReadUnlock();380}381}382383template<typename T, uptr kSize>384uptr AddrHashMap<T, kSize>::calcHash(uptr addr) {385addr += addr << 10;386addr ^= addr >> 6;387return addr % kSize;388}389390} // namespace __sanitizer391392#endif // SANITIZER_ADDRHASHMAP_H393394395