LibJS: Add a basic mark&sweep garbage collector :^)

Objects can now be allocated via the interpreter's heap. Objects that
are allocated in this way will need to be provably reachable from at
least one of the known object graph roots.

The roots are currently determined by Heap::collect_roots().

Anything that wants be collectable garbage should inherit from Cell,
the fundamental atom of the GC heap.

This is pretty neat! :^)
This commit is contained in:
Andreas Kling 2020-03-08 19:23:58 +01:00
parent 6da131d5db
commit 63e4b744ed
Notes: sideshowbarker 2024-07-19 08:49:34 +09:00
13 changed files with 498 additions and 6 deletions

View file

@ -39,7 +39,7 @@ Value ScopeNode::execute(Interpreter& interpreter) const
Value FunctionDeclaration::execute(Interpreter& interpreter) const
{
auto* function = new Function(name(), body());
auto* function = interpreter.heap().allocate<Function>(name(), body());
interpreter.global_object().put(m_name, Value(function));
return Value(function);
}

39
Libraries/LibJS/Cell.cpp Normal file
View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/LogStream.h>
#include <LibJS/Cell.h>
namespace JS {
const LogStream& operator<<(const LogStream& stream, const Cell* cell)
{
if (!cell)
return stream << "Cell{nullptr}";
return stream << cell->class_name() << '{' << static_cast<const void*>(cell) << '}';
}
}

62
Libraries/LibJS/Cell.h Normal file
View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/Forward.h>
namespace JS {
class Cell {
public:
virtual ~Cell() {}
bool is_marked() const { return m_mark; }
void set_marked(bool b) { m_mark = b; }
bool is_live() const { return m_live; }
void set_live(bool b) { m_live = b; }
virtual const char* class_name() const = 0;
class Visitor {
public:
virtual void did_visit(Cell*) = 0;
};
virtual void visit_graph(Visitor& visitor)
{
visitor.did_visit(this);
}
private:
bool m_mark { false };
bool m_live { true };
};
const LogStream& operator<<(const LogStream&, const Cell*);
}

View file

@ -29,6 +29,9 @@
namespace JS {
class ASTNode;
class Cell;
class Heap;
class HeapBlock;
class Interpreter;
class Object;
class ScopeNode;

160
Libraries/LibJS/Heap.cpp Normal file
View file

@ -0,0 +1,160 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/HashTable.h>
#include <LibJS/Heap.h>
#include <LibJS/HeapBlock.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Object.h>
#define HEAP_DEBUG
namespace JS {
Heap::Heap(Interpreter& interpreter)
: m_interpreter(interpreter)
{
}
Heap::~Heap()
{
}
Cell* Heap::allocate_cell(size_t size)
{
for (auto& block : m_blocks) {
if (size > block->cell_size())
continue;
if (auto* cell = block->allocate())
return cell;
}
auto* block = (HeapBlock*)malloc(HeapBlock::block_size);
new (block) HeapBlock(size);
m_blocks.append(NonnullOwnPtr<HeapBlock>(NonnullOwnPtr<HeapBlock>::Adopt, *block));
return block->allocate();
}
void Heap::collect_garbage()
{
HashTable<Cell*> roots;
collect_roots(roots);
HashTable<Cell*> live_cells;
visit_live_cells(roots, live_cells);
clear_all_mark_bits();
mark_live_cells(live_cells);
sweep_dead_cells();
}
void Heap::collect_roots(HashTable<Cell*>& roots)
{
roots.set(&m_interpreter.global_object());
#ifdef HEAP_DEBUG
dbg() << "collect_roots:";
for (auto* root : roots) {
dbg() << " + " << root;
}
#endif
}
class LivenessVisitor final : public Cell::Visitor {
public:
LivenessVisitor(HashTable<Cell*>& live_cells)
: m_live_cells(live_cells)
{
}
virtual void did_visit(Cell* cell) override
{
m_live_cells.set(cell);
}
private:
HashTable<Cell*>& m_live_cells;
};
void Heap::visit_live_cells(const HashTable<Cell*>& roots, HashTable<Cell*>& live_cells)
{
LivenessVisitor visitor(live_cells);
for (auto* root : roots) {
root->visit_graph(visitor);
}
#ifdef HEAP_DEBUG
dbg() << "visit_live_cells:";
for (auto* cell : live_cells) {
dbg() << " @ " << cell;
}
#endif
}
void Heap::clear_all_mark_bits()
{
for (auto& block : m_blocks) {
block->for_each_cell([&](Cell* cell) {
cell->set_marked(false);
});
}
}
void Heap::mark_live_cells(const HashTable<Cell*>& live_cells)
{
#ifdef HEAP_DEBUG
dbg() << "mark_live_cells:";
#endif
for (auto& block : m_blocks) {
block->for_each_cell([&](Cell* cell) {
if (live_cells.contains(cell)) {
#ifdef HEAP_DEBUG
dbg() << " ! " << cell;
#endif
cell->set_marked(true);
}
});
}
}
void Heap::sweep_dead_cells()
{
#ifdef HEAP_DEBUG
dbg() << "sweep_dead_cells:";
#endif
for (auto& block : m_blocks) {
block->for_each_cell([&](Cell* cell) {
if (cell->is_live() && !cell->is_marked()) {
#ifdef HEAP_DEBUG
dbg() << " ~ " << cell;
#endif
block->deallocate(cell);
}
});
}
}
}

69
Libraries/LibJS/Heap.h Normal file
View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/Noncopyable.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibJS/Cell.h>
#include <LibJS/Forward.h>
namespace JS {
class Heap {
AK_MAKE_NONCOPYABLE(Heap);
AK_MAKE_NONMOVABLE(Heap);
public:
explicit Heap(Interpreter&);
~Heap();
template<typename T, typename... Args>
T* allocate(Args&&... args)
{
auto* memory = allocate_cell(sizeof(T));
new (memory) T(forward<Args>(args)...);
return static_cast<T*>(memory);
}
void collect_garbage();
private:
Cell* allocate_cell(size_t);
void collect_roots(HashTable<Cell*>&);
void visit_live_cells(const HashTable<Cell*>& roots, HashTable<Cell*>& live_cells);
void clear_all_mark_bits();
void mark_live_cells(const HashTable<Cell*>& live_cells);
void sweep_dead_cells();
Interpreter& m_interpreter;
Vector<NonnullOwnPtr<HeapBlock>> m_blocks;
};
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/Assertions.h>
#include <LibJS/HeapBlock.h>
namespace JS {
HeapBlock::HeapBlock(size_t cell_size)
: m_cell_size(cell_size)
{
for (size_t i = 0; i < cell_count(); ++i) {
auto* freelist_entry = static_cast<FreelistEntry*>(cell(i));
freelist_entry->set_live(false);
if (i == cell_count() - 1)
freelist_entry->next = nullptr;
else
freelist_entry->next = static_cast<FreelistEntry*>(cell(i + 1));
}
m_freelist = static_cast<FreelistEntry*>(cell(0));
}
Cell* HeapBlock::allocate()
{
if (!m_freelist)
return nullptr;
return exchange(m_freelist, m_freelist->next);
}
void HeapBlock::deallocate(Cell* cell)
{
ASSERT(cell->is_live());
ASSERT(!cell->is_marked());
cell->~Cell();
auto* freelist_entry = static_cast<FreelistEntry*>(cell);
freelist_entry->set_live(false);
freelist_entry->next = m_freelist;
m_freelist = freelist_entry;
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/Types.h>
#include <LibJS/Cell.h>
#include <LibJS/Forward.h>
namespace JS {
class HeapBlock {
public:
static constexpr size_t block_size = 16 * KB;
explicit HeapBlock(size_t cell_size);
size_t cell_size() const { return m_cell_size; }
size_t cell_count() const { return (block_size - sizeof(HeapBlock)) / m_cell_size; }
Cell* cell(size_t index) { return reinterpret_cast<Cell*>(&m_storage[index * cell_size()]); }
Cell* allocate();
void deallocate(Cell*);
template<typename Callback>
void for_each_cell(Callback callback)
{
for (size_t i = 0; i < cell_count(); ++i)
callback(cell(i));
}
private:
struct FreelistEntry : public Cell {
FreelistEntry* next;
};
size_t m_cell_size { 0 };
FreelistEntry* m_freelist { nullptr };
u8 m_storage[];
};
}

View file

@ -32,8 +32,9 @@
namespace JS {
Interpreter::Interpreter()
: m_heap(*this)
{
m_global_object = new Object;
m_global_object = heap().allocate<Object>();
}
Interpreter::~Interpreter()

View file

@ -28,6 +28,7 @@
#include <AK/Vector.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap.h>
namespace JS {
@ -45,12 +46,16 @@ public:
Object& global_object() { return *m_global_object; }
const Object& global_object() const { return *m_global_object; }
Heap& heap() { return m_heap; }
void do_return();
private:
void enter_scope(const ScopeNode&);
void exit_scope(const ScopeNode&);
Heap m_heap;
Vector<ScopeFrame> m_scope_stack;
Object* m_global_object { nullptr };

View file

@ -1,6 +1,9 @@
OBJS = \
AST.o \
Cell.o \
Function.o \
Heap.o \
HeapBlock.o \
Interpreter.o \
Object.o \
Value.o

View file

@ -30,6 +30,14 @@
namespace JS {
Object::Object()
{
}
Object::~Object()
{
}
Value Object::get(String property_name) const
{
return m_properties.get(property_name).value_or(js_undefined());
@ -40,4 +48,13 @@ void Object::put(String property_name, Value value)
m_properties.set(property_name, move(value));
}
void Object::visit_graph(Cell::Visitor& visitor)
{
Cell::visit_graph(visitor);
for (auto& it : m_properties) {
if (it.value.is_object())
it.value.as_object()->visit_graph(visitor);
}
}
}

View file

@ -27,21 +27,24 @@
#pragma once
#include <AK/HashMap.h>
#include <LibJS/Cell.h>
#include <LibJS/Forward.h>
#include <LibJS/Cell.h>
namespace JS {
class Object {
class Object : public Cell {
public:
Object() {}
virtual ~Object() {}
Object();
virtual ~Object();
Value get(String property_name) const;
void put(String property_name, Value);
virtual bool is_function() const { return false; }
virtual const char* class_name() const { return "Object"; }
virtual const char* class_name() const override { return "Object"; }
virtual void visit_graph(Cell::Visitor&) override;
private:
HashMap<String, Value> m_properties;