2020-03-08 15:23:58 -03:00
|
|
|
/*
|
2025-10-16 06:10:01 -03:00
|
|
|
* Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
|
2025-12-27 13:07:11 -03:00
|
|
|
* Copyright (c) 2023-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
2020-03-08 15:23:58 -03:00
|
|
|
*
|
2021-04-22 05:24:48 -03:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-03-08 15:23:58 -03:00
|
|
|
*/
|
|
|
|
|
|
2020-03-09 17:29:22 -03:00
|
|
|
#include <AK/Badge.h>
|
2026-02-28 04:42:54 -03:00
|
|
|
#include <AK/BinarySearch.h>
|
2021-01-24 11:28:26 -03:00
|
|
|
#include <AK/Debug.h>
|
2024-11-14 04:17:33 -03:00
|
|
|
#include <AK/Function.h>
|
2020-03-08 15:23:58 -03:00
|
|
|
#include <AK/HashTable.h>
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
#include <AK/JsonArray.h>
|
|
|
|
|
#include <AK/JsonObject.h>
|
2026-03-02 19:28:08 -03:00
|
|
|
#include <AK/LexicalPath.h>
|
2024-04-07 21:28:28 -03:00
|
|
|
#include <AK/Platform.h>
|
2020-11-08 09:48:16 -03:00
|
|
|
#include <AK/StackInfo.h>
|
2026-03-02 19:28:08 -03:00
|
|
|
#include <AK/StackUnwinder.h>
|
2020-10-15 15:46:52 -03:00
|
|
|
#include <AK/TemporaryChange.h>
|
2020-08-16 15:33:56 -03:00
|
|
|
#include <LibCore/ElapsedTimer.h>
|
2026-03-02 19:28:08 -03:00
|
|
|
#include <LibCore/File.h>
|
|
|
|
|
#include <LibCore/StandardPaths.h>
|
2024-11-14 12:01:23 -03:00
|
|
|
#include <LibGC/CellAllocator.h>
|
|
|
|
|
#include <LibGC/Heap.h>
|
|
|
|
|
#include <LibGC/HeapBlock.h>
|
|
|
|
|
#include <LibGC/NanBoxedValue.h>
|
|
|
|
|
#include <LibGC/Root.h>
|
2025-10-16 06:10:01 -03:00
|
|
|
#include <LibGC/Weak.h>
|
2020-03-16 15:08:59 -03:00
|
|
|
#include <setjmp.h>
|
2020-03-08 15:23:58 -03:00
|
|
|
|
2023-06-30 21:46:12 -03:00
|
|
|
#ifdef HAS_ADDRESS_SANITIZER
|
|
|
|
|
# include <sanitizer/asan_interface.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-02-28 04:42:54 -03:00
|
|
|
#ifdef LIBGC_HAS_CPPTRACE
|
|
|
|
|
# include <cpptrace/cpptrace.hpp>
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
namespace GC {
|
2020-03-08 15:23:58 -03:00
|
|
|
|
2025-11-01 03:55:29 -03:00
|
|
|
static Heap* s_the;
|
|
|
|
|
|
|
|
|
|
Heap& Heap::the()
|
|
|
|
|
{
|
|
|
|
|
return *s_the;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-09 13:35:41 -03:00
|
|
|
Heap::Heap(AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> gather_embedder_roots)
|
|
|
|
|
: m_gather_embedder_roots(move(gather_embedder_roots))
|
2020-03-08 15:23:58 -03:00
|
|
|
{
|
2025-11-01 03:55:29 -03:00
|
|
|
s_the = this;
|
2024-05-02 05:24:23 -03:00
|
|
|
static_assert(HeapBlock::min_possible_cell_size <= 32, "Heap Cell tracking uses too much data!");
|
2023-12-23 11:13:51 -03:00
|
|
|
m_size_based_cell_allocators.append(make<CellAllocator>(64));
|
|
|
|
|
m_size_based_cell_allocators.append(make<CellAllocator>(96));
|
|
|
|
|
m_size_based_cell_allocators.append(make<CellAllocator>(128));
|
|
|
|
|
m_size_based_cell_allocators.append(make<CellAllocator>(256));
|
|
|
|
|
m_size_based_cell_allocators.append(make<CellAllocator>(512));
|
|
|
|
|
m_size_based_cell_allocators.append(make<CellAllocator>(1024));
|
|
|
|
|
m_size_based_cell_allocators.append(make<CellAllocator>(3072));
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Heap::~Heap()
|
|
|
|
|
{
|
2020-03-23 10:11:19 -03:00
|
|
|
collect_garbage(CollectionType::CollectEverything);
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
|
|
|
|
|
2023-11-19 05:45:05 -03:00
|
|
|
void Heap::will_allocate(size_t size)
|
2020-03-08 15:23:58 -03:00
|
|
|
{
|
2020-04-06 07:36:49 -03:00
|
|
|
if (should_collect_on_every_allocation()) {
|
2023-08-08 14:50:32 -03:00
|
|
|
m_allocated_bytes_since_last_gc = 0;
|
2020-03-16 15:18:46 -03:00
|
|
|
collect_garbage();
|
2023-08-08 14:50:32 -03:00
|
|
|
} else if (m_allocated_bytes_since_last_gc + size > m_gc_bytes_threshold) {
|
|
|
|
|
m_allocated_bytes_since_last_gc = 0;
|
2020-04-06 07:36:49 -03:00
|
|
|
collect_garbage();
|
|
|
|
|
}
|
2020-03-16 15:18:46 -03:00
|
|
|
|
2023-08-08 14:50:32 -03:00
|
|
|
m_allocated_bytes_since_last_gc += size;
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
|
|
|
|
|
2023-09-29 14:14:11 -03:00
|
|
|
static void add_possible_value(HashMap<FlatPtr, HeapRoot>& possible_pointers, FlatPtr data, HeapRoot origin, FlatPtr min_block_address, FlatPtr max_block_address)
|
2023-08-18 13:21:33 -03:00
|
|
|
{
|
2024-11-14 03:40:36 -03:00
|
|
|
if constexpr (sizeof(FlatPtr*) == sizeof(NanBoxedValue)) {
|
|
|
|
|
// Because NanBoxedValue stores pointers in non-canonical form we have to check if the top bytes
|
2023-08-18 13:21:33 -03:00
|
|
|
// match any pointer-backed tag, in that case we have to extract the pointer to its
|
|
|
|
|
// canonical form and add that as a possible pointer.
|
2023-09-30 04:48:01 -03:00
|
|
|
FlatPtr possible_pointer;
|
2023-08-18 13:21:33 -03:00
|
|
|
if ((data & SHIFTED_IS_CELL_PATTERN) == SHIFTED_IS_CELL_PATTERN)
|
2024-11-14 03:40:36 -03:00
|
|
|
possible_pointer = NanBoxedValue::extract_pointer_bits(data);
|
2023-08-18 13:21:33 -03:00
|
|
|
else
|
2023-09-30 04:48:01 -03:00
|
|
|
possible_pointer = data;
|
|
|
|
|
if (possible_pointer < min_block_address || possible_pointer > max_block_address)
|
|
|
|
|
return;
|
|
|
|
|
possible_pointers.set(possible_pointer, move(origin));
|
2023-08-18 13:21:33 -03:00
|
|
|
} else {
|
2024-11-14 03:40:36 -03:00
|
|
|
static_assert((sizeof(NanBoxedValue) % sizeof(FlatPtr*)) == 0);
|
2023-09-30 04:48:01 -03:00
|
|
|
if (data < min_block_address || data > max_block_address)
|
|
|
|
|
return;
|
2024-11-14 03:40:36 -03:00
|
|
|
// In the 32-bit case we will look at the top and bottom part of NanBoxedValue separately we just
|
2023-08-18 13:21:33 -03:00
|
|
|
// add both the upper and lower bytes as possible pointers.
|
|
|
|
|
possible_pointers.set(data, move(origin));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 14:14:11 -03:00
|
|
|
void Heap::find_min_and_max_block_addresses(FlatPtr& min_address, FlatPtr& max_address)
|
|
|
|
|
{
|
2023-09-30 04:48:01 -03:00
|
|
|
min_address = explode_byte(0xff);
|
|
|
|
|
max_address = 0;
|
2024-04-23 11:53:59 -03:00
|
|
|
for (auto& allocator : m_all_cell_allocators) {
|
|
|
|
|
min_address = min(min_address, allocator.min_block_address());
|
2025-12-18 17:16:18 -03:00
|
|
|
max_address = max(max_address, allocator.max_block_address() + HeapBlock::BLOCK_SIZE);
|
2024-04-23 11:53:59 -03:00
|
|
|
}
|
2023-09-29 14:14:11 -03:00
|
|
|
}
|
|
|
|
|
|
2023-08-18 13:21:33 -03:00
|
|
|
template<typename Callback>
|
2023-09-21 21:04:16 -03:00
|
|
|
static void for_each_cell_among_possible_pointers(HashTable<HeapBlock*> const& all_live_heap_blocks, HashMap<FlatPtr, HeapRoot>& possible_pointers, Callback callback)
|
2023-08-18 13:21:33 -03:00
|
|
|
{
|
|
|
|
|
for (auto possible_pointer : possible_pointers.keys()) {
|
|
|
|
|
if (!possible_pointer)
|
|
|
|
|
continue;
|
2024-11-14 12:01:23 -03:00
|
|
|
auto* possible_heap_block = HeapBlock::from_cell(reinterpret_cast<Cell const*>(possible_pointer));
|
2023-08-18 13:21:33 -03:00
|
|
|
if (!all_live_heap_blocks.contains(possible_heap_block))
|
|
|
|
|
continue;
|
|
|
|
|
if (auto* cell = possible_heap_block->cell_from_possible_pointer(possible_pointer)) {
|
|
|
|
|
callback(cell, possible_pointer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
class GraphConstructorVisitor final : public Cell::Visitor {
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
public:
|
2024-11-14 12:01:23 -03:00
|
|
|
explicit GraphConstructorVisitor(Heap& heap, HashMap<Cell*, HeapRoot> const& roots)
|
2023-08-18 13:21:33 -03:00
|
|
|
: m_heap(heap)
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
{
|
2023-09-29 14:14:11 -03:00
|
|
|
m_heap.find_min_and_max_block_addresses(m_min_block_address, m_max_block_address);
|
2023-08-18 13:21:33 -03:00
|
|
|
m_heap.for_each_block([&](auto& block) {
|
|
|
|
|
m_all_live_heap_blocks.set(&block);
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
2024-12-13 18:57:31 -03:00
|
|
|
m_work_queue.ensure_capacity(roots.size());
|
2023-08-18 13:21:33 -03:00
|
|
|
|
2024-04-20 13:41:11 -03:00
|
|
|
for (auto& [root, root_origin] : roots) {
|
|
|
|
|
auto& graph_node = m_graph.ensure(bit_cast<FlatPtr>(root));
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
graph_node.class_name = root->class_name();
|
2024-04-20 13:41:11 -03:00
|
|
|
graph_node.root_origin = root_origin;
|
|
|
|
|
|
|
|
|
|
m_work_queue.append(*root);
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
virtual void visit_impl(Cell& cell) override
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
{
|
|
|
|
|
if (m_node_being_visited)
|
|
|
|
|
m_node_being_visited->edges.set(reinterpret_cast<FlatPtr>(&cell));
|
|
|
|
|
|
|
|
|
|
if (m_graph.get(reinterpret_cast<FlatPtr>(&cell)).has_value())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_work_queue.append(cell);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 10:22:51 -03:00
|
|
|
virtual void visit_impl(ReadonlySpan<NanBoxedValue> values) override
|
|
|
|
|
{
|
|
|
|
|
for (auto const& value : values)
|
|
|
|
|
visit(value);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-18 13:21:33 -03:00
|
|
|
virtual void visit_possible_values(ReadonlyBytes bytes) override
|
|
|
|
|
{
|
2023-09-21 21:04:16 -03:00
|
|
|
HashMap<FlatPtr, HeapRoot> possible_pointers;
|
2023-08-18 13:21:33 -03:00
|
|
|
|
|
|
|
|
auto* raw_pointer_sized_values = reinterpret_cast<FlatPtr const*>(bytes.data());
|
|
|
|
|
for (size_t i = 0; i < (bytes.size() / sizeof(FlatPtr)); ++i)
|
2023-09-29 14:14:11 -03:00
|
|
|
add_possible_value(possible_pointers, raw_pointer_sized_values[i], HeapRoot { .type = HeapRoot::Type::HeapFunctionCapturedPointer }, m_min_block_address, m_max_block_address);
|
2023-08-18 13:21:33 -03:00
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
for_each_cell_among_possible_pointers(m_all_live_heap_blocks, possible_pointers, [&](Cell* cell, FlatPtr) {
|
2025-12-27 13:08:41 -03:00
|
|
|
if (cell->state() != Cell::State::Live)
|
|
|
|
|
return;
|
|
|
|
|
|
2023-08-18 13:21:33 -03:00
|
|
|
if (m_node_being_visited)
|
2024-11-11 11:45:51 -03:00
|
|
|
m_node_being_visited->edges.set(reinterpret_cast<FlatPtr>(cell));
|
2023-08-18 13:21:33 -03:00
|
|
|
|
2025-12-27 13:10:31 -03:00
|
|
|
if (m_graph.get(reinterpret_cast<FlatPtr>(cell)).has_value())
|
2023-08-18 13:21:33 -03:00
|
|
|
return;
|
|
|
|
|
m_work_queue.append(*cell);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
void visit_all_cells()
|
|
|
|
|
{
|
|
|
|
|
while (!m_work_queue.is_empty()) {
|
2024-04-20 13:41:11 -03:00
|
|
|
auto cell = m_work_queue.take_last();
|
|
|
|
|
m_node_being_visited = &m_graph.ensure(bit_cast<FlatPtr>(cell.ptr()));
|
|
|
|
|
m_node_being_visited->class_name = cell->class_name();
|
|
|
|
|
cell->visit_edges(*this);
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
m_node_being_visited = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-12 09:25:06 -03:00
|
|
|
AK::JsonObject dump()
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
{
|
|
|
|
|
auto graph = AK::JsonObject();
|
|
|
|
|
for (auto& it : m_graph) {
|
|
|
|
|
AK::JsonArray edges;
|
|
|
|
|
for (auto const& value : it.value.edges) {
|
2025-02-17 15:21:07 -03:00
|
|
|
edges.must_append(MUST(String::formatted("{}", value)));
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto node = AK::JsonObject();
|
|
|
|
|
if (it.value.root_origin.has_value()) {
|
2023-09-21 21:04:16 -03:00
|
|
|
auto type = it.value.root_origin->type;
|
2026-03-02 19:26:29 -03:00
|
|
|
auto const* location = it.value.root_origin->location;
|
2023-09-21 21:04:16 -03:00
|
|
|
switch (type) {
|
2025-12-26 10:51:55 -03:00
|
|
|
case HeapRoot::Type::ConservativeVector:
|
|
|
|
|
node.set("root"sv, "ConservativeVector"sv);
|
|
|
|
|
break;
|
2026-03-02 19:26:29 -03:00
|
|
|
case HeapRoot::Type::HeapFunctionCapturedPointer:
|
|
|
|
|
node.set("root"sv, "HeapFunctionCapturedPointer"sv);
|
|
|
|
|
break;
|
2025-12-27 13:07:11 -03:00
|
|
|
case HeapRoot::Type::MustSurviveGC:
|
|
|
|
|
node.set("root"sv, "MustSurviveGC"sv);
|
|
|
|
|
break;
|
2024-11-14 12:01:23 -03:00
|
|
|
case HeapRoot::Type::Root:
|
2025-02-17 15:21:07 -03:00
|
|
|
node.set("root"sv, MUST(String::formatted("Root {} {}:{}", location->function_name(), location->filename(), location->line_number())));
|
2023-09-21 21:04:16 -03:00
|
|
|
break;
|
2024-12-26 10:32:52 -03:00
|
|
|
case HeapRoot::Type::RootVector:
|
2025-02-17 13:59:45 -03:00
|
|
|
node.set("root"sv, "RootVector"sv);
|
2023-09-21 21:04:16 -03:00
|
|
|
break;
|
2026-03-02 19:26:29 -03:00
|
|
|
case HeapRoot::Type::RootHashMap:
|
|
|
|
|
node.set("root"sv, "RootHashMap"sv);
|
|
|
|
|
break;
|
2023-09-21 21:04:16 -03:00
|
|
|
case HeapRoot::Type::RegisterPointer:
|
2025-02-17 13:59:45 -03:00
|
|
|
node.set("root"sv, "RegisterPointer"sv);
|
2026-02-28 04:42:54 -03:00
|
|
|
if (it.value.root_origin->stack_frame_index.has_value())
|
|
|
|
|
node.set("stack_frame_index"sv, it.value.root_origin->stack_frame_index.value());
|
2023-09-21 21:04:16 -03:00
|
|
|
break;
|
|
|
|
|
case HeapRoot::Type::StackPointer:
|
2025-02-17 13:59:45 -03:00
|
|
|
node.set("root"sv, "StackPointer"sv);
|
2026-02-28 04:42:54 -03:00
|
|
|
if (it.value.root_origin->stack_frame_index.has_value())
|
|
|
|
|
node.set("stack_frame_index"sv, it.value.root_origin->stack_frame_index.value());
|
2023-09-21 21:04:16 -03:00
|
|
|
break;
|
|
|
|
|
case HeapRoot::Type::VM:
|
2025-02-17 13:59:45 -03:00
|
|
|
node.set("root"sv, "VM"sv);
|
2023-09-21 21:04:16 -03:00
|
|
|
break;
|
|
|
|
|
}
|
2026-03-02 19:26:29 -03:00
|
|
|
VERIFY(node.has("root"sv));
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
}
|
|
|
|
|
node.set("class_name"sv, it.value.class_name);
|
|
|
|
|
node.set("edges"sv, edges);
|
2023-12-16 11:19:34 -03:00
|
|
|
graph.set(ByteString::number(it.key), node);
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
}
|
|
|
|
|
|
2023-12-12 09:25:06 -03:00
|
|
|
return graph;
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
struct GraphNode {
|
2023-09-21 21:04:16 -03:00
|
|
|
Optional<HeapRoot> root_origin;
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
StringView class_name;
|
|
|
|
|
HashTable<FlatPtr> edges {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
GraphNode* m_node_being_visited { nullptr };
|
2024-11-14 12:01:23 -03:00
|
|
|
Vector<Ref<Cell>> m_work_queue;
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
HashMap<FlatPtr, GraphNode> m_graph;
|
2023-08-18 13:21:33 -03:00
|
|
|
|
|
|
|
|
Heap& m_heap;
|
|
|
|
|
HashTable<HeapBlock*> m_all_live_heap_blocks;
|
2023-09-29 14:14:11 -03:00
|
|
|
FlatPtr m_min_block_address;
|
|
|
|
|
FlatPtr m_max_block_address;
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
};
|
|
|
|
|
|
2023-12-12 09:25:06 -03:00
|
|
|
AK::JsonObject Heap::dump_graph()
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
{
|
2024-11-14 12:01:23 -03:00
|
|
|
HashMap<Cell*, HeapRoot> roots;
|
2025-12-28 20:19:37 -03:00
|
|
|
HashTable<HeapBlock*> all_live_heap_blocks;
|
2026-02-28 06:49:41 -03:00
|
|
|
Vector<StackFrameInfo> stack_frames;
|
2026-02-28 04:42:54 -03:00
|
|
|
gather_roots(roots, all_live_heap_blocks, &stack_frames);
|
2023-08-18 13:21:33 -03:00
|
|
|
GraphConstructorVisitor visitor(*this, roots);
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
visitor.visit_all_cells();
|
2026-02-28 04:42:54 -03:00
|
|
|
auto graph = visitor.dump();
|
|
|
|
|
|
|
|
|
|
if (!stack_frames.is_empty()) {
|
|
|
|
|
AK::JsonArray stack_frames_array;
|
2026-02-28 06:49:41 -03:00
|
|
|
for (auto const& frame : stack_frames) {
|
|
|
|
|
AK::JsonObject frame_object;
|
|
|
|
|
frame_object.set("label"sv, frame.label);
|
|
|
|
|
frame_object.set("size"sv, frame.size_bytes);
|
|
|
|
|
stack_frames_array.must_append(move(frame_object));
|
|
|
|
|
}
|
2026-02-28 04:42:54 -03:00
|
|
|
graph.set("stack_frames"sv, move(stack_frames_array));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return graph;
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
}
|
|
|
|
|
|
2020-08-16 15:33:56 -03:00
|
|
|
void Heap::collect_garbage(CollectionType collection_type, bool print_report)
|
2020-03-08 15:23:58 -03:00
|
|
|
{
|
2021-02-23 16:42:32 -03:00
|
|
|
VERIFY(!m_collecting_garbage);
|
2020-09-21 09:35:19 -03:00
|
|
|
|
2025-01-23 05:59:22 -03:00
|
|
|
{
|
|
|
|
|
TemporaryChange change(m_collecting_garbage, true);
|
2023-01-02 02:45:28 -03:00
|
|
|
|
2025-01-23 05:59:22 -03:00
|
|
|
Core::ElapsedTimer collection_measurement_timer;
|
|
|
|
|
if (print_report)
|
|
|
|
|
collection_measurement_timer.start();
|
|
|
|
|
|
|
|
|
|
if (collection_type == CollectionType::CollectGarbage) {
|
|
|
|
|
if (m_gc_deferrals) {
|
|
|
|
|
m_should_gc_when_deferral_ends = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
HashMap<Cell*, HeapRoot> roots;
|
2025-12-28 20:19:37 -03:00
|
|
|
HashTable<HeapBlock*> all_live_heap_blocks;
|
|
|
|
|
gather_roots(roots, all_live_heap_blocks);
|
|
|
|
|
mark_live_cells(roots, all_live_heap_blocks);
|
2020-04-19 06:30:47 -03:00
|
|
|
}
|
2025-01-23 05:59:22 -03:00
|
|
|
finalize_unmarked_cells();
|
2025-10-16 06:10:01 -03:00
|
|
|
sweep_weak_blocks();
|
2025-01-23 05:59:22 -03:00
|
|
|
sweep_dead_cells(print_report, collection_measurement_timer);
|
2025-12-25 07:53:21 -03:00
|
|
|
|
|
|
|
|
if (print_report)
|
|
|
|
|
dump_allocators();
|
2020-03-23 10:11:19 -03:00
|
|
|
}
|
2025-01-23 05:59:22 -03:00
|
|
|
|
2025-12-25 07:53:21 -03:00
|
|
|
run_post_gc_tasks();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Heap::run_post_gc_tasks()
|
|
|
|
|
{
|
2025-01-23 05:59:22 -03:00
|
|
|
auto tasks = move(m_post_gc_tasks);
|
|
|
|
|
for (auto& task : tasks)
|
|
|
|
|
task();
|
2025-12-17 19:07:50 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Heap::dump_allocators()
|
|
|
|
|
{
|
|
|
|
|
size_t total_in_committed_blocks = 0;
|
|
|
|
|
size_t total_waste = 0;
|
|
|
|
|
for (auto& allocator : m_all_cell_allocators) {
|
|
|
|
|
struct BlockStats {
|
|
|
|
|
HeapBlock& block;
|
|
|
|
|
size_t live_cells { 0 };
|
|
|
|
|
size_t dead_cells { 0 };
|
|
|
|
|
size_t total_cells { 0 };
|
|
|
|
|
};
|
|
|
|
|
Vector<BlockStats> blocks;
|
|
|
|
|
|
|
|
|
|
size_t total_live_cells = 0;
|
|
|
|
|
size_t total_dead_cells = 0;
|
|
|
|
|
size_t cell_count = (HeapBlock::BLOCK_SIZE - sizeof(HeapBlock)) / allocator.cell_size();
|
|
|
|
|
|
|
|
|
|
allocator.for_each_block([&](HeapBlock& heap_block) {
|
|
|
|
|
BlockStats block { heap_block };
|
|
|
|
|
|
|
|
|
|
heap_block.for_each_cell([&](Cell* cell) {
|
|
|
|
|
if (cell->state() == Cell::State::Live)
|
|
|
|
|
++block.live_cells;
|
|
|
|
|
else if (cell->state() == Cell::State::Dead)
|
|
|
|
|
++block.dead_cells;
|
|
|
|
|
else
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
});
|
|
|
|
|
total_live_cells += block.live_cells;
|
|
|
|
|
total_dead_cells += block.dead_cells;
|
|
|
|
|
|
|
|
|
|
blocks.append({ block });
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (blocks.is_empty())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
total_in_committed_blocks += blocks.size() * HeapBlock::BLOCK_SIZE;
|
|
|
|
|
|
|
|
|
|
StringBuilder builder;
|
2026-02-19 08:59:00 -03:00
|
|
|
if (allocator.class_name().has_value())
|
|
|
|
|
builder.appendff("{} ({}b)", allocator.class_name().value(), allocator.cell_size());
|
2025-12-17 19:07:50 -03:00
|
|
|
else
|
2026-02-19 08:59:00 -03:00
|
|
|
builder.appendff("generic ({}b)", allocator.cell_size());
|
2025-12-17 19:07:50 -03:00
|
|
|
|
|
|
|
|
builder.appendff(" x {}", total_live_cells);
|
|
|
|
|
|
|
|
|
|
size_t cost = blocks.size() * HeapBlock::BLOCK_SIZE / KiB;
|
2025-12-20 13:18:42 -03:00
|
|
|
size_t reserved = allocator.block_allocator().blocks().size() * HeapBlock::BLOCK_SIZE / KiB;
|
2025-12-17 19:07:50 -03:00
|
|
|
builder.appendff(", cost: {} KiB, reserved: {} KiB", cost, reserved);
|
|
|
|
|
|
|
|
|
|
size_t total_dead_bytes = ((blocks.size() * cell_count) - total_live_cells) * allocator.cell_size();
|
|
|
|
|
if (total_dead_bytes) {
|
|
|
|
|
builder.appendff(", waste: {} KiB", total_dead_bytes / KiB);
|
|
|
|
|
total_waste += total_dead_bytes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbgln("{}", builder.string_view());
|
|
|
|
|
|
|
|
|
|
for (auto& block : blocks) {
|
|
|
|
|
dbgln(" block at {:p}: live {} / dead {} / total {} cells", &block.block, block.live_cells, block.dead_cells, block.block.cell_count());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dbgln("Total allocated: {} KiB", total_in_committed_blocks / KiB);
|
|
|
|
|
dbgln("Total wasted on fragmentation: {} KiB", total_waste / KiB);
|
2025-01-23 05:59:22 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Heap::enqueue_post_gc_task(AK::Function<void()> task)
|
|
|
|
|
{
|
|
|
|
|
m_post_gc_tasks.append(move(task));
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 18:18:15 -03:00
|
|
|
void Heap::register_sweep_callback(AK::Function<void()> callback)
|
|
|
|
|
{
|
|
|
|
|
m_sweep_callbacks.append(move(callback));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 06:49:41 -03:00
|
|
|
void Heap::gather_roots(HashMap<Cell*, HeapRoot>& roots, HashTable<HeapBlock*>& all_live_heap_blocks, Vector<StackFrameInfo>* out_stack_frames)
|
2020-03-08 15:23:58 -03:00
|
|
|
{
|
2025-12-28 20:19:37 -03:00
|
|
|
for_each_block([&](auto& block) {
|
|
|
|
|
all_live_heap_blocks.set(&block);
|
2025-12-28 21:33:12 -03:00
|
|
|
|
|
|
|
|
if (block.overrides_must_survive_garbage_collection()) {
|
|
|
|
|
block.template for_each_cell_in_state<Cell::State::Live>([&](Cell* cell) {
|
|
|
|
|
if (cell->must_survive_garbage_collection()) {
|
|
|
|
|
roots.set(cell, HeapRoot { .type = HeapRoot::Type::MustSurviveGC });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 20:19:37 -03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-14 04:17:33 -03:00
|
|
|
m_gather_embedder_roots(roots);
|
2026-02-28 04:42:54 -03:00
|
|
|
gather_conservative_roots(roots, all_live_heap_blocks, out_stack_frames);
|
2020-03-16 15:08:59 -03:00
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
for (auto& root : m_roots)
|
|
|
|
|
roots.set(root.cell(), HeapRoot { .type = HeapRoot::Type::Root, .location = &root.source_location() });
|
2020-03-18 16:03:17 -03:00
|
|
|
|
2025-01-02 16:34:49 -03:00
|
|
|
for (auto& vector : m_root_vectors)
|
LibJS: Let MarkedVector<T> inherit from Vector and handle Cell* + Value
Note: MarkedVector is still relatively new and has zero users right now,
so these changes don't affect any code other than the class itself.
Reasons for this are the rather limited API:
- Despite the name and unlike MarkedValueList, MarkedVector isn't
actually a Vector, it *wraps* a Vector. This means that plenty of
convenient APIs are unavailable and have to be exported on the class
separately and forwarded to the internal Vector, or need to go through
the exposed Span - both not great options.
- Exposing append(Cell*) and prepend(Cell*) on the base class means that
it was possible to append any Cell type, not just T! All the strong
typing guarantees are basically gone, and MarkedVector doesn't do much
more than casting Cells to the appropriate type through the exposed
Span.
All of this combined means that MarkedVector - in its current form -
doesn't provide much value over MarkedValueList, and that we have to
maintain two separate, yet almost identical classes.
Let's fix this!
The updated MarkedVector steals various concepts from the existing
MarkedValueList, especially the ability to copy. On the other hand, it
remains generic enough to handle both Cell* and Value for T, making
MarkedValueList effectively redundant :^)
Additionally, by inheriting from Vector we get all the current and
future APIs without having to select and expose them separately.
MarkedVectorBase remains and takes care of communicating creation and
destruction of the class to the heap. Visiting the contained values is
handled via a pure virtual method gather_roots(), which is being called
by the Heap's function of the same name; much like the VM has one.
From there, values are added to the roots HashTable if they are cells
for T = Value, and unconditionally for any other T.
As a small additional improvement the template now also takes an
inline_capacity parameter, defaulting to 32, and forwards it to the
Vector template; allowing for possible future optimizations of current
uses of MarkedValueList, which hard-codes it to 32.
2022-02-09 06:40:49 -03:00
|
|
|
vector.gather_roots(roots);
|
2021-12-16 14:54:06 -03:00
|
|
|
|
2025-05-03 06:59:17 -03:00
|
|
|
for (auto& hash_map : m_root_hash_maps)
|
|
|
|
|
hash_map.gather_roots(roots);
|
|
|
|
|
|
2021-04-18 13:12:33 -03:00
|
|
|
if constexpr (HEAP_DEBUG) {
|
|
|
|
|
dbgln("gather_roots:");
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
for (auto* root : roots.keys())
|
2021-04-18 13:12:33 -03:00
|
|
|
dbgln(" + {}", root);
|
|
|
|
|
}
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
|
|
|
|
|
2023-06-30 21:46:12 -03:00
|
|
|
#ifdef HAS_ADDRESS_SANITIZER
|
2024-04-07 21:28:28 -03:00
|
|
|
NO_SANITIZE_ADDRESS void Heap::gather_asan_fake_stack_roots(HashMap<FlatPtr, HeapRoot>& possible_pointers, FlatPtr addr, FlatPtr min_block_address, FlatPtr max_block_address)
|
2023-06-30 21:46:12 -03:00
|
|
|
{
|
|
|
|
|
void* begin = nullptr;
|
|
|
|
|
void* end = nullptr;
|
|
|
|
|
void* real_stack = __asan_addr_is_in_fake_stack(__asan_get_current_fake_stack(), reinterpret_cast<void*>(addr), &begin, &end);
|
|
|
|
|
|
|
|
|
|
if (real_stack != nullptr) {
|
|
|
|
|
for (auto* real_stack_addr = reinterpret_cast<void const* const*>(begin); real_stack_addr < end; ++real_stack_addr) {
|
|
|
|
|
void const* real_address = *real_stack_addr;
|
|
|
|
|
if (real_address == nullptr)
|
|
|
|
|
continue;
|
2023-09-29 14:14:11 -03:00
|
|
|
add_possible_value(possible_pointers, reinterpret_cast<FlatPtr>(real_address), HeapRoot { .type = HeapRoot::Type::StackPointer }, min_block_address, max_block_address);
|
2023-06-30 21:46:12 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
2023-09-29 14:14:11 -03:00
|
|
|
void Heap::gather_asan_fake_stack_roots(HashMap<FlatPtr, HeapRoot>&, FlatPtr, FlatPtr, FlatPtr)
|
2023-06-30 21:46:12 -03:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-02-28 06:49:41 -03:00
|
|
|
NO_SANITIZE_ADDRESS void Heap::gather_conservative_roots(HashMap<Cell*, HeapRoot>& roots, HashTable<HeapBlock*> const& all_live_heap_blocks, Vector<StackFrameInfo>* out_stack_frames)
|
2020-03-16 15:08:59 -03:00
|
|
|
{
|
|
|
|
|
FlatPtr dummy;
|
|
|
|
|
|
2021-04-07 10:12:32 -03:00
|
|
|
dbgln_if(HEAP_DEBUG, "gather_conservative_roots:");
|
2020-03-16 15:08:59 -03:00
|
|
|
|
|
|
|
|
jmp_buf buf;
|
|
|
|
|
setjmp(buf);
|
|
|
|
|
|
2023-09-21 21:04:16 -03:00
|
|
|
HashMap<FlatPtr, HeapRoot> possible_pointers;
|
2020-03-16 15:08:59 -03:00
|
|
|
|
2021-05-25 14:03:30 -03:00
|
|
|
auto* raw_jmp_buf = reinterpret_cast<FlatPtr const*>(buf);
|
2020-03-23 09:14:57 -03:00
|
|
|
|
2023-09-29 14:14:11 -03:00
|
|
|
FlatPtr min_block_address, max_block_address;
|
|
|
|
|
find_min_and_max_block_addresses(min_block_address, max_block_address);
|
|
|
|
|
|
2023-03-13 16:43:01 -03:00
|
|
|
for (size_t i = 0; i < ((size_t)sizeof(buf)) / sizeof(FlatPtr); ++i)
|
2023-09-29 14:14:11 -03:00
|
|
|
add_possible_value(possible_pointers, raw_jmp_buf[i], HeapRoot { .type = HeapRoot::Type::RegisterPointer }, min_block_address, max_block_address);
|
2020-03-16 15:08:59 -03:00
|
|
|
|
2021-05-25 14:03:30 -03:00
|
|
|
auto stack_reference = bit_cast<FlatPtr>(&dummy);
|
2026-02-28 04:42:54 -03:00
|
|
|
auto stack_top = m_stack_info.top();
|
|
|
|
|
|
|
|
|
|
// Build frame boundary map for annotation if requested.
|
|
|
|
|
// Each entry maps a frame pointer address to the stack frame index in out_stack_frames.
|
|
|
|
|
struct FrameBoundary {
|
|
|
|
|
FlatPtr start;
|
|
|
|
|
u32 frame_index;
|
|
|
|
|
};
|
|
|
|
|
Vector<FrameBoundary> frame_boundaries;
|
|
|
|
|
|
|
|
|
|
#ifdef LIBGC_HAS_CPPTRACE
|
|
|
|
|
if (out_stack_frames) {
|
|
|
|
|
// Walk the frame pointer chain to collect frame boundaries and return addresses.
|
|
|
|
|
Vector<FlatPtr> frame_starts;
|
|
|
|
|
std::vector<cpptrace::frame_ptr> return_addresses;
|
|
|
|
|
|
2026-03-02 19:28:08 -03:00
|
|
|
FlatPtr current_fp = bit_cast<FlatPtr>(__builtin_frame_address(0));
|
|
|
|
|
AK::unwind_stack_from_frame_pointer(
|
|
|
|
|
current_fp,
|
|
|
|
|
[&](FlatPtr address) -> Optional<FlatPtr> {
|
|
|
|
|
if (address < stack_reference || address >= stack_top)
|
|
|
|
|
return {};
|
|
|
|
|
return *reinterpret_cast<FlatPtr*>(address);
|
|
|
|
|
},
|
|
|
|
|
[&](AK::StackFrame frame) -> IterationDecision {
|
|
|
|
|
// Ensure the previous FP is above the current one (stack grows downward).
|
|
|
|
|
if (frame.previous_frame_pointer != 0 && frame.previous_frame_pointer <= current_fp)
|
|
|
|
|
return IterationDecision::Break;
|
|
|
|
|
frame_starts.append(current_fp);
|
|
|
|
|
return_addresses.push_back(static_cast<cpptrace::frame_ptr>(frame.return_address) - 1);
|
|
|
|
|
current_fp = frame.previous_frame_pointer;
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
2026-02-28 04:42:54 -03:00
|
|
|
|
|
|
|
|
if (!frame_starts.is_empty()) {
|
|
|
|
|
auto resolved = cpptrace::raw_trace { move(return_addresses) }.resolve();
|
|
|
|
|
|
|
|
|
|
auto format_frame_label = [](cpptrace::stacktrace_frame const& frame) -> String {
|
|
|
|
|
StringBuilder label;
|
|
|
|
|
if (!frame.symbol.empty()) {
|
|
|
|
|
label.append(StringView(frame.symbol.c_str(), frame.symbol.length()));
|
|
|
|
|
if (frame.line.has_value()) {
|
|
|
|
|
auto filename = StringView { frame.filename.c_str(), frame.filename.length() };
|
|
|
|
|
auto last_slash = filename.find_last('/');
|
|
|
|
|
if (last_slash.has_value())
|
|
|
|
|
filename = filename.substring_view(*last_slash + 1);
|
|
|
|
|
label.appendff(" {}:{}", filename, frame.line.value());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return MUST(label.to_string());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// resolve() may expand inline frames, so there can be more resolved
|
|
|
|
|
// frames than return addresses. We want the non-inline frame for each
|
|
|
|
|
// return address, since that represents the actual function whose
|
|
|
|
|
// locals occupy the stack range.
|
|
|
|
|
frame_boundaries.ensure_capacity(frame_starts.size());
|
|
|
|
|
size_t raw_frame_index = 0;
|
|
|
|
|
for (size_t i = 0; i < resolved.frames.size() && raw_frame_index < frame_starts.size(); ++i) {
|
|
|
|
|
auto const& frame = resolved.frames[i];
|
2026-02-28 07:30:23 -03:00
|
|
|
if (frame.is_inline) {
|
|
|
|
|
out_stack_frames->append({ .label = format_frame_label(frame) });
|
2026-02-28 04:42:54 -03:00
|
|
|
continue;
|
2026-02-28 07:30:23 -03:00
|
|
|
}
|
2026-02-28 04:42:54 -03:00
|
|
|
|
|
|
|
|
auto frame_label_index = static_cast<u32>(out_stack_frames->size());
|
2026-02-28 06:49:41 -03:00
|
|
|
auto frame_start = frame_starts[raw_frame_index];
|
|
|
|
|
auto frame_end = frame_starts.get(raw_frame_index + 1).value_or(stack_top);
|
|
|
|
|
out_stack_frames->append({ .label = format_frame_label(frame), .size_bytes = frame_end - frame_start });
|
|
|
|
|
frame_boundaries.append({ frame_start, frame_label_index });
|
2026-02-28 04:42:54 -03:00
|
|
|
++raw_frame_index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
(void)out_stack_frames;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Find the frame index for a given stack address. Frame boundaries are sorted ascending
|
|
|
|
|
// by start address. We want the last boundary whose start is <= the address.
|
|
|
|
|
auto frame_index_for_stack_address = [&](FlatPtr address) -> Optional<u32> {
|
|
|
|
|
if (frame_boundaries.is_empty())
|
|
|
|
|
return {};
|
|
|
|
|
if (address < frame_boundaries[0].start || address >= stack_top)
|
|
|
|
|
return {};
|
|
|
|
|
size_t nearby = 0;
|
|
|
|
|
binary_search(frame_boundaries, address, &nearby, [](FlatPtr addr, FrameBoundary const& boundary) {
|
|
|
|
|
return static_cast<int>(addr - boundary.start);
|
|
|
|
|
});
|
|
|
|
|
return frame_boundaries[nearby].frame_index;
|
|
|
|
|
};
|
2020-03-16 15:08:59 -03:00
|
|
|
|
2026-02-28 04:42:54 -03:00
|
|
|
for (FlatPtr stack_address = stack_reference; stack_address < stack_top; stack_address += sizeof(FlatPtr)) {
|
2020-03-16 15:08:59 -03:00
|
|
|
auto data = *reinterpret_cast<FlatPtr*>(stack_address);
|
2026-02-28 04:42:54 -03:00
|
|
|
add_possible_value(possible_pointers, data, HeapRoot { .type = HeapRoot::Type::StackPointer, .stack_frame_index = frame_index_for_stack_address(stack_address) }, min_block_address, max_block_address);
|
2023-09-29 14:14:11 -03:00
|
|
|
gather_asan_fake_stack_roots(possible_pointers, data, min_block_address, max_block_address);
|
2020-03-16 15:08:59 -03:00
|
|
|
}
|
|
|
|
|
|
2024-02-22 09:18:31 -03:00
|
|
|
for (auto& vector : m_conservative_vectors) {
|
|
|
|
|
for (auto possible_value : vector.possible_values()) {
|
|
|
|
|
add_possible_value(possible_pointers, possible_value, HeapRoot { .type = HeapRoot::Type::ConservativeVector }, min_block_address, max_block_address);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
for_each_cell_among_possible_pointers(all_live_heap_blocks, possible_pointers, [&](Cell* cell, FlatPtr possible_pointer) {
|
|
|
|
|
if (cell->state() == Cell::State::Live) {
|
2023-08-18 13:21:33 -03:00
|
|
|
dbgln_if(HEAP_DEBUG, " ?-> {}", (void const*)cell);
|
|
|
|
|
roots.set(cell, *possible_pointers.get(possible_pointer));
|
|
|
|
|
} else {
|
|
|
|
|
dbgln_if(HEAP_DEBUG, " #-> {}", (void const*)cell);
|
2020-03-16 15:08:59 -03:00
|
|
|
}
|
2023-08-18 13:21:33 -03:00
|
|
|
});
|
2020-03-16 15:08:59 -03:00
|
|
|
}
|
|
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
class MarkingVisitor final : public Cell::Visitor {
|
2020-03-08 15:23:58 -03:00
|
|
|
public:
|
2025-12-28 20:19:37 -03:00
|
|
|
explicit MarkingVisitor(Heap& heap, HashMap<Cell*, HeapRoot> const& roots, HashTable<HeapBlock*> const& all_live_heap_blocks)
|
2023-08-18 13:21:33 -03:00
|
|
|
: m_heap(heap)
|
2025-12-28 20:19:37 -03:00
|
|
|
, m_all_live_heap_blocks(all_live_heap_blocks)
|
2023-01-10 16:17:29 -03:00
|
|
|
{
|
2023-09-29 14:14:11 -03:00
|
|
|
m_heap.find_min_and_max_block_addresses(m_min_block_address, m_max_block_address);
|
LibJS: Add GC graph dumper
This change introduces a very basic GC graph dumper. The `dump_graph()`
function outputs JSON data that contains information about all nodes in
the graph, including their class types and edges.
Root nodes will have a property indicating their root type or source
location if the root is captured by a SafeFunction. It would be useful
to add source location for other types of roots in the future.
Output JSON dump have following format:
```json
"4908721208": {
"class_name": "Accessor",
"edges": [
"4909298232",
"4909297976"
]
},
"4907520440": {
"root": "SafeFunction Optional Optional.h:137",
"class_name": "Realm",
"edges": [
"4908269624",
"4924821560",
"4908409240",
"4908483960",
"4924527672"
]
},
"4908251320": {
"class_name": "CSSStyleRule",
"edges": [
"4908302648",
"4925101656",
"4908251192"
]
},
```
2023-08-17 10:34:19 -03:00
|
|
|
for (auto* root : roots.keys()) {
|
2023-01-10 16:17:29 -03:00
|
|
|
visit(root);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-08 15:23:58 -03:00
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
virtual void visit_impl(Cell& cell) override
|
2020-03-08 15:23:58 -03:00
|
|
|
{
|
2021-05-25 13:39:01 -03:00
|
|
|
if (cell.is_marked())
|
2020-03-09 18:11:22 -03:00
|
|
|
return;
|
2021-05-25 14:44:32 -03:00
|
|
|
dbgln_if(HEAP_DEBUG, " ! {}", &cell);
|
2021-09-11 11:44:40 -03:00
|
|
|
|
2021-05-25 13:39:01 -03:00
|
|
|
cell.set_marked(true);
|
2023-01-10 16:17:29 -03:00
|
|
|
m_work_queue.append(cell);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 10:22:51 -03:00
|
|
|
virtual void visit_impl(ReadonlySpan<NanBoxedValue> values) override
|
|
|
|
|
{
|
2026-01-08 17:11:26 -03:00
|
|
|
m_work_queue.grow_capacity(m_work_queue.size() + values.size());
|
2026-01-07 10:22:51 -03:00
|
|
|
|
|
|
|
|
for (auto value : values) {
|
|
|
|
|
if (!value.is_cell())
|
|
|
|
|
continue;
|
|
|
|
|
auto& cell = value.as_cell();
|
|
|
|
|
if (cell.is_marked())
|
|
|
|
|
continue;
|
|
|
|
|
dbgln_if(HEAP_DEBUG, " ! {}", &cell);
|
|
|
|
|
|
|
|
|
|
cell.set_marked(true);
|
|
|
|
|
m_work_queue.unchecked_append(cell);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-18 13:21:33 -03:00
|
|
|
virtual void visit_possible_values(ReadonlyBytes bytes) override
|
|
|
|
|
{
|
2023-09-21 21:04:16 -03:00
|
|
|
HashMap<FlatPtr, HeapRoot> possible_pointers;
|
2023-08-18 13:21:33 -03:00
|
|
|
|
|
|
|
|
auto* raw_pointer_sized_values = reinterpret_cast<FlatPtr const*>(bytes.data());
|
|
|
|
|
for (size_t i = 0; i < (bytes.size() / sizeof(FlatPtr)); ++i)
|
2023-09-29 14:14:11 -03:00
|
|
|
add_possible_value(possible_pointers, raw_pointer_sized_values[i], HeapRoot { .type = HeapRoot::Type::HeapFunctionCapturedPointer }, m_min_block_address, m_max_block_address);
|
2023-08-18 13:21:33 -03:00
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
for_each_cell_among_possible_pointers(m_all_live_heap_blocks, possible_pointers, [&](Cell* cell, FlatPtr) {
|
2023-08-18 13:21:33 -03:00
|
|
|
if (cell->is_marked())
|
|
|
|
|
return;
|
2024-11-14 12:01:23 -03:00
|
|
|
if (cell->state() != Cell::State::Live)
|
2023-08-18 13:21:33 -03:00
|
|
|
return;
|
|
|
|
|
cell->set_marked(true);
|
|
|
|
|
m_work_queue.append(*cell);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-10 16:17:29 -03:00
|
|
|
void mark_all_live_cells()
|
|
|
|
|
{
|
|
|
|
|
while (!m_work_queue.is_empty()) {
|
2024-04-05 17:47:41 -03:00
|
|
|
m_work_queue.take_last()->visit_edges(*this);
|
2023-01-10 16:17:29 -03:00
|
|
|
}
|
2020-03-09 18:11:22 -03:00
|
|
|
}
|
2023-01-10 16:17:29 -03:00
|
|
|
|
|
|
|
|
private:
|
2023-08-18 13:21:33 -03:00
|
|
|
Heap& m_heap;
|
2024-11-14 12:01:23 -03:00
|
|
|
Vector<Ref<Cell>> m_work_queue;
|
2025-12-28 20:19:37 -03:00
|
|
|
HashTable<HeapBlock*> const& m_all_live_heap_blocks;
|
2023-09-29 14:14:11 -03:00
|
|
|
FlatPtr m_min_block_address;
|
|
|
|
|
FlatPtr m_max_block_address;
|
2020-03-09 18:11:22 -03:00
|
|
|
};
|
2020-03-08 15:23:58 -03:00
|
|
|
|
2025-12-28 20:19:37 -03:00
|
|
|
void Heap::mark_live_cells(HashMap<Cell*, HeapRoot> const& roots, HashTable<HeapBlock*> const& all_live_heap_blocks)
|
2020-03-08 15:23:58 -03:00
|
|
|
{
|
2021-04-07 10:12:32 -03:00
|
|
|
dbgln_if(HEAP_DEBUG, "mark_live_cells:");
|
2021-09-07 12:14:05 -03:00
|
|
|
|
2025-12-28 20:19:37 -03:00
|
|
|
MarkingVisitor visitor(*this, roots, all_live_heap_blocks);
|
2025-12-25 06:36:05 -03:00
|
|
|
visitor.mark_all_live_cells();
|
|
|
|
|
|
|
|
|
|
for (auto& inverse_root : m_uprooted_cells)
|
|
|
|
|
inverse_root->set_marked(false);
|
|
|
|
|
|
2023-07-22 01:53:22 -03:00
|
|
|
m_uprooted_cells.clear();
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
|
|
|
|
|
2022-10-20 13:35:19 -03:00
|
|
|
void Heap::finalize_unmarked_cells()
|
|
|
|
|
{
|
|
|
|
|
for_each_block([&](auto& block) {
|
2025-12-28 21:33:12 -03:00
|
|
|
if (!block.overrides_finalize())
|
|
|
|
|
return IterationDecision::Continue;
|
2024-11-14 12:01:23 -03:00
|
|
|
block.template for_each_cell_in_state<Cell::State::Live>([](Cell* cell) {
|
2025-02-27 11:50:22 -03:00
|
|
|
if (!cell->is_marked())
|
2022-10-20 13:35:19 -03:00
|
|
|
cell->finalize();
|
|
|
|
|
});
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 06:10:01 -03:00
|
|
|
void Heap::sweep_weak_blocks()
|
|
|
|
|
{
|
|
|
|
|
for (auto& weak_block : m_usable_weak_blocks) {
|
|
|
|
|
weak_block.sweep();
|
|
|
|
|
}
|
|
|
|
|
Vector<WeakBlock&> now_usable_weak_blocks;
|
|
|
|
|
for (auto& weak_block : m_full_weak_blocks) {
|
|
|
|
|
weak_block.sweep();
|
|
|
|
|
if (weak_block.can_allocate())
|
|
|
|
|
now_usable_weak_blocks.append(weak_block);
|
|
|
|
|
}
|
|
|
|
|
for (auto& weak_block : now_usable_weak_blocks) {
|
|
|
|
|
m_usable_weak_blocks.append(weak_block);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-01 14:58:27 -03:00
|
|
|
void Heap::sweep_dead_cells(bool print_report, Core::ElapsedTimer const& measurement_timer)
|
2020-03-08 15:23:58 -03:00
|
|
|
{
|
2021-04-07 10:12:32 -03:00
|
|
|
dbgln_if(HEAP_DEBUG, "sweep_dead_cells:");
|
2020-03-21 07:45:50 -03:00
|
|
|
Vector<HeapBlock*, 32> empty_blocks;
|
2020-10-06 13:50:47 -03:00
|
|
|
Vector<HeapBlock*, 32> full_blocks_that_became_usable;
|
2020-03-21 07:45:50 -03:00
|
|
|
|
2020-08-16 15:33:56 -03:00
|
|
|
size_t collected_cells = 0;
|
|
|
|
|
size_t live_cells = 0;
|
|
|
|
|
size_t collected_cell_bytes = 0;
|
|
|
|
|
size_t live_cell_bytes = 0;
|
|
|
|
|
|
2020-10-06 13:50:47 -03:00
|
|
|
for_each_block([&](auto& block) {
|
2020-03-21 07:45:50 -03:00
|
|
|
bool block_has_live_cells = false;
|
2020-10-06 13:50:47 -03:00
|
|
|
bool block_was_full = block.is_full();
|
2024-11-14 12:01:23 -03:00
|
|
|
block.template for_each_cell_in_state<Cell::State::Live>([&](Cell* cell) {
|
2025-02-27 11:50:22 -03:00
|
|
|
if (!cell->is_marked()) {
|
2021-05-25 13:35:27 -03:00
|
|
|
dbgln_if(HEAP_DEBUG, " ~ {}", cell);
|
2022-02-04 12:12:39 -03:00
|
|
|
block.deallocate(cell);
|
2021-05-25 13:35:27 -03:00
|
|
|
++collected_cells;
|
|
|
|
|
collected_cell_bytes += block.cell_size();
|
|
|
|
|
} else {
|
|
|
|
|
cell->set_marked(false);
|
|
|
|
|
block_has_live_cells = true;
|
|
|
|
|
++live_cells;
|
|
|
|
|
live_cell_bytes += block.cell_size();
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
|
|
|
|
});
|
2020-03-21 07:45:50 -03:00
|
|
|
if (!block_has_live_cells)
|
2020-10-06 13:50:47 -03:00
|
|
|
empty_blocks.append(&block);
|
|
|
|
|
else if (block_was_full != block.is_full())
|
|
|
|
|
full_blocks_that_became_usable.append(&block);
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
2020-03-21 07:45:50 -03:00
|
|
|
|
2021-10-08 14:47:25 -03:00
|
|
|
for (auto& weak_container : m_weak_containers)
|
|
|
|
|
weak_container.remove_dead_cells({});
|
|
|
|
|
|
2026-03-07 18:18:15 -03:00
|
|
|
for (auto& callback : m_sweep_callbacks)
|
|
|
|
|
callback();
|
|
|
|
|
|
2020-03-21 07:45:50 -03:00
|
|
|
for (auto* block : empty_blocks) {
|
2021-04-07 10:12:32 -03:00
|
|
|
dbgln_if(HEAP_DEBUG, " - HeapBlock empty @ {}: cell_size={}", block, block->cell_size());
|
2023-12-23 11:13:51 -03:00
|
|
|
block->cell_allocator().block_did_become_empty({}, *block);
|
2020-03-21 07:45:50 -03:00
|
|
|
}
|
|
|
|
|
|
2020-10-06 13:50:47 -03:00
|
|
|
for (auto* block : full_blocks_that_became_usable) {
|
2021-04-07 10:12:32 -03:00
|
|
|
dbgln_if(HEAP_DEBUG, " - HeapBlock usable again @ {}: cell_size={}", block, block->cell_size());
|
2023-12-23 11:13:51 -03:00
|
|
|
block->cell_allocator().block_did_become_usable({}, *block);
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
2020-10-06 13:50:47 -03:00
|
|
|
|
2021-04-18 13:12:33 -03:00
|
|
|
if constexpr (HEAP_DEBUG) {
|
|
|
|
|
for_each_block([&](auto& block) {
|
|
|
|
|
dbgln(" > Live HeapBlock @ {}: cell_size={}", &block, block.cell_size());
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-08-16 15:33:56 -03:00
|
|
|
|
2023-08-08 14:50:32 -03:00
|
|
|
m_gc_bytes_threshold = live_cell_bytes > GC_MIN_BYTES_THRESHOLD ? live_cell_bytes : GC_MIN_BYTES_THRESHOLD;
|
|
|
|
|
|
2020-08-16 15:33:56 -03:00
|
|
|
if (print_report) {
|
2024-07-17 02:45:00 -03:00
|
|
|
AK::Duration const time_spent = measurement_timer.elapsed_time();
|
2020-10-06 13:50:47 -03:00
|
|
|
size_t live_block_count = 0;
|
|
|
|
|
for_each_block([&](auto&) {
|
|
|
|
|
++live_block_count;
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
});
|
|
|
|
|
|
2020-10-04 11:44:40 -03:00
|
|
|
dbgln("Garbage collection report");
|
|
|
|
|
dbgln("=============================================");
|
2023-01-02 02:45:28 -03:00
|
|
|
dbgln(" Time spent: {} ms", time_spent.to_milliseconds());
|
2020-10-04 11:44:40 -03:00
|
|
|
dbgln(" Live cells: {} ({} bytes)", live_cells, live_cell_bytes);
|
|
|
|
|
dbgln("Collected cells: {} ({} bytes)", collected_cells, collected_cell_bytes);
|
2025-12-18 17:16:18 -03:00
|
|
|
dbgln(" Live blocks: {} ({} bytes)", live_block_count, live_block_count * HeapBlock::BLOCK_SIZE);
|
|
|
|
|
dbgln(" Freed blocks: {} ({} bytes)", empty_blocks.size(), empty_blocks.size() * HeapBlock::BLOCK_SIZE);
|
2020-10-04 11:44:40 -03:00
|
|
|
dbgln("=============================================");
|
2020-08-16 15:33:56 -03:00
|
|
|
}
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|
2020-03-18 16:03:17 -03:00
|
|
|
|
2023-09-23 08:44:13 -03:00
|
|
|
void Heap::defer_gc()
|
2020-04-19 06:30:47 -03:00
|
|
|
{
|
|
|
|
|
++m_gc_deferrals;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-23 08:44:13 -03:00
|
|
|
void Heap::undefer_gc()
|
2020-04-19 06:30:47 -03:00
|
|
|
{
|
2021-02-23 16:42:32 -03:00
|
|
|
VERIFY(m_gc_deferrals > 0);
|
2020-04-19 06:30:47 -03:00
|
|
|
--m_gc_deferrals;
|
|
|
|
|
|
|
|
|
|
if (!m_gc_deferrals) {
|
|
|
|
|
if (m_should_gc_when_deferral_ends)
|
|
|
|
|
collect_garbage();
|
|
|
|
|
m_should_gc_when_deferral_ends = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-14 12:01:23 -03:00
|
|
|
void Heap::uproot_cell(Cell* cell)
|
2023-07-22 01:53:22 -03:00
|
|
|
{
|
|
|
|
|
m_uprooted_cells.append(cell);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 06:10:01 -03:00
|
|
|
WeakImpl* Heap::create_weak_impl(void* ptr)
|
|
|
|
|
{
|
|
|
|
|
if (m_usable_weak_blocks.is_empty()) {
|
|
|
|
|
// NOTE: These are leaked on Heap destruction, but that's fine since Heap is tied to process lifetime.
|
|
|
|
|
auto* weak_block = WeakBlock::create();
|
|
|
|
|
m_usable_weak_blocks.append(*weak_block);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto* weak_block = m_usable_weak_blocks.first();
|
|
|
|
|
auto* new_weak_impl = weak_block->allocate(static_cast<Cell*>(ptr));
|
|
|
|
|
if (!weak_block->can_allocate()) {
|
|
|
|
|
m_full_weak_blocks.append(*weak_block);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new_weak_impl;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-08 15:23:58 -03:00
|
|
|
}
|