LibGC: Introduce ConservativeHashTable

This commit is contained in:
Luke Wilde 2026-04-09 14:59:02 +01:00 committed by Shannon Booth
parent 520a7c8ebd
commit 85fd3bb7b0
9 changed files with 196 additions and 1 deletions

View file

@ -3,6 +3,7 @@ set(SOURCES
Cell.cpp
CellAllocator.cpp
ConservativeHashMap.cpp
ConservativeHashTable.cpp
ConservativeVector.cpp
Root.cpp
RootHashMap.cpp

View 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);
}
}

View 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>;
}

View file

@ -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;

View file

@ -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) {

View file

@ -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));

View file

@ -15,6 +15,7 @@ namespace GC {
struct GC_API HeapRoot {
enum class Type {
ConservativeHashMap,
ConservativeHashTable,
ConservativeVector,
HeapFunctionCapturedPointer,
MustSurviveGC,

View file

@ -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",
};

View file

@ -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()));
}