From a86012108e77f7d88dde88aad838b3faf4b961df Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Tue, 26 Dec 2023 22:40:39 +0100 Subject: [PATCH] gc seems to work! --- .clang-tidy | 147 +++++++++++++ .idea/cmake.xml | 1 + .idea/codeStyles/Project.xml | 40 ++++ .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/misc.xml | 1 + src/vm/includes/Cell.h | 6 +- src/vm/includes/ConsUtils.h | 6 +- src/vm/includes/MemoryContext.h | 176 +++++++++++++--- src/vm/includes/VM.h | 2 - src/vm/src/ConsUtils.cpp | 17 +- src/vm/src/MemoryContext.cpp | 204 +++++++++++++++++++ src/vm/src/VM.cpp | 39 ---- test/vm/CMakeLists.txt | 11 + test/vm/GCTest.cpp | 92 +++++++++ test/vm/VMWithParserTest.cpp | 10 +- 15 files changed, 672 insertions(+), 86 deletions(-) create mode 100644 .clang-tidy create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 test/vm/GCTest.cpp diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..fd8c681 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,147 @@ +# Generated from CLion Inspection settings +--- +Checks: '-*, +bugprone-argument-comment, +bugprone-assert-side-effect, +bugprone-bad-signal-to-kill-thread, +bugprone-branch-clone, +bugprone-copy-constructor-init, +bugprone-dangling-handle, +bugprone-dynamic-static-initializers, +bugprone-fold-init-type, +bugprone-forward-declaration-namespace, +bugprone-forwarding-reference-overload, +bugprone-inaccurate-erase, +bugprone-incorrect-roundings, +bugprone-integer-division, +bugprone-lambda-function-name, +bugprone-macro-parentheses, +bugprone-macro-repeated-side-effects, +bugprone-misplaced-operator-in-strlen-in-alloc, +bugprone-misplaced-pointer-arithmetic-in-alloc, +bugprone-misplaced-widening-cast, +bugprone-move-forwarding-reference, +bugprone-multiple-statement-macro, +bugprone-no-escape, +bugprone-parent-virtual-call, +bugprone-posix-return, +bugprone-reserved-identifier, +bugprone-sizeof-container, +bugprone-sizeof-expression, +bugprone-spuriously-wake-up-functions, +bugprone-string-constructor, +bugprone-string-integer-assignment, +bugprone-string-literal-with-embedded-nul, +bugprone-suspicious-enum-usage, +bugprone-suspicious-include, +bugprone-suspicious-memset-usage, +bugprone-suspicious-missing-comma, +bugprone-suspicious-semicolon, +bugprone-suspicious-string-compare, +bugprone-suspicious-memory-comparison, +bugprone-suspicious-realloc-usage, +bugprone-swapped-arguments, +bugprone-terminating-continue, +bugprone-throw-keyword-missing, +bugprone-too-small-loop-variable, +bugprone-undefined-memory-manipulation, +bugprone-undelegated-constructor, +bugprone-unhandled-self-assignment, +bugprone-unused-raii, +bugprone-unused-return-value, +bugprone-use-after-move, +bugprone-virtual-near-miss, +cert-dcl21-cpp, +cert-dcl58-cpp, +cert-err34-c, +cert-err52-cpp, +cert-err60-cpp, +cert-flp30-c, +cert-msc50-cpp, +cert-msc51-cpp, +cert-str34-c, +cppcoreguidelines-interfaces-global-init, +cppcoreguidelines-narrowing-conversions, +cppcoreguidelines-pro-type-member-init, +cppcoreguidelines-pro-type-static-cast-downcast, +cppcoreguidelines-slicing, +google-default-arguments, +google-explicit-constructor, +google-runtime-operator, +hicpp-exception-baseclass, +hicpp-multiway-paths-covered, +misc-misplaced-const, +misc-new-delete-overloads, +misc-no-recursion, +misc-non-copyable-objects, +misc-throw-by-value-catch-by-reference, +misc-unconventional-assign-operator, +misc-uniqueptr-reset-release, +modernize-avoid-bind, +modernize-concat-nested-namespaces, +modernize-deprecated-headers, +modernize-deprecated-ios-base-aliases, +modernize-loop-convert, +modernize-make-shared, +modernize-make-unique, +modernize-pass-by-value, +modernize-raw-string-literal, +modernize-redundant-void-arg, +modernize-replace-auto-ptr, +modernize-replace-disallow-copy-and-assign-macro, +modernize-replace-random-shuffle, +modernize-return-braced-init-list, +modernize-shrink-to-fit, +modernize-unary-static-assert, +modernize-use-auto, +modernize-use-bool-literals, +modernize-use-emplace, +modernize-use-equals-default, +modernize-use-equals-delete, +modernize-use-nodiscard, +modernize-use-noexcept, +modernize-use-nullptr, +modernize-use-override, +modernize-use-transparent-functors, +modernize-use-uncaught-exceptions, +mpi-buffer-deref, +mpi-type-mismatch, +openmp-use-default-none, +performance-faster-string-find, +performance-for-range-copy, +performance-implicit-conversion-in-loop, +performance-inefficient-algorithm, +performance-inefficient-string-concatenation, +performance-inefficient-vector-operation, +performance-move-const-arg, +performance-move-constructor-init, +performance-no-automatic-move, +performance-noexcept-move-constructor, +performance-trivially-destructible, +performance-type-promotion-in-math-fn, +performance-unnecessary-copy-initialization, +performance-unnecessary-value-param, +portability-simd-intrinsics, +readability-avoid-const-params-in-decls, +readability-const-return-type, +readability-container-size-empty, +readability-convert-member-functions-to-static, +readability-delete-null-pointer, +readability-deleted-default, +readability-inconsistent-declaration-parameter-name, +readability-make-member-function-const, +readability-misleading-indentation, +readability-misplaced-array-index, +readability-non-const-parameter, +readability-redundant-control-flow, +readability-redundant-declaration, +readability-redundant-function-ptr-dereference, +readability-redundant-smartptr-get, +readability-redundant-string-cstr, +readability-redundant-string-init, +readability-simplify-subscript-expr, +readability-static-accessed-through-instance, +readability-static-definition-in-anonymous-namespace, +readability-string-compare, +readability-uniqueptr-delete-release, +readability-use-anyofallof' \ No newline at end of file diff --git a/.idea/cmake.xml b/.idea/cmake.xml index 802ba29..b7ec5ba 100644 --- a/.idea/cmake.xml +++ b/.idea/cmake.xml @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index f603881..4b411e6 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..4bb41dd --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 7b055cd..bae6c4c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,6 +4,7 @@ + \ No newline at end of file diff --git a/src/vm/includes/Cell.h b/src/vm/includes/Cell.h index 62decaf..d869096 100644 --- a/src/vm/includes/Cell.h +++ b/src/vm/includes/Cell.h @@ -23,7 +23,7 @@ struct Cell { virtual ~Cell() = 0; CellType _type; - bool live = false; + std::atomic live = false; }; struct NumAtomCell : public Cell { @@ -45,8 +45,8 @@ struct ConsCell : public Cell { explicit ConsCell(Cell *car) : Cell(CellType::CONS), _car(car) {} ConsCell(Cell *car, Cell *cdr) : Cell(CellType::CONS), _car(car), _cdr(cdr) {} - Cell *_car = nullptr; - Cell *_cdr = nullptr; + std::atomic _car = nullptr; + std::atomic _cdr = nullptr; }; #endif//PSIL_CELL_H diff --git a/src/vm/includes/ConsUtils.h b/src/vm/includes/ConsUtils.h index 89208e9..3413841 100644 --- a/src/vm/includes/ConsUtils.h +++ b/src/vm/includes/ConsUtils.h @@ -9,13 +9,13 @@ #include "MemoryContext.h" namespace ConsUtils { - static inline MCHandle car(const MCHandle &cell) { return dynamic_cast(*cell)._car; } - static inline MCHandle cdr(const MCHandle &cell) { return dynamic_cast(*cell)._cdr; } + static inline MCHandle car(const MCHandle &cell) { return dynamic_cast(*cell)._car.load(); } + static inline MCHandle cdr(const MCHandle &cell) { return dynamic_cast(*cell)._cdr.load(); } static inline CellValType val(const MCHandle &cell) { return dynamic_cast(*cell)._val; } static inline std::string_view strval(const MCHandle &cell) { return dynamic_cast(*cell)._val; } MCHandle cons(const MCHandle &car, const MCHandle &cdr); MCHandle pop(MCHandle &from); - MCHandle push(MCHandle &to, const MCHandle &what); + void push(MCHandle &to, const MCHandle &what); void append(MCHandle to, const MCHandle &what); MCHandle makeNumCell(int64_t val); MCHandle makeStrCell(std::string val); diff --git a/src/vm/includes/MemoryContext.h b/src/vm/includes/MemoryContext.h index f75c752..4ad8515 100644 --- a/src/vm/includes/MemoryContext.h +++ b/src/vm/includes/MemoryContext.h @@ -6,7 +6,14 @@ #define PSIL_MEMORYCONTEXT_H #include +#include +#include #include +#include +#include +#include +#include +#include #include "Cell.h" @@ -19,53 +26,164 @@ public: template Handle create_cell(Args... args) { - CT *cell = new CT(std::forward(args)...); - _cells.emplace_back(cell); - return cell; + return alloc_cell(std::forward(args)...); } - template<> - Handle create_cell(CellValType val) { - if (numatom_index.contains(val)) - return numatom_index.at(val); - - NumAtomCell *cell = new NumAtomCell(val); - _cells.emplace_back(cell); - numatom_index.emplace(val, cell); - return cell; - } - - template<> - Handle create_cell(std::string val) { - if (stratom_index.contains(val)) - return stratom_index.at(val); - - StrAtomCell *cell = new StrAtomCell(std::move(val)); - _cells.emplace_back(cell); - stratom_index.emplace(cell->_val, cell); - return cell; - } + // template<> + // Handle create_cell(CellValType val) { + // { + // std::lock_guard l(_cells_lock); + // if (_numatom_index.contains(val)) { + // _numatom_index.at(val)->live = true; + // return _numatom_index.at(val); + // } + // } + // Handle newc = alloc_cell(val); + // { + // std::lock_guard l(_cells_lock); + // _numatom_index.emplace(val, newc.get()); + // } + // return newc; + // } + // + // template<> + // Handle create_cell(std::string val) { + // { + // std::lock_guard l(_cells_lock); + // if (_stratom_index.contains(val)) { + // _stratom_index.at(val)->live = true; + // return _stratom_index.at(val); + // } + // } + // Handle newc = alloc_cell(val); + // { + // std::lock_guard l(_cells_lock); + // _stratom_index.emplace(std::move(val), newc.get()); + // } + // return newc; + // } class Handle { public: - Handle(Cell *target) : _target(target) {} + Handle(Cell *target); + ~Handle(); + + Handle(Handle const &other); + Handle &operator=(Handle other); Cell *operator->() const { return _target; } Cell &operator*() const { return *_target; } Cell *get() const noexcept { return _target; } bool operator==(const Handle &rhs) const { - return _target == rhs._target; + if (_target == rhs._target) { + return true; + } else if ((_target != nullptr && rhs._target != nullptr) && (_target->_type == rhs._target->_type)) { + if (_target->_type == CellType::NUMATOM) { + return dynamic_cast(*_target)._val == dynamic_cast(*rhs._target)._val; + } else if (_target->_type == CellType::STRATOM) { + return dynamic_cast(*_target)._val == dynamic_cast(*rhs._target)._val; + } + } + return false; } private: Cell *_target = nullptr; }; + void request_gc_and_wait() { + std::unique_lock l(_gc_done_m); + _gc_done = false; + { + request_gc(); + } + if (!_gc_done) + _gc_done_cv.wait(l, [&] { return _gc_done.load(); }); + } + + void request_gc() { + std::lock_guard l(_gc_request_m); + _gc_request = true; + _gc_request_cv.notify_all(); + } + + size_t cell_count() { + return _cells_num; + } + + void run_dirty(const Handle &h, std::function f); + private: - std::list _cells; - std::unordered_map numatom_index; - std::unordered_map stratom_index; + template + Handle alloc_cell(Args... args) { + + size_t tcellnum; + { + std::lock_guard tmplg(_new_roots_lock); + tcellnum = _temp_cells.size(); + } + + if ((_cells_num + tcellnum) >= (_cell_limit)) { + // We might need to run GC twice as it has to process the messages; + std::cerr << "Running forced GC" << std::endl; + for (int i = 0; i < 3 && (_cells_num + tcellnum) >= (_cell_limit); i++) { + request_gc_and_wait(); + { + std::lock_guard tmplg(_new_roots_lock); + tcellnum = _temp_cells.size(); + } + } + if ((_cells_num + tcellnum) >= (_cell_limit)) { + throw std::runtime_error("Out of cells"); + } + } + + CT *cell = new CT(std::forward(args)...); + + { + std::lock_guard tmplg(_new_roots_lock); + Handle ret(cell); + _temp_cells.emplace(cell); + if ((_cells_num + _temp_cells.size() + 1) >= (size_t) (_cell_limit / 2)) { + request_gc(); + } + return ret; + } + } + + + void add_root(Cell *c); + void remove_root(Cell *c); + + std::mutex _cells_lock; + std::set _cells; + std::atomic _cells_num = 0; + std::set _temp_cells; + std::unordered_map _numatom_index; + std::unordered_map _stratom_index; + + static constexpr size_t _cell_limit = {50000}; + + void gc_thread_entry(); + + std::map _roots; + std::map _new_roots; + std::recursive_mutex _new_roots_lock; + + std::set _gc_dirty_notif_queue; + std::mutex _gc_dirty_notif_queue_lock; + + std::atomic _gc_request = false; + std::mutex _gc_request_m; + std::condition_variable _gc_request_cv; + + std::atomic _gc_done = false; + std::mutex _gc_done_m; + std::condition_variable _gc_done_cv; + + std::thread _gc_thread; + std::atomic _gc_thread_stop = false; }; using MCHandle = MemoryContext::Handle; diff --git a/src/vm/includes/VM.h b/src/vm/includes/VM.h index d4a90d8..1a43ea5 100644 --- a/src/vm/includes/VM.h +++ b/src/vm/includes/VM.h @@ -54,8 +54,6 @@ private: MCHandle PUTNUM = ConsUtils::makeStrCell("PUTNUM"); MCHandle CONS = ConsUtils::makeStrCell("CONS"); - - // void gc(); }; #endif//PSIL_VM_H diff --git a/src/vm/src/ConsUtils.cpp b/src/vm/src/ConsUtils.cpp index ca03511..23487b8 100644 --- a/src/vm/src/ConsUtils.cpp +++ b/src/vm/src/ConsUtils.cpp @@ -17,19 +17,18 @@ MCHandle ConsUtils::pop(MCHandle &from) { return ret; } -MCHandle ConsUtils::push(MCHandle &to, const MCHandle &what) { +void ConsUtils::push(MCHandle &to, const MCHandle &what) { to = cons(what, to); - return to; } void ConsUtils::append(MCHandle to, const MCHandle &what) { assert(to.get() != nullptr); if (car(to).get() == nullptr) { - dynamic_cast(*to)._car = what.get(); + setcar(to, what); return; } - while (dynamic_cast(*to)._cdr != nullptr) to = cdr(to); - dynamic_cast(*to)._cdr = cons(what, nullptr).get(); + while (cdr(to).get() != nullptr) to = cdr(to); + setcdr(to, cons(what, nullptr)); } MCHandle ConsUtils::makeNumCell(int64_t val) { @@ -41,9 +40,13 @@ MCHandle ConsUtils::makeStrCell(std::string val) { } void ConsUtils::setcar(const MCHandle &to, const MCHandle &car) { - dynamic_cast(*to)._car = car.get(); + CURRENT_MC.load()->run_dirty(to, [&] { + dynamic_cast(*to)._car = car.get(); + }); } void ConsUtils::setcdr(const MCHandle &to, const MCHandle &cdr) { - dynamic_cast(*to)._cdr = cdr.get(); + CURRENT_MC.load()->run_dirty(to, [&] { + dynamic_cast(*to)._cdr = cdr.get(); + }); } diff --git a/src/vm/src/MemoryContext.cpp b/src/vm/src/MemoryContext.cpp index 2b74347..c4dd9d0 100644 --- a/src/vm/src/MemoryContext.cpp +++ b/src/vm/src/MemoryContext.cpp @@ -4,14 +4,18 @@ #include "MemoryContext.h" +#include #include #include +#include std::atomic CURRENT_MC = nullptr; MemoryContext::MemoryContext() { MemoryContext *expected = nullptr; if (!CURRENT_MC.compare_exchange_strong(expected, this)) throw std::runtime_error("MC already exists!"); + + _gc_thread = std::thread(std::bind(&MemoryContext::gc_thread_entry, this)); } MemoryContext::~MemoryContext() { @@ -20,4 +24,204 @@ MemoryContext::~MemoryContext() { std::cerr << "Global MC pointer was overwritten!" << std::endl; std::abort(); } + _gc_thread_stop = true; + _gc_request_cv.notify_all(); + _gc_thread.join(); } + +MemoryContext::Handle::Handle(Cell *target) : _target(target) { + if (target != nullptr) + CURRENT_MC.load()->add_root(target); +} + +MemoryContext::Handle::~Handle() { + if (_target != nullptr) + CURRENT_MC.load()->remove_root(_target); +} + +MemoryContext::Handle::Handle(MemoryContext::Handle const &other) : _target(other._target) { + if (_target != nullptr) + CURRENT_MC.load()->add_root(_target); +} + +MemoryContext::Handle &MemoryContext::Handle::operator=(MemoryContext::Handle other) { + std::swap(_target, other._target); + return *this; +} + +void MemoryContext::add_root(Cell *c) { + { + std::lock_guard l(_new_roots_lock); + _new_roots[c]++; + // std::cerr << "new root: " << c << " T: " << static_cast(c->_type) << " NUM: " << _new_roots[c] << "\n"; + } +} +void MemoryContext::remove_root(Cell *c) { + { + std::lock_guard l(_new_roots_lock); + _new_roots[c]--; + // std::cerr << "del root: " << c << " T: " << static_cast(c->_type) << " NUM: " << _new_roots[c] << "\n"; + if (_new_roots[c] == 0) _new_roots.erase(c); + } +} + +void MemoryContext::run_dirty(const MemoryContext::Handle &h, std::function f) { + { + std::lock_guard l(_gc_dirty_notif_queue_lock); + // std::cerr << "dirtied: " << h.get() << "\n"; + _gc_dirty_notif_queue.emplace(h.get()); + f(); + } +} + +void MemoryContext::gc_thread_entry() { + while (true) { + { + std::unique_lock l(_gc_request_m); + _gc_request_cv.wait(l, [&] { return _gc_request || _gc_thread_stop; }); + } + if (_gc_thread_stop) return; + + std::cerr << "gc start " << '\n'; + + std::unordered_set seenroots; + std::queue toVisit; + + auto visitAll = [&]() { + while (!toVisit.empty()) { + Cell *c = toVisit.front(); + toVisit.pop(); + + if (c == nullptr) continue; + + if (!_cells.contains(c)) _cells.emplace(c); + + if (c->live) continue; + + c->live = true; + // std::cerr << "processing c " << c << " " << static_cast(c->_type) << "\n"; + + if (c->_type == CellType::CONS) { + ConsCell &cc = dynamic_cast(*c); + // std::cerr << "processing car " << cc._car << "\n"; + toVisit.emplace(cc._car); + // std::cerr << "real car " << toVisit.back() << "\n"; + // std::cerr << "processing cdr " << cc._cdr << "\n"; + toVisit.emplace(cc._cdr); + // std::cerr << "real cdr " << toVisit.back() << "\n"; + } + } + }; + + { + std::lock_guard cl(_cells_lock); + auto start = std::chrono::high_resolution_clock::now(); + { + decltype(_new_roots) new_roots; + { + decltype(_temp_cells) temp_cells; + { + std::lock_guard l(_new_roots_lock); + std::swap(new_roots, _new_roots); + std::swap(temp_cells, _temp_cells); + } + _cells.insert(temp_cells.begin(), temp_cells.end()); + } + _cells_num = _cells.size(); + + + for (auto const &r: new_roots) { + // std::cerr << "processing new " << r.first << " diff " << r.second << "\n"; + if (r.second == 0) continue; + _roots[r.first] += r.second; + if (_roots[r.first] <= 0) + _roots.erase(r.first); + } + } + auto stop = std::chrono::high_resolution_clock::now(); + std::cerr << "New roots time: " << std::chrono::duration_cast(stop - start).count() << "\n"; + + assert(std::none_of(_cells.begin(), _cells.end(), [](const auto &p) { return p->live.load(); })); + + start = std::chrono::high_resolution_clock::now(); + for (const auto &r: _roots) { + // std::cerr << "processing r " << r.first << " diff " << r.second << "\n"; + seenroots.emplace(r.first); + toVisit.emplace(r.first); + } + visitAll(); + stop = std::chrono::high_resolution_clock::now(); + // std::cerr << "Scanned " << _roots.size() << " roots" << std::endl; + std::cerr << "Roots scan time: " << std::chrono::duration_cast(stop - start).count() << "\n"; + + { + decltype(_gc_dirty_notif_queue) dirtied; + std::unique_lock dql(_gc_dirty_notif_queue_lock); + std::swap(dirtied, _gc_dirty_notif_queue); + + start = std::chrono::high_resolution_clock::now(); + while (!dirtied.empty()) { + dql.unlock(); + for (const auto &r: dirtied) { + // std::cerr << "processing dirty " << r << "\n"; + if (seenroots.contains(r)) continue; + seenroots.emplace(r); + toVisit.emplace(r); + } + visitAll(); + + dirtied = {}; + dql.lock(); + std::swap(dirtied, _gc_dirty_notif_queue); + } + + stop = std::chrono::high_resolution_clock::now(); + std::cerr << "Dirty mark time: " << std::chrono::duration_cast(stop - start).count() << "\n"; + + assert(dql.owns_lock()); + + uint64_t freed = 0; + start = std::chrono::high_resolution_clock::now(); + + std::vector toremove; + for (const auto &l: _cells) { + if (!l->live) { + freed += 1; + + // if (l->_type == CellType::NUMATOM) { + // std::cerr << "deleting num: " << l << "\n"; + // _numatom_index.erase(dynamic_cast(*l)._val); + // } else if (l->_type == CellType::STRATOM) { + // std::cerr << "deleting str: " << l << "\n"; + // _stratom_index.erase(dynamic_cast(*l)._val); + // } + + assert(!_roots.contains(l)); + // std::cerr << "deleting: " << l << "\n"; + toremove.emplace_back(l); + delete l; + } else { + // std::cerr << "resetting num: " << l << "\n"; + l->live = false; + } + } + + for (const auto &l: toremove) { + _cells.erase(l); + } + + stop = std::chrono::high_resolution_clock::now(); + std::cerr << "Sweep time: " << std::chrono::duration_cast(stop - start).count() << "\n"; + std::cerr << "GC Freed " << freed << " cells left: " << _cells_num << " \n"; + } + + { + std::unique_lock l(_gc_done_m); + std::unique_lock l2(_gc_request_m); + _gc_done = true; + _gc_request = false; + _gc_done_cv.notify_all(); + } + } + } +} \ No newline at end of file diff --git a/src/vm/src/VM.cpp b/src/vm/src/VM.cpp index f4d9b1c..afa56e3 100644 --- a/src/vm/src/VM.cpp +++ b/src/vm/src/VM.cpp @@ -86,7 +86,6 @@ void VM::step() { _s = s; push(_s, ret); - // gc(); } else if (poppedH == DUM) { push(_e, nullptr); } else if (poppedH == RAP) { @@ -135,41 +134,3 @@ void VM::step() { assert(false); } } - -//void VM::gc() { -// std::function visit = [&](ConsCell *c) { -// if (c == nullptr) return; -// if (c->live) return; -// -// c->live = true; -// -// if (c->_car) { -// if (c->_car->_type == CellType::CONS) visit(dynamic_cast(c->_car)); -// c->_car->live = true; -// } -// if (c->_cdr) { -// if (c->_cdr->_type == CellType::CONS) visit(dynamic_cast(c->_cdr)); -// c->_cdr->live = true; -// } -// }; -// visit(_s); -// visit(_e); -// visit(_c); -// visit(_d); -// -// uint64_t freed = 0; -// -// _cells.remove_if([&](Cell *l) { -// bool ret = !l->live; -// if (ret) { -// freed += 1; -// delete l; -// } else { -// l->live = false; -// } -// return ret; -// }); -// -// std::cout << "GC Freed " << freed << std::endl; -//} -// \ No newline at end of file diff --git a/test/vm/CMakeLists.txt b/test/vm/CMakeLists.txt index 75a50a4..1faa1b2 100644 --- a/test/vm/CMakeLists.txt +++ b/test/vm/CMakeLists.txt @@ -18,6 +18,17 @@ target_link_libraries( GTest::gtest_main ) +add_executable( + GCTest + GCTest.cpp +) +target_link_libraries( + GCTest + vm + GTest::gtest_main +) + include(GoogleTest) gtest_discover_tests(VMTest) gtest_discover_tests(VMWithParserTest) +gtest_discover_tests(GCTest) diff --git a/test/vm/GCTest.cpp b/test/vm/GCTest.cpp new file mode 100644 index 0000000..f991a2e --- /dev/null +++ b/test/vm/GCTest.cpp @@ -0,0 +1,92 @@ +// +// Created by Stepan Usatiuk on 26.12.2023. +// + +#include + +#include "ConsUtils.h" +#include "MemoryContext.h" + +using namespace ConsUtils; + +TEST(GCTest, GCTest) { + MemoryContext mc; + { + MCHandle c = cons(nullptr, nullptr); + mc.request_gc_and_wait(); + append(c, makeNumCell(1)); + append(c, makeNumCell(2)); + mc.request_gc_and_wait(); + EXPECT_EQ(val(car(c)), 1); + EXPECT_EQ(val(car(cdr(c))), 2); + } + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + EXPECT_EQ(mc.cell_count(), 0); + { + MCHandle c = cons(nullptr, nullptr); + mc.request_gc_and_wait(); + push(c, makeNumCell(1)); + push(c, makeNumCell(2)); + mc.request_gc_and_wait(); + EXPECT_EQ(val(car(c)), 2); + EXPECT_EQ(val(car(cdr(c))), 1); + } + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + EXPECT_EQ(mc.cell_count(), 0); +} + +TEST(GCTest, GCTestAppend) { + MemoryContext mc; + for (int i = 0; i < 25000; i++) { + MCHandle c = cons(nullptr, nullptr); + mc.request_gc(); + append(c, makeNumCell(1)); + mc.request_gc(); + EXPECT_EQ(val(car(c)), 1); + } + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + EXPECT_EQ(mc.cell_count(), 0); +} +TEST(GCTest, GCTestPop) { + MemoryContext mc; + { + MCHandle c = cons(nullptr, nullptr); + static constexpr int test_size = 20000; + for (int i = 0; i < test_size; i++) { + mc.request_gc(); + push(c, makeNumCell(i)); + } + for (int i = test_size - 1; i >= 0; i--) { + mc.request_gc(); + EXPECT_EQ(i, val(pop(c))); + } + } + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + EXPECT_EQ(mc.cell_count(), 0); +} + +TEST(GCTest, GCTestAppend2) { + MemoryContext mc; + MCHandle c = cons(nullptr, nullptr); + static constexpr int test_size = 2000; + for (int i = 0; i < test_size; i++) { + mc.request_gc(); + append(c, makeNumCell(i)); + } + for (int i = 0; i < test_size; i++) { + mc.request_gc(); + EXPECT_EQ(i, val(pop(c))); + } + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + EXPECT_EQ(mc.cell_count(), 0); +} diff --git a/test/vm/VMWithParserTest.cpp b/test/vm/VMWithParserTest.cpp index be5030a..f1f04d8 100644 --- a/test/vm/VMWithParserTest.cpp +++ b/test/vm/VMWithParserTest.cpp @@ -52,15 +52,19 @@ TEST(VMWithParserTest, BasicFunction) { TEST(VMWithParserTest, RecFunction) { std::stringstream ssin; std::stringstream ssout; + MemoryContext mc; { - MemoryContext mc; VM vm(ssin, ssout); Parser parser; parser.loadStr( - "( DUM NIL LDF ( LD ( 1 . 1 ) SEL ( LD ( 1 . 1 ) LDC -1 ADD SEL ( NIL LD ( 1 . 1 ) LDC -1 ADD CONS LD ( 2 . 1 ) AP NIL LD ( 1 . 1 ) LDC -2 ADD CONS LD ( 2 . 1 ) AP ADD JOIN ) ( LDC 1 JOIN ) JOIN ) ( LDC 0 JOIN ) RET ) CONS LDF ( NIL LDC 10 CONS LD ( 1 . 1 ) AP RET ) RAP PUTNUM STOP )"); + "( DUM NIL LDF ( LD ( 1 . 1 ) SEL ( LD ( 1 . 1 ) LDC -1 ADD SEL ( NIL LD ( 1 . 1 ) LDC -1 ADD CONS LD ( 2 . 1 ) AP NIL LD ( 1 . 1 ) LDC -2 ADD CONS LD ( 2 . 1 ) AP ADD JOIN ) ( LDC 1 JOIN ) JOIN ) ( LDC 0 JOIN ) RET ) CONS LDF ( NIL LDC 20 CONS LD ( 1 . 1 ) AP RET ) RAP PUTNUM STOP )"); vm.loadControl(parser.parseExpr()); vm.run(); } + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + mc.request_gc_and_wait(); + EXPECT_EQ(mc.cell_count(), 0); ssout.flush(); - EXPECT_EQ(ssout.str(), "55"); + EXPECT_EQ(ssout.str(), "6765"); } \ No newline at end of file