// // Dis VM // File: garbage_collector.cpp // Author: arr // #include #include #include #include #include #include #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 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> &) override { return true; } }; return std::make_unique(); } 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 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::white, gc_colour_t::grey, gc_colour_t::black }; const auto sweeper_colours = std::array{ 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(&a->gc_reserved); } void set_gc_colour(vm_alloc_t *a, const gc_colour_t c) { a->gc_reserved = reinterpret_cast(c); } } void default_garbage_collector_t::track_allocation(vm_alloc_t *alloc) { assert(alloc != nullptr); if (debug::is_component_tracing_enabled()) 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 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 lock{ _tracking_allocs_lock }; for (auto a : _tracking_allocs) callback(a); } namespace { void mark(const std::vector> &threads, const gc_colour_t curr_colour) { std::queue allocs; auto add_pointer_to_queue = std::function{ [&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(); 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{ [&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 &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::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> &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 lock{ _tracking_allocs_lock }; if (threads.empty() || _tracking_allocs.empty()) return false; const auto log_enabled = debug::is_component_tracing_enabled(); 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; }