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 new file mode 100644 index 0000000..add6d73 --- /dev/null +++ b/src/GC/Makefile @@ -0,0 +1,66 @@ +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: + 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 + +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) -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/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 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..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 new file mode 100644 index 0000000..365a838 --- /dev/null +++ b/src/GC/include/heap.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "chunk.hpp" +#include "profiler.hpp" + +#define HEAP_SIZE 2097152 //65536 +#define FREE_THRESH (uint) 100000 +#define DEBUG + +namespace GC +{ + /** + * Flags for the collect overlead for conditional + * collection (mark/sweep/free/all). + */ + enum CollectOption { + MARK=0x1, + SWEEP=0x2, + MARK_SWEEP = 0x3, + FREE=0x4, + COLLECT_ALL=0x7 + }; + + /** + * The heap class to represent the heap for the + * garbage collection. The heap is a singleton + * instance and can be retrieved by Heap::the() + * inside the heap class. The heap is represented + * by a char array of size 65536 and can enable + * a profiler to track the actions on the heap. + */ + class Heap + { + private: + Heap() : m_heap(static_cast(malloc(HEAP_SIZE))) {} + + ~Heap() + { + std::free((char *)m_heap); + } + + char *const m_heap; + size_t m_size {0}; + // static Heap *m_instance {nullptr}; + uintptr_t *m_stack_top {nullptr}; + bool m_profiler_enable {false}; + + std::vector m_allocated_chunks; + std::vector m_freed_chunks; + + static bool profiler_enabled(); + // static Chunk *get_at(std::vector &list, size_t n); + void collect(); + void sweep(Heap &heap); + Chunk *try_recycle_chunks(size_t size); + void free(Heap &heap); + void free_overlap(Heap &heap); + void mark(uintptr_t *start, const uintptr_t *end, std::vector &worklist); + void print_line(Chunk *chunk); + void print_worklist(std::vector &list); + void mark_step(uintptr_t start, uintptr_t end, std::vector &worklist); + + // Temporary + Chunk *try_recycle_chunks_new(size_t size); + void free_overlap_new(Heap &heap); + + public: + /** + * These are the only five functions which are exposed + * as the API for LLVM. At the absolute start of the + * program the developer has to call init() to ensure + * that the address of the topmost stack frame is + * saved as the limit for scanning the stack in collect. + */ + + static Heap &the(); + static void init(); + static void dispose(); + static void *alloc(size_t size); + void set_profiler(bool mode); + + // Stop the compiler from generating copy-methods + Heap(Heap const&) = delete; + Heap& operator=(Heap const&) = delete; + +#ifdef 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 new file mode 100644 index 0000000..579f421 --- /dev/null +++ b/src/GC/lib/heap.cpp @@ -0,0 +1,659 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "heap.hpp" + +using std::cout, std::endl, std::vector, std::hex, std::dec; + +namespace GC +{ + /** + * This implementation of the() guarantees laziness + * on the instance and a correct destruction with + * the destructor. + * + * @returns The singleton object. + */ + Heap& Heap::the() + { + static Heap instance; + return instance; + } + + /** + * Initialises the heap singleton and saves the address + * of the calling function's stack frame as the stack_top. + * Presumeably this address points to the stack frame of + * the compiled LLVM executable after linking. + */ + void Heap::init() + { + Heap &heap = Heap::the(); + if (heap.profiler_enabled()) + Profiler::record(HeapInit); +// clang complains because arg for __b_f_a is not 0 which is "unsafe" +#pragma clang diagnostic ignored "-Wframe-address" + heap.m_stack_top = static_cast(__builtin_frame_address(1)); + } + + /** + * Disposes the heap and the profiler at program exit + * which also triggers a heap log file dumped if the + * profiler is enabled. + */ + void Heap::dispose() + { + Heap &heap = Heap::the(); + if (heap.profiler_enabled()) + Profiler::dispose(); + } + + /** + * Allocates a given amount of bytes on the heap. + * + * @param size The amount of bytes to be allocated. + * + * @return A pointer to the address where the memory + * has been allocated. This pointer is supposed + * to be casted to and object pointer. + */ + void *Heap::alloc(size_t size) + { + // Singleton + Heap &heap = Heap::the(); + bool profiler_enabled = heap.profiler_enabled(); + + if (profiler_enabled) + Profiler::record(AllocStart, size); + + if (size == 0) + { + cout << "Heap: Cannot alloc 0B. No bytes allocated." << endl; + return nullptr; + } + + if (heap.m_size + size > HEAP_SIZE) + { + heap.collect(); + // If memory is not enough after collect, crash with OOM error + throw std::runtime_error(std::string("Error: Heap out of memory")); + } + + // If a chunk was recycled, return the old chunk address + Chunk *reused_chunk = heap.try_recycle_chunks(size); + if (reused_chunk != nullptr) + { + if (profiler_enabled) + Profiler::record(ReusedChunk, reused_chunk); + return static_cast(reused_chunk->m_start); + } + + // If no free chunks was found (reused_chunk is a nullptr), + // then create a new chunk + auto new_chunk = new Chunk(size, (uintptr_t *)(heap.m_heap + heap.m_size)); + + heap.m_size += size; + heap.m_allocated_chunks.push_back(new_chunk); + + if (profiler_enabled) + Profiler::record(NewChunk, new_chunk); + + return new_chunk->m_start; + } + + /** + * Tries to recycle used and freed chunks that are + * already allocated objects by the OS but freed + * from our Heap. This reduces the amount of GC + * objects slightly which saves time from malloc'ing + * memory from the OS. + * + * @param size Amount of bytes needed for the object + * which is about to be allocated. + * + * @returns If a chunk is found and recycled, a + * pointer to the allocated memory for + * the object is returned. If not, a + * nullptr is returned to signify no + * chunks were found. + */ + Chunk *Heap::try_recycle_chunks(size_t size) + { + Heap &heap = Heap::the(); + // Check if there are any freed chunks large enough for current request + for (size_t i = 0; i < heap.m_freed_chunks.size(); i++) + { + //auto chunk = Heap::get_at(heap.m_freed_chunks, i); + auto chunk = heap.m_freed_chunks[i]; + auto iter = heap.m_freed_chunks.begin(); + advance(iter, i); + if (chunk->m_size > size) + { + // Split the chunk, use one part and add the remaining part to + // the list of freed chunks + size_t diff = chunk->m_size - size; + auto chunk_complement = new Chunk(diff, chunk->m_start + chunk->m_size); + + heap.m_freed_chunks.erase(iter); + heap.m_freed_chunks.push_back(chunk_complement); + heap.m_allocated_chunks.push_back(chunk); + + return chunk; + } + else if (chunk->m_size == size) + { + // Reuse the whole chunk + heap.m_freed_chunks.erase(iter); + heap.m_allocated_chunks.push_back(chunk); + return chunk; + } + } + // If no chunk was found, return nullptr + return nullptr; + } + + /** + * 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; + // } + + /** + * Returns a bool whether the profiler is enabled + * or not. + * + * @returns True or false if the profiler is enabled + * or disabled respectively. + */ + bool Heap::profiler_enabled() { + Heap &heap = Heap::the(); + return heap.m_profiler_enable; + } + + /** + * Collection phase of the garbage collector. When + * an allocation is requested and there is no space + * left on the heap, a collection is triggered. This + * function is private so that the user cannot trigger + * a collection unneccessarily. + */ + void Heap::collect() + { + Heap &heap = Heap::the(); + + if (heap.profiler_enabled()) + Profiler::record(CollectStart); + + // get current stack frame + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + + if (heap.m_stack_top == nullptr) + throw std::runtime_error(std::string("Error: Heap is not initialized, read the docs!")); + + uintptr_t *stack_top = heap.m_stack_top; + + auto work_list = heap.m_allocated_chunks; + mark(stack_bottom, stack_top, work_list); + + sweep(heap); + + free(heap); + } + + /** + * Iterates through the stack, if an element on the stack points to a chunk, + * called a root chunk, that chunk is marked (i.e. reachable). + * Then it recursively follows all chunks which are possibly reachable from + * the root chunk and mark those chunks. + * If a chunk is marked it is removed from the worklist, since it's no longer of + * concern for this method. + * + * Time complexity: 0(N^2 * log(N)) as upper bound. + * Where N is either the size of the worklist or the size of + * the stack frame, depending on which is the largest. + * + * @param start Pointer to the start of the stack frame. + * @param end Pointer to the end of the stack frame. + * @param worklist The currently allocated chunks, which haven't been marked. + */ + void Heap::mark(uintptr_t *start, const uintptr_t* const end, vector &worklist) + { + Heap &heap = Heap::the(); + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(MarkStart); + + // To find adresses thats in the worklist + for (; start <= end; start++) + { + auto it = worklist.begin(); + auto stop = worklist.end(); + while (it != stop) + { + Chunk *chunk = *it; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + + // Check if the stack pointer points to something within the chunk + if (c_start <= *start && *start < c_end) + { + if (!chunk->m_marked) + { + if (profiler_enabled) + Profiler::record(ChunkMarked, chunk); + chunk->m_marked = true; + it = worklist.erase(it); + + // Recursively call mark, to see if the reachable chunk further points to another chunk + mark((uintptr_t *)c_start, (uintptr_t *)c_end, worklist); + } + else + { + ++it; + } + } + else + { + ++it; + } + } + } + } + + + /** + * Sweeps the heap, unmarks the marked chunks for the next cycle, + * adds the unmarked nodes to the list of freed chunks; to be freed. + * + * Time complexity: O(N^2), where N is the number of allocated chunks. + * It is quadratic, in the worst case, + * since each call to erase() is linear. + * + * @param heap Pointer to the heap singleton instance. + */ + void Heap::sweep(Heap &heap) + { + auto iter = heap.m_allocated_chunks.begin(); + bool profiler_enabled = heap.m_profiler_enable; + // This cannot "iter != stop", results in seg fault, since the end gets updated, I think. + while (iter != heap.m_allocated_chunks.end()) + { + Chunk *chunk = *iter; + + // Unmark the marked chunks for the next iteration. + if (chunk->m_marked) + { + chunk->m_marked = false; + ++iter; + } + else + { + // Add the unmarked chunks to freed chunks and remove from + // the list of allocated chunks + if (profiler_enabled) + Profiler::record(ChunkSwept, chunk); + heap.m_freed_chunks.push_back(chunk); + iter = heap.m_allocated_chunks.erase(iter); + } + } + } + + /** + * Frees chunks that was moved to the list m_freed_chunks + * by the sweep phase. If there are more than a certain + * amount of free chunks, delete the free chunks to + * avoid cluttering. + * + * Time complexity: O(N^2), where N is the freed chunks. + * If free_overlap() is called, it runs in O(N^2), + * otherwise O(N). + * + * @param heap Heap singleton instance, only for avoiding + * redundant calls to the singleton get + */ + void Heap::free(Heap &heap) + { + if (heap.m_freed_chunks.size() > FREE_THRESH) + { + bool profiler_enabled = heap.profiler_enabled(); + while (heap.m_freed_chunks.size()) + { + auto chunk = heap.m_freed_chunks.back(); + heap.m_freed_chunks.pop_back(); + if (profiler_enabled) + Profiler::record(ChunkFreed, chunk); + delete chunk; + } + } + // if there are chunks but not more than FREE_THRESH + else if (heap.m_freed_chunks.size()) + { + // essentially, always check for overlap between + // chunks before finishing the allocation + free_overlap(heap); + } + } + + /** + * Checks for overlaps between freed chunks of memory + * and removes overlapping chunks while prioritizing + * the chunks at lower addresses. + * + * Time complexity: O(N^2), where N is the number of freed chunks. + * At each iteration get_at() is called, which is linear. + * + * @param heap Heap singleton instance, only for avoiding + * redundant calls to the singleton get + * + * @note Maybe this should be changed to prioritizing + * larger chunks. Should remove get_at() to indexing, + * since that's constant. + */ + void Heap::free_overlap(Heap &heap) // borde göra en record(ChunkFreed) på onödiga chunks + { + std::vector filtered; + size_t i = 0; + //auto prev = Heap::get_at(heap.m_freed_chunks, i++); + auto prev = heap.m_freed_chunks[i++]; + prev->m_marked = true; + filtered.push_back(prev); + cout << filtered.back()->m_start << endl; + for (; i < heap.m_freed_chunks.size(); i++) + { + prev = filtered.back(); + //auto next = Heap::get_at(heap.m_freed_chunks, i); + auto next = heap.m_freed_chunks[i]; + auto p_start = (uintptr_t)(prev->m_start); + auto p_size = (uintptr_t)(prev->m_size); + auto n_start = (uintptr_t)(next->m_start); + if (n_start >= (p_start + p_size)) + { + next->m_marked = true; + filtered.push_back(next); + } + } + heap.m_freed_chunks.swap(filtered); + + bool profiler_enabled = heap.m_profiler_enable; + // After swap m_freed_chunks contains still available chunks + // and filtered contains all the chunks, so delete unused chunks + for (Chunk *chunk : filtered) + { + // if chunk was filtered away, delete it + if (!chunk->m_marked) + { + if (profiler_enabled) + Profiler::record(ChunkFreed, chunk); + delete chunk; + } + else + { + chunk->m_marked = false; + } + } + } + +#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; + } + + /** + * 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::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 new file mode 100644 index 0000000..92ce506 --- /dev/null +++ b/src/GC/tests/advance.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include + +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++) { + l.push_back(c++); + } + + auto iter = l.begin(); + auto stop = l.end(); + + while (iter != stop) { + cout << *iter << " "; + + iter++; + } + cout << endl; + iter = l.begin(); + while (*iter != *stop) { + cout << *iter << " "; + iter++; + } + cout << endl; + + cout << "rebased" << 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 new file mode 100644 index 0000000..4a0f6f8 --- /dev/null +++ b/src/GC/tests/alloc_free.cpp @@ -0,0 +1,32 @@ +#include + +#include "heap.hpp" + +struct Obj { + int a; + int b; + int c; +}; + +int main() { + GC::Heap::init(); + Obj *obj; + + for (int i = 0; i < 4; i++) { + 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 new file mode 100644 index 0000000..fa30051 --- /dev/null +++ b/src/GC/tests/extern_lib.cpp @@ -0,0 +1,94 @@ +#include +#include + +#include "heap.hpp" + +GC::Heap& singleton_test(); +void init_gc(GC::Heap& heap); +void frame_test(GC::Heap& heap); + +int main() { + std::cout << "in main" << std::endl; + GC::Heap &heap = singleton_test(); + + init_gc(heap); + frame_test(heap); + + heap.dispose(); + + return 0; +} + +/** + * This test is supposed to determine if the singleton pattern + * implementation is working correctly. This test passes if the + * first and second call prints the same memory address. + * + * Result: pass + * + * @return Pointer to the Heap singleton instance +*/ +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 << "===========================" << std::endl; + return heap; +} + + +/** + * This test calls Heap::init() which saves the stack-frame + * address from the calling function (this function). + * Heap::init() is supposed to be called at the absolute + * start of the program to save the address of the + * topmost stack frame. This test doesn't do anything + * but prepares for the next test(s). + * + * @param heap The Heap pointer to the singleton instance. + * +*/ +void init_gc(GC::Heap& heap){ + std::cout << "\n\n INITIALIZING THE HEAP" << std::endl; + std::cout << "===========================" << std::endl; + heap.init(); + heap.set_profiler(true); + std::cout << "===========================" << std::endl; +} + +/** + * This function tests the functionality of the intrinsic + * function `__builtin_frame_address` which returns the + * address of the corresponding level of stack frame. + * When given a param of 0, it returns the current stack frame. + * When given a param of 1, it returns the previous stack + * frame, and so on. + * + * This test passes on two conditions: + * 1) if the address of the current frame is smaller than + * the address of the previous frame (assumed). + * 2) if the previous frame has the same address as the one + * saved in the Heap instance after running Heap::init(). + * + * Result: pass + * + * @param heap The Heap instance +*/ +void frame_test(GC::Heap& heap) { + std::cout << "\n\n TESTING FRAME ADDRESSES" << std::endl; + std::cout << "===========================" << std::endl; + +#pragma clang diagnostic ignored "-Wframe-address" // clang++ directive to ignore warnings about __b_f_a + auto curr_frame = reinterpret_cast(__builtin_frame_address(0)); // addr of curr stack frame + std::cout << "Current stack frame:\t" << curr_frame << std::endl; +#pragma clang diagnostic ignored "-Wframe-address" + 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 + // auto alloced = heap->alloc(sizeof(unsigned long)); + + 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..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 new file mode 100644 index 0000000..c871721 --- /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"; + 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"; + 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)); + + 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.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(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"; + + 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..36717c5 --- /dev/null +++ b/src/GC/tests/linker.cpp @@ -0,0 +1,30 @@ +#include + +#include "heap.hpp" + +struct Obj { + int a; + int b; + int c; +}; + +int main() { + auto heap = GC::Heap::debug_the(); + + std::cout << "heap:\t" << heap << std::endl; + + auto obj = static_cast(GC::Heap::alloc(sizeof(Obj))); + + std::cout << "obj: \t" << obj << std::endl; + + obj->a = 3; + obj->b = 4; + obj->c = 5; + + std::cout << obj->a << ", " << obj->b << ", " << obj->c << std::endl; + + heap->print_contents(); + //delete heap; + + return 0; +} \ No newline at end of file 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/stack.cpp b/src/GC/tests/stack.cpp new file mode 100644 index 0000000..8f8382e --- /dev/null +++ b/src/GC/tests/stack.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +/* + * Stack.cpp + * - Tests stack scanning and stack pointers + * + * Goal: Find the values of the following variables + * and their position on the stack + * - unsigned long a + * - unsigned long b + * - unsigned long global_1 + * - unsigned long global_2 + * + * Result: Passed +*/ + + + + +std::vector iv; + +void collect() { + std::cout << "in collect" << std::endl; + + uintptr_t *stack_start = reinterpret_cast(__builtin_frame_address(0)); + + // denna orsakar segfault om man ger __b_f_a ett värde större än 2 + // uintptr_t *stack_end = reinterpret_cast(__builtin_frame_address(100)); + + std::cout << "SP1:\t" << stack_start << "\nSP2:\t" << (stack_start - 1*sizeof(int)) << std::endl; + std::cout << "SP-:\t" << --stack_start << std::endl; + + const uintptr_t *stack_end = (stack_start + 30*sizeof(int)); + int vars_found = 0; + + while (stack_start < stack_end) { + + if (std::find(iv.begin(), iv.end(), stack_start) != iv.end()) { + vars_found++; + std::cout << "Found " << *(reinterpret_cast(stack_start)) << " at " << stack_start << std::endl; + } + + // std::cout << "SP address:\t\t" << stack_start << "\nSP value:\t\t" << *(reinterpret_cast(stack_start)) << std::endl; + + stack_start++; + } + + if (vars_found == 0) { + std::cout << "Found nothing" << std::endl; + } +} + +int add(unsigned long a, unsigned long b) { + iv.push_back(reinterpret_cast(&a)); + iv.push_back(reinterpret_cast(&b)); + std::cout << "'a':\t" << &a << "\n'b':\t" << &b << std::endl; + collect(); + return a + b; +} + +int main() { + + unsigned long global_1 = 16; + unsigned long global_2 = 32; + + iv.push_back(&global_1); + iv.push_back(&global_2); + + std::cout << "'g1':\t" << &global_1 << "\n'g2':\t" << &global_2 << std::endl; + + add(3,2); + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/stack2.cpp b/src/GC/tests/stack2.cpp new file mode 100644 index 0000000..f1a78bc --- /dev/null +++ b/src/GC/tests/stack2.cpp @@ -0,0 +1,51 @@ +#include +#include + +void dummy1(); +void dummy2(); + +int main() { + + uintptr_t *prev1 = reinterpret_cast(__builtin_frame_address(0)); + uintptr_t *prev2 = static_cast(__builtin_frame_address(0)); + + std::cout << "reinterpret:\t" << prev1 << "\nstatic:\t\t" << prev2 << std::endl; + + std::cout << "Start:\t\t" << prev1 << std::endl; +#pragma clang diagnostic ignored "-Wframe-address" + uintptr_t *tmp = reinterpret_cast(__builtin_frame_address(1)); + std::cout << "Frame 1:\t" << tmp << "\t\tDiff:\t" << std::hex << "0x"<< tmp - prev1 << std::endl; + prev1 = tmp; + +#pragma clang diagnostic ignored "-Wframe-address" + tmp = reinterpret_cast(__builtin_frame_address(2)); + std::cout << "Frame 2:\t" << tmp << "\tDiff:\t" << std::hex << "0x" << tmp - prev1 << std::endl; + prev1 = tmp; + +// arg > 2 for __builtin_frame_address() results in segfault +// #pragma clang diagnostic ignored "-Wframe-address" +// tmp = reinterpret_cast(__builtin_frame_address(3)); +// std::cout << "Frame 3:\t" << tmp << "\tDiff:\t" << std::hex << "0x" << prev1 - tmp << std::endl; + + dummy1(); + + return 0; +} + +void dummy1() { + std::cout << "D1 SFrame:\t" << __builtin_frame_address(0); +#pragma clang diagnostic ignored "-Wframe-address" + std::cout << "\t\tPrev:\t" << __builtin_frame_address(1) << std::endl; + std::cout << "D1 RA:\t\t" << std::hex << __builtin_return_address(0) << std::endl; + dummy2(); +} + +void dummy2() { + std::cout << "Frame:\t\t" << __builtin_frame_address(0); +#pragma clang diagnostic ignored "-Wframe-address" + std::cout << "\t\tPrev:\t" << __builtin_frame_address(1) << std::endl; + void *ra = __builtin_return_address(0); + std::cout << "D2 RA:\t\t" << std::hex << ra << std::endl; + // gives same value as pure 'ra' + // std::cout << "D2 ERA:\t\t" << std::hex << __builtin_extract_return_addr(ra) << std::endl; +} \ No newline at end of file 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 new file mode 100644 index 0000000..83fcf2c --- /dev/null +++ b/src/GC/todo.md @@ -0,0 +1,11 @@ +# Garbage collection + +## Project +Deliver to samuel + +## GC TODO: +- 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