229 lines · 7.0 KB · C++
RawDownload
//// Dis VM// File: garbage_collector.cpp// Author: arr// #include <cinttypes>#include <array>#include <queue>#include <debug.h>#include <vm_memory.h>#include <exceptions.h>#include "garbage_collector.h" using namespace disvm;using namespace disvm::runtime; // Empty destructors for vm garbage collector 'interfaces'vm_garbage_collector_t::~vm_garbage_collector_t(){} std::unique_ptr<vm_garbage_collector_t> disvm::runtime::create_no_op_gc(vm_t &){    class no_op_gc final : public vm_garbage_collector_t    {    public:        void track_allocation(vm_alloc_t *) override        {        }         void enum_tracked_allocations(vm_alloc_callback_t) const override        {        }         bool collect(std::vector<std::shared_ptr<const vm_thread_t>> &) override        {            return true;        }    };     return std::make_unique<no_op_gc>();} default_garbage_collector_t::default_garbage_collector_t(vm_t &vm)    : _collection_epoch{ 0 }    , _vm{ vm }{} default_garbage_collector_t::~default_garbage_collector_t(){    std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };    for (auto a : _tracking_allocs)        dec_ref_count_and_free(a);} namespace{    enum class gc_colour_t    {        white,        grey,        black,    };     const auto current_colours = std::array<gc_colour_t, 3>{ gc_colour_t::white, gc_colour_t::grey, gc_colour_t::black };    const auto sweeper_colours = std::array<gc_colour_t, 3>{ gc_colour_t::grey, gc_colour_t::black, gc_colour_t::white };     gc_colour_t get_current_colour(const std::size_t epoch)    {        return current_colours[epoch % current_colours.size()];    }     gc_colour_t get_sweeper_colour(const std::size_t epoch)    {        return sweeper_colours[epoch % sweeper_colours.size()];    }     gc_colour_t get_gc_colour(const vm_alloc_t *a)    {        return *reinterpret_cast<const gc_colour_t *>(&a->gc_reserved);    }     void set_gc_colour(vm_alloc_t *a, const gc_colour_t c)    {        a->gc_reserved = reinterpret_cast<pointer_t>(c);    }} void default_garbage_collector_t::track_allocation(vm_alloc_t *alloc){    assert(alloc != nullptr);     if (debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>())        debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: track: %#" PRIxPTR "\n", alloc);     alloc->add_ref();    set_gc_colour(alloc, get_current_colour(_collection_epoch));     std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };    _tracking_allocs.push_back(alloc);} void default_garbage_collector_t::enum_tracked_allocations(vm_alloc_callback_t callback) const{    if (callback == nullptr)        throw vm_system_exception{ "Callback should not be null" };     std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };    for (auto a : _tracking_allocs)        callback(a);} namespace{    void mark(const std::vector<std::shared_ptr<const vm_thread_t>> &threads, const gc_colour_t curr_colour)    {        std::queue<vm_alloc_t *> allocs;         auto add_pointer_to_queue = std::function<void(pointer_t p, std::size_t)>{ [&allocs](pointer_t p, std::size_t) -> void        {            auto a = vm_alloc_t::from_allocation(p);            allocs.push(a);        } };         // Get roots from threads        for (const auto &t : threads)        {            const auto &r = t->get_registers();             // Current MP            allocs.push(r.mp_base);             // Traverse the stack for roots            for (auto frame = r.stack.peek_frame(); frame != nullptr; frame = frame->prev_frame())            {                // Previous MP                if (frame->prev_module_ref() != nullptr)                {                    auto prev_mp = frame->prev_module_ref()->mp_base;                    allocs.push(prev_mp);                }                 if (frame->frame_type->map_in_bytes == 0)                    continue;                 enum_pointer_fields(*frame->frame_type, frame->base(), add_pointer_to_queue);            }        }         const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>();        if (log_enabled)        {            debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: roots found: %" PRIuPTR "\n", allocs.size());             // If logging is enabled update the callback with logging            add_pointer_to_queue = std::function<void(pointer_t p, std::size_t)>{ [&allocs](pointer_t p, std::size_t) -> void            {                auto a = vm_alloc_t::from_allocation(p);                debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "    ref: %#" PRIxPTR "\n", a);                allocs.push(a);            } };        }         // Traverse the graph        while (!allocs.empty())        {            auto curr = allocs.front();            allocs.pop();             if (log_enabled)                debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: mark: alloc: %#" PRIxPTR "\n", curr);             if (curr->alloc_type->map_in_bytes > 0)                enum_pointer_fields(*curr->alloc_type, curr->get_allocation(), add_pointer_to_queue);             set_gc_colour(curr, curr_colour);        }    }     void sweep(std::deque<vm_alloc_t *> &allocs, const gc_colour_t sweeper_colour)    {        auto allocs_local = std::move(allocs);         auto freed = uint32_t{ 0 };        for (auto a : allocs_local)        {            const auto colour = get_gc_colour(a);            if (colour == sweeper_colour)            {                dec_ref_count_and_free(a);                freed++;                continue;            }             allocs.push_back(a);        }         if (debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>())            debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: sweep: %u\n", freed);    }} bool default_garbage_collector_t::collect(std::vector<std::shared_ptr<const vm_thread_t>> &threads){    // No need to lock the actual memory allocator since the contract    // for this function call indicates all VM threads should be blocked.    std::lock_guard<std::mutex> lock{ _tracking_allocs_lock };     if (threads.empty() || _tracking_allocs.empty())        return false;     const auto log_enabled = debug::is_component_tracing_enabled<debug::component_trace_t::garbage_collector>();    if (log_enabled)        debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: begin: collect\n");     const auto curr_colour = get_current_colour(_collection_epoch);    mark(threads, curr_colour);     const auto sweeper_colour = get_sweeper_colour(_collection_epoch);    sweep(_tracking_allocs, sweeper_colour);     if (log_enabled)        debug::log_msg(debug::component_trace_t::garbage_collector, debug::log_level_t::debug, "gc: end: collect\n");     ++_collection_epoch;    return true;}