LibGC: Only call finalize() on types that override finalize()

This dramatically cuts down on time spent in the GC's finalizer pass,
since most types don't override finalize().
This commit is contained in:
Andreas Kling 2025-12-29 01:33:12 +01:00 committed by Andreas Kling
parent 75ad452099
commit 2ac363dcba
44 changed files with 105 additions and 22 deletions

View file

@ -43,6 +43,7 @@ class GC_API Cell {
public:
static constexpr bool OVERRIDES_MUST_SURVIVE_GARBAGE_COLLECTION = false;
static constexpr bool OVERRIDES_FINALIZE = false;
virtual ~Cell() = default;

View file

@ -12,10 +12,11 @@
namespace GC {
CellAllocator::CellAllocator(size_t cell_size, StringView class_name, bool overrides_must_survive_garbage_collection)
CellAllocator::CellAllocator(size_t cell_size, StringView class_name, bool overrides_must_survive_garbage_collection, bool overrides_finalize)
: m_class_name(class_name)
, m_cell_size(cell_size)
, m_overrides_must_survive_garbage_collection(overrides_must_survive_garbage_collection)
, m_overrides_finalize(overrides_finalize)
{
}
@ -25,7 +26,7 @@ Cell* CellAllocator::allocate_cell(Heap& heap)
heap.register_cell_allocator({}, *this);
if (m_usable_blocks.is_empty()) {
auto block = HeapBlock::create_with_cell_size(heap, *this, m_cell_size, m_class_name, m_overrides_must_survive_garbage_collection);
auto block = HeapBlock::create_with_cell_size(heap, *this, m_cell_size, m_class_name, m_overrides_must_survive_garbage_collection, m_overrides_finalize);
auto block_ptr = reinterpret_cast<FlatPtr>(block.ptr());
if (m_min_block_address > block_ptr)
m_min_block_address = block_ptr;

View file

@ -17,13 +17,13 @@
static GC::TypeIsolatingCellAllocator<ClassName> cell_allocator
#define GC_DEFINE_ALLOCATOR(ClassName) \
GC::TypeIsolatingCellAllocator<ClassName> ClassName::cell_allocator { #ClassName##sv, ClassName::OVERRIDES_MUST_SURVIVE_GARBAGE_COLLECTION }
GC::TypeIsolatingCellAllocator<ClassName> ClassName::cell_allocator { #ClassName##sv, ClassName::OVERRIDES_MUST_SURVIVE_GARBAGE_COLLECTION, ClassName::OVERRIDES_FINALIZE }
namespace GC {
class GC_API CellAllocator {
public:
CellAllocator(size_t cell_size, StringView = {}, bool overrides_must_survive_garbage_collection = false);
CellAllocator(size_t cell_size, StringView = {}, bool overrides_must_survive_garbage_collection = false, bool overrides_finalize = false);
~CellAllocator() = default;
StringView class_name() const { return m_class_name; }
@ -67,6 +67,7 @@ private:
FlatPtr m_min_block_address { explode_byte(0xff) };
FlatPtr m_max_block_address { 0 };
bool m_overrides_must_survive_garbage_collection { false };
bool m_overrides_finalize { false };
};
template<typename T>
@ -74,8 +75,8 @@ class GC_API TypeIsolatingCellAllocator {
public:
using CellType = T;
TypeIsolatingCellAllocator(StringView class_name, bool overrides_must_survive_garbage_collection)
: allocator(sizeof(T), class_name, overrides_must_survive_garbage_collection)
TypeIsolatingCellAllocator(StringView class_name, bool overrides_must_survive_garbage_collection, bool overrides_finalize)
: allocator(sizeof(T), class_name, overrides_must_survive_garbage_collection, overrides_finalize)
{
}

View file

@ -28,6 +28,8 @@ class GC_API ForeignCell : public Cell {
FOREIGN_CELL(ForeignCell, Cell);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
struct Vtable {
// Holds a pointer to the foreign vtable information such as
// a jclass in Java, or a Swift type metadata pointer

View file

@ -372,6 +372,15 @@ void Heap::gather_roots(HashMap<Cell*, HeapRoot>& roots, HashTable<HeapBlock*>&
{
for_each_block([&](auto& block) {
all_live_heap_blocks.set(&block);
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 });
}
});
}
return IterationDecision::Continue;
});
@ -387,17 +396,6 @@ void Heap::gather_roots(HashMap<Cell*, HeapRoot>& roots, HashTable<HeapBlock*>&
for (auto& hash_map : m_root_hash_maps)
hash_map.gather_roots(roots);
for_each_block([&](HeapBlock& block) {
if (!block.overrides_must_survive_garbage_collection())
return IterationDecision::Continue;
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 });
}
});
return IterationDecision::Continue;
});
if constexpr (HEAP_DEBUG) {
dbgln("gather_roots:");
for (auto* root : roots.keys())
@ -541,6 +539,8 @@ void Heap::mark_live_cells(HashMap<Cell*, HeapRoot> const& roots, HashTable<Heap
void Heap::finalize_unmarked_cells()
{
for_each_block([&](auto& block) {
if (!block.overrides_finalize())
return IterationDecision::Continue;
block.template for_each_cell_in_state<Cell::State::Live>([](Cell* cell) {
if (!cell->is_marked())
cell->finalize();

View file

@ -18,19 +18,20 @@
namespace GC {
NonnullOwnPtr<HeapBlock> HeapBlock::create_with_cell_size(Heap& heap, CellAllocator& cell_allocator, size_t cell_size, [[maybe_unused]] StringView class_name, bool overrides_must_survive_garbage_collection)
NonnullOwnPtr<HeapBlock> HeapBlock::create_with_cell_size(Heap& heap, CellAllocator& cell_allocator, size_t cell_size, [[maybe_unused]] StringView class_name, bool overrides_must_survive_garbage_collection, bool overrides_finalize)
{
char const* name = nullptr;
auto* block = static_cast<HeapBlock*>(cell_allocator.block_allocator().allocate_block(name));
new (block) HeapBlock(heap, cell_allocator, cell_size, overrides_must_survive_garbage_collection);
new (block) HeapBlock(heap, cell_allocator, cell_size, overrides_must_survive_garbage_collection, overrides_finalize);
return NonnullOwnPtr<HeapBlock>(NonnullOwnPtr<HeapBlock>::Adopt, *block);
}
HeapBlock::HeapBlock(Heap& heap, CellAllocator& cell_allocator, size_t cell_size, bool overrides_must_survive_garbage_collection)
HeapBlock::HeapBlock(Heap& heap, CellAllocator& cell_allocator, size_t cell_size, bool overrides_must_survive_garbage_collection, bool overrides_finalize)
: HeapBlockBase(heap)
, m_cell_allocator(cell_allocator)
, m_cell_size(cell_size)
, m_overrides_must_survive_garbage_collection(overrides_must_survive_garbage_collection)
, m_overrides_finalize(overrides_finalize)
{
VERIFY(cell_size >= sizeof(FreelistEntry));
ASAN_POISON_MEMORY_REGION(m_storage, BLOCK_SIZE - sizeof(HeapBlock));

View file

@ -26,7 +26,7 @@ class GC_API HeapBlock : public HeapBlockBase {
public:
using HeapBlockBase::BLOCK_SIZE;
static NonnullOwnPtr<HeapBlock> create_with_cell_size(Heap&, CellAllocator&, size_t cell_size, StringView class_name, bool overrides_must_survive_garbage_collection);
static NonnullOwnPtr<HeapBlock> create_with_cell_size(Heap&, CellAllocator&, size_t cell_size, StringView class_name, bool overrides_must_survive_garbage_collection, bool overrides_finalize);
size_t cell_size() const { return m_cell_size; }
size_t cell_count() const { return (HeapBlock::BLOCK_SIZE - sizeof(HeapBlock)) / m_cell_size; }
@ -93,9 +93,10 @@ public:
CellAllocator& cell_allocator() { return m_cell_allocator; }
bool overrides_must_survive_garbage_collection() const { return m_overrides_must_survive_garbage_collection; }
bool overrides_finalize() const { return m_overrides_finalize; }
private:
HeapBlock(Heap&, CellAllocator&, size_t cell_size, bool overrides_must_survive_garbage_collection);
HeapBlock(Heap&, CellAllocator&, size_t cell_size, bool overrides_must_survive_garbage_collection, bool overrides_finalize);
bool has_lazy_freelist() const { return m_next_lazy_freelist_index < cell_count(); }
@ -115,6 +116,7 @@ private:
u32 m_next_lazy_freelist_index { 0 };
bool m_overrides_must_survive_garbage_collection { false };
bool m_overrides_finalize { false };
Ptr<FreelistEntry> m_freelist;
alignas(__BIGGEST_ALIGNMENT__) u8 m_storage[];

View file

@ -29,6 +29,8 @@ class Animation : public DOM::EventTarget {
GC_DECLARE_ALLOCATOR(Animation);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
static GC::Ref<Animation> create(JS::Realm&, GC::Ptr<AnimationEffect>, Optional<GC::Ptr<AnimationTimeline>>);
static WebIDL::ExceptionOr<GC::Ref<Animation>> construct_impl(JS::Realm&, GC::Ptr<AnimationEffect>, Optional<GC::Ptr<AnimationTimeline>>);

View file

@ -18,6 +18,8 @@ class AnimationTimeline : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(AnimationTimeline);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
NullableCSSNumberish current_time_for_bindings() const
{
return NullableCSSNumberish::from_optional_css_numberish_time(current_time());

View file

@ -181,6 +181,8 @@ class WEB_API Document
GC_DECLARE_ALLOCATOR(Document);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
enum class Type {
XML,
HTML

View file

@ -21,6 +21,8 @@ class WEB_API DocumentObserver final : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(DocumentObserver);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
[[nodiscard]] GC::Ptr<GC::Function<void()>> document_became_active() const { return m_document_became_active; }
void set_document_became_active(Function<void()>);

View file

@ -148,6 +148,8 @@ class WEB_API Node : public EventTarget
WEB_PLATFORM_OBJECT(Node, EventTarget);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
ParentNode* parent_or_shadow_host();
ParentNode const* parent_or_shadow_host() const { return const_cast<Node*>(this)->parent_or_shadow_host(); }

View file

@ -17,6 +17,8 @@ class NodeIterator final : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(NodeIterator);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
static GC::Ref<NodeIterator> create(JS::Realm& realm, Node& root, unsigned what_to_show, GC::Ptr<NodeFilter>);
virtual ~NodeIterator() override;

View file

@ -20,6 +20,8 @@ class WEB_API ShadowRoot final : public DocumentFragment {
GC_DECLARE_ALLOCATOR(ShadowRoot);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
Bindings::ShadowRootMode mode() const { return m_mode; }
Bindings::SlotAssignmentMode slot_assignment() const { return m_slot_assignment; }

View file

@ -17,6 +17,8 @@ class WEB_API FetchRecord final : public JS::Cell {
GC_DECLARE_ALLOCATOR(FetchRecord);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
[[nodiscard]] static GC::Ref<FetchRecord> create(JS::VM&, GC::Ref<Infrastructure::Request>);
[[nodiscard]] static GC::Ref<FetchRecord> create(JS::VM&, GC::Ref<Infrastructure::Request>, GC::Ptr<FetchController>);

View file

@ -20,6 +20,8 @@ class Gamepad final : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(Gamepad);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
static GC::Ref<Gamepad> create(JS::Realm&, SDL_JoystickID);
SDL_JoystickID sdl_joystick_id() const { return m_sdl_joystick_id; }

View file

@ -16,6 +16,8 @@ class BroadcastChannel final : public DOM::EventTarget {
GC_DECLARE_ALLOCATOR(BroadcastChannel);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
[[nodiscard]] static GC::Ref<BroadcastChannel> construct_impl(JS::Realm&, FlyString const& name);
// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-broadcastchannel-name

View file

@ -20,6 +20,8 @@ class WEB_API DedicatedWorkerGlobalScope
GC_DECLARE_ALLOCATOR(DedicatedWorkerGlobalScope);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~DedicatedWorkerGlobalScope() override;
WebIDL::ExceptionOr<void> post_message(JS::Value message, StructuredSerializeOptions const&);

View file

@ -29,6 +29,8 @@ class EventSource : public DOM::EventTarget {
GC_DECLARE_ALLOCATOR(EventSource);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~EventSource() override;
static WebIDL::ExceptionOr<GC::Ref<EventSource>> construct_impl(JS::Realm&, StringView url, EventSourceInit event_source_init_dict = {});

View file

@ -35,6 +35,8 @@ class HTMLImageElement final
LAZY_LOADING_ELEMENT(HTMLImageElement);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~HTMLImageElement() override;
virtual void form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;

View file

@ -37,6 +37,8 @@ class HTMLMediaElement : public HTMLElement {
WEB_PLATFORM_OBJECT(HTMLMediaElement, HTMLElement);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~HTMLMediaElement() override;
virtual bool is_focusable() const override { return true; }

View file

@ -25,6 +25,8 @@ class HTMLVideoElement final : public HTMLMediaElement {
GC_DECLARE_ALLOCATOR(HTMLVideoElement);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~HTMLVideoElement() override;
Layout::VideoBox* layout_node();

View file

@ -28,6 +28,8 @@ class WEB_API MessagePort final
GC_DECLARE_ALLOCATOR(MessagePort);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
[[nodiscard]] static GC::Ref<MessagePort> create(JS::Realm&);
static void for_each_message_port(Function<void(MessagePort&)>);

View file

@ -46,6 +46,8 @@ class WEB_API Navigable : public JS::Cell {
GC_DECLARE_ALLOCATOR(Navigable);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~Navigable() override;
using NullOrError = Optional<String>;

View file

@ -19,6 +19,8 @@ class WEB_API NavigationObserver final : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(NavigationObserver);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
[[nodiscard]] GC::Ptr<GC::Function<void()>> navigation_complete() const { return m_navigation_complete; }
void set_navigation_complete(Function<void()>);

View file

@ -78,6 +78,8 @@ struct WEB_API EnvironmentSettingsObject : public Environment {
GC_CELL(EnvironmentSettingsObject, Environment);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual void finalize() override;
virtual void initialize(JS::Realm&) override;

View file

@ -19,6 +19,8 @@ class SharedResourceRequest final : public JS::Cell {
GC_DECLARE_ALLOCATOR(SharedResourceRequest);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
[[nodiscard]] static GC::Ref<SharedResourceRequest> get_or_create(JS::Realm&, GC::Ref<Page>, URL::URL const&);
virtual ~SharedResourceRequest() override;

View file

@ -24,6 +24,8 @@ class WEB_API SharedWorkerGlobalScope
GC_DECLARE_ALLOCATOR(SharedWorkerGlobalScope);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~SharedWorkerGlobalScope() override;
void set_constructor_origin(URL::Origin origin) { m_constructor_origin = move(origin); }

View file

@ -21,6 +21,8 @@ class WEB_API Storage : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(Storage);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
// https://html.spec.whatwg.org/multipage/webstorage.html#concept-storage-type
enum class Type {
Local,

View file

@ -19,6 +19,8 @@ class TextTrackObserver final : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(TextTrackObserver);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
[[nodiscard]] GC::Ptr<GC::Function<void(TextTrack::ReadinessState)>> track_readiness_observer() const { return m_track_readiness_observer; }
void set_track_readiness_observer(Function<void(TextTrack::ReadinessState)>);

View file

@ -74,6 +74,8 @@ class WEB_API Window final
GC_DECLARE_ALLOCATOR(Window);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
[[nodiscard]] static GC::Ref<Window> create(JS::Realm&);
~Window();

View file

@ -42,6 +42,8 @@ class WEB_API WorkerGlobalScope
GC_DECLARE_ALLOCATOR(WorkerGlobalScope);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~WorkerGlobalScope() override;
// ^WindowOrWorkerGlobalScopeMixin

View file

@ -18,6 +18,8 @@ class IDBDatabaseObserver final : public GC::Cell {
GC_DECLARE_ALLOCATOR(IDBDatabaseObserver);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~IDBDatabaseObserver();
[[nodiscard]] GC::Ptr<GC::Function<void()>> connection_state_changed_observer() const { return m_connection_state_changed_observer; }

View file

@ -17,6 +17,8 @@ class IDBRequestObserver final : public GC::Cell {
GC_DECLARE_ALLOCATOR(IDBRequestObserver);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~IDBRequestObserver();
[[nodiscard]] GC::Ptr<GC::Function<void()>> request_processed_changed_observer() const { return m_request_processed_changed_observer; }

View file

@ -17,6 +17,8 @@ class IDBTransactionObserver final : public GC::Cell {
GC_DECLARE_ALLOCATOR(IDBTransactionObserver);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~IDBTransactionObserver();
[[nodiscard]] GC::Ptr<GC::Function<void()>> transaction_finished_observer() const { return m_transaction_finished_observer; }

View file

@ -16,6 +16,8 @@ class InternalGamepad : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(InternalGamepad);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
static GC::Ref<InternalGamepad> create(JS::Realm&);
virtual ~InternalGamepad() override;

View file

@ -44,6 +44,8 @@ class IntersectionObserver final : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(IntersectionObserver);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
static WebIDL::ExceptionOr<GC::Ref<IntersectionObserver>> construct_impl(JS::Realm&, GC::Ptr<WebIDL::CallbackType> callback, IntersectionObserverInit const& options = {});
virtual ~IntersectionObserver() override;

View file

@ -199,6 +199,8 @@ class GeneratedContentImageProvider final
GC_DECLARE_ALLOCATOR(GeneratedContentImageProvider);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~GeneratedContentImageProvider() override = default;
virtual void finalize() override

View file

@ -19,6 +19,8 @@ class VideoBox final
GC_DECLARE_ALLOCATOR(VideoBox);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual void prepare_for_replaced_layout() override;
HTML::HTMLVideoElement& dom_node();

View file

@ -19,6 +19,8 @@ class ImagePaintable final
GC_DECLARE_ALLOCATOR(ImagePaintable);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
static GC::Ref<ImagePaintable> create(Layout::ImageBox const& layout_box);
static GC::Ref<ImagePaintable> create(Layout::SVGImageBox const& layout_box);

View file

@ -55,6 +55,8 @@ class WEB_API Paintable
GC_CELL(Paintable, JS::Cell);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~Paintable();
void detach_from_layout_node();

View file

@ -23,6 +23,8 @@ class ResizeObserver : public Bindings::PlatformObject {
GC_DECLARE_ALLOCATOR(ResizeObserver);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
static WebIDL::ExceptionOr<GC::Ref<ResizeObserver>> construct_impl(JS::Realm&, WebIDL::CallbackType* callback);
virtual ~ResizeObserver() override;

View file

@ -30,6 +30,7 @@ class WebSocket final : public DOM::EventTarget {
GC_DECLARE_ALLOCATOR(WebSocket);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
static constexpr bool OVERRIDES_MUST_SURVIVE_GARBAGE_COLLECTION = true;
static WebIDL::ExceptionOr<GC::Ref<WebSocket>> construct_impl(JS::Realm&, String const& url, Optional<Variant<String, Vector<String>>> const& protocols);

View file

@ -4904,6 +4904,10 @@ private:
if (interface.extended_attributes.contains("WithFinalizer"sv)) {
generator.append(R"~~~(
public:
static constexpr bool OVERRIDES_FINALIZE = true;
private:
virtual void finalize() override;
)~~~");
}