From 7826cb25563a1df5653c7391477cbad0ade787c0 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 10 Jan 2023 20:17:29 +0100 Subject: [PATCH] LibJS: Use a work queue instead of the C++ stack for the GC mark phase This fixes an issue where we'd run out of C++ stack while traversing large GC heap graphs. --- Userland/Libraries/LibJS/Heap/Heap.cpp | 24 +++++++++++++++---- .../Tests/gc-deeply-nested-object-graph.js | 11 +++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 Userland/Libraries/LibJS/Tests/gc-deeply-nested-object-graph.js diff --git a/Userland/Libraries/LibJS/Heap/Heap.cpp b/Userland/Libraries/LibJS/Heap/Heap.cpp index d8fe220e8b..e452ecfd94 100644 --- a/Userland/Libraries/LibJS/Heap/Heap.cpp +++ b/Userland/Libraries/LibJS/Heap/Heap.cpp @@ -208,7 +208,12 @@ __attribute__((no_sanitize("address"))) void Heap::gather_conservative_roots(Has class MarkingVisitor final : public Cell::Visitor { public: - MarkingVisitor() = default; + explicit MarkingVisitor(HashTable const& roots) + { + for (auto* root : roots) { + visit(root); + } + } virtual void visit_impl(Cell& cell) override { @@ -217,17 +222,26 @@ public: dbgln_if(HEAP_DEBUG, " ! {}", &cell); cell.set_marked(true); - cell.visit_edges(*this); + m_work_queue.append(cell); } + + void mark_all_live_cells() + { + while (!m_work_queue.is_empty()) { + m_work_queue.take_last().visit_edges(*this); + } + } + +private: + Vector m_work_queue; }; void Heap::mark_live_cells(HashTable const& roots) { dbgln_if(HEAP_DEBUG, "mark_live_cells:"); - MarkingVisitor visitor; - for (auto* root : roots) - visitor.visit(root); + MarkingVisitor visitor(roots); + visitor.mark_all_live_cells(); for (auto& inverse_root : m_uprooted_cells) inverse_root->set_marked(false); diff --git a/Userland/Libraries/LibJS/Tests/gc-deeply-nested-object-graph.js b/Userland/Libraries/LibJS/Tests/gc-deeply-nested-object-graph.js new file mode 100644 index 0000000000..8b488d900b --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/gc-deeply-nested-object-graph.js @@ -0,0 +1,11 @@ +test("garbage collection of a deeply-nested object graph", () => { + let root = {}; + let o = root; + + for (let i = 0; i < 200_000; ++i) { + o.next = {}; + o = o.next; + } + + gc(); +});