mirror of
https://github.com/zebrajr/ladybird.git
synced 2026-01-15 12:15:15 +00:00
LibGC: Add GC::Weak<T> as an alternative to AK::WeakPtr<T>
This is a weak pointer that integrates with the garbage collector. It has a number of differences compared to AK::WeakPtr, including: - The "control block" is allocated from a well-packed WeakBlock owned by the GC heap, not just a generic malloc allocation. - Pointers to dead cells are nulled out by the garbage collector immediately before running destructors. - It works on any GC::Cell derived type, meaning you don't have to inherit from AK::Weakable for the ability to be weakly referenced. - The Weak always points to a control block, even when "null" (it then points to a null WeakImpl), which means one less null check when chasing pointers.
This commit is contained in:
committed by
Andreas Kling
parent
127208f3d6
commit
25a5ed94d6
@@ -276,11 +276,13 @@
|
||||
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) __asan_unpoison_memory_region(addr, size)
|
||||
# define LSAN_REGISTER_ROOT_REGION(base, size) __lsan_register_root_region(base, size)
|
||||
# define LSAN_UNREGISTER_ROOT_REGION(base, size) __lsan_unregister_root_region(base, size)
|
||||
# define LSAN_IGNORE_OBJECT(base) __lsan_ignore_object(base)
|
||||
#else
|
||||
# define ASAN_POISON_MEMORY_REGION(addr, size)
|
||||
# define ASAN_UNPOISON_MEMORY_REGION(addr, size)
|
||||
# define LSAN_REGISTER_ROOT_REGION(base, size)
|
||||
# define LSAN_UNREGISTER_ROOT_REGION(base, size)
|
||||
# define LSAN_IGNORE_OBJECT(base)
|
||||
#endif
|
||||
|
||||
#if __has_feature(blocks)
|
||||
|
||||
@@ -9,6 +9,7 @@ set(SOURCES
|
||||
RootVector.cpp
|
||||
Heap.cpp
|
||||
HeapBlock.cpp
|
||||
WeakBlock.cpp
|
||||
WeakContainer.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class Heap;
|
||||
class HeapBlock;
|
||||
class NanBoxedValue;
|
||||
class WeakContainer;
|
||||
class WeakImpl;
|
||||
|
||||
template<typename T>
|
||||
class Function;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <LibGC/HeapBlock.h>
|
||||
#include <LibGC/NanBoxedValue.h>
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibGC/Weak.h>
|
||||
#include <LibGC/WeakInlines.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#ifdef HAS_ADDRESS_SANITIZER
|
||||
@@ -258,6 +260,7 @@ void Heap::collect_garbage(CollectionType collection_type, bool print_report)
|
||||
mark_live_cells(roots);
|
||||
}
|
||||
finalize_unmarked_cells();
|
||||
sweep_weak_blocks();
|
||||
sweep_dead_cells(print_report, collection_measurement_timer);
|
||||
}
|
||||
|
||||
@@ -462,6 +465,22 @@ void Heap::finalize_unmarked_cells()
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Heap::sweep_dead_cells(bool print_report, Core::ElapsedTimer const& measurement_timer)
|
||||
{
|
||||
dbgln_if(HEAP_DEBUG, "sweep_dead_cells:");
|
||||
@@ -559,4 +578,21 @@ void Heap::uproot_cell(Cell* cell)
|
||||
m_uprooted_cells.append(cell);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibGC/RootHashMap.h>
|
||||
#include <LibGC/RootVector.h>
|
||||
#include <LibGC/WeakBlock.h>
|
||||
#include <LibGC/WeakContainer.h>
|
||||
|
||||
namespace GC {
|
||||
@@ -81,6 +82,8 @@ public:
|
||||
|
||||
void enqueue_post_gc_task(AK::Function<void()>);
|
||||
|
||||
WeakImpl* create_weak_impl(void*);
|
||||
|
||||
private:
|
||||
friend class MarkingVisitor;
|
||||
friend class GraphConstructorVisitor;
|
||||
@@ -113,6 +116,7 @@ private:
|
||||
void mark_live_cells(HashMap<Cell*, HeapRoot> const& live_cells);
|
||||
void finalize_unmarked_cells();
|
||||
void sweep_dead_cells(bool print_report, Core::ElapsedTimer const&);
|
||||
void sweep_weak_blocks();
|
||||
|
||||
ALWAYS_INLINE CellAllocator& allocator_for_size(size_t cell_size)
|
||||
{
|
||||
@@ -159,6 +163,9 @@ private:
|
||||
AK::Function<void(HashMap<Cell*, GC::HeapRoot>&)> m_gather_embedder_roots;
|
||||
|
||||
Vector<AK::Function<void()>> m_post_gc_tasks;
|
||||
|
||||
WeakBlock::List m_usable_weak_blocks;
|
||||
WeakBlock::List m_full_weak_blocks;
|
||||
} SWIFT_IMMORTAL_REFERENCE;
|
||||
|
||||
inline void Heap::did_create_root(Badge<RootImpl>, RootImpl& impl)
|
||||
|
||||
166
Libraries/LibGC/Weak.h
Normal file
166
Libraries/LibGC/Weak.h
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <LibGC/Export.h>
|
||||
#include <LibGC/Ptr.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
class WeakBlock;
|
||||
|
||||
class WeakImpl {
|
||||
public:
|
||||
// NOTE: Null GC::Weaks point at this WeakImpl. This allows Weak to always chase the impl pointer without null-checking it.
|
||||
static GC_API WeakImpl the_null_weak_impl;
|
||||
|
||||
WeakImpl() = default;
|
||||
WeakImpl(void* ptr)
|
||||
: m_ptr(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
void* ptr() const { return m_ptr; }
|
||||
void set_ptr(Badge<WeakBlock>, void* ptr) { m_ptr = ptr; }
|
||||
|
||||
bool operator==(WeakImpl const& other) const { return m_ptr == other.m_ptr; }
|
||||
bool operator!=(WeakImpl const& other) const { return m_ptr != other.m_ptr; }
|
||||
|
||||
void ref() const { ++m_ref_count; }
|
||||
void unref() const
|
||||
{
|
||||
VERIFY(m_ref_count);
|
||||
--m_ref_count;
|
||||
}
|
||||
|
||||
size_t ref_count() const { return m_ref_count; }
|
||||
|
||||
enum class State {
|
||||
Allocated,
|
||||
Freelist,
|
||||
};
|
||||
|
||||
void set_state(State state) { m_state = state; }
|
||||
State state() const { return m_state; }
|
||||
|
||||
private:
|
||||
mutable size_t m_ref_count { 0 };
|
||||
State m_state { State::Allocated };
|
||||
void* m_ptr { nullptr };
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class Weak {
|
||||
public:
|
||||
constexpr Weak() = default;
|
||||
Weak(nullptr_t) { }
|
||||
|
||||
Weak(T const* ptr);
|
||||
Weak(T const& ptr);
|
||||
|
||||
template<typename U>
|
||||
Weak(Weak<U> const& other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
Weak(Ref<T> const& other);
|
||||
|
||||
template<typename U>
|
||||
Weak(Ref<U> const& other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
template<typename U>
|
||||
Weak& operator=(Weak<U> const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
{
|
||||
m_impl = other.impl();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Weak& operator=(Ref<T> const& other);
|
||||
|
||||
template<typename U>
|
||||
Weak& operator=(Ref<U> const& other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
Weak& operator=(T const& other);
|
||||
|
||||
template<typename U>
|
||||
Weak& operator=(U const& other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
Weak& operator=(T const* other);
|
||||
|
||||
template<typename U>
|
||||
Weak& operator=(U const* other)
|
||||
requires(IsConvertible<U*, T*>);
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
ASSERT(ptr());
|
||||
return ptr();
|
||||
}
|
||||
|
||||
[[nodiscard]] T& operator*() const
|
||||
{
|
||||
ASSERT(ptr());
|
||||
return *ptr();
|
||||
}
|
||||
|
||||
Ptr<T> ptr() const { return static_cast<T*>(impl().ptr()); }
|
||||
|
||||
explicit operator bool() const { return !!ptr(); }
|
||||
bool operator!() const { return !ptr(); }
|
||||
|
||||
operator T*() const { return ptr(); }
|
||||
|
||||
Ref<T> as_nonnull() const
|
||||
{
|
||||
ASSERT(ptr());
|
||||
return *ptr();
|
||||
}
|
||||
|
||||
WeakImpl& impl() const { return *m_impl; }
|
||||
|
||||
private:
|
||||
NonnullRefPtr<WeakImpl> m_impl { WeakImpl::the_null_weak_impl };
|
||||
};
|
||||
|
||||
template<typename T, typename U>
|
||||
inline bool operator==(Weak<T> const& a, Ptr<U> const& b)
|
||||
{
|
||||
return a.ptr() == b.ptr();
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
inline bool operator==(Weak<T> const& a, Ref<U> const& b)
|
||||
{
|
||||
return a.ptr() == b.ptr();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
|
||||
template<typename T>
|
||||
struct Traits<GC::Weak<T>> : public DefaultTraits<GC::Weak<T>> {
|
||||
static unsigned hash(GC::Weak<T> const& value)
|
||||
{
|
||||
return Traits<T*>::hash(value.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Formatter<GC::Weak<T>> : Formatter<T const*> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, GC::Weak<T> const& value)
|
||||
{
|
||||
return Formatter<T const*>::format(builder, value.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
77
Libraries/LibGC/WeakBlock.cpp
Normal file
77
Libraries/LibGC/WeakBlock.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/Cell.h>
|
||||
#include <LibGC/WeakBlock.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
# include <AK/Windows.h>
|
||||
# include <memoryapi.h>
|
||||
#endif
|
||||
|
||||
namespace GC {
|
||||
|
||||
WeakImpl WeakImpl::the_null_weak_impl;
|
||||
|
||||
WeakBlock* WeakBlock::create()
|
||||
{
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
auto* block = (HeapBlock*)mmap(nullptr, WeakBlock::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
VERIFY(block != MAP_FAILED);
|
||||
#else
|
||||
auto* block = (HeapBlock*)VirtualAlloc(NULL, WeakBlock::BLOCK_SIZE, MEM_COMMIT, PAGE_READWRITE);
|
||||
VERIFY(block);
|
||||
#endif
|
||||
return new (block) WeakBlock;
|
||||
}
|
||||
|
||||
WeakBlock::WeakBlock()
|
||||
{
|
||||
for (size_t i = 0; i < IMPL_COUNT; ++i) {
|
||||
m_impls[i].set_ptr({}, i + 1 < IMPL_COUNT ? &m_impls[i + 1] : nullptr);
|
||||
m_impls[i].set_state(WeakImpl::State::Freelist);
|
||||
}
|
||||
m_freelist = &m_impls[0];
|
||||
}
|
||||
|
||||
WeakBlock::~WeakBlock() = default;
|
||||
|
||||
WeakImpl* WeakBlock::allocate(Cell* cell)
|
||||
{
|
||||
auto* impl = m_freelist;
|
||||
if (!impl)
|
||||
return nullptr;
|
||||
VERIFY(impl->ref_count() == 0);
|
||||
m_freelist = impl->ptr() ? static_cast<WeakImpl*>(impl->ptr()) : nullptr;
|
||||
impl->set_ptr({}, cell);
|
||||
impl->set_state(WeakImpl::State::Allocated);
|
||||
return impl;
|
||||
}
|
||||
|
||||
void WeakBlock::deallocate(WeakImpl* impl)
|
||||
{
|
||||
VERIFY(impl->ref_count() == 0);
|
||||
impl->set_ptr({}, m_freelist);
|
||||
impl->set_state(WeakImpl::State::Freelist);
|
||||
m_freelist = impl;
|
||||
}
|
||||
|
||||
void WeakBlock::sweep()
|
||||
{
|
||||
for (size_t i = 0; i < IMPL_COUNT; ++i) {
|
||||
auto& impl = m_impls[i];
|
||||
if (impl.state() == WeakImpl::State::Freelist)
|
||||
continue;
|
||||
auto* cell = static_cast<Cell*>(impl.ptr());
|
||||
if (!cell || !cell->is_marked())
|
||||
impl.set_ptr({}, nullptr);
|
||||
if (impl.ref_count() == 0)
|
||||
deallocate(&impl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
45
Libraries/LibGC/WeakBlock.h
Normal file
45
Libraries/LibGC/WeakBlock.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/IntrusiveList.h>
|
||||
#include <LibGC/Forward.h>
|
||||
#include <LibGC/Weak.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
class GC_API WeakBlock {
|
||||
public:
|
||||
static constexpr size_t BLOCK_SIZE = 16 * KiB;
|
||||
|
||||
static WeakBlock* create();
|
||||
|
||||
WeakImpl* allocate(Cell*);
|
||||
void deallocate(WeakImpl*);
|
||||
|
||||
bool can_allocate() const { return m_freelist != nullptr; }
|
||||
|
||||
void sweep();
|
||||
|
||||
private:
|
||||
WeakBlock();
|
||||
~WeakBlock();
|
||||
|
||||
IntrusiveListNode<WeakBlock> m_list_node;
|
||||
|
||||
public:
|
||||
using List = IntrusiveList<&WeakBlock::m_list_node>;
|
||||
|
||||
WeakImpl* m_freelist { nullptr };
|
||||
|
||||
static constexpr size_t IMPL_COUNT = (BLOCK_SIZE - sizeof(m_list_node) - sizeof(WeakImpl*)) / sizeof(WeakImpl);
|
||||
WeakImpl m_impls[IMPL_COUNT];
|
||||
};
|
||||
|
||||
static_assert(sizeof(WeakBlock) <= WeakBlock::BLOCK_SIZE);
|
||||
|
||||
}
|
||||
113
Libraries/LibGC/WeakInlines.h
Normal file
113
Libraries/LibGC/WeakInlines.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGC/Weak.h>
|
||||
|
||||
namespace GC {
|
||||
|
||||
template<typename T>
|
||||
Weak<T>::Weak(T const* ptr)
|
||||
: m_impl(ptr ? *ptr->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(ptr))) : WeakImpl::the_null_weak_impl)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>::Weak(T const& ptr)
|
||||
: m_impl(*ptr.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&ptr))))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>::Weak(Weak<U> const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
: m_impl(other.impl())
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>::Weak(Ref<T> const& other)
|
||||
: m_impl(*other.ptr()->heap().create_weak_impl(other.ptr()))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>::Weak(Ref<U> const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
: m_impl(*other.ptr()->heap().create_weak_impl(other.ptr()))
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>& Weak<T>::operator=(U const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
{
|
||||
if (ptr() != other) {
|
||||
m_impl = *other.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&other)));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>& Weak<T>::operator=(Ref<T> const& other)
|
||||
{
|
||||
if (ptr() != other.ptr()) {
|
||||
m_impl = *other.ptr()->heap().create_weak_impl(other.ptr());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>& Weak<T>::operator=(Ref<U> const& other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
{
|
||||
if (ptr() != other.ptr()) {
|
||||
m_impl = *other.ptr()->heap().create_weak_impl(other.ptr());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>& Weak<T>::operator=(T const& other)
|
||||
{
|
||||
if (ptr() != &other) {
|
||||
m_impl = *other.heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(&other)));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Weak<T>& Weak<T>::operator=(T const* other)
|
||||
{
|
||||
if (ptr() != other) {
|
||||
if (other)
|
||||
m_impl = *other->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(other)));
|
||||
else
|
||||
m_impl = WeakImpl::the_null_weak_impl;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
template<typename U>
|
||||
Weak<T>& Weak<T>::operator=(U const* other)
|
||||
requires(IsConvertible<U*, T*>)
|
||||
{
|
||||
if (ptr() != other) {
|
||||
if (other)
|
||||
m_impl = *other->heap().create_weak_impl(const_cast<void*>(static_cast<void const*>(other)));
|
||||
else
|
||||
m_impl = WeakImpl::the_null_weak_impl;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user