diff --git a/.gitignore b/.gitignore index 1daddd6..0b0f588 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ llvm.ll src/GC/lib/*.o src/GC/lib/*.so +src/GC/lib/*.a src/GC/tests/*.out +src/GC/tests/logs \ No newline at end of file diff --git a/src/Accurate_GC/Makefile b/src/Accurate_GC/Makefile new file mode 100644 index 0000000..347e2dc --- /dev/null +++ b/src/Accurate_GC/Makefile @@ -0,0 +1,5 @@ +LEVEL := ../.. +LIBRARYNAME = GC +LOADABLE_MODULE = 1 + +include $(LEVEL)/Makefile.common \ No newline at end of file diff --git a/src/Accurate_GC/gc.cpp b/src/Accurate_GC/gc.cpp new file mode 100644 index 0000000..ddf8bc0 --- /dev/null +++ b/src/Accurate_GC/gc.cpp @@ -0,0 +1,16 @@ +// TODO: include these properly +#include "llvm/CodeGen/GCStrategy.h" +#include "llvm/CodeGen/GCMetadata.h" +#include "llvm/Support/Compiler.h" + +using namespace llvm; + +namespace { + class LLVM_LIBRARY_VISIBILITY GC : public GCStrategy { + public: + GC() {} + }; + + GCRegistry::Add + X("gc", "The bespoken garbage collector."); +} \ No newline at end of file diff --git a/src/Accurate_GC/gc_printer.cpp b/src/Accurate_GC/gc_printer.cpp new file mode 100644 index 0000000..f392c4b --- /dev/null +++ b/src/Accurate_GC/gc_printer.cpp @@ -0,0 +1,16 @@ +#include "llvm/CodeGen/GCMetadataPrinter.h" +#include "llvm/Support/Compiler.h" + +using namespace llvm; + +namespace { + class LLVM_LIBRARY_VISIBILITY GCPrinter : public GCMetadataPrinter { + public: + virtual void beginAssembly(AsmPrinter &AP); + + virtual void finishAssembly(AsmPrinter &AP); + }; + + GCMetadataPrinterRegistry::Add + X("gc", "The bespoken garbage collector."); +} \ No newline at end of file diff --git a/src/Accurate_GC/sample.ll b/src/Accurate_GC/sample.ll new file mode 100644 index 0000000..d737d38 --- /dev/null +++ b/src/Accurate_GC/sample.ll @@ -0,0 +1,4 @@ +define void @f() gc "gc" { +entry: + ret void +} \ No newline at end of file diff --git a/src/Accurate_GC/shadow_stack.cpp b/src/Accurate_GC/shadow_stack.cpp new file mode 100644 index 0000000..2c75629 --- /dev/null +++ b/src/Accurate_GC/shadow_stack.cpp @@ -0,0 +1,63 @@ +/// The map for a single function's stack frame. One of these is +/// compiled as constant data into the executable for each function. +/// +/// Storage of metadata values is elided if the %metadata parameter to +/// @llvm.gcroot is null. +struct FrameMap { + int NumRoots; //< Number of roots in stack frame. (int32_t) + int NumMeta; //< Number of metadata entries. May be < NumRoots. + const void *Meta[0]; //< Metadata for each root. +}; + +/// A link in the dynamic shadow stack. One of these is embedded in +/// the stack frame of each function on the call stack. +struct StackEntry { + StackEntry *Next; //< Link to next stack entry (the caller's). + const FrameMap *Map; //< Pointer to constant FrameMap. + void *Roots[0]; //< Stack roots (in-place array). +}; + +/// The head of the singly-linked list of StackEntries. Functions push +/// and pop onto this in their prologue and epilogue. +/// +/// Since there is only a global list, this technique is not threadsafe. +StackEntry *llvm_gc_root_chain; + +/// Calls Visitor(root, meta) for each GC root on the stack. +/// root and meta are exactly the values passed to +/// @llvm.gcroot. +/// +/// Visitor could be a function to recursively mark live objects. Or it +/// might copy them to another heap or generation. +/// +/// @param Visitor A function to invoke for every GC root on the stack. +void visitGCRoots(void (*Visitor)(void **Root, const void *Meta)) { + for (StackEntry *R = llvm_gc_root_chain; R; R = R->Next) { + unsigned i = 0; + + // For roots [0, NumMeta), the metadata pointer is in the FrameMap. + for (unsigned e = R->Map->NumMeta; i != e; ++i) + Visitor(&R->Roots[i], R->Map->Meta[i]); + + // For roots [NumMeta, NumRoots), the metadata pointer is null. + for (unsigned e = R->Map->NumRoots; i != e; ++i) + Visitor(&R->Roots[i], nullptr); + } +} + + // To access the stack map +void traverseStackMap() { + for (auto I = GCFunctionMetadata::roots_begin(), E = GCFunctionMetadata::end(); I != E; ++I) { + GCFunctionInfo *FI = *I; + unsigned FrameSize = FI->getFrameSize(); + size_t RootCount = FI->roots_size(); + + for (GCFunctionInfo::roots_iterator RI = FI->roots_begin(), + RE = FI->roots_end(); + RI != RE; ++RI) { + int RootNum = RI->Num; + int RootStackOffset = RI->StackOffset; + Constant *RootMetadata = RI->Metadata; + } + } +} \ No newline at end of file diff --git a/src/GC/Makefile b/src/GC/Makefile index 92b02e8..add6d73 100644 --- a/src/GC/Makefile +++ b/src/GC/Makefile @@ -12,33 +12,55 @@ 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: rm -f tests/h_test.out - $(CC) $(WFLAGS) $(STDFLAGS) $(LIB_INCL) tests/h_test.cpp lib/heap.cpp -o 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: - make h_test +h_test_vg: h_test valgrind $(VGFLAGS) tests/h_test.out -h_test_dbg: - make h_test +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: - make linker +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 + 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 \ No newline at end of file + 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) -O3 -g -c -o lib/event.o lib/event.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -c -o lib/profiler.o lib/profiler.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -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 \ No newline at end of file diff --git a/src/GC/docs/benchmarking.md b/src/GC/docs/benchmarking.md new file mode 100644 index 0000000..c2b9279 --- /dev/null +++ b/src/GC/docs/benchmarking.md @@ -0,0 +1,21 @@ +# Benchmarking + +free_overlap(): + 9_000 nodes: + With indexing: + Execution time: 22624 ≈ 22ms ≈ 0s. + Without indexing: + Execution time: 24891 ≈ 24ms ≈ 0s. + + 90_000 nodes: + With indexing: + Execution time: 693642 ≈ 693ms ≈ 0s. + Without indexing: + Execution time: 712297 ≈ 712ms ≈ 0s. + +Linked list test: + 50_000 nodes: + With marking all: + Execution time: 13911478 ≈ 13911ms ≈ 13s. + Without marking: + Execution time: 234361 ≈ 234ms ≈ 0s. \ No newline at end of file diff --git a/src/GC/docs/heap.md b/src/GC/docs/heap.md deleted file mode 100644 index 5db98e6..0000000 --- a/src/GC/docs/heap.md +++ /dev/null @@ -1,47 +0,0 @@ -## Heap Documentation - -### Algorithm notes - - void mark_test(vector worklist) { - while (worklist.size() > 0) { - Chunk *ref = worklist.pop_back(); - Chunk *child = (Chunk*) *ref; - if (child != NULL && !child->marked) { - child->marked = true; - worklist.push_back(child); - mark_test(worklist); - } - } - } - - void mark_from_roots(uintptr_t *start, const uintptr_t *end) { - vector worklist; - for (;start > end; start--) { - Chunk *ref = *start; - if (ref != NULL && !ref->marked) { - ref->marked = true; - worklist.push_back(ref); - mark_test(worklist); - } - } - } - -Alternative marking, pseudocode - - mark_from_roots(): - worklist <- empty - for fld in Roots - ref <- *fld - if ref ≠ null && !marked(ref) - set_marked(ref) - worklist.add(ref) - mark() - - mark(): - while size(worklist) > 0 - ref <- remove_first(worklist) - for fld in Pointers(ref) - child <- *fld - if child ≠ null && !marked(child) - set_marked(child) - worklist.add(child) \ No newline at end of file diff --git a/src/GC/docs/lib/chunk.md b/src/GC/docs/lib/chunk.md new file mode 100644 index 0000000..97230f5 --- /dev/null +++ b/src/GC/docs/lib/chunk.md @@ -0,0 +1,26 @@ +# chunk.hpp + +A chunk struct object is the basic element of what can be +stored on the heap. When `Heap::alloc` is called a +chunk may be created to represent the space of memory +that was allocated on the heap by `alloc`. + +## Members +`bool m_marked`: A boolean flag to mark an object during mark/sweep. + +`uintptr_t *const m_start`: A constant pointer pointing to the start +address of the memory space that was allocated. + +`const size_t m_size`: The size of the memory space that was allocated. + +## Constructors +There are three constructors for a chunk. One regular constructor +and two copy constructors. + +`Chunk(size_t size, uintptr_t *start)`: Used for creating new chunks in +`Heap::alloc`. + +`Chunk(const Chunk *const c)`: A copy constructor used by the profiler +to store chunk data after the initial chunk is deleted. + +`Chunk(const Chunk &c)`: A secondary copy constructor used for debugging. \ No newline at end of file diff --git a/src/GC/docs/lib/event.md b/src/GC/docs/lib/event.md new file mode 100644 index 0000000..8884205 --- /dev/null +++ b/src/GC/docs/lib/event.md @@ -0,0 +1,47 @@ +# event.hpp & event.cpp + +An event class used by the profiler to track actions +on the heap. + +## Members +`const GCEventType m_type`: The type of event recorded. + +`const std::time_t m_timestamp`: The timestamp of the event, +initialized to the current time by `std::time(NULL)`. + +`const Chunk *m_chunk`: The chunk an event is related to. +For example, in `alloc` when a new chunk is created, a +new event is recorded with the type of `NewChunk` and +`m_chunk` then contains a copied version of that new chunk. +If an event is not related to a chunk this member is initialized +to a nullptr. + +`const size_t m_size`: In an `AllocStart` event, this member +stores the amount of bytes requested to `alloc`. Otherwise +this member is initialized to 0. + +## Constructors +`GCEvent(GCEventType type)`: Used for creating events that are +independent of a chunk and size (like `ProfilerDispose`). + +`GCEvent(GCEventType type, Chunk *chunk)`: Used for creating events +that are connected to a chunk (like `ChunkMarked`). + +`GCEvent(GCEventType type, size_t size)`: Used for creating events +that are related to a size (only `AllocStart`). + +## Destructors +`~GCEvent()`: Default destructor and also frees the member +`m_chunk` if it's not the `nullptr`. + +## Functions +`GCEventType get_type()`: Getter for the type of the event. + +`std::time_t get_time_stamp()`: Getter for the timestamp of +the event. + +`const Chunk *get_chunk()`: Getter for the Chunk the event +is related to. The chunk data is constant. + +`const char *type_to_string()`: Translates the type of the +event to a string. \ No newline at end of file diff --git a/src/GC/docs/lib/heap.md b/src/GC/docs/lib/heap.md new file mode 100644 index 0000000..a0c31ab --- /dev/null +++ b/src/GC/docs/lib/heap.md @@ -0,0 +1,54 @@ +# heap.hpp & heap.cpp + +## Members +`char *const m_heap`: This is the pointer to the simulated heap which +collection occurs on. It's a byte array with a constant pointer. + +`size_t m_size`: The size of bytes that has been allocated on the heap. + +`inline static Heap *m_instance`: The singleton instance of Heap. Before +the heap is initialized this is initialized to the null pointer. + +`uintptr_t *m_stack_top`: The address of the topmost stack frame which +serves as the stop for scanning the stack. Initialized as the null pointer +but assigned to the correct address in `Heap::init()`. + +`bool m_profiler_enable`: The state of the profiler, `true` if the +profiler is enabled, `false` otherwise. It is initialized as `false`. + +`std::vector m_allocated_chunks`: Contains pointers to all +chunks that are allocated on the heap and can be reachable (if +a collection has been triggered previously). + +`std::vector m_freed_chunks`: Contains pointer to +chunks that have been freed, used to try and recycle chunks. + +## Constructors +`Heap()`: Default constructor which guarantees to initialize +the `m_heap` pointer and the byte array. Declared private +in accordance with the singleton pattern. + +## Destructors +`~Heap()`: Frees the `m_heap` byte array. Declared private +in accordance with the singleton pattern. + +## Functions +`static void init()`: Initializes the heap singleton and the member +`m_instance`. Must be called before any calls to `alloc()`. + +`static void dispose()`: Disposes the heap singleton which frees +the heap. If the profiler is enabled the profiler is also disposed. + +`static void *alloc(size_t size)`: Tries to allocate `size` amount +of bytes on the heap. The allocation is C-style, meaning `alloc()` +returns a `void *` similar to `malloc` and the user should cast +this pointer to an appropriate type. If this function is called with +the argument of 0, it will return the null pointer. This function can throw +runtime errors on two occasions. One if there is not enough memory +on the heap after a collection is triggered, it will throw a runtime +error with the message "Out of memory". The other occasion is when +a collection is triggered and the heap has not been initialized +properly by calling `init()`. + +`static void set_profiler(bool mode)`: Enables or disables (`true` +or `false`) the profiler. \ No newline at end of file diff --git a/src/GC/docs/lib/profiler.md b/src/GC/docs/lib/profiler.md new file mode 100644 index 0000000..cd925d6 --- /dev/null +++ b/src/GC/docs/lib/profiler.md @@ -0,0 +1,30 @@ +# profiler.hpp & profiler.cpp + +## Members +`inline static Profiler *m_instance`: The pointer to the profiler +singleton instance. + +`std::vector m_events`: A vector of events recorded +by the profiler. The contents are always sorted by time. + +## Constructors +`Profiler()`: Default constructor, declared private because of +the singleton pattern. + +## Destructors +`~Profiler()`: Default destructor, declared private because of +the singleton pattern. This destructor also deletes any events +that were recorded by the profiler to free memory. + +## Functions +`static void record(GCEventType type)`: Records an event independent +of a size and a chunk (like `ProfilerDispose`). + +`static void record(GCEventType type, size_t size)`: Records an event independent +of a chunk but not a size (only `AllocStart`). + +`static void record(GCEventType type, Chunk *chunk)`: Records an event independent +of a size but not a chunk (like `NewChunk`). + +`static void dispose()`: Disposes the profiler by dumping a log file of all +events and deleting events to free memory. \ No newline at end of file diff --git a/src/GC/docs/ref-guide.md b/src/GC/docs/ref-guide.md new file mode 100644 index 0000000..7ee627e --- /dev/null +++ b/src/GC/docs/ref-guide.md @@ -0,0 +1,83 @@ +# GC library - reference guide + +The Heap class is the core of the library and contains all necessary +functions for using the library. This class exposes four public functions +which are `init`, `dispose`, `alloc`, and `set_profiler`. + +To use the library, simply include it as `#include "heap.hpp"` and link +it during compilation. Or you can compile it to a static library using +the target `make static_lib` which compiles everything to an .a file. +It can also be compiled to a shared library if necessary with the target +`make shared_lib` which produces an .so file. + +## Quick guide +1. If you want a profiler, call `Heap::set_profiler(true)`. Otherwise this can be skipped. +2. Call `Heap::init()` to initialize the heap before using `alloc` (**crucial**). +3. Use `Heap::alloc()` as you want. +4. At program exit, call `Heap::dispose()` to free up all the memory used. + +## Functions + +### Heap::init() +When using the library, the user has to, at the start of the program, +call the `void init()` function, which initiates the Heap singleton +and the class member `m_stack_top`. **It is crucial** that this +functions is called from the `main` function of the end program, +as `init` uses the intrinsic function `__builtin_frame_address` +to find the address of the **first** stack frame of the end program. +If the function **is not** called from the `main` function +of the end program, it is not guaranteed that the garbage collector +will collect all objects. + +The intrinsic function used is technically unsafe for this use, +but during testing it has only shown to segfault for values greater +than the one used in `init`. If you run into a segfault, please +contact the developers. + + +### Heap::set_profiler(bool mode) +This function is used to enable or disable the profiler connected +to the Heap. The profiler is primarily used for testing, but can +also be used in general to keep track of the programs history. + +This function takes a single boolean as an argument to represent +the state of the profiler. `true` means that the profiler is enabled +and `false` means that the profiler is disabled. This function +can theoretically be called at any time during program execution, +but it's probably a bad idea. It is recommended to call this function +before the call to `init` or at least at before the first call to +`alloc`. + +### Heap::alloc(size_t size) +The probably most important function in this library. This function +is called to request memory from the "heap". `alloc` takes a single +argument which is a `size_t` (unsigned long) to represent the amount +of bytes to allocate on the heap. The allocation is C-style, meaning +that alloc returns a `void` pointer similar to `malloc`, which +is then supposed to be cast by the user to a proper pointer. When +`alloc` is called and there is already not enough memory left on +the heap to accommodate the request, a collection is triggered +to free up memory for the allocation. Hence the user does not +need to make their own calls to `free` or manually free up memory. + +`alloc` can also return a null pointer, if the user requests to +allocate 0 bytes. This is not recommended. + +`alloc` can also throw runtime errors in two cases. The first one +is of there is not enough memory on the heap available after +a collection, which in case the allocation cannot complete. +The second case is during a collection, where the function +`collect` throws a runtime error if the heap is not already +initialized by a call to `init`. Calls to `alloc` can technically +take place without properly initializing the heap, but this is +not recommended. + +### Heap::dispose() +This function is used to dispose the heap at the program exit. +If the profiler is enabled, it is also disposed from a call +to `dispose`. When the profiler is disposed, a log file is +dumped containing the events on the heap. If the profiler +is disabled, nothing happens to the profiler during `dispose`. +After the profiler is disposed, the heap is deleted which +frees up all the memory used and deletes (hopefully) all +the remaining objects in memory. \ No newline at end of file diff --git a/src/GC/include/chunk.hpp b/src/GC/include/chunk.hpp index 7aa4fb7..595b50b 100644 --- a/src/GC/include/chunk.hpp +++ b/src/GC/include/chunk.hpp @@ -1,15 +1,25 @@ #pragma once +#include #include -#define CHUNK_LIST_CAP 1024 - -namespace GC { - - struct Chunk { - bool marked; - uintptr_t *start; - size_t size; - }; +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..298ccab --- /dev/null +++ b/src/GC/include/event.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#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 index c70ee54..365a838 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -2,89 +2,99 @@ #include #include -#include #include #include +#include #include "chunk.hpp" +#include "profiler.hpp" -#define HEAP_SIZE 65536 +#define HEAP_SIZE 2097152 //65536 +#define FREE_THRESH (uint) 100000 +#define DEBUG -#define MARK (uint) 0x1 -#define SWEEP (uint) 0x2 -#define FREE (uint) 0x4 -#define COLLECT_ALL (uint) 0x7 +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 + }; -#define FREE_THRESH (uint) 20 + /** + * 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))) {} -namespace GC { + ~Heap() + { + std::free((char *)m_heap); + } - class 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}; - private: + std::vector m_allocated_chunks; + std::vector m_freed_chunks; - //Private constructor according to the singleton pattern - Heap() { - m_heap = reinterpret_cast(malloc(HEAP_SIZE)); - m_size = 0; - m_allocated_size = 0; - } + 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); - // BEWARE only for testing, this should be adressed - ~Heap() { - std::free((char *)m_heap); - } + // Temporary + Chunk *try_recycle_chunks_new(size_t size); + void free_overlap_new(Heap &heap); - static inline Heap *the() { // TODO: make private - if (m_instance) // if m_instance is not a nullptr - return m_instance; - m_instance = new Heap(); - return m_instance; - } + 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 inline Chunk *getAt(std::list list, size_t n) { - auto iter = list.begin(); - if (!n) - return *iter; - std::advance(iter, n); - return *iter; - } + static Heap &the(); + static void init(); + static void dispose(); + static void *alloc(size_t size); + void set_profiler(bool mode); - void collect(); - void sweep(Heap *heap); - uintptr_t *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::list worklist); - void print_line(Chunk *chunk); - void print_worklist(std::list list); + // Stop the compiler from generating copy-methods + Heap(Heap const&) = delete; + Heap& operator=(Heap const&) = delete; - inline static Heap *m_instance = nullptr; - const char *m_heap; - size_t m_size; - size_t m_allocated_size; - uintptr_t *m_stack_top = nullptr; - - // maybe change to std::list - std::list m_allocated_chunks; - std::list m_freed_chunks; - - public: - - /** - * These are the only two 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 void init(); // TODO: make static - static void dispose(); // -||- - static void *alloc(size_t size); // -||- - - // DEBUG ONLY - void collect(uint flags); // conditional collection - void check_init(); // print dummy things - void print_contents(); // print dummy things - }; +#ifdef 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..ccdf463 --- /dev/null +++ b/src/GC/include/profiler.hpp @@ -0,0 +1,49 @@ +#pragma once + +#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/event.cpp b/src/GC/lib/event.cpp new file mode 100644 index 0000000..2815a77 --- /dev/null +++ b/src/GC/lib/event.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +#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 ChunkMarked: return "ChunkMarked"; + case ChunkSwept: return "ChunkSwept"; + case ChunkFreed: return "ChunkFreed"; + case NewChunk: return "NewChunk"; + case ReusedChunk: return "ReusedChunk"; + case ProfilerDispose: return "ProfilerDispose"; + default: return "[Unknown]"; + } + } +} \ No newline at end of file diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index c17f680..579f421 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -4,409 +4,656 @@ #include #include #include +#include #include #include -#include "../include/heap.hpp" -using namespace std; +#include "heap.hpp" -namespace GC { +using std::cout, std::endl, std::vector, std::hex, std::dec; - /** - * Initialises the heap singleton and saves the address - * of the calling 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(); - heap->m_stack_top = reinterpret_cast(__builtin_frame_address(1)); - } +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; + } - /** - * Disposes the heap at program exit. - */ - void Heap::dispose() { - Heap *heap = Heap::the(); - delete heap; - } + /** + * 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)); + } - /** - * 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) { + /** + * 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(); + } - // Singleton - Heap *heap = Heap::the(); - - if (size < 0) { - cout << "Heap: Cannot alloc less than 0B. No bytes allocated." << endl; - return nullptr; - } + /** + * 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 (heap->m_size + size > HEAP_SIZE) { - heap->collect(); - // If collect failed, crash with OOM error - assert(heap->m_size + size <= HEAP_SIZE && "Heap: Out Of Memory"); - } + if (size == 0) + { + cout << "Heap: Cannot alloc 0B. No bytes allocated." << endl; + return nullptr; + } - // If a chunk was recycled, return the old chunk address - uintptr_t *reused_chunk = heap->try_recycle_chunks(size); - if (reused_chunk != nullptr) { - return (void *)reused_chunk; - } - - // If no free chunks was found (reused_chunk is a nullptr), - // then create a new chunk - auto new_chunk = new Chunk; - new_chunk->size = size; - new_chunk->start = (uintptr_t *)(heap->m_heap + heap->m_size); + if (heap.m_size + size > HEAP_SIZE) + { + heap.collect(); + // If memory is not enough after collect, crash with OOM error + throw std::runtime_error(std::string("Error: Heap out of memory")); + } - heap->m_size += size; + // 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); + } - heap->m_allocated_chunks.push_back(new_chunk); + // 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)); - // new_chunk should probably be a unique pointer, if that isn't implicit already - return new_chunk->start; - } + heap.m_size += size; + heap.m_allocated_chunks.push_back(new_chunk); - /** - * 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. - */ - uintptr_t *Heap::try_recycle_chunks(size_t size) { - auto 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 cp = heap->m_freed_chunks.at(i); - auto cp = getAt(heap->m_freed_chunks, i); - auto iter = heap->m_freed_chunks.begin(); - advance(iter, i); - if (cp->size > size) - { - // Split the chunk, use one part and add the remaining part to - // the list of freed chunks - size_t diff = cp->size - size; - - auto chunk_complement = new Chunk; - chunk_complement->size = diff; - chunk_complement->start = cp->start + cp->size; + if (profiler_enabled) + Profiler::record(NewChunk, new_chunk); - heap->m_freed_chunks.erase(iter); - heap->m_freed_chunks.push_back(chunk_complement); - heap->m_allocated_chunks.push_back(cp); - - return cp->start; - } - else if (cp->size == size) - { - // Reuse the whole chunk - heap->m_freed_chunks.erase(iter); - heap->m_allocated_chunks.push_back(cp); - return cp->start; - } - } - return nullptr; - } + return new_chunk->m_start; + } - /** - * 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() { - // Get instance - auto heap = Heap::the(); + /** + * 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); - // get current stack - auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + heap.m_freed_chunks.erase(iter); + heap.m_freed_chunks.push_back(chunk_complement); + heap.m_allocated_chunks.push_back(chunk); - // fix this block, it's nästy - uintptr_t *stack_top; - if (heap->m_stack_top != nullptr) - stack_top = heap->m_stack_top; - else - stack_top = (uintptr_t *)0; // temporary + 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; + } - auto work_list = heap->m_allocated_chunks; - mark(stack_bottom, stack_top, work_list); + /** + * Advances an iterator and returns an element + * at position `n`. + * + * @param list The list to retrieve an element from. + * + * @param n The position to retrieve an element at. + * + * @returns The pointer to the chunk at position n in list. + */ + // Chunk *Heap::get_at(std::vector &list, size_t n) + // { + // auto iter = list.begin(); + // if (!n) + // return *iter; + // std::advance(iter, n); + // return *iter; + // } - sweep(heap); + /** + * 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; + } - free(heap); - } + /** + * 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(); - /** - * 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. - * - * @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 *end, list worklist) { - int counter = 0; - // To find adresses thats in the worklist - for (; start < end; start++) { - counter++; - auto it = worklist.begin(); - auto stop = worklist.end(); - // for (auto it = worklist.begin(); it != worklist.end();) { - while (it != stop) { - Chunk *chunk = *it; + if (heap.profiler_enabled()) + Profiler::record(CollectStart); - auto c_start = reinterpret_cast(chunk->start); - auto c_size = reinterpret_cast(chunk->size); - auto c_end = reinterpret_cast(c_start + c_size); + // get current stack frame + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); - cout << "Start points to:\t" << hex << *start << endl; - cout << "Chunk start:\t\t" << hex << c_start << endl; - cout << "Chunk end:\t\t" << hex << c_end << "\n" << endl; + if (heap.m_stack_top == nullptr) + throw std::runtime_error(std::string("Error: Heap is not initialized, read the docs!")); - // Check if the stack pointer aligns with the chunk - if (c_start <= *start && *start < c_end) { - - if (!chunk->marked) { - chunk->marked = true; - // Remove the marked chunk from the worklist - 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; - } - } - } - cout << "Counter: " << counter << endl; - } - - /** - * Sweeps the heap, unmarks the marked chunks for the next cycle, - * adds the unmarked nodes to the list of freed chunks; to be freed. - * - * @param heap Pointer to the heap singleton instance. - */ - void Heap::sweep(Heap *heap) { - auto iter = heap->m_allocated_chunks.begin(); - auto stop = heap->m_allocated_chunks.end(); - // for (auto it = heap->m_allocated_chunks.begin(); it != heap->m_allocated_chunks.end();) { - while (iter != stop) { - Chunk *chunk = *iter; + uintptr_t *stack_top = heap.m_stack_top; - // Unmark the marked chunks for the next iteration. - if (chunk->marked) { - chunk->marked = false; - ++iter; - } - else { - // Add the unmarked chunks to freed chunks and remove from - // the list of allocated chunks - heap->m_freed_chunks.push_back(chunk); - iter = heap->m_allocated_chunks.erase(iter); - } - } - } + auto work_list = heap.m_allocated_chunks; + mark(stack_bottom, stack_top, work_list); - /** - * 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. - * - * @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) { - while (heap->m_freed_chunks.size()) { - auto chunk = heap->m_freed_chunks.back(); - heap->m_freed_chunks.pop_back(); - 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); - } - } + sweep(heap); - /** - * Checks for overlaps between freed chunks of memory - * and removes overlapping chunks while prioritizing - * the chunks at lower addresses. - * - * @param heap Heap singleton instance, only for avoiding - * redundant calls to the singleton get - * - * @note Maybe this should be changed to prioritizing - * larger chunks. - */ - void Heap::free_overlap(Heap *heap) { - std::list filtered; - size_t i = 0; - // filtered.push_back(heap->m_freed_chunks.at(i++)); - filtered.push_back(getAt(heap->m_freed_chunks, i++)); - cout << filtered.back()->start << endl; - for (; i < heap->m_freed_chunks.size(); i++) { - auto prev = filtered.back(); - // auto next = heap->m_freed_chunks.at(i); - auto next = getAt(heap->m_freed_chunks, i); - auto p_start = (uintptr_t)(prev->start); - auto p_size = (uintptr_t)(prev->size); - auto n_start = (uintptr_t)(next->start); - if (n_start >= (p_start + p_size)) { - filtered.push_back(next); - } - } - heap->m_freed_chunks.swap(filtered); - } + free(heap); + } - // ----- ONLY DEBUGGING ----------------------------------------------------------------------- + /** + * 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); - /** - * Prints the result of Heap::init() and a dummy value - * for the current stack frame for reference. - */ - void Heap::check_init() { - auto heap = Heap::the(); - cout << "Heap addr:\t" << heap << endl; - cout << "GC m_stack_top:\t" << heap->m_stack_top << endl; - 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(uint flags) { + // 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); - cout << "DEBUG COLLECT\nFLAGS: "; - if (flags & MARK) - cout << "\n - MARK"; - if (flags & SWEEP) - cout << "\n - SWEEP"; - if (flags & FREE) - cout << "\n - FREE"; - cout << endl; - - auto heap = Heap::the(); + // 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); - // 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 << endl; - uintptr_t *stack_top; + // 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; + } + } + } + } - if (heap->m_stack_top != nullptr) - stack_top = heap->m_stack_top; - else - stack_top = (uintptr_t *) stack_bottom + 80; // dummy value - cout << "Stack end in collect:\t " << stack_top << endl; - auto work_list = heap->m_allocated_chunks; + /** + * 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; - if (flags & MARK) { - mark(stack_bottom, stack_top, work_list); - } + // 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); + } + } + } - if (flags & SWEEP) { - sweep(heap); - } - - if (flags & FREE) { - free(heap); - } - } + /** + * 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); + } + } - // 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->marked) { - child->marked = true; - worklist.push_back(child); - mark_test(worklist); - } - } - } + /** + * 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; + } + } + } - // 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->marked) { - ref->marked = true; - worklist.push_back(ref); - mark_test(worklist); - } - } - } - } +#ifdef 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; + } - // For testing purposes - void Heap::print_line(Chunk *chunk) { - cout << "Marked: " << chunk->marked << "\nStart adr: " << chunk->start << "\nSize: " << chunk->size << " B\n" << endl; - } + /** + * Conditional collection, only to be used in debugging + * + * @param flags Bitmap of flags + */ + void Heap::collect(CollectOption flags) + { + set_profiler(true); - void Heap::print_worklist(std::list list) { - for (auto cp : list) { - cout << "Chunk at:\t" << cp->start << "\nSize:\t\t" << cp->size << endl; - } - } + Heap &heap = Heap::the(); - void Heap::print_contents() { - auto 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; - } - } + 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::set_profiler(bool mode) + { + Heap &heap = Heap::the(); + heap.m_profiler_enable = mode; + } + + 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..29abad4 --- /dev/null +++ b/src/GC/lib/profiler.cpp @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chunk.hpp" +#include "event.hpp" +#include "profiler.hpp" + +// #define MAC_OS + +namespace GC +{ + /** + * Records an event independent of a chunk. + * + * @param type The type of event to record. + */ + void Profiler::record(GCEventType 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) + { + auto event = new GCEvent(type, size); + auto profiler = Profiler::the(); + profiler->m_events.push_back(event); + } + + /** + * 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(). + auto chunk_copy = new Chunk(chunk); + auto event = new GCEvent(type, chunk_copy); + auto profiler = Profiler::the(); + profiler->m_events.push_back(event); + } + + /** + * Prints the history of the recorded events + * to a log file in the /tests/logs folder. + */ + void Profiler::dump_trace() + { + auto profiler = Profiler::the(); + auto start = profiler->m_events.begin(); + auto end = profiler->m_events.end(); + + // File output stream + std::ofstream fstr = profiler->create_file_stream(); + // Buffer for timestamp + char buffer[22]; + // Time variables + std::tm *btm; + std::time_t tt; + const Chunk *chunk; + + while (start != end) + { + auto event = *start++; + + tt = event->get_time_stamp(); + btm = std::localtime(&tt); + std::strftime(buffer, 22, "%a %T", btm); + + fstr << "--------------------------------\n" + << buffer + << "\nEvent:\t" << event->type_to_string(); + + + + 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"; + } + fstr << "--------------------------------" << std::endl; + } + + /** + * 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(); + auto profiler = Profiler::the(); + delete profiler; + } + + /** + * 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"; + } +} \ No newline at end of file diff --git a/src/GC/tests/MarkSweep.cpp b/src/GC/tests/MarkSweep.cpp new file mode 100644 index 0000000..ab219d2 --- /dev/null +++ b/src/GC/tests/MarkSweep.cpp @@ -0,0 +1,87 @@ +#include +#include +#define HEAP_SIZE 65536 // Arbitrary for now, 2^16 +using namespace std; + +/* A simple mark and sweep algorithm */ + +// Shouldn't be exposed. For now, it is +struct ObjectHeader { + size_t size = sizeof(this); + bool marked = false; + +}; + +struct Object : ObjectHeader { + char name; // should be something like id, but for testing sake its char + Object* child; + // Object(char name_) {} + Object(char name_, Object* child_) { + name = name_; + child = child_; + } +}; + +// Representing the heap as a simple struct for now +struct Heap { + Object heap_space[HEAP_SIZE]; +}; + +// For now it assumes that it is given root objects from the start, no root finding included +class MarkSweep { + public: + void mark(Object* obj) { + if (!markedBit(obj)) { + markBit(obj); + Object* ref = obj->child; + if (ref != nullptr) { + mark(ref); + } + } + } + + void sweep(vector worklist) { + for (Object* obj: worklist) { + if (!markedBit(obj) && obj != nullptr) { + delete obj; + } + } + } + + private: + bool markedBit(Object* obj) { + return obj->marked; + } + + void markBit(Object* obj) { + obj->marked = true; + } + +}; + +int main() { + Object* b = new Object('B', nullptr); + // b->name = 'B'; + // b->child = nullptr; + Object* c = new Object('C', b); + // c->name = 'C'; + // c->child = b; // c -> d + Object* d = new Object('D', nullptr); + // d->name = 'D'; + // d->child = nullptr; + + //Heap* heap = new Heap{*c, *b, *d}; + vector worklist = {c, b, d}; + MarkSweep* gc = new MarkSweep(); + + gc->mark(c); + cout << "Expected 1, got: " << b->marked << '\n'; + cout << "Expected 1, got: " << c->marked << '\n'; + cout << "Expected 0, got: " << d->marked << '\n'; + + gc->sweep(worklist); + cout << b->name << '\n'; + cout << c->name << '\n'; + cout << d->name << '\n'; // The object at d is now deleted (freed) + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/advance.cpp b/src/GC/tests/advance.cpp index 0a8a177..92ce506 100644 --- a/src/GC/tests/advance.cpp +++ b/src/GC/tests/advance.cpp @@ -1,10 +1,14 @@ +#include +#include #include #include +#include #include -using namespace std; - int main() { + using namespace std; + using TimeStamp = std::chrono::_V2::system_clock::time_point; + list l; char c = 'a'; for (int i = 1; i <= 5; i++) { @@ -28,7 +32,13 @@ int main() { cout << endl; cout << "rebased" << endl; - // cout << "iter: " << *iter << "\nstop: " << *stop << endl; + cout << "iter: " << *iter << "\nstop: " << *stop << 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; return 0; } \ No newline at end of file diff --git a/src/GC/tests/alloc_free.cpp b/src/GC/tests/alloc_free.cpp index 0e277dc..4a0f6f8 100644 --- a/src/GC/tests/alloc_free.cpp +++ b/src/GC/tests/alloc_free.cpp @@ -9,21 +9,24 @@ struct Obj { }; int main() { - GC::Heap *heap = GC::Heap::the2(); + GC::Heap::init(); Obj *obj; for (int i = 0; i < 4; i++) { - obj = static_cast(heap->alloc(sizeof(Obj))); + obj = static_cast(GC::Heap::alloc(sizeof(Obj))); obj->a = i * i + 1; obj->b = i * i + 2; obj->c = i * i + 3; } // heap->force_collect(); + auto heap = GC::Heap::debug_the(); + heap->collect(COLLECT_ALL); std::cout << obj->a << ", " << obj->b << ", " << obj->c << std::endl; //delete heap; + GC::Heap::dispose(); return 0; } \ No newline at end of file diff --git a/src/GC/tests/events.cpp b/src/GC/tests/events.cpp new file mode 100644 index 0000000..e517092 --- /dev/null +++ b/src/GC/tests/events.cpp @@ -0,0 +1,44 @@ +#include +#include + +using namespace std; +// broken :( +// [event_source(native)] +class ESource { +public: + __event void TestEvent(int eValue); +}; + +// [event_receiver(native)] +class EReceiver { +public: + void Handler1(int eValue) { + cout << "Handler1 with: " << eValue << endl; + } + + void Handler2(int eValue) { + cout << "Handler2 with: " << eValue << endl; + } + + void hookEvent(ESource *eSource) { + __hook(&ESource::TestEvent, eSource, &EReceiver::Handler1); + __hook(&ESource::TestEvent, eSource, &EReceiver::Handler2); + } + + void unhookEvent(ESource *eSource) { + __unhook(&ESource::TestEvent, eSource, &EReceiver::Handler1); + __unhook(&ESource::TestEvent, eSource, &EReceiver::Handler2); + } +}; + +int main() { + + ESource src; + EReceiver rcv; + + rcv.hookEvent(&src); + __raise src.TestEvent(12); + rcv.unhookEvent(&src); + + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/extern_lib.cpp b/src/GC/tests/extern_lib.cpp index 9ee3a5b..fa30051 100644 --- a/src/GC/tests/extern_lib.cpp +++ b/src/GC/tests/extern_lib.cpp @@ -3,17 +3,19 @@ #include "heap.hpp" -GC::Heap *singleton_test(); -void init_gc(GC::Heap *heap); -void frame_test(GC::Heap *heap); +GC::Heap& singleton_test(); +void init_gc(GC::Heap& heap); +void frame_test(GC::Heap& heap); int main() { std::cout << "in main" << std::endl; - auto heap = singleton_test(); + GC::Heap &heap = singleton_test(); init_gc(heap); frame_test(heap); + heap.dispose(); + return 0; } @@ -26,12 +28,12 @@ int main() { * * @return Pointer to the Heap singleton instance */ -GC::Heap *singleton_test() { +GC::Heap& singleton_test() { std::cout << "TESTING SINGLETON INSTANCES" << std::endl; std::cout << "===========================" << std::endl; - std::cout << "Call 1:\t" << GC::Heap::the() << std::endl; // First call which initializes the singleton instance - GC::Heap *heap = GC::Heap::the(); // Second call which should return the initialized instance - std::cout << "Call 2:\t" << heap << std::endl; + std::cout << "Call 1:\t" << &GC::Heap::the() << std::endl; // First call which initializes the singleton instance + GC::Heap &heap = GC::Heap::the(); // Second call which should return the initialized instance + std::cout << "Call 2:\t" << &heap << std::endl; std::cout << "===========================" << std::endl; return heap; } @@ -48,10 +50,11 @@ GC::Heap *singleton_test() { * @param heap The Heap pointer to the singleton instance. * */ -void init_gc(GC::Heap *heap){ +void init_gc(GC::Heap& heap){ std::cout << "\n\n INITIALIZING THE HEAP" << std::endl; std::cout << "===========================" << std::endl; - heap->init(); + heap.init(); + heap.set_profiler(true); std::cout << "===========================" << std::endl; } @@ -73,7 +76,7 @@ void init_gc(GC::Heap *heap){ * * @param heap The Heap instance */ -void frame_test(GC::Heap *heap) { +void frame_test(GC::Heap& heap) { std::cout << "\n\n TESTING FRAME ADDRESSES" << std::endl; std::cout << "===========================" << std::endl; @@ -84,7 +87,7 @@ void frame_test(GC::Heap *heap) { auto prev_frame = reinterpret_cast(__builtin_frame_address(1)); // addr of prev stack frame std::cout << "Previous stack frame:\t" << prev_frame << std::endl; - heap->check_init(); // prints the saved absolute top of the stack + heap.check_init(); // prints the saved absolute top of the stack // auto alloced = heap->alloc(sizeof(unsigned long)); std::cout << "===========================" << std::endl; diff --git a/src/GC/tests/file.cpp b/src/GC/tests/file.cpp new file mode 100644 index 0000000..df9e441 --- /dev/null +++ b/src/GC/tests/file.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include + +void time_string(char *buffer); +void print_log_file(const std::string TESTS_PATH); +void readlink_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(); + + 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; + +} \ No newline at end of file diff --git a/src/GC/tests/game.cpp b/src/GC/tests/game.cpp new file mode 100644 index 0000000..e01ec8e --- /dev/null +++ b/src/GC/tests/game.cpp @@ -0,0 +1,95 @@ +#include + +#include "player.hpp" +#include "heap.hpp" + +#define X_LENGTH 1000 +#define Y_LENGTH 500 +#define MAX_PLAYERS 100 + +/* +* Description: +* This class is designed to test the Garbage Collector with a mock game, +* that consists of several live objects in the form of players, that in +* turn consists partially of Point objects. +* +* Goal: +* to find out if all the objects are allocated successfully +* and to see if they are reachable from the stack, i.e. they can get marked. +* +* Result: +* all objects gets allocated, but only Game object gets marked. +*/ + + +class Game { + +private: + + std::vector *players; + //std::vector *players; + Point *dimensions; + +public: + + Game() { + dimensions->x = X_LENGTH; + dimensions->y = Y_LENGTH; + } + + void init() { + players = static_cast*>(GC::Heap::alloc(sizeof(Player*) * MAX_PLAYERS)); + //players = static_cast*>(GC::Heap::alloc(sizeof(Player) * MAX_PLAYERS)); + dimensions = static_cast(GC::Heap::alloc(sizeof(Point))); + dimensions->x = X_LENGTH; + dimensions->y = Y_LENGTH; + } + + void add_player(Player *p) { + players->push_back(p); + } + + Player* create_player(string *s, Point *pos, Point *size, Point *dir) { + Player *p = static_cast(GC::Heap::alloc(sizeof(Player))); + /* + Cannot allocate by new, since it the allocates outside of "out" heap. That also lead so us having to + define an alternative constructor, that's actually a method. Since our "alloc" does not call the constructor + of the object + */ + p->init(s, pos, size, dir); + return p; + } + + void create_players(int nr) { + for (int i = 0; i < nr; i++) { + + std::string *str = static_cast(GC::Heap::alloc(sizeof(std::string))); + Point *pos = static_cast(GC::Heap::alloc(sizeof(Point))); + Point *size = static_cast(GC::Heap::alloc(sizeof(Point))); + Point *dir = static_cast(GC::Heap::alloc(sizeof(Point))); + + Player *p = create_player(str, pos, size, dir); + add_player(p); + } + } + +}; + +int main() { + GC::Heap::init(); + GC::Heap *gc = GC::Heap::debug_the(); + gc->check_init(); + + Game *game = static_cast(gc->alloc(sizeof(Game))); + game->init(); + game->create_players(2); + + std::cout << "Player size: " << sizeof(Player) << std::endl; + std::cout << "Game size: " << sizeof(Game) << std::endl; + std::cout << "Point size: " << sizeof(Point) << std::endl; + + gc->collect(GC::MARK); + gc->print_contents(); + + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/h_test.cpp b/src/GC/tests/h_test.cpp index ac55bf7..c871721 100644 --- a/src/GC/tests/h_test.cpp +++ b/src/GC/tests/h_test.cpp @@ -1,6 +1,9 @@ -#include "../include/heap.hpp" +#include +#include -GC::Heap *gc = GC::Heap::the(); +#include "heap.hpp" + +using std::cout, std::endl; struct Node { int id; @@ -8,21 +11,20 @@ struct Node { }; Node *create_chain(int depth) { + cout << "entering create_chain"; std::vector nodes; if (depth > 0) { - Node *last_node = static_cast(gc->alloc(sizeof(Node))); + Node *last_node = static_cast(GC::Heap::alloc(sizeof(Node))); last_node->id = depth; last_node->child = nullptr; nodes.push_back(last_node); - for (int i = 0; i < depth; i++) { - Node *node = static_cast(gc->alloc(sizeof(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); } - for (size_t i = 0; i < nodes.size(); i++) { - std::cout << "Element at " << i << ":\t" << nodes.at(i) << std::endl; - } + cout << "\nexiting create_chain" << endl; return nodes[depth]; } else @@ -30,66 +32,75 @@ Node *create_chain(int depth) { } void create_array(size_t size) { - int *arr = static_cast(gc->alloc(sizeof(int) * size)); + int *arr = static_cast(GC::Heap::alloc(sizeof(int) * size)); } void detach_pointer(long **ptr) { + cout << "entering detach_pointer"; long *dummy_ptr = nullptr; *ptr = dummy_ptr; + cout << "\nexiting detach_pointer" << endl; } Node *test_chain(int depth, bool detach) { + cout << "entering test_chain"; auto stack_start = reinterpret_cast(__builtin_frame_address(0)); - std::cout << "Stack start from test_chain:\t" << stack_start << std::endl; Node *node_chain = create_chain(depth); - // This generates a segmentation fault (should be investigated further) if (detach) node_chain->child = nullptr; - return node_chain; + 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->alloc(sizeof(long))); + 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->alloc(sizeof(int))); - char *c = static_cast(gc->alloc(sizeof(int))); - short *s = static_cast(gc->alloc(sizeof(short))); + 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() { - gc->init(); - gc->check_init(); + 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.check_init(); auto stack_start = reinterpret_cast(__builtin_frame_address(0)); - std::cout << "Stack start from main:\t" << stack_start << std::endl; - - // char *c = static_cast(gc->alloc(sizeof(char))); // 0x0 | 0x0 - // int *i = static_cast(gc->alloc(sizeof(int))); // 0x1-0x4 | 0x4-0x8 - // char *c2 = static_cast(gc->alloc(sizeof(char)));// 0x5 | 0x9-0x - // long *l = static_cast(gc->alloc(sizeof(long))); // 0x6-0xd | 0x - - // This is allocated outside of the scope of the GC (if gc->init() isn't called), thus garbage - /* long *longs[21]; - std::cout << "Pointer to ints:\t" << longs << std::endl; - for (int i = 0; i < 21; i++) { - longs[i] = static_cast(gc->alloc(sizeof(long))); - } */ - //Node *root = static_cast(gc->alloc(sizeof(Node))); - Node *root = test_chain(100, true); - std::cout << "Adress of root:\t" << &root << std::endl; - std::cout << "Root points to:\t" << root << std::endl; - std::cout << "Root child:\t" << root->child << std::endl; + Node *root1 = static_cast(gc.alloc(sizeof(Node))); + Node *root2 = static_cast(gc.alloc(sizeof(Node))); + root1 = test_chain(58000, 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"; - gc->collect(MARK); - gc->print_contents(); return 0; } \ No newline at end of file diff --git a/src/GC/tests/linker.cpp b/src/GC/tests/linker.cpp index f3b12f0..36717c5 100644 --- a/src/GC/tests/linker.cpp +++ b/src/GC/tests/linker.cpp @@ -9,11 +9,11 @@ struct Obj { }; int main() { - auto heap = GC::Heap::the2(); + auto heap = GC::Heap::debug_the(); std::cout << "heap:\t" << heap << std::endl; - auto obj = static_cast(heap->alloc(sizeof(Obj))); + auto obj = static_cast(GC::Heap::alloc(sizeof(Obj))); std::cout << "obj: \t" << obj << std::endl; diff --git a/src/GC/tests/player.hpp b/src/GC/tests/player.hpp new file mode 100644 index 0000000..8a8e30f --- /dev/null +++ b/src/GC/tests/player.hpp @@ -0,0 +1,51 @@ +#include + +using std::string; + +class Point { + +public: + + int x, y; + Point() {} + Point(int _x, int _y) : x(_x), y(_y) {} +}; + +class Player { + +private: + + string *name; + Point *position; + Point *size; + Point *direction; + +public: + + Player() {} + +/* Player(string n, Point pos, Point s, Point dir) + : name(n), position(pos.x, pos.y), size(s.x, s.y), direction(dir.x, dir.y) + {} */ + + void move() { + position->x += direction->x; + position->y += direction->y; + } + + void set_speed(int dx, int dy) { + direction->x = dx; + direction->y = dy; + } + + // This is probably neccessary to initialize an object with our GC + // Since allocation and construction cannot be done at the same time + void init(string *n, Point *pos, Point *s, Point *dir) { + name = n; + position = pos; + size = s; + direction = dir; + + } + +}; diff --git a/src/GC/tests/struct_test.cpp b/src/GC/tests/struct_test.cpp new file mode 100644 index 0000000..2b2b677 --- /dev/null +++ b/src/GC/tests/struct_test.cpp @@ -0,0 +1,41 @@ +#include + +#include "heap.hpp" + +using namespace std; + +struct Node { + int value; + Node *left; + Node *right; +}; + +int getValue(); +Node *createNode(); +void insert(); + +int main() { + GC::Heap::init(); + Node *node = static_cast(GC::Heap::alloc(sizeof(Node))); + + return 0; +} + +int getValue() { + cout << "Enter a value to insert: "; + int value; + cin >> value; + return value; +} + +Node *createNode() { + Node *node = static_cast(GC::Heap::alloc(sizeof(Node))); + node->value = getValue(); + return node; +} + +void insert(Node *root) { + Node *node = createNode(); + Node *curr = root; + while (curr) +} \ No newline at end of file diff --git a/src/GC/todo.md b/src/GC/todo.md index f9492da..83fcf2c 100644 --- a/src/GC/todo.md +++ b/src/GC/todo.md @@ -1,15 +1,11 @@ # Garbage collection ## Project - -Goal for next week (24/2): -- Write more complex tests +Deliver to samuel ## GC TODO: -- Merge to main branch -- Double check m_heap_size functionality and when a collection is triggered -- Kolla vektor vs list complexity +- PR till master ## Tests TODO - Write complex datastructures for tests with larger programs - +- Testa `__builtin_frame_address` mer specifikt för att se om första stack framen skannas \ No newline at end of file