diff --git a/Libraries/LibGC/CMakeLists.txt b/Libraries/LibGC/CMakeLists.txt index 26cbadd950..86ec482dd9 100644 --- a/Libraries/LibGC/CMakeLists.txt +++ b/Libraries/LibGC/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES BlockAllocator.cpp Cell.cpp CellAllocator.cpp + ConservativeHashMap.cpp ConservativeVector.cpp Root.cpp RootHashMap.cpp diff --git a/Libraries/LibGC/ConservativeHashMap.cpp b/Libraries/LibGC/ConservativeHashMap.cpp new file mode 100644 index 0000000000..0d5b40a019 --- /dev/null +++ b/Libraries/LibGC/ConservativeHashMap.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +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); +} + +} diff --git a/Libraries/LibGC/ConservativeHashMap.h b/Libraries/LibGC/ConservativeHashMap.h new file mode 100644 index 0000000000..281436f5c7 --- /dev/null +++ b/Libraries/LibGC/ConservativeHashMap.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2026, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace GC { + +class GC_API ConservativeHashMapBase { + AK_MAKE_NONCOPYABLE(ConservativeHashMapBase); + +public: + virtual void for_each_possible_value(AK::Function callback) const = 0; + +protected: + ConservativeHashMapBase(); + explicit ConservativeHashMapBase(Heap&); + ~ConservativeHashMapBase(); + + Heap* m_heap { nullptr }; + IntrusiveListNode m_list_node; + +public: + using List = IntrusiveList<&ConservativeHashMapBase::m_list_node>; +}; + +template, typename ValueTraits = Traits, bool IsOrdered = false> +class GC_API ConservativeHashMap final + : public ConservativeHashMapBase + , public HashMap { + + using HashMapBase = HashMap; + +public: + ConservativeHashMap() + : ConservativeHashMapBase() + { + } + + ConservativeHashMap(ConservativeHashMap const& other) + : ConservativeHashMapBase(*other.m_heap) + , HashMapBase(static_cast(other)) + { + } + + ConservativeHashMap(ConservativeHashMap&& other) + : ConservativeHashMapBase(*other.m_heap) + , HashMapBase(move(static_cast(other))) + { + } + + ConservativeHashMap& operator=(ConservativeHashMap const& other) + { + if (&other == this) + return *this; + HashMapBase::operator=(static_cast(other)); + return *this; + } + + ~ConservativeHashMap() = default; + + virtual void for_each_possible_value(AK::Function 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 ValueTraits = Traits> +using OrderedConservativeHashMap = ConservativeHashMap; + +} diff --git a/Libraries/LibGC/Forward.h b/Libraries/LibGC/Forward.h index c5c5fd1b17..550ef42e82 100644 --- a/Libraries/LibGC/Forward.h +++ b/Libraries/LibGC/Forward.h @@ -34,6 +34,9 @@ class Root; template class ConservativeVector; +template +class ConservativeHashMap; + template class HeapVector; diff --git a/Libraries/LibGC/Heap.cpp b/Libraries/LibGC/Heap.cpp index a9c0ade0d8..e7783ff1d7 100644 --- a/Libraries/LibGC/Heap.cpp +++ b/Libraries/LibGC/Heap.cpp @@ -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 #include #include +#include #include #include #include @@ -87,6 +88,8 @@ public: void did_create_root_hash_table(Badge, RootHashTableBase&); void did_destroy_root_hash_table(Badge, RootHashTableBase&); + void did_create_conservative_hash_map(Badge, ConservativeHashMapBase&); + void did_destroy_conservative_hash_map(Badge, ConservativeHashMapBase&); void did_create_conservative_vector(Badge, ConservativeVectorBase&); void did_destroy_conservative_vector(Badge, 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, RootHash m_root_hash_tables.remove(hash_table); } +inline void Heap::did_create_conservative_hash_map(Badge, 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& 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& vector) { VERIFY(!m_conservative_vectors.contains(vector)); diff --git a/Libraries/LibGC/HeapRoot.h b/Libraries/LibGC/HeapRoot.h index 56e3fed5e3..c1345c55b5 100644 --- a/Libraries/LibGC/HeapRoot.h +++ b/Libraries/LibGC/HeapRoot.h @@ -14,6 +14,7 @@ namespace GC { struct GC_API HeapRoot { enum class Type { + ConservativeHashMap, ConservativeVector, HeapFunctionCapturedPointer, MustSurviveGC, diff --git a/Meta/Lagom/ClangPlugins/LibJSGCPluginAction.cpp b/Meta/Lagom/ClangPlugins/LibJSGCPluginAction.cpp index d2be0b0c88..62f27a0a65 100644 --- a/Meta/Lagom/ClangPlugins/LibJSGCPluginAction.cpp +++ b/Meta/Lagom/ClangPlugins/LibJSGCPluginAction.cpp @@ -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 get_all_qualified_types(clang::QualType cons "GC::RawPtr", "GC::RawRef", "GC::RootVector", + "GC::ConservativeHashMap", "GC::RootHashTable", "GC::Root", }; diff --git a/Tests/LibGC/TestGCContainers.cpp b/Tests/LibGC/TestGCContainers.cpp index beca403aee..280d559285 100644 --- a/Tests/LibGC/TestGCContainers.cpp +++ b/Tests/LibGC/TestGCContainers.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -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(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> map; + + auto cell = heap.allocate(); + 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> map; + + auto cell = heap.allocate(); + 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> map; + + auto cell = heap.allocate(); + 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, int> map; + + auto cell = heap.allocate(); + 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, int> map; + + auto cell = heap.allocate(); + map.set(cell, 42); + map.remove(cell); + + EXPECT(!possible_values_contain(map, cell.ptr())); +}