LibGC: Introduce ConservativeHashMap
This commit is contained in:
parent
883199cb39
commit
f955d4e8a4
9 changed files with 220 additions and 1 deletions
|
|
@ -2,6 +2,7 @@ set(SOURCES
|
|||
BlockAllocator.cpp
|
||||
Cell.cpp
|
||||
CellAllocator.cpp
|
||||
ConservativeHashMap.cpp
|
||||
ConservativeVector.cpp
|
||||
Root.cpp
|
||||
RootHashMap.cpp
|
||||
|
|
|
|||
28
Libraries/LibGC/ConservativeHashMap.cpp
Normal file
28
Libraries/LibGC/ConservativeHashMap.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Luke Wilde <luke@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/ConservativeHashMap.h>
|
||||
#include <LibGC/Heap.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
ConservativeHashMapBase::ConservativeHashMapBase()
|
||||
: ConservativeHashMapBase(Heap::the())
|
||||
{
|
||||
}
|
||||
|
||||
ConservativeHashMapBase::ConservativeHashMapBase(Heap& heap)
|
||||
: m_heap(&heap)
|
||||
{
|
||||
m_heap->did_create_conservative_hash_map({}, *this);
|
||||
}
|
||||
|
||||
ConservativeHashMapBase::~ConservativeHashMapBase()
|
||||
{
|
||||
m_heap->did_destroy_conservative_hash_map({}, *this);
|
||||
}
|
||||
|
||||
}
|
||||
88
Libraries/LibGC/ConservativeHashMap.h
Normal file
88
Libraries/LibGC/ConservativeHashMap.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Luke Wilde <luke@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/IntrusiveList.h>
|
||||
#include <LibGC/Forward.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
class GC_API ConservativeHashMapBase {
|
||||
AK_MAKE_NONCOPYABLE(ConservativeHashMapBase);
|
||||
|
||||
public:
|
||||
virtual void for_each_possible_value(AK::Function<void(FlatPtr)> callback) const = 0;
|
||||
|
||||
protected:
|
||||
ConservativeHashMapBase();
|
||||
explicit ConservativeHashMapBase(Heap&);
|
||||
~ConservativeHashMapBase();
|
||||
|
||||
Heap* m_heap { nullptr };
|
||||
IntrusiveListNode<ConservativeHashMapBase> m_list_node;
|
||||
|
||||
public:
|
||||
using List = IntrusiveList<&ConservativeHashMapBase::m_list_node>;
|
||||
};
|
||||
|
||||
template<typename K, typename V, typename KeyTraits = Traits<K>, typename ValueTraits = Traits<V>, bool IsOrdered = false>
|
||||
class GC_API ConservativeHashMap final
|
||||
: public ConservativeHashMapBase
|
||||
, public HashMap<K, V, KeyTraits, ValueTraits, IsOrdered> {
|
||||
|
||||
using HashMapBase = HashMap<K, V, KeyTraits, ValueTraits, IsOrdered>;
|
||||
|
||||
public:
|
||||
ConservativeHashMap()
|
||||
: ConservativeHashMapBase()
|
||||
{
|
||||
}
|
||||
|
||||
ConservativeHashMap(ConservativeHashMap const& other)
|
||||
: ConservativeHashMapBase(*other.m_heap)
|
||||
, HashMapBase(static_cast<HashMapBase const&>(other))
|
||||
{
|
||||
}
|
||||
|
||||
ConservativeHashMap(ConservativeHashMap&& other)
|
||||
: ConservativeHashMapBase(*other.m_heap)
|
||||
, HashMapBase(move(static_cast<HashMapBase&>(other)))
|
||||
{
|
||||
}
|
||||
|
||||
ConservativeHashMap& operator=(ConservativeHashMap const& other)
|
||||
{
|
||||
if (&other == this)
|
||||
return *this;
|
||||
HashMapBase::operator=(static_cast<HashMapBase const&>(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ConservativeHashMap() = default;
|
||||
|
||||
virtual void for_each_possible_value(AK::Function<void(FlatPtr)> callback) const override
|
||||
{
|
||||
auto scan_bytes = [&](ReadonlyBytes bytes) {
|
||||
for (size_t i = 0; i + sizeof(FlatPtr) <= bytes.size(); i += sizeof(FlatPtr)) {
|
||||
FlatPtr value;
|
||||
memcpy(&value, bytes.offset(i), sizeof(FlatPtr));
|
||||
callback(value);
|
||||
}
|
||||
};
|
||||
for (auto& [key, value] : *this) {
|
||||
scan_bytes({ &key, sizeof(K) });
|
||||
scan_bytes({ &value, sizeof(V) });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename K, typename V, typename KeyTraits = Traits<K>, typename ValueTraits = Traits<V>>
|
||||
using OrderedConservativeHashMap = ConservativeHashMap<K, V, KeyTraits, ValueTraits, true>;
|
||||
|
||||
}
|
||||
|
|
@ -34,6 +34,9 @@ class Root;
|
|||
template<class T, size_t inline_capacity = 0>
|
||||
class ConservativeVector;
|
||||
|
||||
template<typename K, typename V, typename KeyTraits, typename ValueTraits, bool IsOrdered>
|
||||
class ConservativeHashMap;
|
||||
|
||||
template<class T>
|
||||
class HeapVector;
|
||||
|
||||
|
|
|
|||
|
|
@ -476,6 +476,9 @@ public:
|
|||
auto type = it.value.root_origin->type;
|
||||
auto const* location = it.value.root_origin->location;
|
||||
switch (type) {
|
||||
case HeapRoot::Type::ConservativeHashMap:
|
||||
node.set("root"sv, "ConservativeHashMap"sv);
|
||||
break;
|
||||
case HeapRoot::Type::ConservativeVector:
|
||||
node.set("root"sv, "ConservativeVector"sv);
|
||||
break;
|
||||
|
|
@ -968,6 +971,12 @@ NO_SANITIZE_ADDRESS void Heap::gather_conservative_roots(HashMap<Cell*, HeapRoot
|
|||
}
|
||||
}
|
||||
|
||||
for (auto& hash_map : m_conservative_hash_maps) {
|
||||
hash_map.for_each_possible_value([&](FlatPtr possible_value) {
|
||||
add_possible_value(possible_pointers, possible_value, HeapRoot { .type = HeapRoot::Type::ConservativeHashMap }, min_block_address, max_block_address);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
ScopedPhaseTimer timer { g_recording_phase_timings, g_phase_timings.conservative_cell_lookup_us };
|
||||
for_each_cell_among_possible_pointers(m_live_heap_blocks, possible_pointers, [&](Cell* cell, FlatPtr possible_pointer) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <LibCore/Forward.h>
|
||||
#include <LibGC/Cell.h>
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibGC/ConservativeHashMap.h>
|
||||
#include <LibGC/ConservativeVector.h>
|
||||
#include <LibGC/Forward.h>
|
||||
#include <LibGC/HeapRoot.h>
|
||||
|
|
@ -87,6 +88,8 @@ public:
|
|||
void did_create_root_hash_table(Badge<RootHashTableBase>, RootHashTableBase&);
|
||||
void did_destroy_root_hash_table(Badge<RootHashTableBase>, RootHashTableBase&);
|
||||
|
||||
void did_create_conservative_hash_map(Badge<ConservativeHashMapBase>, ConservativeHashMapBase&);
|
||||
void did_destroy_conservative_hash_map(Badge<ConservativeHashMapBase>, ConservativeHashMapBase&);
|
||||
void did_create_conservative_vector(Badge<ConservativeVectorBase>, ConservativeVectorBase&);
|
||||
void did_destroy_conservative_vector(Badge<ConservativeVectorBase>, ConservativeVectorBase&);
|
||||
|
||||
|
|
@ -180,6 +183,7 @@ private:
|
|||
RootVectorBase::List m_root_vectors;
|
||||
RootHashMapBase::List m_root_hash_maps;
|
||||
RootHashTableBase::List m_root_hash_tables;
|
||||
ConservativeHashMapBase::List m_conservative_hash_maps;
|
||||
ConservativeVectorBase::List m_conservative_vectors;
|
||||
WeakContainer::List m_weak_containers;
|
||||
|
||||
|
|
@ -260,6 +264,18 @@ inline void Heap::did_destroy_root_hash_table(Badge<RootHashTableBase>, RootHash
|
|||
m_root_hash_tables.remove(hash_table);
|
||||
}
|
||||
|
||||
inline void Heap::did_create_conservative_hash_map(Badge<ConservativeHashMapBase>, ConservativeHashMapBase& hash_map)
|
||||
{
|
||||
VERIFY(!m_conservative_hash_maps.contains(hash_map));
|
||||
m_conservative_hash_maps.append(hash_map);
|
||||
}
|
||||
|
||||
inline void Heap::did_destroy_conservative_hash_map(Badge<ConservativeHashMapBase>, ConservativeHashMapBase& hash_map)
|
||||
{
|
||||
VERIFY(m_conservative_hash_maps.contains(hash_map));
|
||||
m_conservative_hash_maps.remove(hash_map);
|
||||
}
|
||||
|
||||
inline void Heap::did_create_conservative_vector(Badge<ConservativeVectorBase>, ConservativeVectorBase& vector)
|
||||
{
|
||||
VERIFY(!m_conservative_vectors.contains(vector));
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace GC {
|
|||
|
||||
struct GC_API HeapRoot {
|
||||
enum class Type {
|
||||
ConservativeHashMap,
|
||||
ConservativeVector,
|
||||
HeapFunctionCapturedPointer,
|
||||
MustSurviveGC,
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ static ContainsGCPtrResult record_contains_gc_ptr(clang::CXXRecordDecl const* re
|
|||
"GC::Heap",
|
||||
"GC::MarkedVector",
|
||||
"GC::ConservativeVector",
|
||||
"GC::ConservativeHashMap",
|
||||
"GC::ConservativeHashMapBase",
|
||||
};
|
||||
if (gc_infrastructure_types.contains(qualified_name)) {
|
||||
s_contains_gc_ptr_cache[record] = ContainsGCPtrResult::No;
|
||||
|
|
@ -191,7 +193,7 @@ static ContainsGCPtrResult type_contains_gc_ptr(clang::QualType const& type, std
|
|||
return ContainsGCPtrResult::No;
|
||||
|
||||
// Root types handle their own visiting
|
||||
if (template_name == "GC::Root" || template_name == "GC::RootVector" || template_name == "GC::RootHashTable")
|
||||
if (template_name == "GC::Root" || template_name == "GC::RootVector" || template_name == "GC::ConservativeHashMap" || template_name == "GC::RootHashTable")
|
||||
return ContainsGCPtrResult::No;
|
||||
|
||||
// Check template arguments recursively for containers
|
||||
|
|
@ -231,6 +233,7 @@ static std::vector<clang::QualType> get_all_qualified_types(clang::QualType cons
|
|||
"GC::RawPtr",
|
||||
"GC::RawRef",
|
||||
"GC::RootVector",
|
||||
"GC::ConservativeHashMap",
|
||||
"GC::RootHashTable",
|
||||
"GC::Root",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <AK/NeverDestroyed.h>
|
||||
#include <LibGC/Cell.h>
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibGC/ConservativeHashMap.h>
|
||||
#include <LibGC/ConservativeVector.h>
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibGC/HeapHashTable.h>
|
||||
|
|
@ -57,6 +58,17 @@ static bool possible_values_contain(GC::ConservativeVectorBase const& container,
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool possible_values_contain(GC::ConservativeHashMapBase const& container, GC::Cell* cell)
|
||||
{
|
||||
bool found = false;
|
||||
auto target = bit_cast<FlatPtr>(cell);
|
||||
container.for_each_possible_value([&](FlatPtr value) {
|
||||
if (value == target)
|
||||
found = true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
TEST_CASE(root_vector_reports_roots)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
|
|
@ -254,3 +266,61 @@ TEST_CASE(empty_containers_report_no_roots)
|
|||
|
||||
EXPECT_EQ(roots.size(), 0u);
|
||||
}
|
||||
|
||||
TEST_CASE(conservative_hash_map_reports_possible_values)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
GC::ConservativeHashMap<int, GC::Ref<TestCell>> map;
|
||||
|
||||
auto cell = heap.allocate<TestCell>();
|
||||
map.set(42, cell);
|
||||
|
||||
EXPECT(possible_values_contain(map, cell.ptr()));
|
||||
}
|
||||
|
||||
TEST_CASE(cleared_conservative_hash_map_reports_no_stale_values)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
GC::ConservativeHashMap<int, GC::Ref<TestCell>> map;
|
||||
|
||||
auto cell = heap.allocate<TestCell>();
|
||||
map.set(42, cell);
|
||||
map.clear();
|
||||
|
||||
EXPECT(!possible_values_contain(map, cell.ptr()));
|
||||
}
|
||||
|
||||
TEST_CASE(removed_conservative_hash_map_entry_not_reported)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
GC::ConservativeHashMap<int, GC::Ref<TestCell>> map;
|
||||
|
||||
auto cell = heap.allocate<TestCell>();
|
||||
map.set(42, cell);
|
||||
map.remove(42);
|
||||
|
||||
EXPECT(!possible_values_contain(map, cell.ptr()));
|
||||
}
|
||||
|
||||
TEST_CASE(conservative_hash_map_key_reports_possible_values)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
GC::ConservativeHashMap<GC::Ref<TestCell>, int> map;
|
||||
|
||||
auto cell = heap.allocate<TestCell>();
|
||||
map.set(cell, 42);
|
||||
|
||||
EXPECT(possible_values_contain(map, cell.ptr()));
|
||||
}
|
||||
|
||||
TEST_CASE(removed_conservative_hash_map_key_not_reported)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
GC::ConservativeHashMap<GC::Ref<TestCell>, int> map;
|
||||
|
||||
auto cell = heap.allocate<TestCell>();
|
||||
map.set(cell, 42);
|
||||
map.remove(cell);
|
||||
|
||||
EXPECT(!possible_values_contain(map, cell.ptr()));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue