diff --git a/compiler-rt/lib/sanitizer_common/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/CMakeLists.txt --- a/compiler-rt/lib/sanitizer_common/CMakeLists.txt +++ b/compiler-rt/lib/sanitizer_common/CMakeLists.txt @@ -142,6 +142,7 @@ sanitizer_fuchsia.h sanitizer_getauxval.h sanitizer_hash.h + sanitizer_hashmap.h sanitizer_interceptors_ioctl_netbsd.inc sanitizer_interface_internal.h sanitizer_internal_defs.h diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_hashmap.h b/compiler-rt/lib/sanitizer_common/sanitizer_hashmap.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_hashmap.h @@ -0,0 +1,129 @@ +//===-- sanitizer_hashmap.h -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file is shared between sanitizers run-time libraries. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_HASHMAP_H +#define SANITIZER_HASHMAP_H + +#include "sanitizer_common.h" +#include "sanitizer_hash.h" + +namespace __sanitizer { + +// A single-threaded, dynamically-resized hashmap +// with open addressing and linear probing. +// Currently supports only POD K/V types. +template +class HashMap { + public: + HashMap() { + // We need at least kProbes elements, + // but otherwise use how many fits into a page. + Init(Max(kProbes, 1ul << MostSignificantSetBitIndex(GetPageSizeCached() / + sizeof(Elem)))); + } + ~HashMap() { UnmapOrDie(elems_, mem_size_); } + HashMap(const HashMap &) = delete; + HashMap &operator=(const HashMap &) = delete; + + // Inserts element (k, v) into the map if it's not in the map yet. + // Returns true if the element was inserted. + WARN_UNUSED_RESULT + bool Insert(K k, V v) { + if (Find(k)) + return false; + const uptr h = hash(k); + for (;;) { + for (uptr i = 0; i < kProbes; i++) { + auto &elem = elems_[index(h, i)]; + CHECK(!elem.filled || elem.k != k); + if (elem.filled) + continue; + elem = {k, v, true}; + return true; + } + Grow(); + } + } + + // Removes the element with key k from the map. + // Returns true if the element was found and removed. + // If v is passed, v is set to the element's value. + bool Remove(K k, V *v = nullptr) { return FindRemove(k, v, true); } + + // Searches for the element with key k. + // Returns true if the element was found. + // If v is passed, v is set to the element's value. + WARN_UNUSED_RESULT + bool Find(K k, V *v = nullptr) { return FindRemove(k, v, false); } + + private: + static constexpr uptr kProbes = 8; + + struct Elem { + K k; + V v; + bool filled; + }; + + Elem *elems_; + uptr size_; + uptr mem_size_; + + static uptr hash(K k) { + static_assert(sizeof(K) <= sizeof(u64), "bad key size"); + MurMur2Hash64Builder murmur2; + murmur2.add(static_cast(k)); + return murmur2.get(); + } + + uptr index(uptr h, uptr i) const { return (h + i) & (size_ - 1); } + + bool FindRemove(K k, V *v, bool remove) { + const uptr h = hash(k); + for (uptr i = 0; i < kProbes; i++) { + auto &elem = elems_[index(h, i)]; + if (!elem.filled || elem.k != k) + continue; + if (v) + *v = elem.v; + if (remove) + elem.filled = false; + return true; + } + return false; + } + + void Init(uptr size) { + // Index uses & to calculate modulo. + CHECK(IsPowerOfTwo(size)); + size_ = size; + mem_size_ = RoundUpTo(size_ * sizeof(Elem), GetPageSizeCached()); + elems_ = static_cast(MmapOrDie(mem_size_, "HashMap")); + } + + void Grow() { + Elem *elems = elems_; + uptr size = size_; + uptr mem_size = mem_size_; + Init(size * 2); + for (uptr i = 0; i < size; i++) { + Elem &elem = elems[i]; + if (elem.filled) + CHECK(Insert(elem.k, elem.v)); + } + UnmapOrDie(elems, mem_size); + } +}; + +} // namespace __sanitizer + +#endif // #ifndef SANITIZER_HASHMAP_H diff --git a/compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt --- a/compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt +++ b/compiler-rt/lib/sanitizer_common/tests/CMakeLists.txt @@ -21,6 +21,7 @@ sanitizer_flat_map_test.cpp sanitizer_format_interceptor_test.cpp sanitizer_hash_test.cpp + sanitizer_hashmap_test.cpp sanitizer_ioctl_test.cpp sanitizer_libc_test.cpp sanitizer_linux_test.cpp diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_hashmap_test.cpp b/compiler-rt/lib/sanitizer_common/tests/sanitizer_hashmap_test.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_hashmap_test.cpp @@ -0,0 +1,105 @@ +//===-- sanitizer_hashmap_test.cpp ----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file is a part of *Sanitizer runtime. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_hashmap.h" + +#include + +#include "gtest/gtest.h" + +namespace __sanitizer { + +TEST(HashMap, Basic) { + HashMap m; + EXPECT_FALSE(m.Find(0)); + EXPECT_FALSE(m.Find(1)); + EXPECT_FALSE(m.Remove(0)); + EXPECT_FALSE(m.Remove(1)); + + EXPECT_TRUE(m.Insert(1, 2)); + EXPECT_FALSE(m.Find(0)); + long v = 0; + EXPECT_TRUE(m.Find(1, &v)); + EXPECT_EQ(v, 2); + EXPECT_FALSE(m.Remove(0)); + v = 0; + EXPECT_TRUE(m.Remove(1, &v)); + EXPECT_EQ(v, 2); + EXPECT_FALSE(m.Find(1)); + + EXPECT_TRUE(m.Insert(10, 11)); + EXPECT_TRUE(m.Insert(20, 22)); + v = 0; + EXPECT_TRUE(m.Find(10, &v)); + EXPECT_EQ(v, 11); + v = 0; + EXPECT_TRUE(m.Find(20, &v)); + EXPECT_EQ(v, 22); + v = 0; + EXPECT_TRUE(m.Remove(10, &v)); + EXPECT_EQ(v, 11); + v = 0; + EXPECT_TRUE(m.Remove(20, &v)); + EXPECT_EQ(v, 22); +} + +TEST(HashMap, Grow) { + HashMap m; + const int kCount = 10000; + for (int i = 0; i < kCount; i++) EXPECT_TRUE(m.Insert(i, i + 100)); + EXPECT_FALSE(m.Find(kCount)); + for (int i = 0; i < kCount; i++) { + long v = 0; + EXPECT_TRUE(m.Find(i, &v)); + EXPECT_EQ(v, i + 100); + } + for (int i = 0; i < kCount; i++) { + long v = 0; + EXPECT_TRUE(m.Remove(i, &v)); + EXPECT_EQ(v, i + 100); + } + for (int i = 0; i < kCount; i++) EXPECT_FALSE(m.Find(i)); +} + +TEST(HashMap, Random) { + std::unordered_map m0; + HashMap m1; + unsigned seed = 0; + for (int i = 0; i < 10000; i++) { + const int k = rand_r(&seed) % 100; + switch (rand_r(&seed) % 3) { + case 0: { + const int v = rand_r(&seed); + auto r0 = m0.emplace(k, v); + bool r1 = m1.Insert(k, v); + EXPECT_EQ(r0.second, r1); + break; + } + case 1: { + auto r0 = m0.find(k); + int v1 = 0; + bool r1 = m1.Find(k, &v1); + EXPECT_EQ(r0 != m0.end(), r1); + if (r1) + EXPECT_EQ(r0->second, v1); + break; + } + case 2: { + auto r0 = m0.erase(k); + bool r1 = m1.Remove(k); + EXPECT_EQ(r0, r1); + break; + } + } + } +} + +} // namespace __sanitizer