diff --git a/Libraries/LibGC/CMakeLists.txt b/Libraries/LibGC/CMakeLists.txt index 86ec482dd9..10e65745bb 100644 --- a/Libraries/LibGC/CMakeLists.txt +++ b/Libraries/LibGC/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES Cell.cpp CellAllocator.cpp ConservativeHashMap.cpp + ConservativeHashTable.cpp ConservativeVector.cpp Root.cpp RootHashMap.cpp diff --git a/Libraries/LibGC/ConservativeHashTable.cpp b/Libraries/LibGC/ConservativeHashTable.cpp new file mode 100644 index 0000000000..d407decd3e --- /dev/null +++ b/Libraries/LibGC/ConservativeHashTable.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, Andreas Kling + * Copyright (c) 2026, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +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); +} + +} diff --git a/Libraries/LibGC/ConservativeHashTable.h b/Libraries/LibGC/ConservativeHashTable.h new file mode 100644 index 0000000000..8075d966e8 --- /dev/null +++ b/Libraries/LibGC/ConservativeHashTable.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, Andreas Kling + * Copyright (c) 2026, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace GC { + +class GC_API ConservativeHashTableBase { + AK_MAKE_NONCOPYABLE(ConservativeHashTableBase); + +public: + virtual void for_each_possible_value(AK::Function callback) const = 0; + +protected: + ConservativeHashTableBase(); + explicit ConservativeHashTableBase(Heap&); + ~ConservativeHashTableBase(); + + Heap* m_heap { nullptr }; + IntrusiveListNode m_list_node; + +public: + using List = IntrusiveList<&ConservativeHashTableBase::m_list_node>; +}; + +template, bool IsOrdered = false> +class GC_API ConservativeHashTable final + : public ConservativeHashTableBase + , public HashTable { + + using HashTableBase = HashTable; + +public: + ConservativeHashTable() + : ConservativeHashTableBase() + { + } + + ConservativeHashTable(ConservativeHashTable const& other) + : ConservativeHashTableBase(*other.m_heap) + , HashTableBase(static_cast(other)) + { + } + + ConservativeHashTable(ConservativeHashTable&& other) + : ConservativeHashTableBase(*other.m_heap) + , HashTableBase(move(static_cast(other))) + { + } + + ConservativeHashTable& operator=(ConservativeHashTable const& other) + { + if (&other == this) + return *this; + HashTableBase::operator=(static_cast(other)); + return *this; + } + + ~ConservativeHashTable() = default; + + virtual void for_each_possible_value(AK::Function 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> +using OrderedConservativeHashTable = ConservativeHashTable; + +} diff --git a/Libraries/LibGC/Forward.h b/Libraries/LibGC/Forward.h index 0741ea7220..5f87da0e78 100644 --- a/Libraries/LibGC/Forward.h +++ b/Libraries/LibGC/Forward.h @@ -43,6 +43,9 @@ class ConservativeVector; template class ConservativeHashMap; +template +class ConservativeHashTable; + template class HeapVector; diff --git a/Libraries/LibGC/Heap.cpp b/Libraries/LibGC/Heap.cpp index e7783ff1d7..dd49443054 100644 --- a/Libraries/LibGC/Heap.cpp +++ b/Libraries/LibGC/Heap.cpp @@ -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 #include #include +#include #include #include #include @@ -91,6 +92,8 @@ public: void did_create_conservative_hash_map(Badge, ConservativeHashMapBase&); void did_destroy_conservative_hash_map(Badge, ConservativeHashMapBase&); + void did_create_conservative_hash_table(Badge, ConservativeHashTableBase&); + void did_destroy_conservative_hash_table(Badge, ConservativeHashTableBase&); void did_create_conservative_vector(Badge, ConservativeVectorBase&); void did_destroy_conservative_vector(Badge, 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, 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& 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& vector) { VERIFY(!m_conservative_vectors.contains(vector)); diff --git a/Libraries/LibGC/HeapRoot.h b/Libraries/LibGC/HeapRoot.h index c1345c55b5..124224d790 100644 --- a/Libraries/LibGC/HeapRoot.h +++ b/Libraries/LibGC/HeapRoot.h @@ -15,6 +15,7 @@ namespace GC { struct GC_API HeapRoot { enum class Type { ConservativeHashMap, + ConservativeHashTable, ConservativeVector, HeapFunctionCapturedPointer, MustSurviveGC, diff --git a/Meta/Lagom/ClangPlugins/LibJSGCPluginAction.cpp b/Meta/Lagom/ClangPlugins/LibJSGCPluginAction.cpp index 06cde141f2..ff1f080555 100644 --- a/Meta/Lagom/ClangPlugins/LibJSGCPluginAction.cpp +++ b/Meta/Lagom/ClangPlugins/LibJSGCPluginAction.cpp @@ -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 get_all_qualified_types(clang::QualType cons "GC::RawRef", "GC::RootVector", "GC::ConservativeHashMap", + "GC::ConservativeHashTable", "GC::RootHashTable", "GC::Root", }; diff --git a/Tests/LibGC/TestGCContainers.cpp b/Tests/LibGC/TestGCContainers.cpp index 5eae30610f..b92765a76d 100644 --- a/Tests/LibGC/TestGCContainers.cpp +++ b/Tests/LibGC/TestGCContainers.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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(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> table; + + auto cell = heap.allocate(); + 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> table; + + auto cell = heap.allocate(); + 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> table; + + auto cell = heap.allocate(); + table.set(cell); + table.remove(cell); + + EXPECT(!possible_values_contain(table, cell.ptr())); +}