From fda9e6728f84b2919c89cb94e99af59533be1e85 Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Fri, 7 Apr 2023 20:40:01 +0200 Subject: [PATCH] Cleaned up include guards --- src/GC/include/cheap.h | 4 +- src/GC/include/event.hpp | 53 +++ src/GC/include/heap.hpp | 97 ++++++ src/GC/include/profiler.hpp | 50 +++ src/GC/lib/cheap.cpp | 4 +- src/GC/lib/event.cpp | 4 - src/GC/lib/heap.cpp | 636 ++++++++++++++++++++++++++++++++++++ 7 files changed, 840 insertions(+), 8 deletions(-) create mode 100644 src/GC/include/event.hpp create mode 100644 src/GC/include/heap.hpp create mode 100644 src/GC/include/profiler.hpp create mode 100644 src/GC/lib/heap.cpp diff --git a/src/GC/include/cheap.h b/src/GC/include/cheap.h index d2c649d..f4cd03c 100644 --- a/src/GC/include/cheap.h +++ b/src/GC/include/cheap.h @@ -7,9 +7,9 @@ extern "C" { #endif -// #define DEBUG +// #define WRAPPER_DEBUG -#ifdef DEBUG +#ifdef WRAPPER_DEBUG typedef struct cheap { void *obj; diff --git a/src/GC/include/event.hpp b/src/GC/include/event.hpp new file mode 100644 index 0000000..d4d5e10 --- /dev/null +++ b/src/GC/include/event.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "chunk.hpp" + +namespace GC +{ + /** + * Types of events that can occur on the heap. + */ + enum GCEventType + { + HeapInit, + AllocStart, + CollectStart, + MarkStart, + ChunkMarked, + ChunkSwept, + ChunkFreed, + NewChunk, + ReusedChunk, + ProfilerDispose + }; + + /** + * Stores metadeta about an event on the heap. + */ + class GCEvent + { + private: + const GCEventType m_type; + const std::time_t m_timestamp {std::time(NULL)}; + const Chunk *m_chunk {nullptr}; + const size_t m_size {0}; + + public: + GCEvent(GCEventType type) : m_type(type) {} + GCEvent(GCEventType type, Chunk *chunk) : m_type(type), m_chunk(chunk) {} + GCEvent(GCEventType type, size_t size) : m_type(type), m_size(size) {} + + ~GCEvent() { + if (m_chunk != nullptr) + delete m_chunk; + } + + GCEventType get_type(); + std::time_t get_time_stamp(); + const Chunk *get_chunk(); + size_t get_size(); + const char *type_to_string(); + }; +} \ No newline at end of file diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp new file mode 100644 index 0000000..36fa83b --- /dev/null +++ b/src/GC/include/heap.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include +#include + +#include "chunk.hpp" +#include "profiler.hpp" + +#define HEAP_SIZE 2097152 //65536 +#define FREE_THRESH (uint) 100000 +// #define HEAP_DEBUG + +namespace GC +{ + /** + * Flags for the collect overlead for conditional + * collection (mark/sweep/free/all). + */ + enum CollectOption { + MARK=0x1, + SWEEP=0x2, + MARK_SWEEP = 0x3, + FREE=0x4, + COLLECT_ALL=0x7 + }; + + /** + * The heap class to represent the heap for the + * garbage collection. The heap is a singleton + * instance and can be retrieved by Heap::the() + * inside the heap class. The heap is represented + * by a char array of size 65536 and can enable + * a profiler to track the actions on the heap. + */ + class Heap + { + private: + Heap() : m_heap(static_cast(malloc(HEAP_SIZE))) {} + + ~Heap() + { + std::free((char *)m_heap); + } + + char *const m_heap; + size_t m_size {0}; + // static Heap *m_instance {nullptr}; + uintptr_t *m_stack_top {nullptr}; + bool m_profiler_enable {false}; + + std::vector m_allocated_chunks; + std::vector m_freed_chunks; + + static bool profiler_enabled(); + // static Chunk *get_at(std::vector &list, size_t n); + void collect(); + void sweep(Heap &heap); + Chunk *try_recycle_chunks(size_t size); + void free(Heap &heap); + void free_overlap(Heap &heap); + void mark(uintptr_t *start, const uintptr_t *end, std::vector &worklist); + void print_line(Chunk *chunk); + void print_worklist(std::vector &list); + void mark_step(uintptr_t start, uintptr_t end, std::vector &worklist); + + // Temporary + Chunk *try_recycle_chunks_new(size_t size); + void free_overlap_new(Heap &heap); + + public: + /** + * These are the only five functions which are exposed + * as the API for LLVM. At the absolute start of the + * program the developer has to call init() to ensure + * that the address of the topmost stack frame is + * saved as the limit for scanning the stack in collect. + */ + + static Heap &the(); + static void init(); + static void dispose(); + static void *alloc(size_t size); + void set_profiler(bool mode); + + // Stop the compiler from generating copy-methods + Heap(Heap const&) = delete; + Heap& operator=(Heap const&) = delete; + +#ifdef HEAP_DEBUG + void collect(CollectOption flags); // conditional collection + void check_init(); // print dummy things + void print_contents(); // print dummy things + void print_allocated_chunks(Heap *heap); // print the contents in m_allocated_chunks + void print_summary(); +#endif + }; +} \ No newline at end of file diff --git a/src/GC/include/profiler.hpp b/src/GC/include/profiler.hpp new file mode 100644 index 0000000..4864dd6 --- /dev/null +++ b/src/GC/include/profiler.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include "chunk.hpp" +#include "event.hpp" + +namespace GC { + + class Profiler { + private: + Profiler() {} + ~Profiler() + { + for (GCEvent *c : m_events) + delete c; + } + + /** + * Returns the instance of the Profiler singleton. + * If m_instance is the nullptr and the profiler + * is not initialized yet, initialize it and return + * the pointer to it. Otherwise return the previously + * initialized pointer. + * + * @returns The pointer to the profiler singleton. + */ + static Profiler *the() + { + if (m_instance) + return m_instance; + m_instance = new Profiler(); + return m_instance; + } + + inline static Profiler *m_instance {nullptr}; + std::vector m_events; + + std::ofstream create_file_stream(); + std::string get_log_folder(); + static void dump_trace(); + + public: + static void record(GCEventType type); + static void record(GCEventType type, size_t size); + static void record(GCEventType type, Chunk *chunk); + static void dispose(); + }; +} \ No newline at end of file diff --git a/src/GC/lib/cheap.cpp b/src/GC/lib/cheap.cpp index 29a0b10..f6c24a9 100644 --- a/src/GC/lib/cheap.cpp +++ b/src/GC/lib/cheap.cpp @@ -1,10 +1,10 @@ #include -#include +#include #include "heap.hpp" #include "cheap.h" -#ifndef DEBUG +#ifndef WRAPPER_DEBUG struct cheap { void *obj; diff --git a/src/GC/lib/event.cpp b/src/GC/lib/event.cpp index 185c613..89a2a71 100644 --- a/src/GC/lib/event.cpp +++ b/src/GC/lib/event.cpp @@ -1,7 +1,3 @@ -// #include -// #include -// #include - #include "chunk.hpp" #include "event.hpp" diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp new file mode 100644 index 0000000..61319f0 --- /dev/null +++ b/src/GC/lib/heap.cpp @@ -0,0 +1,636 @@ +#include +#include +#include +#include + +#include "heap.hpp" + +using std::cout, std::endl, std::vector, std::hex, std::dec; + +namespace GC +{ + /** + * This implementation of the() guarantees laziness + * on the instance and a correct destruction with + * the destructor. + * + * @returns The singleton object. + */ + Heap& Heap::the() + { + static Heap instance; + return instance; + } + + /** + * Initialises the heap singleton and saves the address + * of the calling function's stack frame as the stack_top. + * Presumeably this address points to the stack frame of + * the compiled LLVM executable after linking. + */ + void Heap::init() + { + Heap &heap = Heap::the(); + if (heap.profiler_enabled()) + Profiler::record(HeapInit); +// clang complains because arg for __b_f_a is not 0 which is "unsafe" +#pragma clang diagnostic ignored "-Wframe-address" + heap.m_stack_top = static_cast(__builtin_frame_address(1)); + } + + /** + * Disposes the heap and the profiler at program exit + * which also triggers a heap log file dumped if the + * profiler is enabled. + */ + void Heap::dispose() + { + Heap &heap = Heap::the(); + if (heap.profiler_enabled()) + Profiler::dispose(); + } + + /** + * Allocates a given amount of bytes on the heap. + * + * @param size The amount of bytes to be allocated. + * + * @return A pointer to the address where the memory + * has been allocated. This pointer is supposed + * to be casted to and object pointer. + */ + void *Heap::alloc(size_t size) + { + // Singleton + Heap &heap = Heap::the(); + bool profiler_enabled = heap.profiler_enabled(); + + if (profiler_enabled) + Profiler::record(AllocStart, size); + + if (size == 0) + { + cout << "Heap: Cannot alloc 0B. No bytes allocated." << endl; + return nullptr; + } + + if (heap.m_size + size > HEAP_SIZE) + { + heap.collect(); + // If memory is not enough after collect, crash with OOM error + if (heap.m_size + size > HEAP_SIZE) + throw std::runtime_error(std::string("Error: Heap out of memory")); + } + + // If a chunk was recycled, return the old chunk address + Chunk *reused_chunk = heap.try_recycle_chunks(size); + if (reused_chunk != nullptr) + { + if (profiler_enabled) + Profiler::record(ReusedChunk, reused_chunk); + return static_cast(reused_chunk->m_start); + } + + // If no free chunks was found (reused_chunk is a nullptr), + // then create a new chunk + auto new_chunk = new Chunk(size, (uintptr_t *)(heap.m_heap + heap.m_size)); + + heap.m_size += size; + heap.m_allocated_chunks.push_back(new_chunk); + + if (profiler_enabled) + Profiler::record(NewChunk, new_chunk); + + return new_chunk->m_start; + } + + /** + * Tries to recycle used and freed chunks that are + * already allocated objects by the OS but freed + * from our Heap. This reduces the amount of GC + * objects slightly which saves time from malloc'ing + * memory from the OS. + * + * @param size Amount of bytes needed for the object + * which is about to be allocated. + * + * @returns If a chunk is found and recycled, a + * pointer to the allocated memory for + * the object is returned. If not, a + * nullptr is returned to signify no + * chunks were found. + */ + Chunk *Heap::try_recycle_chunks(size_t size) + { + Heap &heap = Heap::the(); + // Check if there are any freed chunks large enough for current request + for (size_t i = 0; i < heap.m_freed_chunks.size(); i++) + { + //auto chunk = Heap::get_at(heap.m_freed_chunks, i); + auto chunk = heap.m_freed_chunks[i]; + auto iter = heap.m_freed_chunks.begin(); + advance(iter, i); + if (chunk->m_size > size) + { + // Split the chunk, use one part and add the remaining part to + // the list of freed chunks + size_t diff = chunk->m_size - size; + auto chunk_complement = new Chunk(diff, chunk->m_start + chunk->m_size); + + heap.m_freed_chunks.erase(iter); + heap.m_freed_chunks.push_back(chunk_complement); + heap.m_allocated_chunks.push_back(chunk); + + return chunk; + } + else if (chunk->m_size == size) + { + // Reuse the whole chunk + heap.m_freed_chunks.erase(iter); + heap.m_allocated_chunks.push_back(chunk); + return chunk; + } + } + // If no chunk was found, return nullptr + return nullptr; + } + + /** + * Returns a bool whether the profiler is enabled + * or not. + * + * @returns True or false if the profiler is enabled + * or disabled respectively. + */ + bool Heap::profiler_enabled() { + Heap &heap = Heap::the(); + return heap.m_profiler_enable; + } + + /** + * Collection phase of the garbage collector. When + * an allocation is requested and there is no space + * left on the heap, a collection is triggered. This + * function is private so that the user cannot trigger + * a collection unneccessarily. + */ + void Heap::collect() + { + Heap &heap = Heap::the(); + + if (heap.profiler_enabled()) + Profiler::record(CollectStart); + + // get current stack frame + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + + if (heap.m_stack_top == nullptr) + throw std::runtime_error(std::string("Error: Heap is not initialized, read the docs!")); + + uintptr_t *stack_top = heap.m_stack_top; + + auto work_list = heap.m_allocated_chunks; + mark(stack_bottom, stack_top, work_list); + + sweep(heap); + + free(heap); + } + + /** + * Iterates through the stack, if an element on the stack points to a chunk, + * called a root chunk, that chunk is marked (i.e. reachable). + * Then it recursively follows all chunks which are possibly reachable from + * the root chunk and mark those chunks. + * If a chunk is marked it is removed from the worklist, since it's no longer of + * concern for this method. + * + * Time complexity: 0(N^2 * log(N)) as upper bound. + * Where N is either the size of the worklist or the size of + * the stack frame, depending on which is the largest. + * + * @param start Pointer to the start of the stack frame. + * @param end Pointer to the end of the stack frame. + * @param worklist The currently allocated chunks, which haven't been marked. + */ + void Heap::mark(uintptr_t *start, const uintptr_t* const end, vector &worklist) + { + Heap &heap = Heap::the(); + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(MarkStart); + + // To find adresses thats in the worklist + for (; start <= end; start++) + { + auto it = worklist.begin(); + auto stop = worklist.end(); + while (it != stop) + { + Chunk *chunk = *it; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + + // Check if the stack pointer points to something within the chunk + if (c_start <= *start && *start < c_end) + { + if (!chunk->m_marked) + { + if (profiler_enabled) + Profiler::record(ChunkMarked, chunk); + chunk->m_marked = true; + it = worklist.erase(it); + + // Recursively call mark, to see if the reachable chunk further points to another chunk + mark((uintptr_t *)c_start, (uintptr_t *)c_end, worklist); + } + else + { + ++it; + } + } + else + { + ++it; + } + } + } + } + + + /** + * Sweeps the heap, unmarks the marked chunks for the next cycle, + * adds the unmarked nodes to the list of freed chunks; to be freed. + * + * Time complexity: O(N^2), where N is the number of allocated chunks. + * It is quadratic, in the worst case, + * since each call to erase() is linear. + * + * @param heap Pointer to the heap singleton instance. + */ + void Heap::sweep(Heap &heap) + { + auto iter = heap.m_allocated_chunks.begin(); + bool profiler_enabled = heap.m_profiler_enable; + // This cannot "iter != stop", results in seg fault, since the end gets updated, I think. + while (iter != heap.m_allocated_chunks.end()) + { + Chunk *chunk = *iter; + + // Unmark the marked chunks for the next iteration. + if (chunk->m_marked) + { + chunk->m_marked = false; + ++iter; + } + else + { + // Add the unmarked chunks to freed chunks and remove from + // the list of allocated chunks + if (profiler_enabled) + Profiler::record(ChunkSwept, chunk); + heap.m_freed_chunks.push_back(chunk); + iter = heap.m_allocated_chunks.erase(iter); + } + } + } + + /** + * Frees chunks that was moved to the list m_freed_chunks + * by the sweep phase. If there are more than a certain + * amount of free chunks, delete the free chunks to + * avoid cluttering. + * + * Time complexity: O(N^2), where N is the freed chunks. + * If free_overlap() is called, it runs in O(N^2), + * otherwise O(N). + * + * @param heap Heap singleton instance, only for avoiding + * redundant calls to the singleton get + */ + void Heap::free(Heap &heap) + { + if (heap.m_freed_chunks.size() > FREE_THRESH) + { + bool profiler_enabled = heap.profiler_enabled(); + while (heap.m_freed_chunks.size()) + { + auto chunk = heap.m_freed_chunks.back(); + heap.m_freed_chunks.pop_back(); + if (profiler_enabled) + Profiler::record(ChunkFreed, chunk); + delete chunk; + } + } + // if there are chunks but not more than FREE_THRESH + else if (heap.m_freed_chunks.size()) + { + // essentially, always check for overlap between + // chunks before finishing the allocation + free_overlap(heap); + } + } + + /** + * Checks for overlaps between freed chunks of memory + * and removes overlapping chunks while prioritizing + * the chunks at lower addresses. + * + * Time complexity: O(N^2), where N is the number of freed chunks. + * At each iteration get_at() is called, which is linear. + * + * @param heap Heap singleton instance, only for avoiding + * redundant calls to the singleton get + * + * @note Maybe this should be changed to prioritizing + * larger chunks. Should remove get_at() to indexing, + * since that's constant. + */ + void Heap::free_overlap(Heap &heap) // borde göra en record(ChunkFreed) på onödiga chunks + { + std::vector filtered; + size_t i = 0; + //auto prev = Heap::get_at(heap.m_freed_chunks, i++); + auto prev = heap.m_freed_chunks[i++]; + prev->m_marked = true; + filtered.push_back(prev); + cout << filtered.back()->m_start << endl; + for (; i < heap.m_freed_chunks.size(); i++) + { + prev = filtered.back(); + //auto next = Heap::get_at(heap.m_freed_chunks, i); + auto next = heap.m_freed_chunks[i]; + auto p_start = (uintptr_t)(prev->m_start); + auto p_size = (uintptr_t)(prev->m_size); + auto n_start = (uintptr_t)(next->m_start); + if (n_start >= (p_start + p_size)) + { + next->m_marked = true; + filtered.push_back(next); + } + } + heap.m_freed_chunks.swap(filtered); + + bool profiler_enabled = heap.m_profiler_enable; + // After swap m_freed_chunks contains still available chunks + // and filtered contains all the chunks, so delete unused chunks + for (Chunk *chunk : filtered) + { + // if chunk was filtered away, delete it + if (!chunk->m_marked) + { + if (profiler_enabled) + Profiler::record(ChunkFreed, chunk); + delete chunk; + } + else + { + chunk->m_marked = false; + } + } + } + + void Heap::set_profiler(bool mode) + { + Heap &heap = Heap::the(); + heap.m_profiler_enable = mode; + } + +#ifdef HEAP_DEBUG + /** + * Prints the result of Heap::init() and a dummy value + * for the current stack frame for reference. + */ + void Heap::check_init() + { + Heap &heap = Heap::the(); + cout << "Heap addr:\t" << &heap << "\n"; + cout << "GC m_stack_top:\t" << heap.m_stack_top << "\n"; + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + cout << "GC stack_bottom:\t" << stack_bottom << endl; + } + + /** + * Conditional collection, only to be used in debugging + * + * @param flags Bitmap of flags + */ + void Heap::collect(CollectOption flags) + { + set_profiler(true); + + Heap &heap = Heap::the(); + + if (heap.m_profiler_enable) + Profiler::record(CollectStart); + + cout << "DEBUG COLLECT\nFLAGS: "; + if (flags & MARK) + cout << "\n - MARK"; + if (flags & SWEEP) + cout << "\n - SWEEP"; + if (flags & FREE) + cout << "\n - FREE"; + cout << "\n"; + + // get the frame adress, whwere local variables and saved registers are located + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + cout << "Stack bottom in collect:\t" << stack_bottom << "\n"; + uintptr_t *stack_top = heap.m_stack_top; + + cout << "Stack end in collect:\t " << stack_top << endl; + auto work_list = heap.m_allocated_chunks; + + if (flags & MARK) + mark(stack_bottom, stack_top, work_list); + + if (flags & SWEEP) + sweep(heap); + + if (flags & FREE) + free(heap); + } + + // Mark child references from the root references + void mark_test(vector &worklist) + { + while (worklist.size() > 0) + { + Chunk *ref = worklist.back(); + worklist.pop_back(); + Chunk *child = (Chunk *)ref; // this is probably not correct + if (child != nullptr && !child->m_marked) + { + child->m_marked = true; + worklist.push_back(child); + mark_test(worklist); + } + } + } + + // Mark the root references and look for child references to them + void mark_from_roots(uintptr_t *start, const uintptr_t *end) + { + vector worklist; + for (; start > end; start--) + { + if (*start % 8 == 0) + { // all pointers must be aligned as double words + Chunk *ref = (Chunk *)*start; + if (ref != nullptr && !ref->m_marked) + { + ref->m_marked = true; + worklist.push_back(ref); + mark_test(worklist); + } + } + } + } + + // For testing purposes + void Heap::print_line(Chunk *chunk) + { + cout << "Marked: " << chunk->m_marked << "\nStart adr: " << chunk->m_start << "\nSize: " << chunk->m_size << " B\n" + << endl; + } + + void Heap::print_worklist(std::vector &list) + { + for (auto cp : list) + cout << "Chunk at:\t" << cp->m_start << "\nSize:\t\t" << cp->m_size << "\n"; + cout << endl; + } + + void Heap::print_contents() + { + Heap &heap = Heap::the(); + if (heap.m_allocated_chunks.size()) + { + cout << "\nALLOCATED CHUNKS #" << dec << heap.m_allocated_chunks.size() << endl; + for (auto chunk : heap.m_allocated_chunks) + print_line(chunk); + } + else + { + cout << "NO ALLOCATIONS\n" << endl; + } + if (heap.m_freed_chunks.size()) + { + cout << "\nFREED CHUNKS #" << dec << heap.m_freed_chunks.size() << endl; + for (auto fchunk : heap.m_freed_chunks) + print_line(fchunk); + } + else + { + cout << "NO FREED CHUNKS" << endl; + } + } + + void Heap::print_summary() + { + Heap &heap = Heap::the(); + if (heap.m_allocated_chunks.size()) + { + cout << "\nALLOCATED CHUNKS #" << dec << heap.m_allocated_chunks.size() << endl; + } + else + { + cout << "NO ALLOCATIONS\n" << endl; + } + if (heap.m_freed_chunks.size()) + { + cout << "\nFREED CHUNKS #" << dec << heap.m_freed_chunks.size() << endl; + } + else + { + cout << "NO FREED CHUNKS" << endl; + } + } + + void Heap::print_allocated_chunks(Heap *heap) { + cout << "--- Allocated Chunks ---\n" << endl; + for (auto chunk : heap->m_allocated_chunks) { + print_line(chunk); + } + } + + Chunk *Heap::try_recycle_chunks_new(size_t size) + { + Heap &heap = Heap::the(); + // Check if there are any freed chunks large enough for current request + for (size_t i = 0; i < heap.m_freed_chunks.size(); i++) + { + auto chunk = heap.m_freed_chunks[i]; //Heap::get_at(heap.m_freed_chunks, i); + auto iter = heap.m_freed_chunks.begin(); + //advance(iter, i); + i++; + if (chunk->m_size > size) + { + // Split the chunk, use one part and add the remaining part to + // the list of freed chunks + size_t diff = chunk->m_size - size; + auto chunk_complement = new Chunk(diff, chunk->m_start + chunk->m_size); + + heap.m_freed_chunks.erase(iter); + heap.m_freed_chunks.push_back(chunk_complement); + heap.m_allocated_chunks.push_back(chunk); + + return chunk; + } + else if (chunk->m_size == size) + { + // Reuse the whole chunk + heap.m_freed_chunks.erase(iter); + heap.m_allocated_chunks.push_back(chunk); + return chunk; + } + } + // If no chunk was found, return nullptr + return nullptr; + } + + void Heap::free_overlap_new(Heap &heap) // borde göra en record(ChunkFreed) på onödiga chunks + { + std::vector filtered; + size_t i = 0; + auto prev = heap.m_freed_chunks[i++]; //Heap::get_at(heap.m_freed_chunks, i++); + prev->m_marked = true; + filtered.push_back(prev); + cout << filtered.back()->m_start << endl; + for (; i < heap.m_freed_chunks.size(); i++) + { + prev = filtered.back(); + auto next = heap.m_freed_chunks[i]; //Heap::get_at(heap.m_freed_chunks, i); + auto p_start = (uintptr_t)(prev->m_start); + auto p_size = (uintptr_t)(prev->m_size); + auto n_start = (uintptr_t)(next->m_start); + if (n_start >= (p_start + p_size)) + { + next->m_marked = true; + filtered.push_back(next); + } + } + heap.m_freed_chunks.swap(filtered); + + bool profiler_enabled = heap.m_profiler_enable; + // After swap m_freed_chunks contains still available chunks + // and filtered contains all the chunks, so delete unused chunks + for (Chunk *chunk : filtered) + { + // if chunk was filtered away, delete it + if (!chunk->m_marked) + { + if (profiler_enabled) + Profiler::record(ChunkFreed, chunk); + delete chunk; + } + else + { + chunk->m_marked = false; + } + } + } + +#endif +} \ No newline at end of file