diff --git a/src/GC/Makefile b/src/GC/Makefile new file mode 100644 index 0000000..0e037b6 --- /dev/null +++ b/src/GC/Makefile @@ -0,0 +1,102 @@ +CC = clang++ +CWD = $(shell pwd) +LIB_INCL = -I$(CWD)/include +LIB_SO = -L$(CWD)/lib +LIB_LINK = $(CWD)/lib +CFLAGS = -Wall -Wextra -v -g -std=gnu++20 -stdlib=libc++ -I +VGFLAGS = --leak-check=full --show-leak-kinds=all +STDFLAGS = -std=gnu++20 -stdlib=libc++ +WFLAGS = -Wall -Wextra +DBGFLAGS = -g + +advance: + $(CC) $(WFLAGS) $(STDFLAGS) tests/advance.cpp -o tests/advance.out + +file: + $(CC) $(WFLAGS) $(STDFLAGS) tests/file.cpp -o tests/file.out + +heap: + $(CC) $(WFLAGS) $(STDFLAGS) $(LIB_INCL) lib/heap.cpp + +h_test: static_lib + rm -f tests/h_test.out +# $(CC) $(WFLAGS) $(STDFLAGS) $(LIB_INCL) tests/h_test.cpp lib/heap.cpp lib/profiler.cpp lib/event.cpp -o tests/h_test.out + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -o tests/h_test.out tests/h_test.cpp lib/gcoll.a + +h_test_vg: h_test + valgrind $(VGFLAGS) tests/h_test.out + +h_test_dbg: h_test + lldb tests/h_test.out launch + +linker: + rm -f tests/linker.out + $(CC) $(WFLAGS) $(STDFLAGS) $(LIB_INCL) tests/linker.cpp lib/heap.cpp -o tests/linker.out + +linker_vg: linker + valgrind $(VGFLAGS) tests/linker.out + +game: + rm -f tests/game.out + $(CC) $(WFLAGS) $(STDFLAGS) $(LIB_INCL) tests/game.cpp lib/heap.cpp lib/profiler.cpp lib/event.cpp -o tests/game.out + +wrapper_test: + rm -f lib/event.o lib/profiler.o lib/heap.o lib/coll.a tests/wrapper_test.out +# compile object files + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -g -c -o lib/event.o lib/event.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -g -c -o lib/profiler.o lib/profiler.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -g -c -o lib/heap.o lib/heap.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -g -c -o lib/cheap.o lib/cheap.cpp -fPIC +# compile object files into library + ar rcs lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o lib/cheap.o + clang -stdlib=libc++ $(WFLAGS) $(LIB_INCL) -o tests/wrapper_test.out tests/wrapper_test.c lib/gcoll.a -lstdc++ + +extern_lib: +# remove old files + rm -f lib/heap.o lib/libheap.so tests/extern_lib.out +# compile heap to object file + $(CC) $(STDFLAGS) -c -fPIC -o lib/heap.o lib/heap.cpp + + $(CC) $(STDFLAGS) -shared -o lib/libheap.so lib/heap.o + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -v tests/extern_lib.cpp lib/heap.cpp -o tests/extern_lib.out + $(CC) $(STDFLAGS) $(LIB_INCL) $(LIB_SO) -v -Wall -o tests/extern_lib.out tests/extern_lib.cpp -lheap + LD_LIBRARY_PATH=$(LIB_LINK) tests/extern_lib.out + +static_lib: +# remove old files + rm -f lib/event.o lib/profiler.o lib/heap.o lib/gcoll.a tests/extern_lib.out +# compile object files + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -c -o lib/event.o lib/event.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -c -o lib/profiler.o lib/profiler.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -c -o lib/heap.o lib/heap.cpp -fPIC +# create static library + ar r lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o + +# create test program +static_lib_test: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/extern_lib.out tests/extern_lib.cpp lib/gcoll.a + +alloc_free_list: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/alloc_fl.out tests/alloc_free_list.cpp lib/gcoll.a + +linked_list_test: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/linkedlist.out tests/linkedlist.cpp lib/gcoll.a + +revrange: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/revrange.out tests/revrange.cpp lib/gcoll.a + +pointers: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/pointers.out tests/pointers.cpp lib/gcoll.a + +wrapper: +# remove old files + rm -f lib/event.o lib/profiler.o lib/heap.o lib/coll.a tests/wrapper.out +# compile object files + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/event.o lib/event.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/profiler.o lib/profiler.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/heap.o lib/heap.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/cheap.o lib/cheap.cpp -fPIC +# compile object files into library + ar rcs lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o lib/cheap.o +# compile test program wrapper.c with normal clang + clang -stdlib=libc++ $(WFLAGS) $(LIB_INCL) -o tests/wrapper.out tests/wrapper.c lib/gcoll.a -lstdc++ \ No newline at end of file diff --git a/src/GC/docs/lib/cheap.md b/src/GC/docs/lib/cheap.md new file mode 100644 index 0000000..e5c5993 --- /dev/null +++ b/src/GC/docs/lib/cheap.md @@ -0,0 +1,40 @@ +# cheap.h & cheap.cpp + +A wrapper interface for the class `GC::Heap` for easier use +in LLVM (no nasty namespaces). This interface is relatively +straight-forward and only defines functions to use the already +public functions in the class `GC::Heap`. + +The functions are declared in a normal C-style header and +defined as "pure" C-functions. Because the public functions +exposed in `GC::Heap` are static, some of the functions +just call the static functions but are wrapped as C-functions. + +For the non-static function `GC::Heap::set_profiler()` and the +singleton get-instance function `GC::Heap::the()` a struct +is used to encapsulate the heap-object. If this library is +compiled with `DEBUG` defined a struct is typedef-ed and +can be used everywhere, otherwise this struct is opaque +and cannot be used explicitly. This struct only contains +a pointer to the heap instance and is called `cheap_t`. + +## Functions +`cheap_t *cheap_the()`: Returns an encapsulated singleton +instance. It is encapsulated in an opaque struct as the +instance itself is not meant to be used outside the C++ +library. + +`void cheap_init()`: Simply calls the `Heap::init()` +function. + +`void cheap_dispose()`: Only calls the `Heap::dispose()` +function. + +`void *cheap_alloc(unsigned long size)`: Calls `Heap::alloc(size_t size)` +and returns whatever `alloc` returns. + +`void cheap_set_profiler(cheap_t *cheap, bool mode)`: +The argument `cheap` is the encapsulated Heap singleton instance. +`mode` is the same as for `Heap::set_profiler(bool mode)`. + +For more documentation on functionality, see `src/GC/docs/lib/heap.md`. \ No newline at end of file diff --git a/src/GC/include/cheap.h b/src/GC/include/cheap.h new file mode 100644 index 0000000..84f5971 --- /dev/null +++ b/src/GC/include/cheap.h @@ -0,0 +1,37 @@ +#ifndef CHEAP_H +#define CHEAP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEBUG +#define WRAPPER_DEBUG + +#ifdef WRAPPER_DEBUG +typedef struct cheap +{ + void *obj; +} cheap_t; +#else +struct cheap; +typedef struct cheap cheap_t; +#endif + +#define FuncCallsOnly 0x1E +#define ChunkOpsOnly 0x3E0 + +cheap_t *cheap_the(); +void cheap_init(); +void cheap_dispose(); +void *cheap_alloc(unsigned long size); +void cheap_set_profiler(cheap_t *cheap, bool mode); +void cheap_profiler_log_options(cheap_t *cheap, unsigned long flag); + +#ifdef __cplusplus +} +#endif + +#endif /* __CHEAP_H__ */ \ No newline at end of file diff --git a/src/GC/include/chunk.hpp b/src/GC/include/chunk.hpp new file mode 100644 index 0000000..595b50b --- /dev/null +++ b/src/GC/include/chunk.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace GC +{ + /** + * The basic element of what can be stored on + * the heap. A chunk contains a start address + * on the actual heap, the size of memory that + * is allocated at that address and if the + * chunk is reachable (marked). + */ + struct Chunk + { + bool m_marked {false}; + uintptr_t *const m_start {nullptr}; + const size_t m_size {0}; + + Chunk(size_t size, uintptr_t *start) : m_start(start), m_size(size) {} + Chunk(const Chunk *const c) : m_marked(c->m_marked), m_start(c->m_start), m_size(c->m_size) {} + Chunk(const Chunk &c) : m_marked(c.m_marked), m_start(c.m_start), m_size(c.m_size) {} + }; +} \ No newline at end of file diff --git a/src/GC/include/event.hpp b/src/GC/include/event.hpp new file mode 100644 index 0000000..c18b1ce --- /dev/null +++ b/src/GC/include/event.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "chunk.hpp" + +namespace GC +{ + /** + * Types of events that can occur on the heap. + */ + enum GCEventType + { + HeapInit = 1 << 0, + AllocStart = 1 << 1, + CollectStart = 1 << 2, + MarkStart = 1 << 3, + SweepStart = 1 << 4, + ChunkMarked = 1 << 5, + ChunkSwept = 1 << 6, + ChunkFreed = 1 << 7, + NewChunk = 1 << 8, + ReusedChunk = 1 << 9, + ProfilerDispose = 1 << 10, + FreeStart = 1 << 11 + }; + + /** + * 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..4d8330f --- /dev/null +++ b/src/GC/include/heap.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "chunk.hpp" +#include "profiler.hpp" + +#define HEAP_SIZE 160000//65536 +#define FREE_THRESH (uint) 5 +// #define HEAP_DEBUG + +namespace GC +{ + /** + * Flags for the collect overlead for conditional + * collection (mark/sweep/free/all). + */ + enum CollectOption { + MARK = 1 << 0, + SWEEP = 1 << 1, + MARK_SWEEP = 1 << 2, + FREE = 1 << 3, + COLLECT_ALL = 0b1111 // all flags above + }; + + struct AddrRange + { + const uintptr_t *start, *end; + + AddrRange(uintptr_t *_start, uintptr_t *_end) : start(_start), end(_end) {} + }; + + /** + * 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; + std::list m_free_list; + std::unordered_map m_chunk_table; + + static bool profiler_enabled(); + // static Chunk *get_at(std::vector &list, size_t n); + void collect(uintptr_t *stack_bottom); + void sweep(Heap &heap); + Chunk *try_recycle_chunks(size_t size); + void free(Heap &heap); + void free_overlap(Heap &heap); + void mark_hash(uintptr_t *start, const uintptr_t *end); + Chunk* find_pointer_hash(uintptr_t *start, const uintptr_t *end); + void create_table(); + void print_line(Chunk *chunk); + void print_worklist(std::vector &list); + void mark_step(uintptr_t start, uintptr_t end, std::vector &worklist); + void mark_range(std::vector &ranges, std::vector &worklist); + + void find_roots(uintptr_t *stack_bottom, std::vector &roots); + void mark(std::vector &roots); + void find_chunks(uintptr_t *stack_addr, std::queue> &chunk_spaces); + + // 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); + void set_profiler_log_options(RecordOption flags); + + // 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..56acf7b --- /dev/null +++ b/src/GC/include/profiler.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +#include "chunk.hpp" +#include "event.hpp" + +// #define FunctionCallTypes +// #define ChunkOpsTypes + +namespace GC { + + enum RecordOption + { + TimingInfo = 0, + FunctionCalls = (GC::AllocStart | GC::CollectStart | GC::MarkStart | GC::SweepStart | GC::FreeStart), + ChunkOps = (GC::ChunkMarked | GC::ChunkSwept | GC::ChunkFreed | GC::NewChunk | GC::ReusedChunk), + AllOps = 0xFFFFFF + }; + + struct ProfilerEvent + { + uint m_n {1}; + const GCEventType m_type; + + ProfilerEvent(GCEventType type) : m_type(type) {} + }; + + class Profiler { + private: + Profiler() {} + ~Profiler() + { + for (GCEvent *c : m_events) + delete c; + } + + static Profiler &the(); + inline static Profiler *m_instance {nullptr}; + std::vector m_events; + ProfilerEvent *m_last_prof_event {new ProfilerEvent(HeapInit)}; + std::vector m_prof_events; + RecordOption flags {AllOps}; + + std::chrono::microseconds alloc_time {0}; + // size_t alloc_counts {0}; + std::chrono::microseconds collect_time {0}; + // size_t collect_counts {0}; + + static void record_data(GCEvent *type); + std::ofstream create_file_stream(); + std::string get_log_folder(); + static void dump_trace(); + static void dump_prof_trace(bool timing_only); + static void dump_chunk_trace(); + // static void dump_trace_short(); + // static void dump_trace_full(); + static void print_chunk_event(GCEvent *event, char buffer[22]); + static const char *type_to_string(GCEventType type); + + public: + static RecordOption log_options(); + static void set_log_options(RecordOption flags); + static void record(GCEventType type); + static void record(GCEventType type, size_t size); + static void record(GCEventType type, Chunk *chunk); + static void record(GCEventType type, std::chrono::microseconds time); + static void dispose(); + }; +} \ No newline at end of file diff --git a/src/GC/lib/cheap.cpp b/src/GC/lib/cheap.cpp new file mode 100644 index 0000000..7870c75 --- /dev/null +++ b/src/GC/lib/cheap.cpp @@ -0,0 +1,61 @@ +#include +#include + +#include "heap.hpp" +#include "cheap.h" + +#ifndef WRAPPER_DEBUG +struct cheap +{ + void *obj; +}; +#endif + +cheap_t *cheap_the() +{ + cheap_t *c; + GC::Heap *heap; + + c = static_cast(malloc(sizeof(cheap_t))); + heap = &GC::Heap::the(); + c->obj = heap; + + return c; +} + +void cheap_init() +{ + GC::Heap::init(); +} + +void cheap_dispose() +{ + GC::Heap::dispose(); +} + +void *cheap_alloc(unsigned long size) +{ + return GC::Heap::alloc(size); +} + +void cheap_set_profiler(cheap_t *cheap, bool mode) +{ + GC::Heap *heap = static_cast(cheap->obj); + + heap->set_profiler(mode); +} + +void cheap_profiler_log_options(cheap_t *cheap, unsigned long flags) +{ + GC::Heap *heap = static_cast(cheap->obj); + + GC::RecordOption cast_flag; + if (flags == FuncCallsOnly) + cast_flag = GC::FunctionCalls; + else if (flags == ChunkOpsOnly) + cast_flag = GC::ChunkOps; + else + cast_flag = GC::AllOps; + + heap->set_profiler_log_options(cast_flag); +} \ No newline at end of file diff --git a/src/GC/lib/event.cpp b/src/GC/lib/event.cpp new file mode 100644 index 0000000..1e7bfe8 --- /dev/null +++ b/src/GC/lib/event.cpp @@ -0,0 +1,73 @@ +#include "chunk.hpp" +#include "event.hpp" + +namespace GC +{ + /** + * @returns The type of the event + */ + GCEventType GCEvent::get_type() + { + return m_type; + } + + /** + * @returns The time the event happened in + * the form of time_t. + */ + std::time_t GCEvent::get_time_stamp() + { + return m_timestamp; + } + + /** + * If the event is related to a chunk, this + * function returns the chunk that it is + * related to. If the event is independent + * of a chunk, it returns the nullptr. + * + * @returns A chunk pointer or the nullptr. + */ + const Chunk *GCEvent::get_chunk() + { + return m_chunk; + } + + /** + * If the event is an AllocStart event, this + * returns the size of the alloc() request. + * otherwise this returns 0. + * + * @returns A number representing the number + * of bytes requested to alloc() + * or 0 if the event is not an + * AllocStart event. + */ + size_t GCEvent::get_size() + { + return m_size; + } + + /** + * @returns The string conversion of the event type. + */ + const char *GCEvent::type_to_string() + { + switch (m_type) + { + case HeapInit: return "HeapInit"; + case AllocStart: return "AllocStart"; + case CollectStart: return "CollectStart"; + case MarkStart: return "MarkStart"; + case SweepStart: return "SweepStart"; + case ChunkMarked: return "ChunkMarked"; + case ChunkSwept: return "ChunkSwept"; + case ChunkFreed: return "ChunkFreed"; + case NewChunk: return "NewChunk"; + case ReusedChunk: return "ReusedChunk"; + case ProfilerDispose: return "ProfilerDispose"; + case FreeStart: return "FreeStart"; + default: return "[Unknown]"; + } + } +} \ No newline at end of file diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp new file mode 100644 index 0000000..3213a67 --- /dev/null +++ b/src/GC/lib/heap.cpp @@ -0,0 +1,824 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "heap.hpp" + +#define time_now std::chrono::high_resolution_clock::now() +#define to_us std::chrono::duration_cast + +using std::cout, std::endl, std::vector, std::hex, std::dec, std::unordered_map; + +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)); + // TODO: handle this below + //heap.m_heap_top = heap.m_heap; + } + + void Heap::set_profiler_log_options(RecordOption flags) + { + Profiler::set_log_options(flags); + } + + /** + * 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) + { + auto a_start = time_now; + // 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) + { + // auto a_ms = to_us(c_start - a_start); + // Profiler::record(AllocStart, a_ms); + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + heap.collect(stack_bottom); + // If memory is not enough after collect, crash with OOM error + if (heap.m_size > HEAP_SIZE) + { + throw std::runtime_error(std::string("Error: Heap out of memory")); + } + //throw std::runtime_error(std::string("Error: Heap out of memory")); + } + if (heap.m_size + size > HEAP_SIZE) + { + if (profiler_enabled) + Profiler::dispose(); + 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); + auto a_end = time_now; + auto a_ms = to_us(a_end - a_start); + Profiler::record(AllocStart, a_ms); + 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; + // TODO: handle this below + //heap.m_total_size += size; + heap.m_allocated_chunks.push_back(new_chunk); + + if (profiler_enabled) + Profiler::record(NewChunk, new_chunk); + + auto a_end = time_now; + auto a_ms = to_us(a_end - a_start); + Profiler::record(AllocStart, a_ms); + 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(); + i++; + //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(uintptr_t *stack_bottom) + { + auto c_start = time_now; + + Heap &heap = Heap::the(); + + if (heap.profiler_enabled()) + Profiler::record(CollectStart); + + // get current stack frame + 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); + + // Testing mark_hash, previous woking implementation above + // create_table(); + // mark_hash(stack_bottom, stack_top); + + create_table(); + vector roots; + // cout << "\nb4 find_roots\n"; + find_roots(stack_bottom, roots); + + // cout << "b4 mark\n";'' + mark(roots); + + // cout << "b4 sweep\n"; + sweep(heap); + + // cout << "b4 free\n"; + free(heap); + + auto c_end = time_now; + + Profiler::record(CollectStart, to_us(c_end - c_start)); + } + + void Heap::find_roots(uintptr_t *stack_bottom, vector &roots) + { + auto heap_bottom = reinterpret_cast(m_heap); + auto heap_top = reinterpret_cast(m_heap + HEAP_SIZE); + + while (stack_bottom < m_stack_top) + { + if (heap_bottom < *stack_bottom && *stack_bottom < heap_top) + { + roots.push_back(stack_bottom); + } + stack_bottom++; + } + } + + void Heap::mark(vector &roots) + { + bool prof_enabled = m_profiler_enable; + if (prof_enabled) + Profiler::record(MarkStart); + + auto iter = roots.begin(), end = roots.end(); + std::queue> chunk_spaces; + + while (iter != end) + { + find_chunks(*iter++, chunk_spaces); + } + + while (!chunk_spaces.empty()) + { + auto range = chunk_spaces.front(); + chunk_spaces.pop(); + + auto addr_bottom = reinterpret_cast(range.first); + auto addr_top = reinterpret_cast(range.second); + + while (addr_bottom < addr_top) + { + find_chunks(addr_bottom, chunk_spaces); + addr_bottom++; + } + } + } + + void Heap::find_chunks(uintptr_t *stack_addr, std::queue> &chunk_spaces) + { + Heap &heap = Heap::the(); + + auto it = heap.m_chunk_table.find(*stack_addr); + if (it != heap.m_chunk_table.end()) + { + auto chunk = it->second; + + if (!chunk->m_marked) + { + 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); + + chunk->m_marked = true; + chunk_spaces.push(std::make_pair(c_start, c_end)); + } + + } + +/* auto iter = m_allocated_chunks.begin(); + auto end = m_allocated_chunks.end(); + + while (iter != end) + { + auto chunk = *iter++; + + if (chunk->m_marked) + continue; + + 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); + + if (c_start < *stack_addr && *stack_addr < c_end) + { + chunk->m_marked = true; + chunk_spaces.push(std::make_pair(c_start, c_end)); + } + } */ + + } + + void Heap::create_table() + { + Heap &heap = Heap::the(); + unordered_map chunk_table; + for (auto chunk : heap.m_allocated_chunks) { + auto pair = std::make_pair(reinterpret_cast(chunk->m_start), chunk); + heap.m_chunk_table.insert(pair); + } + } + + void Heap::mark_hash(uintptr_t *start, const uintptr_t* const end) + { + Heap &heap = Heap::the(); + + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(MarkStart); + + for (; start <= end; start++) + { + auto search = heap.m_chunk_table.find(*start); + if (search != heap.m_chunk_table.end()) + { + Chunk *chunk = search->second; + 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); + if (!chunk->m_marked) + { + chunk->m_marked = true; + + if (profiler_enabled) + Profiler::record(ChunkMarked, chunk); + + //mark_hash(chunk->m_start, c_end); + Chunk *next = find_pointer_hash((uintptr_t *) c_start, (uintptr_t *) c_end); + while (next != NULL) + { + if (!next->m_marked) + { + next->m_marked = true; + + if (profiler_enabled) + Profiler::record(ChunkMarked, chunk); + + auto c_start = reinterpret_cast(next->m_start); + auto c_size = reinterpret_cast(next->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + next = find_pointer_hash((uintptr_t *) c_start, (uintptr_t *) c_end); + } + } + } + } + } + } + + /** + * 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) + { + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(SweepStart); + auto iter = heap.m_allocated_chunks.begin(); + // std::cout << "Chunks alloced: " << heap.m_allocated_chunks.size() << std::endl; + // 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); + heap.m_size -= chunk->m_size; + // cout << "Decremented total heap size with: " << chunk->m_size << endl; + // cout << "Total size is: " << heap.m_size << endl; + } + } + // std::cout << "Chunks left: " << heap.m_allocated_chunks.size() << std::endl; + } + + /** + * 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) + { + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(FreeStart); + 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); + // heap.m_size -= chunk->m_size; + // cout << "Decremented total heap size with: " << chunk->m_size << endl; + // cout << "Total size is: " << heap.m_size << endl; + 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); + heap.m_size -= chunk->m_size; + cout << "Decremented total heap size with: " << chunk->m_size << endl; + cout << "Total size is: " << heap.m_size << endl; + delete chunk; + } + else + { + chunk->m_marked = false; + } + } + } + + void Heap::set_profiler(bool mode) + { + Heap &heap = Heap::the(); + heap.m_profiler_enable = mode; + } + + Chunk* find_pointer(uintptr_t *start, const uintptr_t* const end, vector &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) + { + return chunk; + } + return NULL; + } + } + } + + // Checks if a given chunk points to another chunk and returns it + Chunk* Heap::find_pointer_hash(uintptr_t *start, const uintptr_t* const end) { + Heap &heap = Heap::the(); + for (; start <= end; start++) { + auto search = heap.m_chunk_table.find(*start); + if (search != heap.m_chunk_table.end()) { + return search->second; + } + return NULL; + } + } + +#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 diff --git a/src/GC/lib/profiler.cpp b/src/GC/lib/profiler.cpp new file mode 100644 index 0000000..690da39 --- /dev/null +++ b/src/GC/lib/profiler.cpp @@ -0,0 +1,316 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chunk.hpp" +#include "event.hpp" +#include "profiler.hpp" + +// #define MAC_OS + +namespace GC +{ + Profiler& Profiler::the() + { + static Profiler instance; + return instance; + } + + RecordOption Profiler::log_options() + { + Profiler &prof = Profiler::the(); + return prof.flags; + } + + void Profiler::set_log_options(RecordOption flags) + { + Profiler &prof = Profiler::the(); + prof.flags = flags; + } + + void Profiler::record_data(GCEvent *event) + { + Profiler &prof = Profiler::the(); + prof.m_events.push_back(event); + + if (prof.m_last_prof_event->m_type == event->get_type()) + prof.m_last_prof_event->m_n++; + else + { + prof.m_prof_events.push_back(prof.m_last_prof_event); + prof.m_last_prof_event = new ProfilerEvent(event->get_type()); + } + } + + /** + * Records an event independent of a chunk. + * + * @param type The type of event to record. + */ + void Profiler::record(GCEventType type) + { + Profiler &prof = Profiler::the(); + if (prof.flags & type) + Profiler::record_data(new GCEvent(type)); + // auto event = new GCEvent(type); + // auto profiler = Profiler::the(); + // profiler.m_events.push_back(event); + } + + /** + * This overload is only used with an AllocStart + * event. + * + * @param type The type of event to record. + * + * @param size The size of requested to alloc(). + */ + void Profiler::record(GCEventType type, size_t size) + { + Profiler &prof = Profiler::the(); + if (prof.flags & type) + Profiler::record_data(new GCEvent(type, size)); + // auto event = new GCEvent(type, size); + // auto profiler = Profiler::the(); + // profiler.m_events.push_back(event); + } + + void Profiler::dump_trace() + { + Profiler &prof = Profiler::the(); + if (prof.flags == TimingInfo) + dump_prof_trace(true); + else if (prof.flags & FunctionCalls) + dump_prof_trace(false); + else + dump_chunk_trace(); + } + + /** + * Records an event related to a chunk. + * + * @param type The type of event to record. + * + * @param chunk The chunk the event is connected + * to. + */ + void Profiler::record(GCEventType type, Chunk *chunk) + { + // Create a copy of chunk to store in the profiler + // because in free() chunks are deleted and cannot + // be referenced by the profiler. These copied + // chunks are deleted by the profiler on dispose(). + Profiler &prof = Profiler::the(); + if (prof.flags & type) + { + auto chunk_copy = new Chunk(chunk); + auto event = new GCEvent(type, chunk_copy); + Profiler::record_data(event); + } + // auto profiler = Profiler::the(); + // profiler.m_events.push_back(event); + } + + void Profiler::record(GCEventType type, std::chrono::microseconds time) + { + Profiler &prof = Profiler::the(); + if (type == AllocStart) + { + prof.alloc_time += time; + } + else if (type == CollectStart) + { + prof.collect_time += time; + } + } + + void Profiler::dump_prof_trace(bool timing_only) + { + Profiler &prof = Profiler::the(); + prof.m_prof_events.push_back(prof.m_last_prof_event); + auto start = prof.m_prof_events.begin(); + auto end = prof.m_prof_events.end(); + int allocs = 0, collects = 0; + + char buffer[22]; + std::ofstream fstr = prof.create_file_stream(); + + while (start != end) + { + auto event = *start++; + + if (event->m_type == AllocStart) + allocs += event->m_n; + else if (event->m_type == CollectStart) + collects += event->m_n; + + if (!timing_only) + { + fstr << "\n--------------------------------\n" + << Profiler::type_to_string(event->m_type) << " " + << event->m_n << " times:"; + } + } + fstr << "\n--------------------------------"; + + fstr << "\n\nTime spent on allocations:\t" << prof.alloc_time.count() << " microseconds" + << "\nAllocation cycles:\t" << allocs + << "\nTime spent on collections:\t" << prof.collect_time.count() << " microseconds" + << "\nCollection cycles:\t" << collects + << "\n--------------------------------"; + } + + /** + * Prints the history of the recorded events + * to a log file in the /tests/logs folder. + */ + void Profiler::dump_chunk_trace() + { + Profiler &prof = Profiler::the(); + auto start = prof.m_events.begin(); + auto end = prof.m_events.end(); + + // Buffer for timestamp + char buffer[22]; + + while (start != end) + { + auto event = *start++; + auto e_type = event->get_type(); + + prof.print_chunk_event(event, buffer); + } + } + + void Profiler::print_chunk_event(GCEvent *event, char buffer[22]) + { + Profiler &prof = Profiler::the(); + // File output stream + std::ofstream fstr = prof.create_file_stream(); + std::time_t tt = event->get_time_stamp(); + std::tm *btm = std::localtime(&tt); + std::strftime(buffer, 22, "%a %T", btm); + + fstr << "--------------------------------\n" + << buffer + << "\nEvent:\t" << Profiler::type_to_string(event->get_type()); + // event->type_to_string(); + + + + const Chunk *chunk = event->get_chunk(); + + if (event->get_type() == AllocStart) + { + fstr << "\nSize: " << event->get_size(); + } + else if (chunk) + { + fstr << "\nChunk: " << chunk->m_start + << "\n Size: " << chunk->m_size + << "\n Mark: " << chunk->m_marked; + } + fstr << "\n"; + } + + /** + * Deletes the profiler singleton and all + * the events recorded after recording + * the ProfilerDispose event and dumping + * the history to a log file. + */ + void Profiler::dispose() + { + Profiler::record(ProfilerDispose); + Profiler::dump_trace(); + } + + /** + * Creates a filestream for the future + * log file to print the history to in + * dump_trace(). + * + * @returns The output stream to the file. + */ + std::ofstream Profiler::create_file_stream() + { + // get current time + std::time_t tt = std::time(NULL); + std::tm *ptm = std::localtime(&tt); + + // format to string + char buffer[32]; + std::strftime(buffer, 32, "/log_%a_%H_%M_%S.txt", ptm); + std::string filename(buffer); + + // const std::string ABS_PATH = "/home/virre/dev/systemF/org/language/src/GC/"; + // // const std::string ABS_PATH = "/Users/valtermiari/Desktop/DV/Bachelors/code/language/src/GC"; + // std::string fullpath = ABS_PATH + filename; + + const std::string fullpath = get_log_folder() + filename; + + std::ofstream fstr(fullpath); + return fstr; + } + + /** + * This function retrieves the path to the folder + * of the executable to use for log files. + * + * @returns The path to the logs folder. + * + * @throws A runtime error if the call + * to readlink() fails. + */ + std::string Profiler::get_log_folder() + { +#ifndef MAC_OS + char buffer[1024]; + // chars read from path + ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer)-1); + + // if readlink fails + if (len == -1) + { + throw std::runtime_error(std::string("Error: readlink failed on '/proc/self/exe/'")); + } + + buffer[len] = '\0'; + + // convert to string for string operators + auto path = std::string(buffer); + + // remove filename + size_t last_slash = path.find_last_of('/'); + std::string folder = path.substr(0, last_slash); +#else + auto folder = std::string("/Users/valtermiari/Desktop/DV/Bachelors/code/language/src/GC/tests"); +#endif + return folder + "/logs"; + } + + const char *Profiler::type_to_string(GCEventType type) + { + switch (type) + { + case HeapInit: return "HeapInit"; + case AllocStart: return "AllocStart"; + case CollectStart: return "CollectStart"; + case MarkStart: return "MarkStart"; + case ChunkMarked: return "ChunkMarked"; + case ChunkSwept: return "ChunkSwept"; + case ChunkFreed: return "ChunkFreed"; + case NewChunk: return "NewChunk"; + case ReusedChunk: return "ReusedChunk"; + case ProfilerDispose: return "ProfilerDispose"; + case SweepStart: return "SweepStart"; + case FreeStart: return "FreeStart"; + default: return "[Unknown]"; + } + } +} \ No newline at end of file diff --git a/src/GC/tests/advance.cpp b/src/GC/tests/advance.cpp new file mode 100644 index 0000000..89dca71 --- /dev/null +++ b/src/GC/tests/advance.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include + +// void time_test() +// { +// using TimeStamp = std::chrono::_V2::system_clock::time_point; + +// std::list l; +// char c = 'a'; +// for (int i = 1; i <= 5; i++) { +// l.push_back(c++); +// } + +// auto iter = l.begin(); +// auto stop = l.end(); + +// while (iter != stop) { +// std::cout << *iter << " "; + +// iter++; +// } +// std::cout << std::endl; +// iter = l.begin(); +// while (*iter != *stop) { +// std::cout << *iter << " "; +// iter++; +// } +// std::cout << std::endl; + +// std::cout << "rebased" << std::endl; +// std::cout << "iter: " << *iter << "\nstop: " << *stop << std::endl; + +// TimeStamp ts = std::chrono::system_clock::now(); +// std::time_t tt = std::chrono::system_clock::to_time_t(ts); +// std::string tstr = std::ctime(&tt); +// tstr.resize(tstr.size()-1); +// std::cout << tstr << std::endl; +// } + +void iter_test() +{ + std::list list; + list.push_back(1); + list.push_back(2); + list.push_back(4); + list.push_back(5); + + auto iter = list.begin(); + + while (iter != list.end()) + { + if (*iter == 4) + { + iter = list.erase(iter); + std::cout << *iter << "\n"; + list.insert(iter, 3); + // list.insert(iter, 3); + // std::cout << "n: " << *(++iter) << "\n"; + // iter = list.erase(++iter); + } + iter++; + } + + for (int i : list) + { + std::cout << i << " "; + } + std::cout << std::endl; +} + + + +int main() { + std::cout << "hello" << std::endl; + + iter_test(); + + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/alloc_free_list.cpp b/src/GC/tests/alloc_free_list.cpp new file mode 100644 index 0000000..a0d1a27 --- /dev/null +++ b/src/GC/tests/alloc_free_list.cpp @@ -0,0 +1,250 @@ +#include +#include + +#include "heap.hpp" + +using GC::Chunk; + +void alloc_test(); +void add_to_free_list(Chunk *chunk); +void merge_free_list(Chunk *chunk, bool do_merge); +void do_merge_list(); +void print_free_list(); + +std::list m_free_list; + +int main() +{ + alloc_test(); + + // std::list test; + + // test.push_back(1); + // test.push_back(2); + // test.push_back(3); + // test.push_back(4); + // test.push_back(5); + + // auto iter = test.begin(); + + // std::cout << "First? " << *(iter++) << "\n"; + // std::cout << "Second? " << *(iter--) << "\n"; + // std::cout << "First? " << *iter << std::endl; + + // auto i = test.begin(); + // while (i != test.end()) + // { + // std::cout << *i << " "; + // ++i; + // } + + // if (i == test.end()) + // std::cout << "great success!"; + + // std::cout << std::endl; + + return 0; +} + +void alloc_test() +{ + auto tmp = static_cast(__builtin_frame_address(0)); + + auto c1 = new Chunk((size_t)(8), tmp); + auto c2 = new Chunk((size_t)(4), c1->m_start + (size_t)(8)); + auto c3 = new Chunk((size_t)(16), c2->m_start + (size_t)(4)); + auto c4 = new Chunk((size_t)(4), c3->m_start + (size_t)(16)); + auto c5 = new Chunk((size_t)(32), c4->m_start + (size_t)(4)); + + // std::cout << "test: " << (uintptr_t *)(tmp + (size_t)(2)) << std::endl; + + std::cout << "tmp: " << tmp << "\ntmp: " << (tmp + (size_t)(28)) << std::endl; + + // add_to_free_list(c1); + // add_to_free_list(c2); + // add_to_free_list(c3); + // add_to_free_list(c4); + // add_to_free_list(c5); + + merge_free_list(c1, false); + merge_free_list(c2, false); + merge_free_list(c3, false); + merge_free_list(c4, false); + merge_free_list(c5, false); + + std::cout << "----- BEFORE MERGE ----------------------"; + // print_free_list(); + + do_merge_list(); + + std::cout << "----- AFTER MERGE -----------------------"; + // print_free_list(); +} + +void add_to_free_list(Chunk *chunk) +{ + Chunk *curr; + auto iter = m_free_list.begin(); + uintptr_t *prev_start = nullptr; + uintptr_t *prev_end = nullptr; + + if (m_free_list.size() == 0) + { + m_free_list.push_back(chunk); + return; + } + + while (iter != m_free_list.end()) + { + curr = *iter; + + // If the curr chunk is aligned before param + if (curr->m_start + curr->m_size == chunk->m_start) + { + Chunk *merged = new Chunk( + curr->m_size + chunk->m_size, + curr->m_start); + iter = m_free_list.erase(iter); + m_free_list.insert(iter, merged); + return; + } + + // If the curr chunk is aligned after param + if (chunk->m_start + chunk->m_size == curr->m_start) + { + Chunk *merged = new Chunk( + curr->m_size + chunk->m_size, + chunk->m_start); + iter = m_free_list.erase(iter); + m_free_list.insert(iter, merged); + return; + } + + // If the first chunk starts after param + if (prev_start == nullptr && curr->m_start > chunk->m_start) + { + m_free_list.insert(iter, chunk); + return; + } + + if (prev_end < chunk->m_start && (chunk->m_start + chunk->m_size) < curr->m_start) + { + m_free_list.insert(iter, chunk); + return; + } + + prev_start = curr->m_start; + prev_end = prev_start + curr->m_size; + iter++; + } + + // This is only reachable if the chunk is at the end + m_free_list.push_back(chunk); +} + +void merge_free_list(Chunk *chunk, bool do_merge) +{ + auto i = m_free_list.begin(); + uintptr_t *prev_start = nullptr, *prev_end; + bool chunk_inserted = false; + + while (i != m_free_list.end()) + { + + // if chunk is left-aligned + if ((*i)->m_start + (*i)->m_size == chunk->m_start) + { + m_free_list.insert(++i, chunk); + chunk_inserted = true; + break; + } + + // if chunk is right-aligned + if (chunk->m_start + chunk->m_size == (*i)->m_start) + { + m_free_list.insert(i, chunk); + chunk_inserted = true; + break; + } + + // is new first + if (prev_start == nullptr && (*i)->m_start > chunk->m_start) + { + m_free_list.insert(i, chunk); + chunk_inserted = true; + break; + } + + // if between chunks + if (prev_end < chunk->m_start && (chunk->m_start + chunk->m_size) < (*i)->m_start) + { + m_free_list.insert(i, chunk); + chunk_inserted = true; + break; + } + + prev_start = (*i)->m_start; + prev_end = (*i)->m_start + (*i)->m_size; + i++; + } + + // is new last + if (!chunk_inserted && i == m_free_list.end()) + m_free_list.push_back(chunk); + + if (do_merge) + do_merge_list(); +} + +void do_merge_list() +{ + std::cout << "DO MERGE" << std::endl; + auto i = m_free_list.begin(); + Chunk *prev = *(i++), *curr; + print_free_list(); + + while (i != m_free_list.end()) + { + curr = *i; + + if ((prev->m_start + prev->m_size) == curr->m_start) + { + Chunk *merged = new Chunk( + prev->m_size + curr->m_size, + prev->m_start + ); + + // replace current and previous with merged + i = m_free_list.erase(i); + i = m_free_list.erase(--i); + m_free_list.insert(i, merged); + + prev = merged; + } + else + { + prev = curr; + i++; + } + print_free_list(); + } + print_free_list(); +} + +void print_free_list() +{ + std::cout << "free-list count: " << m_free_list.size() << "\n"; + + auto iter = m_free_list.begin(); + size_t cnt = 1; + + while (iter != m_free_list.end()) + { + std::cout << "C" << cnt << ":\n\tstart: " << (*iter)->m_start + << "\n\tsize: " << (*iter)->m_size << "\n"; + iter++; + cnt++; + } + + std::cout << std::endl; +} \ No newline at end of file diff --git a/src/GC/tests/file.cpp b/src/GC/tests/file.cpp new file mode 100644 index 0000000..f4a0373 --- /dev/null +++ b/src/GC/tests/file.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include + +void time_string(char *buffer); +void print_log_file(const std::string TESTS_PATH); +void readlink_test(); +void null_test(); + +int main() +{ + // char time_buffer[31]; + // time_string(time_buffer); + + // const std::string TESTS_PATH = "/home/virre/dev/systemF/org/language/src/GC/tests/"; + // print_log_file(TESTS_PATH); + + // readlink_test(); + + null_test(); + + return 0; +} + +void time_string(char *const buffer) +{ + std::time_t tt = std::time(NULL); + std::tm *ptm = std::localtime(&tt); + std::strftime(buffer, 31, "/logs/log_%a_%H_%M_%S.txt", ptm); + std::cout << buffer << std::endl; +} + +void print_log_file(const std::string TESTS_PATH) +{ + std::string path = TESTS_PATH + "/testlog.txt"; + + std::ofstream testF(path); + + testF << "hellow york"; + + testF.close(); +} + +void readlink_test() +{ + char buffer[1024]; + ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer)-1); + if (len == -1) + { + std::cout << "readlink error" << std::endl; + return; + } + + buffer[len] = '\0'; + std::cout << "readlink:\n" << "'''" << buffer << "'''"; // << std::endl; + + auto path = std::string(buffer); + std::cout << path << "\nlen: " << path.size() << "\ncap:" << path.capacity(); + + size_t last_slash = path.find_last_of('/'); + std::string folder = path.substr(0, last_slash); + + std::cout << "\n" << folder; + + std::string log_path = folder + "/log_file_bla.txt"; + std::cout << "\n" << log_path << std::endl; + +} + +void null_test() { + int *p = nullptr; + + std::cout << "P: " << nullptr << std::endl; +} \ No newline at end of file diff --git a/src/GC/tests/h_test.cpp b/src/GC/tests/h_test.cpp new file mode 100644 index 0000000..6ce727e --- /dev/null +++ b/src/GC/tests/h_test.cpp @@ -0,0 +1,106 @@ +#include +#include + +#include "heap.hpp" + +using std::cout, std::endl; + +struct Node { + int id; + Node *child; +}; + +Node *create_chain(int depth) { + cout << "entering create_chain" << endl; + std::vector nodes; + if (depth > 0) { + Node *last_node = static_cast(GC::Heap::alloc(sizeof(Node))); + last_node->id = depth; + last_node->child = nullptr; + nodes.push_back(last_node); + for (size_t i = 0; i < depth; i++) { + Node *node = static_cast(GC::Heap::alloc(sizeof(Node))); + node->id = depth-i; + node->child = nodes[i]; + nodes.push_back(node); + } + cout << "\nexiting create_chain" << endl; + return nodes[depth]; + } + else + return 0; +} + +void create_array(size_t size) { + int *arr = static_cast(GC::Heap::alloc(sizeof(int) * size)); +} + +void detach_pointer(long **ptr) { + cout << "entering detach_pointer" << endl; + long *dummy_ptr = nullptr; + *ptr = dummy_ptr; + cout << "\nexiting detach_pointer" << endl; +} + +Node *test_chain(int depth, bool detach) { + cout << "entering test_chain" << endl; + auto stack_start = reinterpret_cast(__builtin_frame_address(0)); + + Node *node_chain = create_chain(depth); + if (detach) + node_chain->child = nullptr; + + cout << "\nexiting test_chain" << endl; + return node_chain; +} + +void test_some_types() { + cout << "entering test_some_types" << endl; + auto stack_start = reinterpret_cast(__builtin_frame_address(0)); + std::cout << "Stack start from test_some_types:\t" << stack_start << std::endl; + + long *l = static_cast(GC::Heap::alloc(sizeof(long))); + std::cout << "l points to:\t\t" << l << std::endl; + detach_pointer(&l); + std::cout << "l points to:\t\t" << l << std::endl; + + // Some more dummy values of different sizes, to test stack pointer alignment + int *i = static_cast(GC::Heap::alloc(sizeof(int))); + char *c = static_cast(GC::Heap::alloc(sizeof(int))); + short *s = static_cast(GC::Heap::alloc(sizeof(short))); + cout << "exiting test_some_types" << endl; +} + +int main() { + cout << "entering main" << endl; + using namespace std::literals; + + auto start = std::chrono::high_resolution_clock::now(); + //std::cout << "Value of start: " << start.time_since_epoch().count() << std::endl; + GC::Heap::init(); + GC::Heap &gc = GC::Heap::the(); + gc.set_profiler(true); + GC::Profiler::set_log_options(GC::FunctionCalls); + gc.check_init(); + auto stack_start = reinterpret_cast(__builtin_frame_address(0)); + + Node *root1 = static_cast(gc.alloc(sizeof(Node))); + Node *root2 = static_cast(gc.alloc(sizeof(Node))); + root1 = test_chain(100000, false); + //root2 = test_chain(58000, false); + + gc.collect(GC::COLLECT_ALL); + auto end = std::chrono::high_resolution_clock::now(); + //std::cout << "Value of end: " << end.time_since_epoch().count() << std::endl; + + gc.print_summary(); + gc.dispose(); + + std::cout + << "Execution time: " + << std::chrono::duration_cast(end - start).count() << " ≈ " + << (end - start) / 1ms << "ms ≈ " + << (end - start) / 1s << "s.\n"; + + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/h_test.out.dSYM/Contents/Info.plist b/src/GC/tests/h_test.out.dSYM/Contents/Info.plist new file mode 100644 index 0000000..3db2218 --- /dev/null +++ b/src/GC/tests/h_test.out.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.h_test.out + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/src/GC/tests/h_test.out.dSYM/Contents/Resources/DWARF/h_test.out b/src/GC/tests/h_test.out.dSYM/Contents/Resources/DWARF/h_test.out new file mode 100644 index 0000000..0d4c74f Binary files /dev/null and b/src/GC/tests/h_test.out.dSYM/Contents/Resources/DWARF/h_test.out differ diff --git a/src/GC/tests/linkedlist.cpp b/src/GC/tests/linkedlist.cpp new file mode 100644 index 0000000..474e9b3 --- /dev/null +++ b/src/GC/tests/linkedlist.cpp @@ -0,0 +1,58 @@ +#include +#include + +#include "heap.hpp" + +#define allocNode static_cast(GC::Heap::alloc(sizeof(Node))) + +using std::cout, std::endl; + +struct Node // sizeof(Node) = 16 +{ + int value; + Node *next {nullptr}; +}; + +Node *create_list(size_t length) +{ + Node *head = allocNode; + head->value = 0; + + Node *prev = head; + Node *next; + + for (size_t i = 1; i < length; i++) + { + next = allocNode; + next->value = i; + prev->next = next; + prev = next; + } + + return head; +} + +#define LIST_SIZE 1000 + +void list_test1() +{ + Node *list_1 = create_list(LIST_SIZE); +} + +int main() +{ + GC::Heap::init(); + GC::Heap &heap = GC::Heap::the(); + heap.set_profiler(true); + // GC::Profiler::set_log_options(GC::FunctionCalls); + // GC::Profiler::set_log_options(GC::ChunkOps); + GC::Profiler::set_log_options(GC::TimingInfo); + + // make_test(); + for (int i = 0; i < 1000; i++) + list_test1(); + + GC::Heap::dispose(); + + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/linker.cpp b/src/GC/tests/linker.cpp new file mode 100644 index 0000000..fb5b979 --- /dev/null +++ b/src/GC/tests/linker.cpp @@ -0,0 +1,16 @@ +#include + +#include "heap.hpp" + +struct Obj { + int a; + int b; + int c; +}; + +int main() { + + + + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/pointers.cpp b/src/GC/tests/pointers.cpp new file mode 100644 index 0000000..265ca30 --- /dev/null +++ b/src/GC/tests/pointers.cpp @@ -0,0 +1,39 @@ +#include +#include +#include + +#include "heap.hpp" + +using std::cout, std::endl, std::hex; + +struct Node { + int value; + Node *next {nullptr}; +}; + +void test(Node *n) { + size_t n_size = 16; + + auto c_start = reinterpret_cast(n); + auto c_size = reinterpret_cast(n_size); + auto c_end = reinterpret_cast(c_start + c_size); + + cout << "Node *n:\t" << n << "\n"; + cout << "n_size: \t0x" << std::hex << n_size << "\n"; + cout << "c_start:\t0x" << std::hex << c_start << "\n"; + cout << "c_size: \t0x" << std::hex << c_size << "\n"; + cout << "c_end: \t0x" << std::hex << c_end << endl; +} + +int main() { + GC::Heap::init(); + GC::Heap &heap = GC::Heap::the(); + heap.set_profiler(true); + heap.set_profiler_log_options(GC::FunctionCalls); + + Node *n = static_cast(GC::Heap::alloc(sizeof(Node))); + test(n); + + GC::Heap::dispose(); + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/revrange.cpp b/src/GC/tests/revrange.cpp new file mode 100644 index 0000000..6a11c57 --- /dev/null +++ b/src/GC/tests/revrange.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "heap.hpp" + +#define allocNode static_cast(GC::Heap::alloc(sizeof(Node))) + +using std::cout, std::endl; + +struct Node { + int value; + Node *next {nullptr}; +}; + +void revRange(int n) { + Node *next = nullptr; + Node *prev = allocNode; + while (n > 0) { + next = allocNode; + prev->next = next; + prev->value = n--; + prev = next; + } +} + +void make_test() { + int n = 10; + while (n > 0) + revRange(1000); +} + +int main() { + GC::Heap::init(); + GC::Heap &heap = GC::Heap::the(); + heap.set_profiler(true); + GC::Profiler::set_log_options(GC::FunctionCalls); + + make_test(); + + GC::Heap::dispose(); + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/wrapper.c b/src/GC/tests/wrapper.c new file mode 100644 index 0000000..d6f042c --- /dev/null +++ b/src/GC/tests/wrapper.c @@ -0,0 +1,96 @@ +#include +#include +#include + +#include "cheap.h" + +typedef struct object +{ + int x, y, z; + double velocity; +} Object; + +void test_init() +{ + printf("----- IN TEST_INIT ----------------------------\n"); + + cheap_init(); + + printf("----- EXIT TEST_INIT --------------------------\n"); +} + +/* Uncomment ONLY if run with DEBUG defined in cheap.h */ + +cheap_t *test_the() +{ + printf("----- IN TEST_THE -----------------------------\n"); + + cheap_t *fst_heap = cheap_the(); + + printf("Heap 1:\t%p\n", fst_heap->obj); + + cheap_t *snd_heap = cheap_the(); + + printf("Heap 2:\t%p\n", snd_heap->obj); + + printf("----- EXIT TEST_THE ---------------------------\n"); + + free(snd_heap); + return fst_heap; +} + +void test_profiler(cheap_t *heap) +{ + printf("----- IN TEST_PROFILER ------------------------\n"); + + cheap_set_profiler(heap, false); + cheap_set_profiler(heap, true); + cheap_profiler_log_options(heap, FuncCallsOnly); + + printf("----- EXIT TEST_PROFILER ----------------------\n"); +} + +Object *test_alloc() +{ + printf("----- IN TEST_ALLOC ---------------------------\n"); + + Object *o; + o = (Object *)(cheap_alloc(sizeof(Object))); + + o->x = 3; + o->y = 4; + o->z = 5; + o->velocity = 1.0f; + + printf("----- EXIT TEST_ALLOC -------------------------\n"); + return o; +} + +void test_dispose() +{ + printf("----- IN TEST_DISPOSE -------------------------\n"); + + cheap_dispose(); + + printf("----- EXIT TEST_DISPOSE -----------------------\n"); +} + +int main() +{ + test_init(); + + /* Uncomment ONLY if run with DEBUG defined in cheap.h */ + cheap_t *heap = test_the(); + test_profiler(heap); + + Object *o = test_alloc(); + printf("Object size: %lu\n", sizeof(Object)); + printf("Object:\n\tx: %d\n\ty: %d\n\tz: %d\n\tvel: %f\n", o->x, o->y, o->z, o->velocity); + + test_dispose(); + + /* Sefault I don't understand, don't uncomment */ + // free(heap); + // free(o); + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/wrapper_test.c b/src/GC/tests/wrapper_test.c new file mode 100644 index 0000000..2055b8c --- /dev/null +++ b/src/GC/tests/wrapper_test.c @@ -0,0 +1,53 @@ +#include +#include + +#include "cheap.h" + +typedef struct node { + int id; + struct node *child; +} Node; + +// Global variables make the test less complex +Node *HEAD = NULL; +Node *CURRENT = NULL; + +void insert_first(int node_id) { + Node *new_head; + new_head = (Node*)(cheap_alloc(sizeof(Node))); + new_head->id = node_id; + new_head->child = HEAD; + + HEAD = new_head; +} + +// Creates a linked list of length depth. Global head "HEAD" is updated. +Node *create_linked_list(int depth) { + HEAD = (Node*)(cheap_alloc(sizeof(Node))); + HEAD->id = 0; + // Purposely omitting adding a child to "last_node", since its the last node + for (int i = 1; i < depth - 1; i++) { + insert_first(i); + } + return HEAD; +} + +void create_garbage(int amount) { + for (int i = 0; i < amount; i++) { + long *garbage = (long*)(cheap_alloc(sizeof(long))); + } +} + +int main () { + cheap_init(); + cheap_t *heap = cheap_the(); + cheap_set_profiler(heap, true); + + // Every node in this list should be marked + Node *head = create_linked_list(5); + // Everything create here should be swept + create_garbage(30); + + cheap_dispose(); + return 0; +} \ No newline at end of file