LibGC: Introduce ConservativeHashTable
This commit is contained in:
parent
520a7c8ebd
commit
85fd3bb7b0
9 changed files with 196 additions and 1 deletions
|
|
@ -3,6 +3,7 @@ set(SOURCES
|
|||
Cell.cpp
|
||||
CellAllocator.cpp
|
||||
ConservativeHashMap.cpp
|
||||
ConservativeHashTable.cpp
|
||||
ConservativeVector.cpp
|
||||
Root.cpp
|
||||
RootHashMap.cpp
|
||||
|
|
|
|||
29
Libraries/LibGC/ConservativeHashTable.cpp
Normal file
29
Libraries/LibGC/ConservativeHashTable.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2026, Luke Wilde <luke@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/ConservativeHashTable.h>
|
||||
#include <LibGC/Heap.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
ConservativeHashTableBase::ConservativeHashTableBase()
|
||||
: ConservativeHashTableBase(Heap::the())
|
||||
{
|
||||
}
|
||||
|
||||
ConservativeHashTableBase::ConservativeHashTableBase(Heap& heap)
|
||||
: m_heap(&heap)
|
||||
{
|
||||
m_heap->did_create_conservative_hash_table({}, *this);
|
||||
}
|
||||
|
||||
ConservativeHashTableBase::~ConservativeHashTableBase()
|
||||
{
|
||||
m_heap->did_destroy_conservative_hash_table({}, *this);
|
||||
}
|
||||
|
||||
}
|
||||
86
Libraries/LibGC/ConservativeHashTable.h
Normal file
86
Libraries/LibGC/ConservativeHashTable.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2026, Luke Wilde <luke@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/IntrusiveList.h>
|
||||
#include <LibGC/Forward.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
class GC_API ConservativeHashTableBase {
|
||||
AK_MAKE_NONCOPYABLE(ConservativeHashTableBase);
|
||||
|
||||
public:
|
||||
virtual void for_each_possible_value(AK::Function<void(FlatPtr)> callback) const = 0;
|
||||
|
||||
protected:
|
||||
ConservativeHashTableBase();
|
||||
explicit ConservativeHashTableBase(Heap&);
|
||||
~ConservativeHashTableBase();
|
||||
|
||||
Heap* m_heap { nullptr };
|
||||
IntrusiveListNode<ConservativeHashTableBase> m_list_node;
|
||||
|
||||
public:
|
||||
using List = IntrusiveList<&ConservativeHashTableBase::m_list_node>;
|
||||
};
|
||||
|
||||
template<typename T, typename TraitsForT = Traits<T>, bool IsOrdered = false>
|
||||
class GC_API ConservativeHashTable final
|
||||
: public ConservativeHashTableBase
|
||||
, public HashTable<T, TraitsForT, IsOrdered> {
|
||||
|
||||
using HashTableBase = HashTable<T, TraitsForT, IsOrdered>;
|
||||
|
||||
public:
|
||||
ConservativeHashTable()
|
||||
: ConservativeHashTableBase()
|
||||
{
|
||||
}
|
||||
|
||||
ConservativeHashTable(ConservativeHashTable const& other)
|
||||
: ConservativeHashTableBase(*other.m_heap)
|
||||
, HashTableBase(static_cast<HashTableBase const&>(other))
|
||||
{
|
||||
}
|
||||
|
||||
ConservativeHashTable(ConservativeHashTable&& other)
|
||||
: ConservativeHashTableBase(*other.m_heap)
|
||||
, HashTableBase(move(static_cast<HashTableBase&>(other)))
|
||||
{
|
||||
}
|
||||
|
||||
ConservativeHashTable& operator=(ConservativeHashTable const& other)
|
||||
{
|
||||
if (&other == this)
|
||||
return *this;
|
||||
HashTableBase::operator=(static_cast<HashTableBase const&>(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ConservativeHashTable() = default;
|
||||
|
||||
virtual void for_each_possible_value(AK::Function<void(FlatPtr)> callback) const override
|
||||
{
|
||||
for (auto& entry : *this) {
|
||||
auto entry_bytes = ReadonlyBytes { &entry, sizeof(T) };
|
||||
for (size_t i = 0; i + sizeof(FlatPtr) <= entry_bytes.size(); i += sizeof(FlatPtr)) {
|
||||
FlatPtr value;
|
||||
memcpy(&value, entry_bytes.offset(i), sizeof(FlatPtr));
|
||||
callback(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename TraitsForT = Traits<T>>
|
||||
using OrderedConservativeHashTable = ConservativeHashTable<T, TraitsForT, true>;
|
||||
|
||||
}
|
||||
|
|
@ -43,6 +43,9 @@ class ConservativeVector;
|
|||
template<typename K, typename V, typename KeyTraits, typename ValueTraits, bool IsOrdered>
|
||||
class ConservativeHashMap;
|
||||
|
||||
template<typename T, typename TraitsForT, bool IsOrdered>
|
||||
class ConservativeHashTable;
|
||||
|
||||
template<class T>
|
||||
class HeapVector;
|
||||
|
||||
|
|
|
|||
|
|
@ -479,6 +479,9 @@ public:
|
|||
case HeapRoot::Type::ConservativeHashMap:
|
||||
node.set("root"sv, "ConservativeHashMap"sv);
|
||||
break;
|
||||
case HeapRoot::Type::ConservativeHashTable:
|
||||
node.set("root"sv, "ConservativeHashTable"sv);
|
||||
break;
|
||||
case HeapRoot::Type::ConservativeVector:
|
||||
node.set("root"sv, "ConservativeVector"sv);
|
||||
break;
|
||||
|
|
@ -977,6 +980,12 @@ NO_SANITIZE_ADDRESS void Heap::gather_conservative_roots(HashMap<Cell*, HeapRoot
|
|||
});
|
||||
}
|
||||
|
||||
for (auto& hash_table : m_conservative_hash_tables) {
|
||||
hash_table.for_each_possible_value([&](FlatPtr possible_value) {
|
||||
add_possible_value(possible_pointers, possible_value, HeapRoot { .type = HeapRoot::Type::ConservativeHashTable }, 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) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <LibGC/Cell.h>
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibGC/ConservativeHashMap.h>
|
||||
#include <LibGC/ConservativeHashTable.h>
|
||||
#include <LibGC/ConservativeVector.h>
|
||||
#include <LibGC/Forward.h>
|
||||
#include <LibGC/HeapRoot.h>
|
||||
|
|
@ -91,6 +92,8 @@ public:
|
|||
|
||||
void did_create_conservative_hash_map(Badge<ConservativeHashMapBase>, ConservativeHashMapBase&);
|
||||
void did_destroy_conservative_hash_map(Badge<ConservativeHashMapBase>, ConservativeHashMapBase&);
|
||||
void did_create_conservative_hash_table(Badge<ConservativeHashTableBase>, ConservativeHashTableBase&);
|
||||
void did_destroy_conservative_hash_table(Badge<ConservativeHashTableBase>, ConservativeHashTableBase&);
|
||||
void did_create_conservative_vector(Badge<ConservativeVectorBase>, ConservativeVectorBase&);
|
||||
void did_destroy_conservative_vector(Badge<ConservativeVectorBase>, ConservativeVectorBase&);
|
||||
|
||||
|
|
@ -185,6 +188,7 @@ private:
|
|||
RootHashMapBase::List m_root_hash_maps;
|
||||
RootHashTableBase::List m_root_hash_tables;
|
||||
ConservativeHashMapBase::List m_conservative_hash_maps;
|
||||
ConservativeHashTableBase::List m_conservative_hash_tables;
|
||||
ConservativeVectorBase::List m_conservative_vectors;
|
||||
WeakContainer::List m_weak_containers;
|
||||
|
||||
|
|
@ -277,6 +281,18 @@ inline void Heap::did_destroy_conservative_hash_map(Badge<ConservativeHashMapBas
|
|||
m_conservative_hash_maps.remove(hash_map);
|
||||
}
|
||||
|
||||
inline void Heap::did_create_conservative_hash_table(Badge<ConservativeHashTableBase>, ConservativeHashTableBase& hash_table)
|
||||
{
|
||||
VERIFY(!m_conservative_hash_tables.contains(hash_table));
|
||||
m_conservative_hash_tables.append(hash_table);
|
||||
}
|
||||
|
||||
inline void Heap::did_destroy_conservative_hash_table(Badge<ConservativeHashTableBase>, ConservativeHashTableBase& hash_table)
|
||||
{
|
||||
VERIFY(m_conservative_hash_tables.contains(hash_table));
|
||||
m_conservative_hash_tables.remove(hash_table);
|
||||
}
|
||||
|
||||
inline void Heap::did_create_conservative_vector(Badge<ConservativeVectorBase>, ConservativeVectorBase& vector)
|
||||
{
|
||||
VERIFY(!m_conservative_vectors.contains(vector));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace GC {
|
|||
struct GC_API HeapRoot {
|
||||
enum class Type {
|
||||
ConservativeHashMap,
|
||||
ConservativeHashTable,
|
||||
ConservativeVector,
|
||||
HeapFunctionCapturedPointer,
|
||||
MustSurviveGC,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ static ContainsGCPtrResult record_contains_gc_ptr(clang::CXXRecordDecl const* re
|
|||
"GC::ConservativeVector",
|
||||
"GC::ConservativeHashMap",
|
||||
"GC::ConservativeHashMapBase",
|
||||
"GC::ConservativeHashTable",
|
||||
"GC::ConservativeHashTableBase",
|
||||
};
|
||||
if (gc_infrastructure_types.contains(qualified_name)) {
|
||||
s_contains_gc_ptr_cache[record] = ContainsGCPtrResult::No;
|
||||
|
|
@ -193,7 +195,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::ConservativeHashMap" || template_name == "GC::RootHashTable")
|
||||
if (template_name == "GC::Root" || template_name == "GC::RootVector" || template_name == "GC::ConservativeHashMap" || template_name == "GC::ConservativeHashTable" || template_name == "GC::RootHashTable")
|
||||
return ContainsGCPtrResult::No;
|
||||
|
||||
// Check template arguments recursively for containers
|
||||
|
|
@ -234,6 +236,7 @@ static std::vector<clang::QualType> get_all_qualified_types(clang::QualType cons
|
|||
"GC::RawRef",
|
||||
"GC::RootVector",
|
||||
"GC::ConservativeHashMap",
|
||||
"GC::ConservativeHashTable",
|
||||
"GC::RootHashTable",
|
||||
"GC::Root",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <LibGC/Cell.h>
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibGC/ConservativeHashMap.h>
|
||||
#include <LibGC/ConservativeHashTable.h>
|
||||
#include <LibGC/ConservativeVector.h>
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibGC/HeapHashTable.h>
|
||||
|
|
@ -70,6 +71,17 @@ static bool possible_values_contain(GC::ConservativeHashMapBase const& container
|
|||
return found;
|
||||
}
|
||||
|
||||
static bool possible_values_contain(GC::ConservativeHashTableBase 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();
|
||||
|
|
@ -476,3 +488,38 @@ TEST_CASE(weak_hash_map_ensure_handles_non_cell_value)
|
|||
default_value = 13;
|
||||
EXPECT_EQ(map.get(*default_key).value(), 13);
|
||||
}
|
||||
|
||||
TEST_CASE(conservative_hash_table_reports_possible_values)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
GC::ConservativeHashTable<GC::Ref<TestCell>> table;
|
||||
|
||||
auto cell = heap.allocate<TestCell>();
|
||||
table.set(cell);
|
||||
|
||||
EXPECT(possible_values_contain(table, cell.ptr()));
|
||||
}
|
||||
|
||||
TEST_CASE(cleared_conservative_hash_table_reports_no_stale_values)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
GC::ConservativeHashTable<GC::Ref<TestCell>> table;
|
||||
|
||||
auto cell = heap.allocate<TestCell>();
|
||||
table.set(cell);
|
||||
table.clear();
|
||||
|
||||
EXPECT(!possible_values_contain(table, cell.ptr()));
|
||||
}
|
||||
|
||||
TEST_CASE(removed_conservative_hash_table_entry_not_reported)
|
||||
{
|
||||
auto& heap = test_heap();
|
||||
GC::ConservativeHashTable<GC::Ref<TestCell>> table;
|
||||
|
||||
auto cell = heap.allocate<TestCell>();
|
||||
table.set(cell);
|
||||
table.remove(cell);
|
||||
|
||||
EXPECT(!possible_values_contain(table, cell.ptr()));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue