From 63e4b744ed176890465d5920b2421a48b193e978 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 8 Mar 2020 19:23:58 +0100 Subject: [PATCH] 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! :^) --- Libraries/LibJS/AST.cpp | 2 +- Libraries/LibJS/Cell.cpp | 39 ++++++++ Libraries/LibJS/Cell.h | 62 +++++++++++++ Libraries/LibJS/Forward.h | 3 + Libraries/LibJS/Heap.cpp | 160 ++++++++++++++++++++++++++++++++ Libraries/LibJS/Heap.h | 69 ++++++++++++++ Libraries/LibJS/HeapBlock.cpp | 64 +++++++++++++ Libraries/LibJS/HeapBlock.h | 66 +++++++++++++ Libraries/LibJS/Interpreter.cpp | 3 +- Libraries/LibJS/Interpreter.h | 5 + Libraries/LibJS/Makefile | 3 + Libraries/LibJS/Object.cpp | 17 ++++ Libraries/LibJS/Object.h | 11 ++- 13 files changed, 498 insertions(+), 6 deletions(-) create mode 100644 Libraries/LibJS/Cell.cpp create mode 100644 Libraries/LibJS/Cell.h create mode 100644 Libraries/LibJS/Heap.cpp create mode 100644 Libraries/LibJS/Heap.h create mode 100644 Libraries/LibJS/HeapBlock.cpp create mode 100644 Libraries/LibJS/HeapBlock.h diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 49bc943ec4..b6862f594c 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -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(name(), body()); interpreter.global_object().put(m_name, Value(function)); return Value(function); } diff --git a/Libraries/LibJS/Cell.cpp b/Libraries/LibJS/Cell.cpp new file mode 100644 index 0000000000..3aedb7cb66 --- /dev/null +++ b/Libraries/LibJS/Cell.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include + +namespace JS { + +const LogStream& operator<<(const LogStream& stream, const Cell* cell) +{ + if (!cell) + return stream << "Cell{nullptr}"; + return stream << cell->class_name() << '{' << static_cast(cell) << '}'; +} + +} diff --git a/Libraries/LibJS/Cell.h b/Libraries/LibJS/Cell.h new file mode 100644 index 0000000000..31347f761b --- /dev/null +++ b/Libraries/LibJS/Cell.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 + +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*); + +} diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index d7ca20d622..ee5494da5d 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -29,6 +29,9 @@ namespace JS { class ASTNode; +class Cell; +class Heap; +class HeapBlock; class Interpreter; class Object; class ScopeNode; diff --git a/Libraries/LibJS/Heap.cpp b/Libraries/LibJS/Heap.cpp new file mode 100644 index 0000000000..e3a53da2f9 --- /dev/null +++ b/Libraries/LibJS/Heap.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include +#include + +#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(NonnullOwnPtr::Adopt, *block)); + return block->allocate(); +} + +void Heap::collect_garbage() +{ + HashTable roots; + collect_roots(roots); + + HashTable 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& 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& live_cells) + : m_live_cells(live_cells) + { + } + + virtual void did_visit(Cell* cell) override + { + m_live_cells.set(cell); + } + +private: + HashTable& m_live_cells; +}; + +void Heap::visit_live_cells(const HashTable& roots, HashTable& 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& 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); + } + }); + } +} + +} diff --git a/Libraries/LibJS/Heap.h b/Libraries/LibJS/Heap.h new file mode 100644 index 0000000000..3e29a452ea --- /dev/null +++ b/Libraries/LibJS/Heap.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include +#include +#include + +namespace JS { + +class Heap { + AK_MAKE_NONCOPYABLE(Heap); + AK_MAKE_NONMOVABLE(Heap); + +public: + explicit Heap(Interpreter&); + ~Heap(); + + template + T* allocate(Args&&... args) + { + auto* memory = allocate_cell(sizeof(T)); + new (memory) T(forward(args)...); + return static_cast(memory); + } + + void collect_garbage(); + +private: + Cell* allocate_cell(size_t); + + void collect_roots(HashTable&); + void visit_live_cells(const HashTable& roots, HashTable& live_cells); + void clear_all_mark_bits(); + void mark_live_cells(const HashTable& live_cells); + void sweep_dead_cells(); + + Interpreter& m_interpreter; + Vector> m_blocks; +}; + +} diff --git a/Libraries/LibJS/HeapBlock.cpp b/Libraries/LibJS/HeapBlock.cpp new file mode 100644 index 0000000000..016726e90b --- /dev/null +++ b/Libraries/LibJS/HeapBlock.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include + +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(cell(i)); + freelist_entry->set_live(false); + if (i == cell_count() - 1) + freelist_entry->next = nullptr; + else + freelist_entry->next = static_cast(cell(i + 1)); + } + m_freelist = static_cast(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(cell); + freelist_entry->set_live(false); + freelist_entry->next = m_freelist; + m_freelist = freelist_entry; +} + +} diff --git a/Libraries/LibJS/HeapBlock.h b/Libraries/LibJS/HeapBlock.h new file mode 100644 index 0000000000..d2bd5c42de --- /dev/null +++ b/Libraries/LibJS/HeapBlock.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include + +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(&m_storage[index * cell_size()]); } + + Cell* allocate(); + void deallocate(Cell*); + + template + 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[]; +}; + +} diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index aabac0771a..102ee10ed1 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -32,8 +32,9 @@ namespace JS { Interpreter::Interpreter() + : m_heap(*this) { - m_global_object = new Object; + m_global_object = heap().allocate(); } Interpreter::~Interpreter() diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index cdc4bda5a7..3de8e0a99e 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/Libraries/LibJS/Interpreter.h @@ -28,6 +28,7 @@ #include #include +#include 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 m_scope_stack; Object* m_global_object { nullptr }; diff --git a/Libraries/LibJS/Makefile b/Libraries/LibJS/Makefile index af5a558be4..de00553990 100644 --- a/Libraries/LibJS/Makefile +++ b/Libraries/LibJS/Makefile @@ -1,6 +1,9 @@ OBJS = \ AST.o \ + Cell.o \ Function.o \ + Heap.o \ + HeapBlock.o \ Interpreter.o \ Object.o \ Value.o diff --git a/Libraries/LibJS/Object.cpp b/Libraries/LibJS/Object.cpp index e7359807be..ae96485d00 100644 --- a/Libraries/LibJS/Object.cpp +++ b/Libraries/LibJS/Object.cpp @@ -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); + } +} + } diff --git a/Libraries/LibJS/Object.h b/Libraries/LibJS/Object.h index 7c1697b8c8..1f9ce7e018 100644 --- a/Libraries/LibJS/Object.h +++ b/Libraries/LibJS/Object.h @@ -27,21 +27,24 @@ #pragma once #include +#include #include +#include 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 m_properties;