From ee53759ca53fe7070d7d7bb175437ba1611c932e Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Wed, 29 Mar 2023 08:31:34 +0200 Subject: [PATCH 01/17] Started working on a C wrapper --- src/GC/include/cheap.h | 22 ++++++++++++++++++++++ src/GC/lib/cheap.cpp | 25 +++++++++++++++++++++++++ src/GC/lib/event.cpp | 6 +++--- 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 src/GC/include/cheap.h create mode 100644 src/GC/lib/cheap.cpp diff --git a/src/GC/include/cheap.h b/src/GC/include/cheap.h new file mode 100644 index 0000000..2db2b8d --- /dev/null +++ b/src/GC/include/cheap.h @@ -0,0 +1,22 @@ +#ifndef __CHEAP_H__ +#define __CHEAP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct cheap; +typedef struct cheap cheap_t; + +cheap_t *cheap_the(); +void cheap_init(); +void cheap_dispose(); +void *cheap_alloc(unsigned long size); +void cheap_set_profiler(bool mode); + +#ifdef __cplusplus +} +#endif + + +#endif /* __CHEAP_H__ */ \ No newline at end of file diff --git a/src/GC/lib/cheap.cpp b/src/GC/lib/cheap.cpp new file mode 100644 index 0000000..f9f7172 --- /dev/null +++ b/src/GC/lib/cheap.cpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "heap.hpp" +#include "cheap.h" + +struct cheap { + void *obj; +}; + +cheap_t *cheap_the() { + cheap_t *c; + GC::Heap *heap; + + c = (cheap_t *)malloc(sizeof(*c)); + heap = &GC::Heap::the(); + c->obj = heap; + + return c; +} + +void cheap_init() { + GC::Heap::init(); +} \ No newline at end of file diff --git a/src/GC/lib/event.cpp b/src/GC/lib/event.cpp index 2815a77..185c613 100644 --- a/src/GC/lib/event.cpp +++ b/src/GC/lib/event.cpp @@ -1,6 +1,6 @@ -#include -#include -#include +// #include +// #include +// #include #include "chunk.hpp" #include "event.hpp" From 16aa1be30902ede97eb058ce12c6c18522c3e0de Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Wed, 29 Mar 2023 15:13:24 +0200 Subject: [PATCH 02/17] Wrapper for GC finished, untested --- src/GC/include/cheap.h | 26 ++++++++---- src/GC/lib/cheap.cpp | 30 +++++++++++--- src/GC/tests/file.cpp | 11 +++++- src/GC/tests/wrapper.c | 90 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 src/GC/tests/wrapper.c diff --git a/src/GC/include/cheap.h b/src/GC/include/cheap.h index 2db2b8d..1e11ae2 100644 --- a/src/GC/include/cheap.h +++ b/src/GC/include/cheap.h @@ -1,22 +1,32 @@ -#ifndef __CHEAP_H__ -#define __CHEAP_H__ +#ifndef CHEAP_H +#define CHEAP_H + +#include #ifdef __cplusplus extern "C" { #endif +#define DEBUG + +#ifdef DEBUG +typedef struct cheap +{ + void *obj; +} cheap_t; +#else struct cheap; typedef struct cheap cheap_t; +#endif -cheap_t *cheap_the(); -void cheap_init(); -void cheap_dispose(); -void *cheap_alloc(unsigned long size); -void cheap_set_profiler(bool mode); +cheap_t *cheap_the() noexcept; +void cheap_init() noexcept; +void cheap_dispose() noexcept; +void *cheap_alloc(unsigned long size) noexcept; +void cheap_set_profiler(cheap_t *cheap, bool mode) noexcept; #ifdef __cplusplus } #endif - #endif /* __CHEAP_H__ */ \ No newline at end of file diff --git a/src/GC/lib/cheap.cpp b/src/GC/lib/cheap.cpp index f9f7172..1643e17 100644 --- a/src/GC/lib/cheap.cpp +++ b/src/GC/lib/cheap.cpp @@ -1,25 +1,43 @@ -#pragma once - #include #include "heap.hpp" #include "cheap.h" -struct cheap { +struct cheap +{ void *obj; }; -cheap_t *cheap_the() { +cheap_t *cheap_the() +{ cheap_t *c; GC::Heap *heap; - c = (cheap_t *)malloc(sizeof(*c)); + c = static_cast(malloc(sizeof(cheap_t))); heap = &GC::Heap::the(); c->obj = heap; return c; } -void cheap_init() { +void cheap_init() +{ GC::Heap::init(); +} + +void cheap_dispose() +{ + GC::Heap::dispose(); +} + +void *cheap_alloc(unsigned long size) +{ + return GC::Heap::alloc(size); +} + +void cheap_set_profiler(cheap_t *cheap, bool mode) +{ + GC::Heap *heap = static_cast(cheap->obj); + + heap->set_profiler(mode); } \ No newline at end of file diff --git a/src/GC/tests/file.cpp b/src/GC/tests/file.cpp index df9e441..f4a0373 100644 --- a/src/GC/tests/file.cpp +++ b/src/GC/tests/file.cpp @@ -8,6 +8,7 @@ void time_string(char *buffer); void print_log_file(const std::string TESTS_PATH); void readlink_test(); +void null_test(); int main() { @@ -17,7 +18,9 @@ int main() // const std::string TESTS_PATH = "/home/virre/dev/systemF/org/language/src/GC/tests/"; // print_log_file(TESTS_PATH); - readlink_test(); + // readlink_test(); + + null_test(); return 0; } @@ -65,4 +68,10 @@ void readlink_test() std::string log_path = folder + "/log_file_bla.txt"; std::cout << "\n" << log_path << std::endl; +} + +void null_test() { + int *p = nullptr; + + std::cout << "P: " << nullptr << std::endl; } \ No newline at end of file diff --git a/src/GC/tests/wrapper.c b/src/GC/tests/wrapper.c new file mode 100644 index 0000000..1a77472 --- /dev/null +++ b/src/GC/tests/wrapper.c @@ -0,0 +1,90 @@ +#include +#include + +#include "cheap.h" + +typedef struct object +{ + int x, y, z; + double velocity; +} Object; + +void test_init() +{ + printf("----- IN TEST_INIT ----------------------------\n"); + + cheap_init(); + + printf("----- EXIT TEST_INIT --------------------------\n"); +} + +cheap_t *test_the() +{ + printf("----- IN TEST_THE -----------------------------\n"); + + cheap_t *fst_heap = cheap_the(); + + printf("Heap 1:\t%p\n", fst_heap->obj); + + cheap_t *snd_heap = cheap_the(); + + printf("Heap 2:\t%p\n", snd_heap->obj); + + printf("----- EXIT TEST_THE ---------------------------\n"); + + free(snd_heap); + return fst_heap; +} + +void test_profiler(cheap_t *heap) +{ + printf("----- IN TEST_PROFILER ------------------------\n"); + + cheap_set_profiler(heap, false); + cheap_set_profiler(heap, true); + + printf("----- EXIT TEST_PROFILER ----------------------\n"); +} + +Object *test_alloc() +{ + printf("----- IN TEST_ALLOC ---------------------------\n"); + + Object *o; + o = (Object *)(cheap_alloc(sizeof(Object))); + + o->x = 3; + o->y = 4; + o->z = 5; + o->velocity = 1.0f; + + printf("----- EXIT TEST_ALLOC -------------------------\n"); + return o; +} + +void test_dispose() +{ + printf("----- IN TEST_DISPOSE -------------------------\n"); + + cheap_dispose(); + + printf("----- EXIT TEST_DISPOSE -----------------------\n"); +} + +int main(int argc, char **argv) +{ + test_init(); + + cheap_t *heap = test_the(); + + test_profiler(heap); + + Object *o = test_alloc(); + printf("Object:\n\tx: %d\n\ty: %d\n\tz: %d\n\tvel: %f\n", o->x, o->y, o->z, o->velocity); + + test_dispose(); + + free(heap); + free(o); + return 0; +} \ No newline at end of file From 12816ea9de0761e6a7fe8ad3e7a93c2317c78170 Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Wed, 29 Mar 2023 16:05:54 +0200 Subject: [PATCH 03/17] Wrapper works --- src/GC/Makefile | 11 ++++++++++- src/GC/include/cheap.h | 12 ++++++------ src/GC/lib/cheap.cpp | 5 +++++ src/GC/tests/wrapper.c | 39 ++++++++++++++++++++++----------------- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/GC/Makefile b/src/GC/Makefile index add6d73..1fda452 100644 --- a/src/GC/Makefile +++ b/src/GC/Makefile @@ -63,4 +63,13 @@ static_lib: # 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 + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/extern_lib.out tests/extern_lib.cpp lib/gcoll.a + +wrapper: + rm -f lib/event.o lib/profiler.o lib/heap.o lib/coll.a tests/wrapper.out + $(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 + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -c -o lib/cheap.o lib/cheap.cpp -fPIC + ar rcs lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o lib/cheap.o + clang -stdlib=libc++ $(WFLAGS) $(LIB_INCL) -o tests/wrapper.out tests/wrapper.c lib/gcoll.a -lstdc++ diff --git a/src/GC/include/cheap.h b/src/GC/include/cheap.h index 1e11ae2..d2c649d 100644 --- a/src/GC/include/cheap.h +++ b/src/GC/include/cheap.h @@ -7,7 +7,7 @@ extern "C" { #endif -#define DEBUG +// #define DEBUG #ifdef DEBUG typedef struct cheap @@ -19,11 +19,11 @@ struct cheap; typedef struct cheap cheap_t; #endif -cheap_t *cheap_the() noexcept; -void cheap_init() noexcept; -void cheap_dispose() noexcept; -void *cheap_alloc(unsigned long size) noexcept; -void cheap_set_profiler(cheap_t *cheap, bool mode) noexcept; +cheap_t *cheap_the(); +void cheap_init(); +void cheap_dispose(); +void *cheap_alloc(unsigned long size); +void cheap_set_profiler(cheap_t *cheap, bool mode); #ifdef __cplusplus } diff --git a/src/GC/lib/cheap.cpp b/src/GC/lib/cheap.cpp index 1643e17..29a0b10 100644 --- a/src/GC/lib/cheap.cpp +++ b/src/GC/lib/cheap.cpp @@ -1,12 +1,15 @@ #include +#include #include "heap.hpp" #include "cheap.h" +#ifndef DEBUG struct cheap { void *obj; }; +#endif cheap_t *cheap_the() { @@ -27,7 +30,9 @@ void cheap_init() void cheap_dispose() { + std::cout << "In dispose\n"; GC::Heap::dispose(); + std::cout << "Out dispose" << std::endl; } void *cheap_alloc(unsigned long size) diff --git a/src/GC/tests/wrapper.c b/src/GC/tests/wrapper.c index 1a77472..bcd4859 100644 --- a/src/GC/tests/wrapper.c +++ b/src/GC/tests/wrapper.c @@ -1,5 +1,6 @@ #include #include +#include #include "cheap.h" @@ -18,23 +19,25 @@ void test_init() printf("----- EXIT TEST_INIT --------------------------\n"); } -cheap_t *test_the() -{ - printf("----- IN TEST_THE -----------------------------\n"); +/* Uncomment ONLY if run with DEBUG defined in cheap.h */ - cheap_t *fst_heap = cheap_the(); +// cheap_t *test_the() +// { +// printf("----- IN TEST_THE -----------------------------\n"); - printf("Heap 1:\t%p\n", fst_heap->obj); +// cheap_t *fst_heap = cheap_the(); - cheap_t *snd_heap = cheap_the(); +// printf("Heap 1:\t%p\n", fst_heap->obj); - printf("Heap 2:\t%p\n", snd_heap->obj); +// cheap_t *snd_heap = cheap_the(); - printf("----- EXIT TEST_THE ---------------------------\n"); +// printf("Heap 2:\t%p\n", snd_heap->obj); - free(snd_heap); - return fst_heap; -} +// printf("----- EXIT TEST_THE ---------------------------\n"); + +// free(snd_heap); +// return fst_heap; +// } void test_profiler(cheap_t *heap) { @@ -71,20 +74,22 @@ void test_dispose() printf("----- EXIT TEST_DISPOSE -----------------------\n"); } -int main(int argc, char **argv) +int main() { test_init(); - cheap_t *heap = test_the(); - - test_profiler(heap); + /* Uncomment ONLY if run with DEBUG defined in cheap.h */ + // cheap_t *heap = test_the(); + // test_profiler(heap); Object *o = test_alloc(); + printf("Object size: %lu\n", sizeof(Object)); printf("Object:\n\tx: %d\n\ty: %d\n\tz: %d\n\tvel: %f\n", o->x, o->y, o->z, o->velocity); test_dispose(); - free(heap); - free(o); + /* Sefault I don't understand, don't uncomment */ + // free(heap); + // free(o); return 0; } \ No newline at end of file From 01c93a631f5920fcfcf05bb86c4876170311cabc Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Wed, 29 Mar 2023 21:27:47 +0200 Subject: [PATCH 04/17] Wrapper docs --- src/GC/Makefile | 20 ++++++++++++-------- src/GC/docs/lib/cheap.md | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 src/GC/docs/lib/cheap.md diff --git a/src/GC/Makefile b/src/GC/Makefile index 1fda452..f66f692 100644 --- a/src/GC/Makefile +++ b/src/GC/Makefile @@ -55,9 +55,9 @@ 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 + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/event.o lib/event.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/profiler.o lib/profiler.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/heap.o lib/heap.cpp -fPIC # create static library ar r lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o @@ -65,11 +65,15 @@ static_lib: static_lib_test: static_lib $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/extern_lib.out tests/extern_lib.cpp lib/gcoll.a -wrapper: +wrapper: +# remove old files rm -f lib/event.o lib/profiler.o lib/heap.o lib/coll.a tests/wrapper.out - $(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 - $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -c -o lib/cheap.o lib/cheap.cpp -fPIC +# compile object files + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/event.o lib/event.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/profiler.o lib/profiler.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/heap.o lib/heap.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/cheap.o lib/cheap.cpp -fPIC +# compile object files into library ar rcs lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o lib/cheap.o +# compile test program wrapper.c with normal clang clang -stdlib=libc++ $(WFLAGS) $(LIB_INCL) -o tests/wrapper.out tests/wrapper.c lib/gcoll.a -lstdc++ diff --git a/src/GC/docs/lib/cheap.md b/src/GC/docs/lib/cheap.md new file mode 100644 index 0000000..e5c5993 --- /dev/null +++ b/src/GC/docs/lib/cheap.md @@ -0,0 +1,40 @@ +# cheap.h & cheap.cpp + +A wrapper interface for the class `GC::Heap` for easier use +in LLVM (no nasty namespaces). This interface is relatively +straight-forward and only defines functions to use the already +public functions in the class `GC::Heap`. + +The functions are declared in a normal C-style header and +defined as "pure" C-functions. Because the public functions +exposed in `GC::Heap` are static, some of the functions +just call the static functions but are wrapped as C-functions. + +For the non-static function `GC::Heap::set_profiler()` and the +singleton get-instance function `GC::Heap::the()` a struct +is used to encapsulate the heap-object. If this library is +compiled with `DEBUG` defined a struct is typedef-ed and +can be used everywhere, otherwise this struct is opaque +and cannot be used explicitly. This struct only contains +a pointer to the heap instance and is called `cheap_t`. + +## Functions +`cheap_t *cheap_the()`: Returns an encapsulated singleton +instance. It is encapsulated in an opaque struct as the +instance itself is not meant to be used outside the C++ +library. + +`void cheap_init()`: Simply calls the `Heap::init()` +function. + +`void cheap_dispose()`: Only calls the `Heap::dispose()` +function. + +`void *cheap_alloc(unsigned long size)`: Calls `Heap::alloc(size_t size)` +and returns whatever `alloc` returns. + +`void cheap_set_profiler(cheap_t *cheap, bool mode)`: +The argument `cheap` is the encapsulated Heap singleton instance. +`mode` is the same as for `Heap::set_profiler(bool mode)`. + +For more documentation on functionality, see `src/GC/docs/lib/heap.md`. \ No newline at end of file From 17fd56ef7ef123427b5be2bbd7f9318a5f6ba4d0 Mon Sep 17 00:00:00 2001 From: valtermiari Date: Thu, 30 Mar 2023 08:48:33 +0200 Subject: [PATCH 05/17] Start of wrapper test --- src/GC/Makefile | 24 +++++++++++++++++++- src/GC/tests/wrapper_test.c | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/GC/tests/wrapper_test.c diff --git a/src/GC/Makefile b/src/GC/Makefile index add6d73..fea7ae4 100644 --- a/src/GC/Makefile +++ b/src/GC/Makefile @@ -40,6 +40,17 @@ game: rm -f tests/game.out $(CC) $(WFLAGS) $(STDFLAGS) $(LIB_INCL) tests/game.cpp lib/heap.cpp lib/profiler.cpp lib/event.cpp -o tests/game.out +wrapper_test: + rm -f lib/event.o lib/profiler.o lib/heap.o lib/coll.a tests/wrapper_test.out +# compile object files + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -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 + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -c -o lib/cheap.o lib/cheap.cpp -fPIC +# compile object files into library + ar rcs lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o lib/cheap.o + clang -stdlib=libc++ $(WFLAGS) $(LIB_INCL) -o tests/wrapper_test.out tests/wrapper_test.c lib/gcoll.a -lstdc++ + extern_lib: # remove old files rm -f lib/heap.o lib/libheap.so tests/extern_lib.out @@ -63,4 +74,15 @@ static_lib: # 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 + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/extern_lib.out tests/extern_lib.cpp lib/gcoll.a + +static_lib_c: +# 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 + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -c -o lib/cheap.o lib/cheap.cpp -fPIC +# create static library + ar r lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o \ No newline at end of file diff --git a/src/GC/tests/wrapper_test.c b/src/GC/tests/wrapper_test.c new file mode 100644 index 0000000..729cf69 --- /dev/null +++ b/src/GC/tests/wrapper_test.c @@ -0,0 +1,45 @@ +#include +#include + +#include "cheap.h" + +typedef struct node { + int id; + struct node *child; +} Node; + +// Global variables make the test less complex +Node *HEAD = NULL; +Node *CURRENT = NULL; + +// Creates a linked list of length depth. Global head "HEAD" is updated. +void *create_linked_list(int depth) { + HEAD = (Node*)(cheap_alloc(sizeof(Node))); + HEAD->id = 0; + // Purposely omitting adding a child to "last_node", since its the last node + for (int i = 1; i < depth - 1; i++) { + insert_first(i); + } +} + +void *insert_first(int node_id) { + Node *new_head; + new_head = (Node*)(cheap_alloc(sizeof(Node))); + new_head->id = node_id; + new_head->child = HEAD; + + HEAD = new_head; +} + +void test_linked_list(int list_length){ + cheap_init(); + cheap_t *heap = cheap_the(); + cheap_set_profiler(heap, true); + create_linked_list(list_length); + cheap_dispose(); + free(heap); +} + +int main (int argc, char **argv) { + test_linked_list(30); +} \ No newline at end of file From c9e2bc227886f6470890ba76d3d849c138d50823 Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Fri, 7 Apr 2023 20:40:01 +0200 Subject: [PATCH 06/17] Cleaned up include guards --- src/GC/include/cheap.h | 4 ++-- src/GC/include/event.hpp | 3 --- src/GC/include/heap.hpp | 7 ++----- src/GC/include/profiler.hpp | 1 + src/GC/lib/cheap.cpp | 4 ++-- src/GC/lib/event.cpp | 4 ---- src/GC/lib/heap.cpp | 41 ++++++++----------------------------- 7 files changed, 16 insertions(+), 48 deletions(-) diff --git a/src/GC/include/cheap.h b/src/GC/include/cheap.h index d2c649d..f4cd03c 100644 --- a/src/GC/include/cheap.h +++ b/src/GC/include/cheap.h @@ -7,9 +7,9 @@ extern "C" { #endif -// #define DEBUG +// #define WRAPPER_DEBUG -#ifdef DEBUG +#ifdef WRAPPER_DEBUG typedef struct cheap { void *obj; diff --git a/src/GC/include/event.hpp b/src/GC/include/event.hpp index 298ccab..d4d5e10 100644 --- a/src/GC/include/event.hpp +++ b/src/GC/include/event.hpp @@ -1,9 +1,6 @@ #pragma once #include -#include -#include -#include #include "chunk.hpp" diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index 365a838..36fa83b 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -1,8 +1,5 @@ #pragma once -#include -#include -#include #include #include @@ -11,7 +8,7 @@ #define HEAP_SIZE 2097152 //65536 #define FREE_THRESH (uint) 100000 -#define DEBUG +// #define HEAP_DEBUG namespace GC { @@ -89,7 +86,7 @@ namespace GC Heap(Heap const&) = delete; Heap& operator=(Heap const&) = delete; -#ifdef DEBUG +#ifdef HEAP_DEBUG void collect(CollectOption flags); // conditional collection void check_init(); // print dummy things void print_contents(); // print dummy things diff --git a/src/GC/include/profiler.hpp b/src/GC/include/profiler.hpp index ccdf463..4864dd6 100644 --- a/src/GC/include/profiler.hpp +++ b/src/GC/include/profiler.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "chunk.hpp" diff --git a/src/GC/lib/cheap.cpp b/src/GC/lib/cheap.cpp index 29a0b10..f6c24a9 100644 --- a/src/GC/lib/cheap.cpp +++ b/src/GC/lib/cheap.cpp @@ -1,10 +1,10 @@ #include -#include +#include #include "heap.hpp" #include "cheap.h" -#ifndef DEBUG +#ifndef WRAPPER_DEBUG struct cheap { void *obj; diff --git a/src/GC/lib/event.cpp b/src/GC/lib/event.cpp index 185c613..89a2a71 100644 --- a/src/GC/lib/event.cpp +++ b/src/GC/lib/event.cpp @@ -1,7 +1,3 @@ -// #include -// #include -// #include - #include "chunk.hpp" #include "event.hpp" diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 579f421..61319f0 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -1,9 +1,4 @@ -#include -#include -#include -#include #include -#include #include #include #include @@ -83,7 +78,8 @@ namespace GC { 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 (heap.m_size + size > HEAP_SIZE) + throw std::runtime_error(std::string("Error: Heap out of memory")); } // If a chunk was recycled, return the old chunk address @@ -159,25 +155,6 @@ namespace GC 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. @@ -414,7 +391,13 @@ namespace GC } } -#ifdef DEBUG + void Heap::set_profiler(bool mode) + { + Heap &heap = Heap::the(); + heap.m_profiler_enable = mode; + } + +#ifdef HEAP_DEBUG /** * Prints the result of Heap::init() and a dummy value * for the current stack frame for reference. @@ -565,12 +548,6 @@ namespace GC } } - 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) { From d7ea27e9cd138cbe3066a3bdcd74ef8ef2c84396 Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Mon, 1 May 2023 14:36:44 +0200 Subject: [PATCH 07/17] testing testing... --- src/GC/Makefile | 14 +- src/GC/include/event.hpp | 22 +-- src/GC/include/heap.hpp | 27 ++-- src/GC/include/profiler.hpp | 49 +++--- src/GC/lib/heap.cpp | 138 ++++++++++++++++- src/GC/lib/profiler.cpp | 186 +++++++++++++++++------ src/GC/tests/advance.cpp | 89 +++++++---- src/GC/tests/alloc_free_list.cpp | 250 +++++++++++++++++++++++++++++++ src/GC/tests/events.cpp | 44 ------ src/GC/tests/linkedlist.cpp | 87 +++++++++++ src/GC/tests/linker.cpp | 16 +- 11 files changed, 750 insertions(+), 172 deletions(-) create mode 100644 src/GC/tests/alloc_free_list.cpp delete mode 100644 src/GC/tests/events.cpp create mode 100644 src/GC/tests/linkedlist.cpp diff --git a/src/GC/Makefile b/src/GC/Makefile index 6b33ca8..5eed227 100644 --- a/src/GC/Makefile +++ b/src/GC/Makefile @@ -43,10 +43,10 @@ game: wrapper_test: rm -f lib/event.o lib/profiler.o lib/heap.o lib/coll.a tests/wrapper_test.out # compile object files - $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -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 - $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -c -o lib/cheap.o lib/cheap.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -g -c -o lib/event.o lib/event.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -g -c -o lib/profiler.o lib/profiler.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -g -c -o lib/heap.o lib/heap.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -g -c -o lib/cheap.o lib/cheap.cpp -fPIC # compile object files into library ar rcs lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o lib/cheap.o clang -stdlib=libc++ $(WFLAGS) $(LIB_INCL) -o tests/wrapper_test.out tests/wrapper_test.c lib/gcoll.a -lstdc++ @@ -76,6 +76,12 @@ static_lib: static_lib_test: static_lib $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/extern_lib.out tests/extern_lib.cpp lib/gcoll.a +alloc_free_list: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/alloc_fl.out tests/alloc_free_list.cpp lib/gcoll.a + +linked_list_test: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/linkedlist.out tests/linkedlist.cpp lib/gcoll.a + wrapper: # remove old files rm -f lib/event.o lib/profiler.o lib/heap.o lib/coll.a tests/wrapper.out diff --git a/src/GC/include/event.hpp b/src/GC/include/event.hpp index d4d5e10..c18b1ce 100644 --- a/src/GC/include/event.hpp +++ b/src/GC/include/event.hpp @@ -11,16 +11,18 @@ namespace GC */ enum GCEventType { - HeapInit, - AllocStart, - CollectStart, - MarkStart, - ChunkMarked, - ChunkSwept, - ChunkFreed, - NewChunk, - ReusedChunk, - ProfilerDispose + HeapInit = 1 << 0, + AllocStart = 1 << 1, + CollectStart = 1 << 2, + MarkStart = 1 << 3, + SweepStart = 1 << 4, + ChunkMarked = 1 << 5, + ChunkSwept = 1 << 6, + ChunkFreed = 1 << 7, + NewChunk = 1 << 8, + ReusedChunk = 1 << 9, + ProfilerDispose = 1 << 10, + FreeStart = 1 << 11 }; /** diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index 36fa83b..f1a97b3 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -1,14 +1,15 @@ #pragma once +#include #include #include #include "chunk.hpp" #include "profiler.hpp" -#define HEAP_SIZE 2097152 //65536 -#define FREE_THRESH (uint) 100000 -// #define HEAP_DEBUG +#define HEAP_SIZE 160 //65536 +#define FREE_THRESH (uint) 0 +#define HEAP_DEBUG namespace GC { @@ -17,12 +18,12 @@ namespace GC * collection (mark/sweep/free/all). */ enum CollectOption { - MARK=0x1, - SWEEP=0x2, - MARK_SWEEP = 0x3, - FREE=0x4, - COLLECT_ALL=0x7 - }; + MARK = 1 << 0, + SWEEP = 1 << 1, + MARK_SWEEP = 1 << 2, + FREE = 1 << 3, + COLLECT_ALL = 0b1111 // all flags above + }; /** * The heap class to represent the heap for the @@ -44,12 +45,14 @@ namespace GC char *const m_heap; size_t m_size {0}; + char *m_heap_top {nullptr}; // static Heap *m_instance {nullptr}; uintptr_t *m_stack_top {nullptr}; bool m_profiler_enable {false}; std::vector m_allocated_chunks; std::vector m_freed_chunks; + std::list m_free_list; static bool profiler_enabled(); // static Chunk *get_at(std::vector &list, size_t n); @@ -66,7 +69,9 @@ namespace GC // Temporary Chunk *try_recycle_chunks_new(size_t size); void free_overlap_new(Heap &heap); - +#ifndef HEAP_DEBUG + void add_to_free_list(Chunk *chunk); +#endif public: /** * These are the only five functions which are exposed @@ -80,6 +85,7 @@ namespace GC static void init(); static void dispose(); static void *alloc(size_t size); + static void *alloc_free_list(size_t size); void set_profiler(bool mode); // Stop the compiler from generating copy-methods @@ -87,6 +93,7 @@ namespace GC Heap& operator=(Heap const&) = delete; #ifdef HEAP_DEBUG + void add_to_free_list(Chunk *chunk); void collect(CollectOption flags); // conditional collection void check_init(); // print dummy things void print_contents(); // print dummy things diff --git a/src/GC/include/profiler.hpp b/src/GC/include/profiler.hpp index 4864dd6..bd14048 100644 --- a/src/GC/include/profiler.hpp +++ b/src/GC/include/profiler.hpp @@ -2,12 +2,31 @@ #include #include +#include #include "chunk.hpp" #include "event.hpp" +// #define FunctionCallTypes +// #define ChunkOpsTypes + namespace GC { + enum RecordOption + { + FunctionCalls = (GC::AllocStart | GC::CollectStart | GC::MarkStart | GC::SweepStart), + ChunkOps = (GC::ChunkMarked | GC::ChunkSwept | GC::ChunkFreed | GC::NewChunk | GC::ReusedChunk), + AllOps = ~0 + }; + + struct ProfilerEvent + { + uint m_n {1}; + const GCEventType m_type; + + ProfilerEvent(GCEventType type) : m_type(type) {} + }; + class Profiler { private: Profiler() {} @@ -17,31 +36,27 @@ namespace GC { 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; - } - + static Profiler &the(); inline static Profiler *m_instance {nullptr}; std::vector m_events; + ProfilerEvent *m_last_prof_event {new ProfilerEvent(HeapInit)}; + std::vector m_prof_events; + RecordOption flags; + static void record_data(GCEvent *type); std::ofstream create_file_stream(); std::string get_log_folder(); static void dump_trace(); + static void dump_prof_trace(); + static void dump_chunk_trace(); + // static void dump_trace_short(); + // static void dump_trace_full(); + static void print_chunk_event(GCEvent *event, char buffer[22]); + static const char *type_to_string(GCEventType type); public: + static RecordOption log_options(); + static void set_log_options(RecordOption flags); static void record(GCEventType type); static void record(GCEventType type, size_t size); static void record(GCEventType type, Chunk *chunk); diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 61319f0..b3a2d6b 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -36,6 +36,7 @@ namespace GC // 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)); + heap.m_heap_top = heap.m_heap; } /** @@ -50,6 +51,131 @@ namespace GC Profiler::dispose(); } + void *Heap::alloc_free_list(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; + } + + // Try to find a fragmented section to recycle + Chunk *recycle = nullptr; + auto iter = heap.m_free_list.begin(); + while (iter != heap.m_free_list.end()) + { + if ((*iter)->m_size >= size) + { + recycle = *iter; + heap.m_free_list.erase(++iter); + break; + } + iter++; + } + + // If memory fragment was found + if (recycle != nullptr) + { + heap.m_size += size; + + // If fragment is larger than request, split it + if (recycle->m_size > size) + { + auto new_part = new Chunk(size, recycle->m_start); + auto complement = new Chunk( + recycle->m_size - size, + (recycle->m_start + size) + ); + delete recycle; + // TODO: add complement to free_list + heap.add_to_free_list(complement); + + heap.m_allocated_chunks.push_back(new_part); + + return (void *)(new_part->m_start); + } + + heap.m_allocated_chunks.push_back(recycle); + return (void *)(recycle->m_start); + } + + uintptr_t *max_size = (uintptr_t *)(heap.m_heap + HEAP_SIZE); + uintptr_t *new_size = (uintptr_t *)(heap.m_heap_top + size); + + if (new_size <= max_size) + { + auto new_chunk = new Chunk(size, (uintptr_t *)(heap.m_heap_top)); + heap.m_allocated_chunks.push_back(new_chunk); + + if (profiler_enabled) + Profiler::record(NewChunk, new_chunk); + + heap.m_heap_top += size; + heap.m_size += size; + } + + // Only throws if the allocation failed + throw std::runtime_error(std::string("Error: Heap out of memory")); + } + + void Heap::add_to_free_list(Chunk *chunk) + { + Chunk *curr; + auto iter = m_free_list.begin(); + uintptr_t *prev_start = nullptr; + uintptr_t *prev_end = nullptr; + + while (iter != m_free_list.end()) + { + curr = *iter; + + // If the curr chunk is aligned before param + if (curr->m_start + curr->m_size == chunk->m_start) + { + Chunk *merged = new Chunk( + curr->m_size + chunk->m_size, + curr->m_start + ); + iter = m_free_list.erase(iter); + m_free_list.insert(iter, merged); + return; + } + + // If the curr chunk is aligned after param + if (chunk->m_start + chunk->m_size == curr->m_start) + { + Chunk *merged = new Chunk( + curr->m_size + chunk->m_size, + chunk->m_start + ); + iter = m_free_list.erase(iter); + m_free_list.insert(iter, merged); + return; + } + + // If the first chunk starts after param + if (prev_start == nullptr && curr->m_start > chunk->m_start) + { + m_free_list.insert(iter, chunk); + return; + } + + prev_start = curr->m_start; + prev_end = prev_start + curr->m_size; + iter++; + } + + // This is only reachable if the chunk is at the end + m_free_list.push_back(chunk); + } + /** * Allocates a given amount of bytes on the heap. * @@ -79,7 +205,11 @@ namespace GC heap.collect(); // If memory is not enough after collect, crash with OOM error if (heap.m_size + size > HEAP_SIZE) + { + if (profiler_enabled) + Profiler::dispose(); throw std::runtime_error(std::string("Error: Heap out of memory")); + } } // If a chunk was recycled, return the old chunk address @@ -271,8 +401,10 @@ namespace GC */ void Heap::sweep(Heap &heap) { - auto iter = heap.m_allocated_chunks.begin(); bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(SweepStart); + auto iter = heap.m_allocated_chunks.begin(); // This cannot "iter != stop", results in seg fault, since the end gets updated, I think. while (iter != heap.m_allocated_chunks.end()) { @@ -292,6 +424,7 @@ namespace GC Profiler::record(ChunkSwept, chunk); heap.m_freed_chunks.push_back(chunk); iter = heap.m_allocated_chunks.erase(iter); + heap.m_size -= chunk->m_size; } } } @@ -311,6 +444,9 @@ namespace GC */ void Heap::free(Heap &heap) { + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(FreeStart); if (heap.m_freed_chunks.size() > FREE_THRESH) { bool profiler_enabled = heap.profiler_enabled(); diff --git a/src/GC/lib/profiler.cpp b/src/GC/lib/profiler.cpp index 29abad4..53bba13 100644 --- a/src/GC/lib/profiler.cpp +++ b/src/GC/lib/profiler.cpp @@ -15,6 +15,38 @@ namespace GC { + Profiler& Profiler::the() + { + static Profiler instance; + return instance; + } + + RecordOption Profiler::log_options() + { + Profiler &prof = Profiler::the(); + return prof.flags; + } + + void Profiler::set_log_options(RecordOption flags) + { + Profiler &prof = Profiler::the(); + prof.flags = flags; + } + + void Profiler::record_data(GCEvent *event) + { + Profiler &prof = Profiler::the(); + prof.m_events.push_back(event); + + if (prof.m_last_prof_event->m_type == event->get_type()) + prof.m_last_prof_event->m_n++; + else + { + prof.m_prof_events.push_back(prof.m_last_prof_event); + prof.m_last_prof_event = new ProfilerEvent(event->get_type()); + } + } + /** * Records an event independent of a chunk. * @@ -22,9 +54,12 @@ namespace GC */ void Profiler::record(GCEventType type) { - auto event = new GCEvent(type); - auto profiler = Profiler::the(); - profiler->m_events.push_back(event); + Profiler &prof = Profiler::the(); + if (prof.flags & type) + Profiler::record_data(new GCEvent(type)); + // auto event = new GCEvent(type); + // auto profiler = Profiler::the(); + // profiler.m_events.push_back(event); } /** @@ -37,9 +72,21 @@ namespace GC */ void Profiler::record(GCEventType type, size_t size) { - auto event = new GCEvent(type, size); - auto profiler = Profiler::the(); - profiler->m_events.push_back(event); + Profiler &prof = Profiler::the(); + if (prof.flags & type) + Profiler::record_data(new GCEvent(type, size)); + // auto event = new GCEvent(type, size); + // auto profiler = Profiler::the(); + // profiler.m_events.push_back(event); + } + + void Profiler::dump_trace() + { + Profiler &prof = Profiler::the(); + if (prof.flags & FunctionCalls) + dump_prof_trace(); + else + dump_chunk_trace(); } /** @@ -56,60 +103,89 @@ namespace GC // 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); + Profiler &prof = Profiler::the(); + if (prof.flags & type) + { + auto chunk_copy = new Chunk(chunk); + auto event = new GCEvent(type, chunk_copy); + Profiler::record_data(event); + } + // auto profiler = Profiler::the(); + // profiler.m_events.push_back(event); + } + + void Profiler::dump_prof_trace() + { + Profiler &prof = Profiler::the(); + prof.m_prof_events.push_back(prof.m_last_prof_event); + auto start = prof.m_prof_events.begin(); + auto end = prof.m_prof_events.end(); + + char buffer[22]; + std::ofstream fstr = prof.create_file_stream(); + + while (start != end) + { + auto event = *start++; + + fstr << "\n--------------------------------\n" + << Profiler::type_to_string(event->m_type) + << "\nTimes:\t" << event->m_n; + } + fstr << "\n--------------------------------"; } /** * Prints the history of the recorded events * to a log file in the /tests/logs folder. */ - void Profiler::dump_trace() + void Profiler::dump_chunk_trace() { - auto profiler = Profiler::the(); - auto start = profiler->m_events.begin(); - auto end = profiler->m_events.end(); + Profiler &prof = Profiler::the(); + auto start = prof.m_events.begin(); + auto end = prof.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++; + auto e_type = event->get_type(); - 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"; + prof.print_chunk_event(event, buffer); } - fstr << "--------------------------------" << std::endl; + } + + void Profiler::print_chunk_event(GCEvent *event, char buffer[22]) + { + Profiler &prof = Profiler::the(); + // File output stream + std::ofstream fstr = prof.create_file_stream(); + std::time_t tt = event->get_time_stamp(); + std::tm *btm = std::localtime(&tt); + std::strftime(buffer, 22, "%a %T", btm); + + fstr << "--------------------------------\n" + << buffer + << "\nEvent:\t" << Profiler::type_to_string(event->get_type()); + // event->type_to_string(); + + + + const Chunk *chunk = event->get_chunk(); + + if (event->get_type() == AllocStart) + { + fstr << "\nSize: " << event->get_size(); + } + else if (chunk) + { + fstr << "\nChunk: " << chunk->m_start + << "\n Size: " << chunk->m_size + << "\n Mark: " << chunk->m_marked; + } + fstr << "\n"; } /** @@ -122,8 +198,6 @@ namespace GC { Profiler::record(ProfilerDispose); Profiler::dump_trace(); - auto profiler = Profiler::the(); - delete profiler; } /** @@ -189,4 +263,24 @@ namespace GC #endif return folder + "/logs"; } + + const char *Profiler::type_to_string(GCEventType type) + { + switch (type) + { + case HeapInit: return "HeapInit"; + case AllocStart: return "AllocStart"; + case CollectStart: return "CollectStart"; + case MarkStart: return "MarkStart"; + case ChunkMarked: return "ChunkMarked"; + case ChunkSwept: return "ChunkSwept"; + case ChunkFreed: return "ChunkFreed"; + case NewChunk: return "NewChunk"; + case ReusedChunk: return "ReusedChunk"; + case ProfilerDispose: return "ProfilerDispose"; + case SweepStart: return "SweepStart"; + case FreeStart: return "FreeStart"; + default: return "[Unknown]"; + } + } } \ No newline at end of file diff --git a/src/GC/tests/advance.cpp b/src/GC/tests/advance.cpp index 92ce506..89dca71 100644 --- a/src/GC/tests/advance.cpp +++ b/src/GC/tests/advance.cpp @@ -5,40 +5,79 @@ #include #include -int main() { - using namespace std; - using TimeStamp = std::chrono::_V2::system_clock::time_point; +// void time_test() +// { +// 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++); - } +// std::list l; +// char c = 'a'; +// for (int i = 1; i <= 5; i++) { +// l.push_back(c++); +// } - auto iter = l.begin(); - auto stop = l.end(); +// auto iter = l.begin(); +// auto stop = l.end(); - while (iter != stop) { - cout << *iter << " "; +// while (iter != stop) { +// std::cout << *iter << " "; +// iter++; +// } +// std::cout << std::endl; +// iter = l.begin(); +// while (*iter != *stop) { +// std::cout << *iter << " "; +// iter++; +// } +// std::cout << std::endl; + +// std::cout << "rebased" << std::endl; +// std::cout << "iter: " << *iter << "\nstop: " << *stop << std::endl; + +// TimeStamp ts = std::chrono::system_clock::now(); +// std::time_t tt = std::chrono::system_clock::to_time_t(ts); +// std::string tstr = std::ctime(&tt); +// tstr.resize(tstr.size()-1); +// std::cout << tstr << std::endl; +// } + +void iter_test() +{ + std::list list; + list.push_back(1); + list.push_back(2); + list.push_back(4); + list.push_back(5); + + auto iter = list.begin(); + + while (iter != list.end()) + { + if (*iter == 4) + { + iter = list.erase(iter); + std::cout << *iter << "\n"; + list.insert(iter, 3); + // list.insert(iter, 3); + // std::cout << "n: " << *(++iter) << "\n"; + // iter = list.erase(++iter); + } iter++; } - cout << endl; - iter = l.begin(); - while (*iter != *stop) { - cout << *iter << " "; - iter++; + + for (int i : list) + { + std::cout << i << " "; } - cout << endl; + std::cout << std::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; + +int main() { + std::cout << "hello" << std::endl; + + iter_test(); return 0; } \ No newline at end of file diff --git a/src/GC/tests/alloc_free_list.cpp b/src/GC/tests/alloc_free_list.cpp new file mode 100644 index 0000000..a0d1a27 --- /dev/null +++ b/src/GC/tests/alloc_free_list.cpp @@ -0,0 +1,250 @@ +#include +#include + +#include "heap.hpp" + +using GC::Chunk; + +void alloc_test(); +void add_to_free_list(Chunk *chunk); +void merge_free_list(Chunk *chunk, bool do_merge); +void do_merge_list(); +void print_free_list(); + +std::list m_free_list; + +int main() +{ + alloc_test(); + + // std::list test; + + // test.push_back(1); + // test.push_back(2); + // test.push_back(3); + // test.push_back(4); + // test.push_back(5); + + // auto iter = test.begin(); + + // std::cout << "First? " << *(iter++) << "\n"; + // std::cout << "Second? " << *(iter--) << "\n"; + // std::cout << "First? " << *iter << std::endl; + + // auto i = test.begin(); + // while (i != test.end()) + // { + // std::cout << *i << " "; + // ++i; + // } + + // if (i == test.end()) + // std::cout << "great success!"; + + // std::cout << std::endl; + + return 0; +} + +void alloc_test() +{ + auto tmp = static_cast(__builtin_frame_address(0)); + + auto c1 = new Chunk((size_t)(8), tmp); + auto c2 = new Chunk((size_t)(4), c1->m_start + (size_t)(8)); + auto c3 = new Chunk((size_t)(16), c2->m_start + (size_t)(4)); + auto c4 = new Chunk((size_t)(4), c3->m_start + (size_t)(16)); + auto c5 = new Chunk((size_t)(32), c4->m_start + (size_t)(4)); + + // std::cout << "test: " << (uintptr_t *)(tmp + (size_t)(2)) << std::endl; + + std::cout << "tmp: " << tmp << "\ntmp: " << (tmp + (size_t)(28)) << std::endl; + + // add_to_free_list(c1); + // add_to_free_list(c2); + // add_to_free_list(c3); + // add_to_free_list(c4); + // add_to_free_list(c5); + + merge_free_list(c1, false); + merge_free_list(c2, false); + merge_free_list(c3, false); + merge_free_list(c4, false); + merge_free_list(c5, false); + + std::cout << "----- BEFORE MERGE ----------------------"; + // print_free_list(); + + do_merge_list(); + + std::cout << "----- AFTER MERGE -----------------------"; + // print_free_list(); +} + +void add_to_free_list(Chunk *chunk) +{ + Chunk *curr; + auto iter = m_free_list.begin(); + uintptr_t *prev_start = nullptr; + uintptr_t *prev_end = nullptr; + + if (m_free_list.size() == 0) + { + m_free_list.push_back(chunk); + return; + } + + while (iter != m_free_list.end()) + { + curr = *iter; + + // If the curr chunk is aligned before param + if (curr->m_start + curr->m_size == chunk->m_start) + { + Chunk *merged = new Chunk( + curr->m_size + chunk->m_size, + curr->m_start); + iter = m_free_list.erase(iter); + m_free_list.insert(iter, merged); + return; + } + + // If the curr chunk is aligned after param + if (chunk->m_start + chunk->m_size == curr->m_start) + { + Chunk *merged = new Chunk( + curr->m_size + chunk->m_size, + chunk->m_start); + iter = m_free_list.erase(iter); + m_free_list.insert(iter, merged); + return; + } + + // If the first chunk starts after param + if (prev_start == nullptr && curr->m_start > chunk->m_start) + { + m_free_list.insert(iter, chunk); + return; + } + + if (prev_end < chunk->m_start && (chunk->m_start + chunk->m_size) < curr->m_start) + { + m_free_list.insert(iter, chunk); + return; + } + + prev_start = curr->m_start; + prev_end = prev_start + curr->m_size; + iter++; + } + + // This is only reachable if the chunk is at the end + m_free_list.push_back(chunk); +} + +void merge_free_list(Chunk *chunk, bool do_merge) +{ + auto i = m_free_list.begin(); + uintptr_t *prev_start = nullptr, *prev_end; + bool chunk_inserted = false; + + while (i != m_free_list.end()) + { + + // if chunk is left-aligned + if ((*i)->m_start + (*i)->m_size == chunk->m_start) + { + m_free_list.insert(++i, chunk); + chunk_inserted = true; + break; + } + + // if chunk is right-aligned + if (chunk->m_start + chunk->m_size == (*i)->m_start) + { + m_free_list.insert(i, chunk); + chunk_inserted = true; + break; + } + + // is new first + if (prev_start == nullptr && (*i)->m_start > chunk->m_start) + { + m_free_list.insert(i, chunk); + chunk_inserted = true; + break; + } + + // if between chunks + if (prev_end < chunk->m_start && (chunk->m_start + chunk->m_size) < (*i)->m_start) + { + m_free_list.insert(i, chunk); + chunk_inserted = true; + break; + } + + prev_start = (*i)->m_start; + prev_end = (*i)->m_start + (*i)->m_size; + i++; + } + + // is new last + if (!chunk_inserted && i == m_free_list.end()) + m_free_list.push_back(chunk); + + if (do_merge) + do_merge_list(); +} + +void do_merge_list() +{ + std::cout << "DO MERGE" << std::endl; + auto i = m_free_list.begin(); + Chunk *prev = *(i++), *curr; + print_free_list(); + + while (i != m_free_list.end()) + { + curr = *i; + + if ((prev->m_start + prev->m_size) == curr->m_start) + { + Chunk *merged = new Chunk( + prev->m_size + curr->m_size, + prev->m_start + ); + + // replace current and previous with merged + i = m_free_list.erase(i); + i = m_free_list.erase(--i); + m_free_list.insert(i, merged); + + prev = merged; + } + else + { + prev = curr; + i++; + } + print_free_list(); + } + print_free_list(); +} + +void print_free_list() +{ + std::cout << "free-list count: " << m_free_list.size() << "\n"; + + auto iter = m_free_list.begin(); + size_t cnt = 1; + + while (iter != m_free_list.end()) + { + std::cout << "C" << cnt << ":\n\tstart: " << (*iter)->m_start + << "\n\tsize: " << (*iter)->m_size << "\n"; + iter++; + cnt++; + } + + std::cout << std::endl; +} \ No newline at end of file diff --git a/src/GC/tests/events.cpp b/src/GC/tests/events.cpp deleted file mode 100644 index e517092..0000000 --- a/src/GC/tests/events.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#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/linkedlist.cpp b/src/GC/tests/linkedlist.cpp new file mode 100644 index 0000000..850e4f6 --- /dev/null +++ b/src/GC/tests/linkedlist.cpp @@ -0,0 +1,87 @@ +#include +#include + +#include "heap.hpp" + +#define allocNode static_cast(GC::Heap::alloc(sizeof(Node))) + +using std::cout, std::endl; + +struct Node // sizeof(Node) = 16 +{ + int value; + Node *next {nullptr}; +}; + +Node *create_list(size_t length) +{ + Node *head = allocNode; + head->value = 0; + + Node *prev = head; + + for (size_t i = 1; i < length; i++) + { + Node *next = allocNode; + next->value = i; + prev->next = next; + prev = next; + } + + return head; +} + +void print_list(Node* head) +{ + cout << "\nPrinting list...\n"; + while (head != nullptr) + { + cout << head->value << " "; + head = head->next; + } + cout << endl; +} + +void clear_list(Node *head) +{ + while (head != nullptr) + { + Node *tmp = head->next; + head->next = nullptr; + head = tmp; + } +} + +void run_list_test1() +{ + Node *list_a = create_list(10); + print_list(list_a); +} + +void run_list_test2() +{ + Node *list_b = create_list(10); + print_list(list_b); +} + +void run_list_test3() +{ + Node *list_c = create_list(10); + print_list(list_c); +} + +int main() +{ + GC::Heap::init(); + GC::Heap &heap = GC::Heap::the(); + heap.set_profiler(true); + GC::Profiler::set_log_options(GC::FunctionCalls); + + run_list_test1(); + run_list_test2(); + run_list_test3(); + + GC::Heap::dispose(); + + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/linker.cpp b/src/GC/tests/linker.cpp index 36717c5..fb5b979 100644 --- a/src/GC/tests/linker.cpp +++ b/src/GC/tests/linker.cpp @@ -9,22 +9,8 @@ struct Obj { }; 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 From 1741281fd84b7223fc495ac5299d1cb30098e2dc Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Mon, 1 May 2023 15:43:51 +0200 Subject: [PATCH 08/17] profiler improvement --- src/GC/Makefile | 2 +- src/GC/include/heap.hpp | 9 +-- src/GC/include/profiler.hpp | 6 ++ src/GC/lib/heap.cpp | 144 +++++------------------------------- src/GC/lib/profiler.cpp | 29 +++++++- src/GC/tests/h_test.cpp | 1 + src/GC/tests/linkedlist.cpp | 23 ++---- 7 files changed, 61 insertions(+), 153 deletions(-) diff --git a/src/GC/Makefile b/src/GC/Makefile index 5eed227..1c2690a 100644 --- a/src/GC/Makefile +++ b/src/GC/Makefile @@ -18,7 +18,7 @@ file: heap: $(CC) $(WFLAGS) $(STDFLAGS) $(LIB_INCL) lib/heap.cpp -h_test: +h_test: static_lib rm -f tests/h_test.out # $(CC) $(WFLAGS) $(STDFLAGS) $(LIB_INCL) tests/h_test.cpp lib/heap.cpp lib/profiler.cpp lib/event.cpp -o tests/h_test.out $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -g -o tests/h_test.out tests/h_test.cpp lib/gcoll.a diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index f1a97b3..cebf89d 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -7,8 +7,8 @@ #include "chunk.hpp" #include "profiler.hpp" -#define HEAP_SIZE 160 //65536 -#define FREE_THRESH (uint) 0 +#define HEAP_SIZE 65536 +#define FREE_THRESH (uint) 100 #define HEAP_DEBUG namespace GC @@ -69,9 +69,6 @@ namespace GC // Temporary Chunk *try_recycle_chunks_new(size_t size); void free_overlap_new(Heap &heap); -#ifndef HEAP_DEBUG - void add_to_free_list(Chunk *chunk); -#endif public: /** * These are the only five functions which are exposed @@ -85,7 +82,6 @@ namespace GC static void init(); static void dispose(); static void *alloc(size_t size); - static void *alloc_free_list(size_t size); void set_profiler(bool mode); // Stop the compiler from generating copy-methods @@ -93,7 +89,6 @@ namespace GC Heap& operator=(Heap const&) = delete; #ifdef HEAP_DEBUG - void add_to_free_list(Chunk *chunk); void collect(CollectOption flags); // conditional collection void check_init(); // print dummy things void print_contents(); // print dummy things diff --git a/src/GC/include/profiler.hpp b/src/GC/include/profiler.hpp index bd14048..b8661ad 100644 --- a/src/GC/include/profiler.hpp +++ b/src/GC/include/profiler.hpp @@ -43,6 +43,11 @@ namespace GC { std::vector m_prof_events; RecordOption flags; + std::chrono::microseconds alloc_time {0}; + // size_t alloc_counts {0}; + std::chrono::microseconds collect_time {0}; + // size_t collect_counts {0}; + static void record_data(GCEvent *type); std::ofstream create_file_stream(); std::string get_log_folder(); @@ -60,6 +65,7 @@ namespace GC { static void record(GCEventType type); static void record(GCEventType type, size_t size); static void record(GCEventType type, Chunk *chunk); + static void record(GCEventType type, std::chrono::microseconds time); static void dispose(); }; } \ No newline at end of file diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index b3a2d6b..2060b93 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -2,9 +2,13 @@ #include #include #include +#include #include "heap.hpp" +#define time_now std::chrono::high_resolution_clock::now() +#define to_us std::chrono::duration_cast + using std::cout, std::endl, std::vector, std::hex, std::dec; namespace GC @@ -51,131 +55,6 @@ namespace GC Profiler::dispose(); } - void *Heap::alloc_free_list(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; - } - - // Try to find a fragmented section to recycle - Chunk *recycle = nullptr; - auto iter = heap.m_free_list.begin(); - while (iter != heap.m_free_list.end()) - { - if ((*iter)->m_size >= size) - { - recycle = *iter; - heap.m_free_list.erase(++iter); - break; - } - iter++; - } - - // If memory fragment was found - if (recycle != nullptr) - { - heap.m_size += size; - - // If fragment is larger than request, split it - if (recycle->m_size > size) - { - auto new_part = new Chunk(size, recycle->m_start); - auto complement = new Chunk( - recycle->m_size - size, - (recycle->m_start + size) - ); - delete recycle; - // TODO: add complement to free_list - heap.add_to_free_list(complement); - - heap.m_allocated_chunks.push_back(new_part); - - return (void *)(new_part->m_start); - } - - heap.m_allocated_chunks.push_back(recycle); - return (void *)(recycle->m_start); - } - - uintptr_t *max_size = (uintptr_t *)(heap.m_heap + HEAP_SIZE); - uintptr_t *new_size = (uintptr_t *)(heap.m_heap_top + size); - - if (new_size <= max_size) - { - auto new_chunk = new Chunk(size, (uintptr_t *)(heap.m_heap_top)); - heap.m_allocated_chunks.push_back(new_chunk); - - if (profiler_enabled) - Profiler::record(NewChunk, new_chunk); - - heap.m_heap_top += size; - heap.m_size += size; - } - - // Only throws if the allocation failed - throw std::runtime_error(std::string("Error: Heap out of memory")); - } - - void Heap::add_to_free_list(Chunk *chunk) - { - Chunk *curr; - auto iter = m_free_list.begin(); - uintptr_t *prev_start = nullptr; - uintptr_t *prev_end = nullptr; - - while (iter != m_free_list.end()) - { - curr = *iter; - - // If the curr chunk is aligned before param - if (curr->m_start + curr->m_size == chunk->m_start) - { - Chunk *merged = new Chunk( - curr->m_size + chunk->m_size, - curr->m_start - ); - iter = m_free_list.erase(iter); - m_free_list.insert(iter, merged); - return; - } - - // If the curr chunk is aligned after param - if (chunk->m_start + chunk->m_size == curr->m_start) - { - Chunk *merged = new Chunk( - curr->m_size + chunk->m_size, - chunk->m_start - ); - iter = m_free_list.erase(iter); - m_free_list.insert(iter, merged); - return; - } - - // If the first chunk starts after param - if (prev_start == nullptr && curr->m_start > chunk->m_start) - { - m_free_list.insert(iter, chunk); - return; - } - - prev_start = curr->m_start; - prev_end = prev_start + curr->m_size; - iter++; - } - - // This is only reachable if the chunk is at the end - m_free_list.push_back(chunk); - } - /** * Allocates a given amount of bytes on the heap. * @@ -187,6 +66,7 @@ namespace GC */ void *Heap::alloc(size_t size) { + auto a_start = time_now; // Singleton Heap &heap = Heap::the(); bool profiler_enabled = heap.profiler_enabled(); @@ -202,6 +82,8 @@ namespace GC if (heap.m_size + size > HEAP_SIZE) { + // auto a_ms = to_us(c_start - a_start); + // Profiler::record(AllocStart, a_ms); heap.collect(); // If memory is not enough after collect, crash with OOM error if (heap.m_size + size > HEAP_SIZE) @@ -218,6 +100,9 @@ namespace GC { if (profiler_enabled) Profiler::record(ReusedChunk, reused_chunk); + auto a_end = time_now; + auto a_ms = to_us(a_end - a_start); + Profiler::record(AllocStart, a_ms); return static_cast(reused_chunk->m_start); } @@ -231,6 +116,9 @@ namespace GC if (profiler_enabled) Profiler::record(NewChunk, new_chunk); + auto a_end = time_now; + auto a_ms = to_us(a_end - a_start); + Profiler::record(AllocStart, a_ms); return new_chunk->m_start; } @@ -306,6 +194,8 @@ namespace GC */ void Heap::collect() { + auto c_start = time_now; + Heap &heap = Heap::the(); if (heap.profiler_enabled()) @@ -325,6 +215,10 @@ namespace GC sweep(heap); free(heap); + + auto c_end = time_now; + + Profiler::record(CollectStart, to_us(c_end - c_start)); } /** diff --git a/src/GC/lib/profiler.cpp b/src/GC/lib/profiler.cpp index 53bba13..ae31f0d 100644 --- a/src/GC/lib/profiler.cpp +++ b/src/GC/lib/profiler.cpp @@ -114,12 +114,26 @@ namespace GC // profiler.m_events.push_back(event); } + void Profiler::record(GCEventType type, std::chrono::microseconds time) + { + Profiler &prof = Profiler::the(); + if (type == AllocStart) + { + prof.alloc_time += time; + } + else if (type == CollectStart) + { + prof.collect_time += time; + } + } + void Profiler::dump_prof_trace() { Profiler &prof = Profiler::the(); prof.m_prof_events.push_back(prof.m_last_prof_event); auto start = prof.m_prof_events.begin(); auto end = prof.m_prof_events.end(); + int allocs = 0, collects = 0; char buffer[22]; std::ofstream fstr = prof.create_file_stream(); @@ -128,11 +142,22 @@ namespace GC { auto event = *start++; + if (event->m_type == AllocStart) + allocs += event->m_n; + else if (event->m_type == CollectStart) + collects += event->m_n; + fstr << "\n--------------------------------\n" - << Profiler::type_to_string(event->m_type) - << "\nTimes:\t" << event->m_n; + << Profiler::type_to_string(event->m_type) << " " + << event->m_n << " times:"; } fstr << "\n--------------------------------"; + + fstr << "\n\nTime spent on allocations:\t" << prof.alloc_time.count() << " microseconds" + << "\nAllocation cycles:\t" << allocs + << "\nTime spent on collections:\t" << prof.collect_time.count() << " microseconds" + << "\nCollection cycles:\t" << collects + << "\n--------------------------------"; } /** diff --git a/src/GC/tests/h_test.cpp b/src/GC/tests/h_test.cpp index c871721..625e36a 100644 --- a/src/GC/tests/h_test.cpp +++ b/src/GC/tests/h_test.cpp @@ -80,6 +80,7 @@ int main() { GC::Heap::init(); GC::Heap &gc = GC::Heap::the(); gc.set_profiler(true); + GC::Profiler::set_log_options(GC::FunctionCalls); gc.check_init(); auto stack_start = reinterpret_cast(__builtin_frame_address(0)); diff --git a/src/GC/tests/linkedlist.cpp b/src/GC/tests/linkedlist.cpp index 850e4f6..61ab3c4 100644 --- a/src/GC/tests/linkedlist.cpp +++ b/src/GC/tests/linkedlist.cpp @@ -52,22 +52,10 @@ void clear_list(Node *head) } } -void run_list_test1() +void run_list_test() { - Node *list_a = create_list(10); - print_list(list_a); -} - -void run_list_test2() -{ - Node *list_b = create_list(10); - print_list(list_b); -} - -void run_list_test3() -{ - Node *list_c = create_list(10); - print_list(list_c); + Node *list = create_list(10); + print_list(list); } int main() @@ -77,9 +65,8 @@ int main() heap.set_profiler(true); GC::Profiler::set_log_options(GC::FunctionCalls); - run_list_test1(); - run_list_test2(); - run_list_test3(); + for (int i = 0; i < 10; i++) + run_list_test(); GC::Heap::dispose(); From a4413e55f3c51cdeb41596475433b6127e831f6f Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Mon, 1 May 2023 15:58:20 +0200 Subject: [PATCH 09/17] profiler now fixed in wrapper also --- src/GC/include/cheap.h | 6 +++++- src/GC/include/heap.hpp | 1 + src/GC/include/profiler.hpp | 2 +- src/GC/lib/cheap.cpp | 15 +++++++++++++++ src/GC/lib/heap.cpp | 5 +++++ src/GC/tests/wrapper.c | 27 ++++++++++++++------------- 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/GC/include/cheap.h b/src/GC/include/cheap.h index f4cd03c..7d803a8 100644 --- a/src/GC/include/cheap.h +++ b/src/GC/include/cheap.h @@ -7,7 +7,7 @@ extern "C" { #endif -// #define WRAPPER_DEBUG +#define WRAPPER_DEBUG #ifdef WRAPPER_DEBUG typedef struct cheap @@ -19,11 +19,15 @@ struct cheap; typedef struct cheap cheap_t; #endif +#define FuncCallsOnly 0x1E +#define ChunkOpsOnly 0x3E0 + cheap_t *cheap_the(); void cheap_init(); void cheap_dispose(); void *cheap_alloc(unsigned long size); void cheap_set_profiler(cheap_t *cheap, bool mode); +void cheap_profiler_log_options(cheap_t *cheap, unsigned long flag); #ifdef __cplusplus } diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index cebf89d..eb161c0 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -83,6 +83,7 @@ namespace GC static void dispose(); static void *alloc(size_t size); void set_profiler(bool mode); + void set_profiler_log_options(RecordOption flags); // Stop the compiler from generating copy-methods Heap(Heap const&) = delete; diff --git a/src/GC/include/profiler.hpp b/src/GC/include/profiler.hpp index b8661ad..f70ca3b 100644 --- a/src/GC/include/profiler.hpp +++ b/src/GC/include/profiler.hpp @@ -16,7 +16,7 @@ namespace GC { { FunctionCalls = (GC::AllocStart | GC::CollectStart | GC::MarkStart | GC::SweepStart), ChunkOps = (GC::ChunkMarked | GC::ChunkSwept | GC::ChunkFreed | GC::NewChunk | GC::ReusedChunk), - AllOps = ~0 + AllOps = 0xFFFFFF }; struct ProfilerEvent diff --git a/src/GC/lib/cheap.cpp b/src/GC/lib/cheap.cpp index f6c24a9..42179b6 100644 --- a/src/GC/lib/cheap.cpp +++ b/src/GC/lib/cheap.cpp @@ -45,4 +45,19 @@ void cheap_set_profiler(cheap_t *cheap, bool mode) GC::Heap *heap = static_cast(cheap->obj); heap->set_profiler(mode); +} + +void cheap_profiler_log_options(cheap_t *cheap, unsigned long flags) +{ + GC::Heap *heap = static_cast(cheap->obj); + + GC::RecordOption cast_flag; + if (flags == FuncCallsOnly) + cast_flag = GC::FunctionCalls; + else if (flags == ChunkOpsOnly) + cast_flag = GC::ChunkOps; + else + cast_flag = GC::AllOps; + + heap->set_profiler_log_options(cast_flag); } \ No newline at end of file diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 2060b93..fade27a 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -43,6 +43,11 @@ namespace GC heap.m_heap_top = heap.m_heap; } + void Heap::set_profiler_log_options(RecordOption flags) + { + Profiler::set_log_options(flags); + } + /** * Disposes the heap and the profiler at program exit * which also triggers a heap log file dumped if the diff --git a/src/GC/tests/wrapper.c b/src/GC/tests/wrapper.c index bcd4859..d6f042c 100644 --- a/src/GC/tests/wrapper.c +++ b/src/GC/tests/wrapper.c @@ -21,23 +21,23 @@ void test_init() /* Uncomment ONLY if run with DEBUG defined in cheap.h */ -// cheap_t *test_the() -// { -// printf("----- IN TEST_THE -----------------------------\n"); +cheap_t *test_the() +{ + printf("----- IN TEST_THE -----------------------------\n"); -// cheap_t *fst_heap = cheap_the(); + cheap_t *fst_heap = cheap_the(); -// printf("Heap 1:\t%p\n", fst_heap->obj); + printf("Heap 1:\t%p\n", fst_heap->obj); -// cheap_t *snd_heap = cheap_the(); + cheap_t *snd_heap = cheap_the(); -// printf("Heap 2:\t%p\n", snd_heap->obj); + printf("Heap 2:\t%p\n", snd_heap->obj); -// printf("----- EXIT TEST_THE ---------------------------\n"); + printf("----- EXIT TEST_THE ---------------------------\n"); -// free(snd_heap); -// return fst_heap; -// } + free(snd_heap); + return fst_heap; +} void test_profiler(cheap_t *heap) { @@ -45,6 +45,7 @@ void test_profiler(cheap_t *heap) cheap_set_profiler(heap, false); cheap_set_profiler(heap, true); + cheap_profiler_log_options(heap, FuncCallsOnly); printf("----- EXIT TEST_PROFILER ----------------------\n"); } @@ -79,8 +80,8 @@ int main() test_init(); /* Uncomment ONLY if run with DEBUG defined in cheap.h */ - // cheap_t *heap = test_the(); - // test_profiler(heap); + cheap_t *heap = test_the(); + test_profiler(heap); Object *o = test_alloc(); printf("Object size: %lu\n", sizeof(Object)); From 74e02826e69060ef2cbf4e6eb25d54e65ef61c41 Mon Sep 17 00:00:00 2001 From: valtermiari Date: Thu, 4 May 2023 13:51:53 +0200 Subject: [PATCH 10/17] Added Hash map marking --- src/GC/include/cheap.h | 2 +- src/GC/include/heap.hpp | 9 ++- src/GC/lib/heap.cpp | 54 ++++++++++++++++-- src/GC/lib/profiler.cpp | 2 +- src/GC/tests/h_test.cpp | 11 ++-- .../tests/h_test.out.dSYM/Contents/Info.plist | 20 +++++++ .../Contents/Resources/DWARF/h_test.out | Bin 0 -> 230420 bytes src/GC/tests/wrapper_test.c | 44 ++++++++------ 8 files changed, 109 insertions(+), 33 deletions(-) create mode 100644 src/GC/tests/h_test.out.dSYM/Contents/Info.plist create mode 100644 src/GC/tests/h_test.out.dSYM/Contents/Resources/DWARF/h_test.out diff --git a/src/GC/include/cheap.h b/src/GC/include/cheap.h index d2c649d..0f880e8 100644 --- a/src/GC/include/cheap.h +++ b/src/GC/include/cheap.h @@ -7,7 +7,7 @@ extern "C" { #endif -// #define DEBUG +#define DEBUG #ifdef DEBUG typedef struct cheap diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index 365a838..f877667 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -5,12 +5,13 @@ #include #include #include +#include #include "chunk.hpp" #include "profiler.hpp" -#define HEAP_SIZE 2097152 //65536 -#define FREE_THRESH (uint) 100000 +#define HEAP_SIZE 2097152 //256 //65536 //2097152 +#define FREE_THRESH (uint) 100000 //1000 #define DEBUG namespace GC @@ -47,12 +48,14 @@ namespace GC char *const m_heap; size_t m_size {0}; + size_t m_total_size {0}; // static Heap *m_instance {nullptr}; uintptr_t *m_stack_top {nullptr}; bool m_profiler_enable {false}; std::vector m_allocated_chunks; std::vector m_freed_chunks; + std::unordered_map m_chunk_table; static bool profiler_enabled(); // static Chunk *get_at(std::vector &list, size_t n); @@ -62,6 +65,8 @@ namespace GC void free(Heap &heap); void free_overlap(Heap &heap); void mark(uintptr_t *start, const uintptr_t *end, std::vector &worklist); + void mark_hash(uintptr_t *start, const uintptr_t *end); + void create_table(); void print_line(Chunk *chunk); void print_worklist(std::vector &list); void mark_step(uintptr_t start, uintptr_t end, std::vector &worklist); diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 579f421..d10ac90 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -7,10 +7,11 @@ #include #include #include +#include #include "heap.hpp" -using std::cout, std::endl, std::vector, std::hex, std::dec; +using std::cout, std::endl, std::vector, std::hex, std::dec, std::unordered_map; namespace GC { @@ -83,7 +84,11 @@ namespace GC { 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 (heap.m_size > HEAP_SIZE) + { + throw std::runtime_error(std::string("Error: Heap out of memory")); + } + //throw std::runtime_error(std::string("Error: Heap out of memory")); } // If a chunk was recycled, return the old chunk address @@ -100,6 +105,7 @@ namespace GC auto new_chunk = new Chunk(size, (uintptr_t *)(heap.m_heap + heap.m_size)); heap.m_size += size; + heap.m_total_size += size; heap.m_allocated_chunks.push_back(new_chunk); if (profiler_enabled) @@ -133,7 +139,8 @@ namespace GC //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); + i++; + //advance(iter, i); if (chunk->m_size > size) { // Split the chunk, use one part and add the remaining part to @@ -212,8 +219,12 @@ namespace GC uintptr_t *stack_top = heap.m_stack_top; - auto work_list = heap.m_allocated_chunks; - mark(stack_bottom, stack_top, work_list); + //auto work_list = heap.m_allocated_chunks; + //mark(stack_bottom, stack_top, work_list); + + // Testing mark_hash, previous woking implementation above + create_table(); + mark_hash(stack_bottom, stack_top); sweep(heap); @@ -281,6 +292,37 @@ namespace GC } } + void Heap::create_table() + { + Heap &heap = Heap::the(); + unordered_map chunk_table; + for (auto chunk : heap.m_allocated_chunks) { + auto pair = std::make_pair(reinterpret_cast(chunk->m_start), chunk); + heap.m_chunk_table.insert(pair); + } + } + + void Heap::mark_hash(uintptr_t *start, const uintptr_t* const end) + { + Heap &heap = Heap::the(); + for (; start <= end; start++) + { + auto search = heap.m_chunk_table.find(*start); + if (search != heap.m_chunk_table.end()) + { + Chunk *chunk = search->second; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + if (!chunk->m_marked) + { + chunk->m_marked = true; + mark_hash(chunk->m_start, c_end); + } + } + } + } + /** * Sweeps the heap, unmarks the marked chunks for the next cycle, @@ -343,6 +385,7 @@ namespace GC heap.m_freed_chunks.pop_back(); if (profiler_enabled) Profiler::record(ChunkFreed, chunk); + heap.m_size -= chunk->m_size; delete chunk; } } @@ -405,6 +448,7 @@ namespace GC { if (profiler_enabled) Profiler::record(ChunkFreed, chunk); + heap.m_size -= chunk->m_size; delete chunk; } else diff --git a/src/GC/lib/profiler.cpp b/src/GC/lib/profiler.cpp index 29abad4..d2e3246 100644 --- a/src/GC/lib/profiler.cpp +++ b/src/GC/lib/profiler.cpp @@ -11,7 +11,7 @@ #include "event.hpp" #include "profiler.hpp" -// #define MAC_OS +#define MAC_OS namespace GC { diff --git a/src/GC/tests/h_test.cpp b/src/GC/tests/h_test.cpp index c871721..3e0a00b 100644 --- a/src/GC/tests/h_test.cpp +++ b/src/GC/tests/h_test.cpp @@ -11,7 +11,7 @@ struct Node { }; Node *create_chain(int depth) { - cout << "entering create_chain"; + cout << "entering create_chain" << endl; std::vector nodes; if (depth > 0) { Node *last_node = static_cast(GC::Heap::alloc(sizeof(Node))); @@ -36,14 +36,14 @@ void create_array(size_t size) { } void detach_pointer(long **ptr) { - cout << "entering detach_pointer"; + cout << "entering detach_pointer" << endl; long *dummy_ptr = nullptr; *ptr = dummy_ptr; cout << "\nexiting detach_pointer" << endl; } Node *test_chain(int depth, bool detach) { - cout << "entering test_chain"; + cout << "entering test_chain" << endl; auto stack_start = reinterpret_cast(__builtin_frame_address(0)); Node *node_chain = create_chain(depth); @@ -85,9 +85,8 @@ int main() { 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); - + root1 = test_chain(100000, false); + //root2 = test_chain(58000, false); gc.collect(GC::COLLECT_ALL); auto end = std::chrono::high_resolution_clock::now(); diff --git a/src/GC/tests/h_test.out.dSYM/Contents/Info.plist b/src/GC/tests/h_test.out.dSYM/Contents/Info.plist new file mode 100644 index 0000000..3db2218 --- /dev/null +++ b/src/GC/tests/h_test.out.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.h_test.out + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/src/GC/tests/h_test.out.dSYM/Contents/Resources/DWARF/h_test.out b/src/GC/tests/h_test.out.dSYM/Contents/Resources/DWARF/h_test.out new file mode 100644 index 0000000000000000000000000000000000000000..0d4c74f39d3423cdae3f0355d38363a62a48c92a GIT binary patch literal 230420 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 TaDkM8#~47XF2R@$yz3tT=|f3L literal 0 HcmV?d00001 diff --git a/src/GC/tests/wrapper_test.c b/src/GC/tests/wrapper_test.c index 729cf69..2055b8c 100644 --- a/src/GC/tests/wrapper_test.c +++ b/src/GC/tests/wrapper_test.c @@ -12,17 +12,7 @@ typedef struct node { Node *HEAD = NULL; Node *CURRENT = NULL; -// Creates a linked list of length depth. Global head "HEAD" is updated. -void *create_linked_list(int depth) { - HEAD = (Node*)(cheap_alloc(sizeof(Node))); - HEAD->id = 0; - // Purposely omitting adding a child to "last_node", since its the last node - for (int i = 1; i < depth - 1; i++) { - insert_first(i); - } -} - -void *insert_first(int node_id) { +void insert_first(int node_id) { Node *new_head; new_head = (Node*)(cheap_alloc(sizeof(Node))); new_head->id = node_id; @@ -31,15 +21,33 @@ void *insert_first(int node_id) { HEAD = new_head; } -void test_linked_list(int list_length){ +// Creates a linked list of length depth. Global head "HEAD" is updated. +Node *create_linked_list(int depth) { + HEAD = (Node*)(cheap_alloc(sizeof(Node))); + HEAD->id = 0; + // Purposely omitting adding a child to "last_node", since its the last node + for (int i = 1; i < depth - 1; i++) { + insert_first(i); + } + return HEAD; +} + +void create_garbage(int amount) { + for (int i = 0; i < amount; i++) { + long *garbage = (long*)(cheap_alloc(sizeof(long))); + } +} + +int main () { cheap_init(); cheap_t *heap = cheap_the(); cheap_set_profiler(heap, true); - create_linked_list(list_length); - cheap_dispose(); - free(heap); -} -int main (int argc, char **argv) { - test_linked_list(30); + // Every node in this list should be marked + Node *head = create_linked_list(5); + // Everything create here should be swept + create_garbage(30); + + cheap_dispose(); + return 0; } \ No newline at end of file From a910d5449edca5673a6bdeb0a34db44120f34c76 Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Thu, 4 May 2023 18:18:25 +0200 Subject: [PATCH 11/17] rip gc Co-authored-by: ValterMiari --- src/GC/Makefile | 12 +++-- src/GC/include/heap.hpp | 16 ++++-- src/GC/include/profiler.hpp | 2 +- src/GC/lib/heap.cpp | 102 ++++++++++++++++++++++++++++++++++-- src/GC/tests/linkedlist.cpp | 54 ++++++++++++++++--- src/GC/tests/pointers.cpp | 39 ++++++++++++++ src/GC/tests/revrange.cpp | 43 +++++++++++++++ 7 files changed, 249 insertions(+), 19 deletions(-) create mode 100644 src/GC/tests/pointers.cpp create mode 100644 src/GC/tests/revrange.cpp diff --git a/src/GC/Makefile b/src/GC/Makefile index 1c2690a..4a852a0 100644 --- a/src/GC/Makefile +++ b/src/GC/Makefile @@ -66,9 +66,9 @@ 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 -c -o lib/event.o lib/event.cpp -fPIC - $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/profiler.o lib/profiler.cpp -fPIC - $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -O3 -c -o lib/heap.o lib/heap.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -c -o lib/event.o lib/event.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -c -o lib/profiler.o lib/profiler.cpp -fPIC + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -c -o lib/heap.o lib/heap.cpp -fPIC # create static library ar r lib/gcoll.a lib/event.o lib/profiler.o lib/heap.o @@ -82,6 +82,12 @@ alloc_free_list: static_lib linked_list_test: static_lib $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/linkedlist.out tests/linkedlist.cpp lib/gcoll.a +revrange: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/revrange.out tests/revrange.cpp lib/gcoll.a + +pointers: static_lib + $(CC) $(STDFLAGS) $(WFLAGS) $(LIB_INCL) -o tests/pointers.out tests/pointers.cpp lib/gcoll.a + wrapper: # remove old files rm -f lib/event.o lib/profiler.o lib/heap.o lib/coll.a tests/wrapper.out diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index eb161c0..2d116bb 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -7,9 +7,9 @@ #include "chunk.hpp" #include "profiler.hpp" -#define HEAP_SIZE 65536 -#define FREE_THRESH (uint) 100 -#define HEAP_DEBUG +#define HEAP_SIZE 320//65536 +#define FREE_THRESH (uint) 0 +// #define HEAP_DEBUG namespace GC { @@ -23,7 +23,14 @@ namespace GC MARK_SWEEP = 1 << 2, FREE = 1 << 3, COLLECT_ALL = 0b1111 // all flags above - }; + }; + + struct AddrRange + { + const uintptr_t *start, *end; + + AddrRange(uintptr_t *_start, uintptr_t *_end) : start(_start), end(_end) {} + }; /** * The heap class to represent the heap for the @@ -65,6 +72,7 @@ namespace GC void print_line(Chunk *chunk); void print_worklist(std::vector &list); void mark_step(uintptr_t start, uintptr_t end, std::vector &worklist); + void mark_range(std::vector &ranges, std::vector &worklist); // Temporary Chunk *try_recycle_chunks_new(size_t size); diff --git a/src/GC/include/profiler.hpp b/src/GC/include/profiler.hpp index f70ca3b..1c44ef6 100644 --- a/src/GC/include/profiler.hpp +++ b/src/GC/include/profiler.hpp @@ -14,7 +14,7 @@ namespace GC { enum RecordOption { - FunctionCalls = (GC::AllocStart | GC::CollectStart | GC::MarkStart | GC::SweepStart), + FunctionCalls = (GC::AllocStart | GC::CollectStart | GC::MarkStart | GC::SweepStart | GC::FreeStart), ChunkOps = (GC::ChunkMarked | GC::ChunkSwept | GC::ChunkFreed | GC::NewChunk | GC::ReusedChunk), AllOps = 0xFFFFFF }; diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index fade27a..ff1743d 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -207,7 +207,7 @@ namespace GC Profiler::record(CollectStart); // get current stack frame - auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + auto stack_bottom = reinterpret_cast(__builtin_frame_address(2)); if (heap.m_stack_top == nullptr) throw std::runtime_error(std::string("Error: Heap is not initialized, read the docs!")); @@ -244,11 +244,14 @@ namespace GC */ void Heap::mark(uintptr_t *start, const uintptr_t* const end, vector &worklist) { + // cout << "\nWorklist size: " << worklist.size() << "\n"; Heap &heap = Heap::the(); bool profiler_enabled = heap.m_profiler_enable; if (profiler_enabled) Profiler::record(MarkStart); + vector rangeWL; + // To find adresses thats in the worklist for (; start <= end; start++) { @@ -258,8 +261,8 @@ namespace GC { 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); + 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) @@ -271,8 +274,22 @@ namespace GC chunk->m_marked = true; it = worklist.erase(it); + Chunk *next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); + while (next != NULL) { + if (!next->m_marked) + { + next->m_marked = true; + auto c_start = reinterpret_cast(next->m_start); + auto c_size = reinterpret_cast(next->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); + } + } + // Recursively call mark, to see if the reachable chunk further points to another chunk - mark((uintptr_t *)c_start, (uintptr_t *)c_end, worklist); + // mark((uintptr_t *)c_start, (uintptr_t *)c_end, worklist); + // AddrRange *range = new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end); + rangeWL.push_back(new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end)); } else { @@ -285,8 +302,60 @@ namespace GC } } } + mark_range(rangeWL, worklist); + rangeWL.clear(); } + void Heap::mark_range(vector &ranges, vector &worklist) + { + Heap &heap = Heap::the(); + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(MarkStart); + + auto iter = ranges.begin(); + auto stop = ranges.end(); + + while (iter != stop) + { + auto range = *iter++; + uintptr_t *start = (uintptr_t *)range->start; + const uintptr_t *end = range->end; + if (start == nullptr) + cout << "\nstart is null\n"; + for (; start <= end; start++) + { + auto wliter = worklist.begin(); + auto wlstop = worklist.end(); + while (wliter != wlstop) + { + Chunk *chunk = *wliter; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + + if (c_start <= *start && *start < c_end) + { + if (!chunk->m_marked) + { + chunk->m_marked = true; + wliter = worklist.erase(wliter); + ranges.push_back(new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end)); + stop = ranges.end(); + } + else + { + wliter++; + } + } + else + { + wliter++; + } + } + } + } + } /** * Sweeps the heap, unmarks the marked chunks for the next cycle, @@ -304,6 +373,7 @@ namespace GC if (profiler_enabled) Profiler::record(SweepStart); auto iter = heap.m_allocated_chunks.begin(); + std::cout << "Chunks alloced: " << heap.m_allocated_chunks.size() << std::endl; // This cannot "iter != stop", results in seg fault, since the end gets updated, I think. while (iter != heap.m_allocated_chunks.end()) { @@ -326,6 +396,7 @@ namespace GC heap.m_size -= chunk->m_size; } } + std::cout << "Chunks left: " << heap.m_allocated_chunks.size() << std::endl; } /** @@ -390,7 +461,7 @@ namespace GC auto prev = heap.m_freed_chunks[i++]; prev->m_marked = true; filtered.push_back(prev); - cout << filtered.back()->m_start << endl; + // cout << filtered.back()->m_start << endl; for (; i < heap.m_freed_chunks.size(); i++) { prev = filtered.back(); @@ -432,6 +503,27 @@ namespace GC heap.m_profiler_enable = mode; } + Chunk* find_pointer(uintptr_t *start, const uintptr_t* const end, vector &worklist) { + for (; start <= end; start++) { + auto it = worklist.begin(); + auto stop = worklist.end(); + while (it != stop) + { + Chunk *chunk = *it; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + + // Check if the stack pointer points to something within the chunk + if (c_start <= *start && *start < c_end) + { + return chunk; + } + return NULL; + } + } + } + #ifdef HEAP_DEBUG /** * Prints the result of Heap::init() and a dummy value diff --git a/src/GC/tests/linkedlist.cpp b/src/GC/tests/linkedlist.cpp index 61ab3c4..ccb07d9 100644 --- a/src/GC/tests/linkedlist.cpp +++ b/src/GC/tests/linkedlist.cpp @@ -19,10 +19,11 @@ Node *create_list(size_t length) head->value = 0; Node *prev = head; + Node *next; for (size_t i = 1; i < length; i++) { - Node *next = allocNode; + next = allocNode; next->value = i; prev->next = next; prev = next; @@ -52,10 +53,51 @@ void clear_list(Node *head) } } -void run_list_test() +#define LIST_SIZE 10 + +void list_test1() { - Node *list = create_list(10); - print_list(list); + Node *list_1 = create_list(LIST_SIZE); + // print_list(list_1); +} + +void list_test2() +{ + Node *list_2 = create_list(LIST_SIZE); + // print_list(list_2); +} + +void list_test3() +{ + Node *list_3 = create_list(LIST_SIZE); + // print_list(list_3); +} + +void list_test4() +{ + Node *list_4 = create_list(LIST_SIZE); + // print_list(list_4); +} + +void list_test5() +{ + Node *list_5 = create_list(LIST_SIZE); + // print_list(list_5); +} + +void list_test6() +{ + Node *list_6 = create_list(LIST_SIZE); + // print_list(list_6); +} + +void make_test() { + list_test1(); + list_test2(); + list_test3(); + list_test4(); + list_test5(); + list_test6(); } int main() @@ -64,9 +106,9 @@ int main() GC::Heap &heap = GC::Heap::the(); heap.set_profiler(true); GC::Profiler::set_log_options(GC::FunctionCalls); + // GC::Profiler::set_log_options(GC::AllOps); - for (int i = 0; i < 10; i++) - run_list_test(); + make_test(); GC::Heap::dispose(); diff --git a/src/GC/tests/pointers.cpp b/src/GC/tests/pointers.cpp new file mode 100644 index 0000000..265ca30 --- /dev/null +++ b/src/GC/tests/pointers.cpp @@ -0,0 +1,39 @@ +#include +#include +#include + +#include "heap.hpp" + +using std::cout, std::endl, std::hex; + +struct Node { + int value; + Node *next {nullptr}; +}; + +void test(Node *n) { + size_t n_size = 16; + + auto c_start = reinterpret_cast(n); + auto c_size = reinterpret_cast(n_size); + auto c_end = reinterpret_cast(c_start + c_size); + + cout << "Node *n:\t" << n << "\n"; + cout << "n_size: \t0x" << std::hex << n_size << "\n"; + cout << "c_start:\t0x" << std::hex << c_start << "\n"; + cout << "c_size: \t0x" << std::hex << c_size << "\n"; + cout << "c_end: \t0x" << std::hex << c_end << endl; +} + +int main() { + GC::Heap::init(); + GC::Heap &heap = GC::Heap::the(); + heap.set_profiler(true); + heap.set_profiler_log_options(GC::FunctionCalls); + + Node *n = static_cast(GC::Heap::alloc(sizeof(Node))); + test(n); + + GC::Heap::dispose(); + return 0; +} \ No newline at end of file diff --git a/src/GC/tests/revrange.cpp b/src/GC/tests/revrange.cpp new file mode 100644 index 0000000..6a11c57 --- /dev/null +++ b/src/GC/tests/revrange.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "heap.hpp" + +#define allocNode static_cast(GC::Heap::alloc(sizeof(Node))) + +using std::cout, std::endl; + +struct Node { + int value; + Node *next {nullptr}; +}; + +void revRange(int n) { + Node *next = nullptr; + Node *prev = allocNode; + while (n > 0) { + next = allocNode; + prev->next = next; + prev->value = n--; + prev = next; + } +} + +void make_test() { + int n = 10; + while (n > 0) + revRange(1000); +} + +int main() { + GC::Heap::init(); + GC::Heap &heap = GC::Heap::the(); + heap.set_profiler(true); + GC::Profiler::set_log_options(GC::FunctionCalls); + + make_test(); + + GC::Heap::dispose(); + return 0; +} \ No newline at end of file From 208ff861df9e5785ba5ac86353dd651d084c390b Mon Sep 17 00:00:00 2001 From: valtermiari Date: Fri, 5 May 2023 13:16:45 +0200 Subject: [PATCH 12/17] Fixed bug in size handling and mark hash --- src/GC/include/heap.hpp | 1 + src/GC/lib/heap.cpp | 97 ++++++++++++++++++++++++++++++++----- src/GC/tests/linkedlist.cpp | 2 +- 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index b6fe104..c9eebf8 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -71,6 +71,7 @@ namespace GC void free_overlap(Heap &heap); void mark(uintptr_t *start, const uintptr_t *end, std::vector &worklist); void mark_hash(uintptr_t *start, const uintptr_t *end); + Chunk* find_pointer_hash(uintptr_t *start, const uintptr_t *end); void create_table(); void print_line(Chunk *chunk); void print_worklist(std::vector &list); diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index f999ef8..1afb622 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -41,7 +41,8 @@ namespace GC // 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)); - heap.m_heap_top = heap.m_heap; + // TODO: handle this below + //heap.m_heap_top = heap.m_heap; } void Heap::set_profiler_log_options(RecordOption flags) @@ -98,12 +99,11 @@ namespace GC } //throw std::runtime_error(std::string("Error: Heap out of memory")); } - if (heap.m_size + size > HEAP_SIZE) - { - if (profiler_enabled) - Profiler::dispose(); - throw std::runtime_error(std::string("Error: Heap out of memory")); - } + if (heap.m_size + size > HEAP_SIZE) + { + if (profiler_enabled) + Profiler::dispose(); + throw std::runtime_error(std::string("Error: Heap out of memory")); } // If a chunk was recycled, return the old chunk address @@ -123,7 +123,8 @@ namespace GC auto new_chunk = new Chunk(size, (uintptr_t *)(heap.m_heap + heap.m_size)); heap.m_size += size; - heap.m_total_size += size; + // TODO: handle this below + //heap.m_total_size += size; heap.m_allocated_chunks.push_back(new_chunk); if (profiler_enabled) @@ -287,7 +288,7 @@ namespace GC chunk->m_marked = true; it = worklist.erase(it); - Chunk *next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); +/* Chunk *next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); while (next != NULL) { if (!next->m_marked) { @@ -297,7 +298,7 @@ namespace GC auto c_end = reinterpret_cast(c_start + c_size); next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); } - } + } */ // Recursively call mark, to see if the reachable chunk further points to another chunk // mark((uintptr_t *)c_start, (uintptr_t *)c_end, worklist); @@ -370,6 +371,62 @@ namespace GC } } + void Heap::create_table() + { + Heap &heap = Heap::the(); + unordered_map chunk_table; + for (auto chunk : heap.m_allocated_chunks) { + auto pair = std::make_pair(reinterpret_cast(chunk->m_start), chunk); + heap.m_chunk_table.insert(pair); + } + } + + void Heap::mark_hash(uintptr_t *start, const uintptr_t* const end) + { + Heap &heap = Heap::the(); + + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(MarkStart); + + for (; start <= end; start++) + { + auto search = heap.m_chunk_table.find(*start); + if (search != heap.m_chunk_table.end()) + { + Chunk *chunk = search->second; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + if (!chunk->m_marked) + { + chunk->m_marked = true; + + if (profiler_enabled) + Profiler::record(ChunkMarked, chunk); + + //mark_hash(chunk->m_start, c_end); + Chunk *next = find_pointer_hash((uintptr_t *) c_start, (uintptr_t *) c_end); + while (next != NULL) + { + if (!next->m_marked) + { + next->m_marked = true; + + if (profiler_enabled) + Profiler::record(ChunkMarked, chunk); + + auto c_start = reinterpret_cast(next->m_start); + auto c_size = reinterpret_cast(next->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + next = find_pointer_hash((uintptr_t *) c_start, (uintptr_t *) c_end); + } + } + } + } + } + } + /** * Sweeps the heap, unmarks the marked chunks for the next cycle, * adds the unmarked nodes to the list of freed chunks; to be freed. @@ -406,7 +463,9 @@ namespace GC Profiler::record(ChunkSwept, chunk); heap.m_freed_chunks.push_back(chunk); iter = heap.m_allocated_chunks.erase(iter); - heap.m_size -= chunk->m_size; + //heap.m_size -= chunk->m_size; + cout << "Decremented total heap size with: " << chunk->m_size << endl; + cout << "Total size is: " << heap.m_size << endl; } } std::cout << "Chunks left: " << heap.m_allocated_chunks.size() << std::endl; @@ -440,6 +499,8 @@ namespace GC if (profiler_enabled) Profiler::record(ChunkFreed, chunk); heap.m_size -= chunk->m_size; + cout << "Decremented total heap size with: " << chunk->m_size << endl; + cout << "Total size is: " << heap.m_size << endl; delete chunk; } } @@ -503,6 +564,8 @@ namespace GC if (profiler_enabled) Profiler::record(ChunkFreed, chunk); heap.m_size -= chunk->m_size; + cout << "Decremented total heap size with: " << chunk->m_size << endl; + cout << "Total size is: " << heap.m_size << endl; delete chunk; } else @@ -539,6 +602,18 @@ namespace GC } } + // Checks if a given chunk points to another chunk and returns it + Chunk* Heap::find_pointer_hash(uintptr_t *start, const uintptr_t* const end) { + Heap &heap = Heap::the(); + for (; start <= end; start++) { + auto search = heap.m_chunk_table.find(*start); + if (search != heap.m_chunk_table.end()) { + return search->second; + } + return NULL; + } + } + #ifdef HEAP_DEBUG /** * Prints the result of Heap::init() and a dummy value diff --git a/src/GC/tests/linkedlist.cpp b/src/GC/tests/linkedlist.cpp index ccb07d9..b562a77 100644 --- a/src/GC/tests/linkedlist.cpp +++ b/src/GC/tests/linkedlist.cpp @@ -106,7 +106,7 @@ int main() GC::Heap &heap = GC::Heap::the(); heap.set_profiler(true); GC::Profiler::set_log_options(GC::FunctionCalls); - // GC::Profiler::set_log_options(GC::AllOps); + //GC::Profiler::set_log_options(GC::AllOps); make_test(); From c09da8a8cdd36970dd398f2eed4abaf927a172a7 Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Fri, 5 May 2023 17:57:26 +0200 Subject: [PATCH 13/17] now it works ok??? --- src/GC/include/heap.hpp | 12 +- src/GC/lib/heap.cpp | 157 ++++--- src/GC/lib/heap_old.cpp | 853 ++++++++++++++++++++++++++++++++++++ src/GC/lib/profiler.cpp | 2 +- src/GC/tests/linkedlist.cpp | 8 +- 5 files changed, 956 insertions(+), 76 deletions(-) create mode 100644 src/GC/lib/heap_old.cpp diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index c9eebf8..c56c7fa 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -4,12 +4,13 @@ #include #include #include +#include #include "chunk.hpp" #include "profiler.hpp" -#define HEAP_SIZE 320//65536 -#define FREE_THRESH (uint) 0 +#define HEAP_SIZE 16000//65536 +#define FREE_THRESH (uint) 100 // #define HEAP_DEBUG namespace GC @@ -64,12 +65,11 @@ namespace GC static bool profiler_enabled(); // static Chunk *get_at(std::vector &list, size_t n); - void collect(); + void collect(uintptr_t *stack_bottom); void sweep(Heap &heap); Chunk *try_recycle_chunks(size_t size); void free(Heap &heap); void free_overlap(Heap &heap); - void mark(uintptr_t *start, const uintptr_t *end, std::vector &worklist); void mark_hash(uintptr_t *start, const uintptr_t *end); Chunk* find_pointer_hash(uintptr_t *start, const uintptr_t *end); void create_table(); @@ -78,6 +78,10 @@ namespace GC void mark_step(uintptr_t start, uintptr_t end, std::vector &worklist); void mark_range(std::vector &ranges, std::vector &worklist); + void find_roots(uintptr_t *stack_bottom, std::vector &roots); + void mark(std::vector &roots); + void find_chunks(uintptr_t *stack_addr, std::queue> &chunk_spaces); + // Temporary Chunk *try_recycle_chunks_new(size_t size); void free_overlap_new(Heap &heap); diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 1afb622..7db79c4 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "heap.hpp" @@ -91,7 +93,8 @@ namespace GC { // auto a_ms = to_us(c_start - a_start); // Profiler::record(AllocStart, a_ms); - heap.collect(); + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + heap.collect(stack_bottom); // If memory is not enough after collect, crash with OOM error if (heap.m_size > HEAP_SIZE) { @@ -207,7 +210,7 @@ namespace GC * function is private so that the user cannot trigger * a collection unneccessarily. */ - void Heap::collect() + void Heap::collect(uintptr_t *stack_bottom) { auto c_start = time_now; @@ -217,22 +220,31 @@ namespace GC Profiler::record(CollectStart); // get current stack frame - auto stack_bottom = reinterpret_cast(__builtin_frame_address(2)); + // auto stack_bottom = reinterpret_cast(__builtin_frame_address(2)); 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; + // uintptr_t *stack_top = heap.m_stack_top; //auto work_list = heap.m_allocated_chunks; //mark(stack_bottom, stack_top, work_list); // Testing mark_hash, previous woking implementation above - create_table(); - mark_hash(stack_bottom, stack_top); + // create_table(); + // mark_hash(stack_bottom, stack_top); + vector roots; + cout << "\nb4 find_roots\n"; + find_roots(stack_bottom, roots); + + cout << "b4 mark\n"; + mark(roots); + + cout << "b4 sweep\n"; sweep(heap); + cout << "b4 free\n"; free(heap); auto c_end = time_now; @@ -240,6 +252,23 @@ namespace GC Profiler::record(CollectStart, to_us(c_end - c_start)); } + void Heap::find_roots(uintptr_t *stack_bottom, vector &roots) + { + Heap &heap = Heap::the(); + auto stack_top = heap.m_stack_top; + auto heap_bottom = reinterpret_cast(heap.m_heap); + auto heap_top = reinterpret_cast(heap.m_heap + HEAP_SIZE); + + while (stack_bottom < stack_top) + { + if (heap_bottom < *stack_bottom && *stack_bottom < heap_top) + { + roots.push_back(stack_bottom); + } + stack_bottom++; + } + } + /** * 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). @@ -256,68 +285,58 @@ namespace GC * @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) + void Heap::mark(vector &roots) { - // cout << "\nWorklist size: " << worklist.size() << "\n"; - Heap &heap = Heap::the(); - bool profiler_enabled = heap.m_profiler_enable; - if (profiler_enabled) + bool prof_enabled = m_profiler_enable; + if (prof_enabled) Profiler::record(MarkStart); - vector rangeWL; + auto iter = roots.begin(), end = roots.end(); + // vector> chunk_spaces; + std::queue> chunk_spaces; + // std::set visited_addresses; - // To find adresses thats in the worklist - for (; start <= end; start++) + cout << "b4 find_chunks of roots\n"; + while (iter != end) { - auto it = worklist.begin(); - auto stop = worklist.end(); - while (it != stop) + find_chunks(*iter++, chunk_spaces); + } + + cout << "b4 find_chunks of chunks\n"; + while (!chunk_spaces.empty()) + { + auto range = chunk_spaces.front(); + chunk_spaces.pop(); + + auto stack_addr = reinterpret_cast(range.first); + + find_chunks(stack_addr, chunk_spaces); + } + } + + void Heap::find_chunks(uintptr_t *stack_addr, std::queue> &chunk_spaces) + { + auto iter = m_allocated_chunks.begin(); + auto end = m_allocated_chunks.end(); + + while (iter != end) + { + auto chunk = *iter++; + + if (chunk->m_marked) + continue; + + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + + if (c_start < *stack_addr && *stack_addr < c_end) { - Chunk *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); - -/* Chunk *next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); - while (next != NULL) { - if (!next->m_marked) - { - next->m_marked = true; - auto c_start = reinterpret_cast(next->m_start); - auto c_size = reinterpret_cast(next->m_size); - auto c_end = reinterpret_cast(c_start + c_size); - next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); - } - } */ - - // Recursively call mark, to see if the reachable chunk further points to another chunk - // mark((uintptr_t *)c_start, (uintptr_t *)c_end, worklist); - // AddrRange *range = new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end); - rangeWL.push_back(new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end)); - } - else - { - ++it; - } - } - else - { - ++it; - } + chunk->m_marked = true; + // chunk_spaces.push_back(std::make_pair(c_start, c_end)); + chunk_spaces.push(std::make_pair(c_start, c_end)); } } - mark_range(rangeWL, worklist); - rangeWL.clear(); } void Heap::mark_range(vector &ranges, vector &worklist) @@ -371,7 +390,7 @@ namespace GC } } - void Heap::create_table() + void Heap::create_table() { Heap &heap = Heap::the(); unordered_map chunk_table; @@ -403,7 +422,7 @@ namespace GC chunk->m_marked = true; if (profiler_enabled) - Profiler::record(ChunkMarked, chunk); + Profiler::record(ChunkMarked, chunk); //mark_hash(chunk->m_start, c_end); Chunk *next = find_pointer_hash((uintptr_t *) c_start, (uintptr_t *) c_end); @@ -463,12 +482,12 @@ namespace GC Profiler::record(ChunkSwept, chunk); heap.m_freed_chunks.push_back(chunk); iter = heap.m_allocated_chunks.erase(iter); - //heap.m_size -= chunk->m_size; - cout << "Decremented total heap size with: " << chunk->m_size << endl; - cout << "Total size is: " << heap.m_size << endl; + heap.m_size -= chunk->m_size; + // cout << "Decremented total heap size with: " << chunk->m_size << endl; + // cout << "Total size is: " << heap.m_size << endl; } } - std::cout << "Chunks left: " << heap.m_allocated_chunks.size() << std::endl; + // std::cout << "Chunks left: " << heap.m_allocated_chunks.size() << std::endl; } /** @@ -498,9 +517,9 @@ namespace GC heap.m_freed_chunks.pop_back(); if (profiler_enabled) Profiler::record(ChunkFreed, chunk); - heap.m_size -= chunk->m_size; - cout << "Decremented total heap size with: " << chunk->m_size << endl; - cout << "Total size is: " << heap.m_size << endl; + // heap.m_size -= chunk->m_size; + // cout << "Decremented total heap size with: " << chunk->m_size << endl; + // cout << "Total size is: " << heap.m_size << endl; delete chunk; } } diff --git a/src/GC/lib/heap_old.cpp b/src/GC/lib/heap_old.cpp new file mode 100644 index 0000000..9146bf8 --- /dev/null +++ b/src/GC/lib/heap_old.cpp @@ -0,0 +1,853 @@ +#include +#include +#include +#include +#include +#include + +#include "heap.hpp" + +#define time_now std::chrono::high_resolution_clock::now() +#define to_us std::chrono::duration_cast + +using std::cout, std::endl, std::vector, std::hex, std::dec, std::unordered_map; + +namespace GC +{ + /** + * This implementation of the() guarantees laziness + * on the instance and a correct destruction with + * the destructor. + * + * @returns The singleton object. + */ + Heap& Heap::the() + { + static Heap instance; + return instance; + } + + /** + * Initialises the heap singleton and saves the address + * of the calling function's stack frame as the stack_top. + * Presumeably this address points to the stack frame of + * the compiled LLVM executable after linking. + */ + void Heap::init() + { + Heap &heap = Heap::the(); + if (heap.profiler_enabled()) + Profiler::record(HeapInit); +// clang complains because arg for __b_f_a is not 0 which is "unsafe" +#pragma clang diagnostic ignored "-Wframe-address" + heap.m_stack_top = static_cast(__builtin_frame_address(1)); + // TODO: handle this below + //heap.m_heap_top = heap.m_heap; + } + + void Heap::set_profiler_log_options(RecordOption flags) + { + Profiler::set_log_options(flags); + } + + /** + * Disposes the heap and the profiler at program exit + * which also triggers a heap log file dumped if the + * profiler is enabled. + */ + void Heap::dispose() + { + Heap &heap = Heap::the(); + if (heap.profiler_enabled()) + Profiler::dispose(); + } + + /** + * Allocates a given amount of bytes on the heap. + * + * @param size The amount of bytes to be allocated. + * + * @return A pointer to the address where the memory + * has been allocated. This pointer is supposed + * to be casted to and object pointer. + */ + void *Heap::alloc(size_t size) + { + auto a_start = time_now; + // Singleton + Heap &heap = Heap::the(); + bool profiler_enabled = heap.profiler_enabled(); + + if (profiler_enabled) + Profiler::record(AllocStart, size); + + if (size == 0) + { + cout << "Heap: Cannot alloc 0B. No bytes allocated." << endl; + return nullptr; + } + + if (heap.m_size + size > HEAP_SIZE) + { + // auto a_ms = to_us(c_start - a_start); + // Profiler::record(AllocStart, a_ms); + heap.collect(); + // If memory is not enough after collect, crash with OOM error + if (heap.m_size > HEAP_SIZE) + { + throw std::runtime_error(std::string("Error: Heap out of memory")); + } + //throw std::runtime_error(std::string("Error: Heap out of memory")); + } + if (heap.m_size + size > HEAP_SIZE) + { + if (profiler_enabled) + Profiler::dispose(); + throw std::runtime_error(std::string("Error: Heap out of memory")); + } + + // If a chunk was recycled, return the old chunk address + Chunk *reused_chunk = heap.try_recycle_chunks(size); + if (reused_chunk != nullptr) + { + if (profiler_enabled) + Profiler::record(ReusedChunk, reused_chunk); + auto a_end = time_now; + auto a_ms = to_us(a_end - a_start); + Profiler::record(AllocStart, a_ms); + return static_cast(reused_chunk->m_start); + } + + // If no free chunks was found (reused_chunk is a nullptr), + // then create a new chunk + auto new_chunk = new Chunk(size, (uintptr_t *)(heap.m_heap + heap.m_size)); + + heap.m_size += size; + // TODO: handle this below + //heap.m_total_size += size; + heap.m_allocated_chunks.push_back(new_chunk); + + if (profiler_enabled) + Profiler::record(NewChunk, new_chunk); + + auto a_end = time_now; + auto a_ms = to_us(a_end - a_start); + Profiler::record(AllocStart, a_ms); + return new_chunk->m_start; + } + + /** + * Tries to recycle used and freed chunks that are + * already allocated objects by the OS but freed + * from our Heap. This reduces the amount of GC + * objects slightly which saves time from malloc'ing + * memory from the OS. + * + * @param size Amount of bytes needed for the object + * which is about to be allocated. + * + * @returns If a chunk is found and recycled, a + * pointer to the allocated memory for + * the object is returned. If not, a + * nullptr is returned to signify no + * chunks were found. + */ + Chunk *Heap::try_recycle_chunks(size_t size) + { + Heap &heap = Heap::the(); + // Check if there are any freed chunks large enough for current request + for (size_t i = 0; i < heap.m_freed_chunks.size(); i++) + { + //auto chunk = Heap::get_at(heap.m_freed_chunks, i); + auto chunk = heap.m_freed_chunks[i]; + auto iter = heap.m_freed_chunks.begin(); + i++; + //advance(iter, i); + if (chunk->m_size > size) + { + // Split the chunk, use one part and add the remaining part to + // the list of freed chunks + size_t diff = chunk->m_size - size; + auto chunk_complement = new Chunk(diff, chunk->m_start + chunk->m_size); + + heap.m_freed_chunks.erase(iter); + heap.m_freed_chunks.push_back(chunk_complement); + heap.m_allocated_chunks.push_back(chunk); + + return chunk; + } + else if (chunk->m_size == size) + { + // Reuse the whole chunk + heap.m_freed_chunks.erase(iter); + heap.m_allocated_chunks.push_back(chunk); + return chunk; + } + } + // If no chunk was found, return nullptr + return nullptr; + } + + /** + * Returns a bool whether the profiler is enabled + * or not. + * + * @returns True or false if the profiler is enabled + * or disabled respectively. + */ + bool Heap::profiler_enabled() { + Heap &heap = Heap::the(); + return heap.m_profiler_enable; + } + + /** + * Collection phase of the garbage collector. When + * an allocation is requested and there is no space + * left on the heap, a collection is triggered. This + * function is private so that the user cannot trigger + * a collection unneccessarily. + */ + void Heap::collect() + { + auto c_start = time_now; + + Heap &heap = Heap::the(); + + if (heap.profiler_enabled()) + Profiler::record(CollectStart); + + // get current stack frame + auto stack_bottom = reinterpret_cast(__builtin_frame_address(2)); + + if (heap.m_stack_top == nullptr) + throw std::runtime_error(std::string("Error: Heap is not initialized, read the docs!")); + + uintptr_t *stack_top = heap.m_stack_top; + + //auto work_list = heap.m_allocated_chunks; + //mark(stack_bottom, stack_top, work_list); + + // Testing mark_hash, previous woking implementation above + create_table(); + mark_hash(stack_bottom, stack_top); + + sweep(heap); + + free(heap); + + auto c_end = time_now; + + Profiler::record(CollectStart, to_us(c_end - c_start)); + } + + /** + * 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) + { + // cout << "\nWorklist size: " << worklist.size() << "\n"; + Heap &heap = Heap::the(); + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(MarkStart); + + vector rangeWL; + + // 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); + +/* Chunk *next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); + while (next != NULL) { + if (!next->m_marked) + { + next->m_marked = true; + auto c_start = reinterpret_cast(next->m_start); + auto c_size = reinterpret_cast(next->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + next = find_pointer((uintptr_t *) c_start, (uintptr_t *) c_end, worklist); + } + } */ + + // Recursively call mark, to see if the reachable chunk further points to another chunk + // mark((uintptr_t *)c_start, (uintptr_t *)c_end, worklist); + // AddrRange *range = new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end); + rangeWL.push_back(new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end)); + } + else + { + ++it; + } + } + else + { + ++it; + } + } + } + mark_range(rangeWL, worklist); + rangeWL.clear(); + } + + void Heap::mark_range(vector &ranges, vector &worklist) + { + Heap &heap = Heap::the(); + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(MarkStart); + + auto iter = ranges.begin(); + auto stop = ranges.end(); + + while (iter != stop) + { + auto range = *iter++; + uintptr_t *start = (uintptr_t *)range->start; + const uintptr_t *end = range->end; + if (start == nullptr) + cout << "\nstart is null\n"; + for (; start <= end; start++) + { + auto wliter = worklist.begin(); + auto wlstop = worklist.end(); + while (wliter != wlstop) + { + Chunk *chunk = *wliter; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + + if (c_start <= *start && *start < c_end) + { + if (!chunk->m_marked) + { + chunk->m_marked = true; + wliter = worklist.erase(wliter); + ranges.push_back(new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end)); + stop = ranges.end(); + } + else + { + wliter++; + } + } + else + { + wliter++; + } + } + } + } + } + + void Heap::create_table() + { + Heap &heap = Heap::the(); + unordered_map chunk_table; + for (auto chunk : heap.m_allocated_chunks) { + auto pair = std::make_pair(reinterpret_cast(chunk->m_start), chunk); + heap.m_chunk_table.insert(pair); + } + } + + void Heap::mark_hash(uintptr_t *start, const uintptr_t* const end) + { + Heap &heap = Heap::the(); + + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(MarkStart); + + for (; start <= end; start++) + { + auto search = heap.m_chunk_table.find(*start); + if (search != heap.m_chunk_table.end()) + { + Chunk *chunk = search->second; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + if (!chunk->m_marked) + { + chunk->m_marked = true; + + if (profiler_enabled) + Profiler::record(ChunkMarked, chunk); + + //mark_hash(chunk->m_start, c_end); + Chunk *next = find_pointer_hash((uintptr_t *) c_start, (uintptr_t *) c_end); + while (next != NULL) + { + if (!next->m_marked) + { + next->m_marked = true; + + if (profiler_enabled) + Profiler::record(ChunkMarked, chunk); + + auto c_start = reinterpret_cast(next->m_start); + auto c_size = reinterpret_cast(next->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + next = find_pointer_hash((uintptr_t *) c_start, (uintptr_t *) c_end); + } + } + } + } + } + } + + /** + * Sweeps the heap, unmarks the marked chunks for the next cycle, + * adds the unmarked nodes to the list of freed chunks; to be freed. + * + * Time complexity: O(N^2), where N is the number of allocated chunks. + * It is quadratic, in the worst case, + * since each call to erase() is linear. + * + * @param heap Pointer to the heap singleton instance. + */ + void Heap::sweep(Heap &heap) + { + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(SweepStart); + auto iter = heap.m_allocated_chunks.begin(); + std::cout << "Chunks alloced: " << heap.m_allocated_chunks.size() << std::endl; + // This cannot "iter != stop", results in seg fault, since the end gets updated, I think. + while (iter != heap.m_allocated_chunks.end()) + { + Chunk *chunk = *iter; + + // Unmark the marked chunks for the next iteration. + if (chunk->m_marked) + { + chunk->m_marked = false; + ++iter; + } + else + { + // Add the unmarked chunks to freed chunks and remove from + // the list of allocated chunks + if (profiler_enabled) + Profiler::record(ChunkSwept, chunk); + heap.m_freed_chunks.push_back(chunk); + iter = heap.m_allocated_chunks.erase(iter); + //heap.m_size -= chunk->m_size; + cout << "Decremented total heap size with: " << chunk->m_size << endl; + cout << "Total size is: " << heap.m_size << endl; + } + } + std::cout << "Chunks left: " << heap.m_allocated_chunks.size() << std::endl; + } + + /** + * Frees chunks that was moved to the list m_freed_chunks + * by the sweep phase. If there are more than a certain + * amount of free chunks, delete the free chunks to + * avoid cluttering. + * + * Time complexity: O(N^2), where N is the freed chunks. + * If free_overlap() is called, it runs in O(N^2), + * otherwise O(N). + * + * @param heap Heap singleton instance, only for avoiding + * redundant calls to the singleton get + */ + void Heap::free(Heap &heap) + { + bool profiler_enabled = heap.m_profiler_enable; + if (profiler_enabled) + Profiler::record(FreeStart); + if (heap.m_freed_chunks.size() > FREE_THRESH) + { + bool profiler_enabled = heap.profiler_enabled(); + while (heap.m_freed_chunks.size()) + { + auto chunk = heap.m_freed_chunks.back(); + heap.m_freed_chunks.pop_back(); + if (profiler_enabled) + Profiler::record(ChunkFreed, chunk); + heap.m_size -= chunk->m_size; + cout << "Decremented total heap size with: " << chunk->m_size << endl; + cout << "Total size is: " << heap.m_size << endl; + delete chunk; + } + } + // if there are chunks but not more than FREE_THRESH + else if (heap.m_freed_chunks.size()) + { + // essentially, always check for overlap between + // chunks before finishing the allocation + free_overlap(heap); + } + } + + /** + * Checks for overlaps between freed chunks of memory + * and removes overlapping chunks while prioritizing + * the chunks at lower addresses. + * + * Time complexity: O(N^2), where N is the number of freed chunks. + * At each iteration get_at() is called, which is linear. + * + * @param heap Heap singleton instance, only for avoiding + * redundant calls to the singleton get + * + * @note Maybe this should be changed to prioritizing + * larger chunks. Should remove get_at() to indexing, + * since that's constant. + */ + void Heap::free_overlap(Heap &heap) // borde göra en record(ChunkFreed) på onödiga chunks + { + std::vector filtered; + size_t i = 0; + //auto prev = Heap::get_at(heap.m_freed_chunks, i++); + auto prev = heap.m_freed_chunks[i++]; + prev->m_marked = true; + filtered.push_back(prev); + // cout << filtered.back()->m_start << endl; + for (; i < heap.m_freed_chunks.size(); i++) + { + prev = filtered.back(); + //auto next = Heap::get_at(heap.m_freed_chunks, i); + auto next = heap.m_freed_chunks[i]; + auto p_start = (uintptr_t)(prev->m_start); + auto p_size = (uintptr_t)(prev->m_size); + auto n_start = (uintptr_t)(next->m_start); + if (n_start >= (p_start + p_size)) + { + next->m_marked = true; + filtered.push_back(next); + } + } + heap.m_freed_chunks.swap(filtered); + + bool profiler_enabled = heap.m_profiler_enable; + // After swap m_freed_chunks contains still available chunks + // and filtered contains all the chunks, so delete unused chunks + for (Chunk *chunk : filtered) + { + // if chunk was filtered away, delete it + if (!chunk->m_marked) + { + if (profiler_enabled) + Profiler::record(ChunkFreed, chunk); + heap.m_size -= chunk->m_size; + cout << "Decremented total heap size with: " << chunk->m_size << endl; + cout << "Total size is: " << heap.m_size << endl; + delete chunk; + } + else + { + chunk->m_marked = false; + } + } + } + + void Heap::set_profiler(bool mode) + { + Heap &heap = Heap::the(); + heap.m_profiler_enable = mode; + } + + Chunk* find_pointer(uintptr_t *start, const uintptr_t* const end, vector &worklist) { + for (; start <= end; start++) { + auto it = worklist.begin(); + auto stop = worklist.end(); + while (it != stop) + { + Chunk *chunk = *it; + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + + // Check if the stack pointer points to something within the chunk + if (c_start <= *start && *start < c_end) + { + return chunk; + } + return NULL; + } + } + } + + // Checks if a given chunk points to another chunk and returns it + Chunk* Heap::find_pointer_hash(uintptr_t *start, const uintptr_t* const end) { + Heap &heap = Heap::the(); + for (; start <= end; start++) { + auto search = heap.m_chunk_table.find(*start); + if (search != heap.m_chunk_table.end()) { + return search->second; + } + return NULL; + } + } + +#ifdef HEAP_DEBUG + /** + * Prints the result of Heap::init() and a dummy value + * for the current stack frame for reference. + */ + void Heap::check_init() + { + Heap &heap = Heap::the(); + cout << "Heap addr:\t" << &heap << "\n"; + cout << "GC m_stack_top:\t" << heap.m_stack_top << "\n"; + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + cout << "GC stack_bottom:\t" << stack_bottom << endl; + } + + /** + * Conditional collection, only to be used in debugging + * + * @param flags Bitmap of flags + */ + void Heap::collect(CollectOption flags) + { + set_profiler(true); + + Heap &heap = Heap::the(); + + if (heap.m_profiler_enable) + Profiler::record(CollectStart); + + cout << "DEBUG COLLECT\nFLAGS: "; + if (flags & MARK) + cout << "\n - MARK"; + if (flags & SWEEP) + cout << "\n - SWEEP"; + if (flags & FREE) + cout << "\n - FREE"; + cout << "\n"; + + // get the frame adress, whwere local variables and saved registers are located + auto stack_bottom = reinterpret_cast(__builtin_frame_address(0)); + cout << "Stack bottom in collect:\t" << stack_bottom << "\n"; + uintptr_t *stack_top = heap.m_stack_top; + + cout << "Stack end in collect:\t " << stack_top << endl; + auto work_list = heap.m_allocated_chunks; + + if (flags & MARK) + mark(stack_bottom, stack_top, work_list); + + if (flags & SWEEP) + sweep(heap); + + if (flags & FREE) + free(heap); + } + + // Mark child references from the root references + void mark_test(vector &worklist) + { + while (worklist.size() > 0) + { + Chunk *ref = worklist.back(); + worklist.pop_back(); + Chunk *child = (Chunk *)ref; // this is probably not correct + if (child != nullptr && !child->m_marked) + { + child->m_marked = true; + worklist.push_back(child); + mark_test(worklist); + } + } + } + + // Mark the root references and look for child references to them + void mark_from_roots(uintptr_t *start, const uintptr_t *end) + { + vector worklist; + for (; start > end; start--) + { + if (*start % 8 == 0) + { // all pointers must be aligned as double words + Chunk *ref = (Chunk *)*start; + if (ref != nullptr && !ref->m_marked) + { + ref->m_marked = true; + worklist.push_back(ref); + mark_test(worklist); + } + } + } + } + + // For testing purposes + void Heap::print_line(Chunk *chunk) + { + cout << "Marked: " << chunk->m_marked << "\nStart adr: " << chunk->m_start << "\nSize: " << chunk->m_size << " B\n" + << endl; + } + + void Heap::print_worklist(std::vector &list) + { + for (auto cp : list) + cout << "Chunk at:\t" << cp->m_start << "\nSize:\t\t" << cp->m_size << "\n"; + cout << endl; + } + + void Heap::print_contents() + { + Heap &heap = Heap::the(); + if (heap.m_allocated_chunks.size()) + { + cout << "\nALLOCATED CHUNKS #" << dec << heap.m_allocated_chunks.size() << endl; + for (auto chunk : heap.m_allocated_chunks) + print_line(chunk); + } + else + { + cout << "NO ALLOCATIONS\n" << endl; + } + if (heap.m_freed_chunks.size()) + { + cout << "\nFREED CHUNKS #" << dec << heap.m_freed_chunks.size() << endl; + for (auto fchunk : heap.m_freed_chunks) + print_line(fchunk); + } + else + { + cout << "NO FREED CHUNKS" << endl; + } + } + + void Heap::print_summary() + { + Heap &heap = Heap::the(); + if (heap.m_allocated_chunks.size()) + { + cout << "\nALLOCATED CHUNKS #" << dec << heap.m_allocated_chunks.size() << endl; + } + else + { + cout << "NO ALLOCATIONS\n" << endl; + } + if (heap.m_freed_chunks.size()) + { + cout << "\nFREED CHUNKS #" << dec << heap.m_freed_chunks.size() << endl; + } + else + { + cout << "NO FREED CHUNKS" << endl; + } + } + + void Heap::print_allocated_chunks(Heap *heap) { + cout << "--- Allocated Chunks ---\n" << endl; + for (auto chunk : heap->m_allocated_chunks) { + print_line(chunk); + } + } + + Chunk *Heap::try_recycle_chunks_new(size_t size) + { + Heap &heap = Heap::the(); + // Check if there are any freed chunks large enough for current request + for (size_t i = 0; i < heap.m_freed_chunks.size(); i++) + { + auto chunk = heap.m_freed_chunks[i]; //Heap::get_at(heap.m_freed_chunks, i); + auto iter = heap.m_freed_chunks.begin(); + //advance(iter, i); + i++; + if (chunk->m_size > size) + { + // Split the chunk, use one part and add the remaining part to + // the list of freed chunks + size_t diff = chunk->m_size - size; + auto chunk_complement = new Chunk(diff, chunk->m_start + chunk->m_size); + + heap.m_freed_chunks.erase(iter); + heap.m_freed_chunks.push_back(chunk_complement); + heap.m_allocated_chunks.push_back(chunk); + + return chunk; + } + else if (chunk->m_size == size) + { + // Reuse the whole chunk + heap.m_freed_chunks.erase(iter); + heap.m_allocated_chunks.push_back(chunk); + return chunk; + } + } + // If no chunk was found, return nullptr + return nullptr; + } + + void Heap::free_overlap_new(Heap &heap) // borde göra en record(ChunkFreed) på onödiga chunks + { + std::vector filtered; + size_t i = 0; + auto prev = heap.m_freed_chunks[i++]; //Heap::get_at(heap.m_freed_chunks, i++); + prev->m_marked = true; + filtered.push_back(prev); + cout << filtered.back()->m_start << endl; + for (; i < heap.m_freed_chunks.size(); i++) + { + prev = filtered.back(); + auto next = heap.m_freed_chunks[i]; //Heap::get_at(heap.m_freed_chunks, i); + auto p_start = (uintptr_t)(prev->m_start); + auto p_size = (uintptr_t)(prev->m_size); + auto n_start = (uintptr_t)(next->m_start); + if (n_start >= (p_start + p_size)) + { + next->m_marked = true; + filtered.push_back(next); + } + } + heap.m_freed_chunks.swap(filtered); + + bool profiler_enabled = heap.m_profiler_enable; + // After swap m_freed_chunks contains still available chunks + // and filtered contains all the chunks, so delete unused chunks + for (Chunk *chunk : filtered) + { + // if chunk was filtered away, delete it + if (!chunk->m_marked) + { + if (profiler_enabled) + Profiler::record(ChunkFreed, chunk); + delete chunk; + } + else + { + chunk->m_marked = false; + } + } + } + +#endif +} \ No newline at end of file diff --git a/src/GC/lib/profiler.cpp b/src/GC/lib/profiler.cpp index 78a0b8e..ae31f0d 100644 --- a/src/GC/lib/profiler.cpp +++ b/src/GC/lib/profiler.cpp @@ -11,7 +11,7 @@ #include "event.hpp" #include "profiler.hpp" -#define MAC_OS +// #define MAC_OS namespace GC { diff --git a/src/GC/tests/linkedlist.cpp b/src/GC/tests/linkedlist.cpp index b562a77..ada55bd 100644 --- a/src/GC/tests/linkedlist.cpp +++ b/src/GC/tests/linkedlist.cpp @@ -53,7 +53,7 @@ void clear_list(Node *head) } } -#define LIST_SIZE 10 +#define LIST_SIZE 1000 void list_test1() { @@ -61,6 +61,7 @@ void list_test1() // print_list(list_1); } +/* void list_test2() { Node *list_2 = create_list(LIST_SIZE); @@ -99,6 +100,7 @@ void make_test() { list_test5(); list_test6(); } +*/ int main() { @@ -108,7 +110,9 @@ int main() GC::Profiler::set_log_options(GC::FunctionCalls); //GC::Profiler::set_log_options(GC::AllOps); - make_test(); + // make_test(); + for (int i = 0; i < 1000; i++) + list_test1(); GC::Heap::dispose(); From b3ed111099155d1f3d75cb18cc9a4344c0b4368b Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Fri, 5 May 2023 17:59:07 +0200 Subject: [PATCH 14/17] removed cout bloatware --- src/GC/lib/heap.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 7db79c4..960e724 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -235,16 +235,16 @@ namespace GC // mark_hash(stack_bottom, stack_top); vector roots; - cout << "\nb4 find_roots\n"; + // cout << "\nb4 find_roots\n"; find_roots(stack_bottom, roots); - cout << "b4 mark\n"; + // cout << "b4 mark\n";'' mark(roots); - cout << "b4 sweep\n"; + // cout << "b4 sweep\n"; sweep(heap); - cout << "b4 free\n"; + // cout << "b4 free\n"; free(heap); auto c_end = time_now; @@ -296,13 +296,13 @@ namespace GC std::queue> chunk_spaces; // std::set visited_addresses; - cout << "b4 find_chunks of roots\n"; + // cout << "b4 find_chunks of roots\n"; while (iter != end) { find_chunks(*iter++, chunk_spaces); } - cout << "b4 find_chunks of chunks\n"; + // cout << "b4 find_chunks of chunks\n"; while (!chunk_spaces.empty()) { auto range = chunk_spaces.front(); @@ -462,7 +462,7 @@ namespace GC if (profiler_enabled) Profiler::record(SweepStart); auto iter = heap.m_allocated_chunks.begin(); - std::cout << "Chunks alloced: " << heap.m_allocated_chunks.size() << std::endl; + // std::cout << "Chunks alloced: " << heap.m_allocated_chunks.size() << std::endl; // This cannot "iter != stop", results in seg fault, since the end gets updated, I think. while (iter != heap.m_allocated_chunks.end()) { From 830d863c70d124ea3bb37624aa6bdba33da12cf4 Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Sat, 6 May 2023 10:23:09 +0200 Subject: [PATCH 15/17] bugfix --- src/GC/lib/heap.cpp | 75 +++++++-------------------------------------- 1 file changed, 11 insertions(+), 64 deletions(-) diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 960e724..352c94b 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -220,7 +220,7 @@ namespace GC Profiler::record(CollectStart); // get current stack frame - // auto stack_bottom = reinterpret_cast(__builtin_frame_address(2)); + 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!")); @@ -254,12 +254,10 @@ namespace GC void Heap::find_roots(uintptr_t *stack_bottom, vector &roots) { - Heap &heap = Heap::the(); - auto stack_top = heap.m_stack_top; - auto heap_bottom = reinterpret_cast(heap.m_heap); - auto heap_top = reinterpret_cast(heap.m_heap + HEAP_SIZE); + auto heap_bottom = reinterpret_cast(m_heap); + auto heap_top = reinterpret_cast(m_heap + HEAP_SIZE); - while (stack_bottom < stack_top) + while (stack_bottom < m_stack_top) { if (heap_bottom < *stack_bottom && *stack_bottom < heap_top) { @@ -292,25 +290,26 @@ namespace GC Profiler::record(MarkStart); auto iter = roots.begin(), end = roots.end(); - // vector> chunk_spaces; std::queue> chunk_spaces; - // std::set visited_addresses; - // cout << "b4 find_chunks of roots\n"; while (iter != end) { find_chunks(*iter++, chunk_spaces); } - // cout << "b4 find_chunks of chunks\n"; while (!chunk_spaces.empty()) { auto range = chunk_spaces.front(); chunk_spaces.pop(); - auto stack_addr = reinterpret_cast(range.first); + auto addr_bottom = reinterpret_cast(range.first); + auto addr_top = reinterpret_cast(range.second); - find_chunks(stack_addr, chunk_spaces); + while (addr_bottom < addr_top) + { + find_chunks(addr_bottom, chunk_spaces); + addr_bottom++; + } } } @@ -333,63 +332,11 @@ namespace GC if (c_start < *stack_addr && *stack_addr < c_end) { chunk->m_marked = true; - // chunk_spaces.push_back(std::make_pair(c_start, c_end)); chunk_spaces.push(std::make_pair(c_start, c_end)); } } } - void Heap::mark_range(vector &ranges, vector &worklist) - { - Heap &heap = Heap::the(); - bool profiler_enabled = heap.m_profiler_enable; - if (profiler_enabled) - Profiler::record(MarkStart); - - auto iter = ranges.begin(); - auto stop = ranges.end(); - - while (iter != stop) - { - auto range = *iter++; - uintptr_t *start = (uintptr_t *)range->start; - const uintptr_t *end = range->end; - if (start == nullptr) - cout << "\nstart is null\n"; - for (; start <= end; start++) - { - auto wliter = worklist.begin(); - auto wlstop = worklist.end(); - while (wliter != wlstop) - { - Chunk *chunk = *wliter; - auto c_start = reinterpret_cast(chunk->m_start); - auto c_size = reinterpret_cast(chunk->m_size); - auto c_end = reinterpret_cast(c_start + c_size); - - if (c_start <= *start && *start < c_end) - { - if (!chunk->m_marked) - { - chunk->m_marked = true; - wliter = worklist.erase(wliter); - ranges.push_back(new AddrRange((uintptr_t *)c_start, (uintptr_t *)c_end)); - stop = ranges.end(); - } - else - { - wliter++; - } - } - else - { - wliter++; - } - } - } - } - } - void Heap::create_table() { Heap &heap = Heap::the(); From 50566246fe81d4f8174cee5ad29a9a2825a93602 Mon Sep 17 00:00:00 2001 From: Victor Olin Date: Mon, 8 May 2023 11:00:07 +0200 Subject: [PATCH 16/17] short profiler logs --- src/GC/include/heap.hpp | 4 +-- src/GC/include/profiler.hpp | 5 +-- src/GC/lib/heap.cpp | 18 +--------- src/GC/lib/profiler.cpp | 13 ++++--- src/GC/tests/linkedlist.cpp | 68 ++----------------------------------- 5 files changed, 18 insertions(+), 90 deletions(-) diff --git a/src/GC/include/heap.hpp b/src/GC/include/heap.hpp index c56c7fa..4d8330f 100644 --- a/src/GC/include/heap.hpp +++ b/src/GC/include/heap.hpp @@ -9,8 +9,8 @@ #include "chunk.hpp" #include "profiler.hpp" -#define HEAP_SIZE 16000//65536 -#define FREE_THRESH (uint) 100 +#define HEAP_SIZE 160000//65536 +#define FREE_THRESH (uint) 5 // #define HEAP_DEBUG namespace GC diff --git a/src/GC/include/profiler.hpp b/src/GC/include/profiler.hpp index 1c44ef6..56acf7b 100644 --- a/src/GC/include/profiler.hpp +++ b/src/GC/include/profiler.hpp @@ -14,6 +14,7 @@ namespace GC { enum RecordOption { + TimingInfo = 0, FunctionCalls = (GC::AllocStart | GC::CollectStart | GC::MarkStart | GC::SweepStart | GC::FreeStart), ChunkOps = (GC::ChunkMarked | GC::ChunkSwept | GC::ChunkFreed | GC::NewChunk | GC::ReusedChunk), AllOps = 0xFFFFFF @@ -41,7 +42,7 @@ namespace GC { std::vector m_events; ProfilerEvent *m_last_prof_event {new ProfilerEvent(HeapInit)}; std::vector m_prof_events; - RecordOption flags; + RecordOption flags {AllOps}; std::chrono::microseconds alloc_time {0}; // size_t alloc_counts {0}; @@ -52,7 +53,7 @@ namespace GC { std::ofstream create_file_stream(); std::string get_log_folder(); static void dump_trace(); - static void dump_prof_trace(); + static void dump_prof_trace(bool timing_only); static void dump_chunk_trace(); // static void dump_trace_short(); // static void dump_trace_full(); diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 352c94b..91f1e02 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -266,23 +266,7 @@ namespace GC stack_bottom++; } } - - /** - * 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(vector &roots) { bool prof_enabled = m_profiler_enable; diff --git a/src/GC/lib/profiler.cpp b/src/GC/lib/profiler.cpp index ae31f0d..690da39 100644 --- a/src/GC/lib/profiler.cpp +++ b/src/GC/lib/profiler.cpp @@ -83,8 +83,10 @@ namespace GC void Profiler::dump_trace() { Profiler &prof = Profiler::the(); - if (prof.flags & FunctionCalls) - dump_prof_trace(); + if (prof.flags == TimingInfo) + dump_prof_trace(true); + else if (prof.flags & FunctionCalls) + dump_prof_trace(false); else dump_chunk_trace(); } @@ -127,7 +129,7 @@ namespace GC } } - void Profiler::dump_prof_trace() + void Profiler::dump_prof_trace(bool timing_only) { Profiler &prof = Profiler::the(); prof.m_prof_events.push_back(prof.m_last_prof_event); @@ -147,9 +149,12 @@ namespace GC else if (event->m_type == CollectStart) collects += event->m_n; - fstr << "\n--------------------------------\n" + if (!timing_only) + { + fstr << "\n--------------------------------\n" << Profiler::type_to_string(event->m_type) << " " << event->m_n << " times:"; + } } fstr << "\n--------------------------------"; diff --git a/src/GC/tests/linkedlist.cpp b/src/GC/tests/linkedlist.cpp index ada55bd..474e9b3 100644 --- a/src/GC/tests/linkedlist.cpp +++ b/src/GC/tests/linkedlist.cpp @@ -32,83 +32,21 @@ Node *create_list(size_t length) return head; } -void print_list(Node* head) -{ - cout << "\nPrinting list...\n"; - while (head != nullptr) - { - cout << head->value << " "; - head = head->next; - } - cout << endl; -} - -void clear_list(Node *head) -{ - while (head != nullptr) - { - Node *tmp = head->next; - head->next = nullptr; - head = tmp; - } -} - #define LIST_SIZE 1000 void list_test1() { Node *list_1 = create_list(LIST_SIZE); - // print_list(list_1); } -/* -void list_test2() -{ - Node *list_2 = create_list(LIST_SIZE); - // print_list(list_2); -} - -void list_test3() -{ - Node *list_3 = create_list(LIST_SIZE); - // print_list(list_3); -} - -void list_test4() -{ - Node *list_4 = create_list(LIST_SIZE); - // print_list(list_4); -} - -void list_test5() -{ - Node *list_5 = create_list(LIST_SIZE); - // print_list(list_5); -} - -void list_test6() -{ - Node *list_6 = create_list(LIST_SIZE); - // print_list(list_6); -} - -void make_test() { - list_test1(); - list_test2(); - list_test3(); - list_test4(); - list_test5(); - list_test6(); -} -*/ - int main() { GC::Heap::init(); GC::Heap &heap = GC::Heap::the(); heap.set_profiler(true); - GC::Profiler::set_log_options(GC::FunctionCalls); - //GC::Profiler::set_log_options(GC::AllOps); + // GC::Profiler::set_log_options(GC::FunctionCalls); + // GC::Profiler::set_log_options(GC::ChunkOps); + GC::Profiler::set_log_options(GC::TimingInfo); // make_test(); for (int i = 0; i < 1000; i++) From 560f8f9b2f39aad387eac577af469828cfeae3dd Mon Sep 17 00:00:00 2001 From: valtermiari Date: Mon, 8 May 2023 12:03:55 +0200 Subject: [PATCH 17/17] Hash table lookup for marking --- src/GC/lib/heap.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/GC/lib/heap.cpp b/src/GC/lib/heap.cpp index 91f1e02..3213a67 100644 --- a/src/GC/lib/heap.cpp +++ b/src/GC/lib/heap.cpp @@ -234,6 +234,7 @@ namespace GC // create_table(); // mark_hash(stack_bottom, stack_top); + create_table(); vector roots; // cout << "\nb4 find_roots\n"; find_roots(stack_bottom, roots); @@ -299,7 +300,26 @@ namespace GC void Heap::find_chunks(uintptr_t *stack_addr, std::queue> &chunk_spaces) { - auto iter = m_allocated_chunks.begin(); + Heap &heap = Heap::the(); + + auto it = heap.m_chunk_table.find(*stack_addr); + if (it != heap.m_chunk_table.end()) + { + auto chunk = it->second; + + if (!chunk->m_marked) + { + auto c_start = reinterpret_cast(chunk->m_start); + auto c_size = reinterpret_cast(chunk->m_size); + auto c_end = reinterpret_cast(c_start + c_size); + + chunk->m_marked = true; + chunk_spaces.push(std::make_pair(c_start, c_end)); + } + + } + +/* auto iter = m_allocated_chunks.begin(); auto end = m_allocated_chunks.end(); while (iter != end) @@ -318,7 +338,8 @@ namespace GC chunk->m_marked = true; chunk_spaces.push(std::make_pair(c_start, c_end)); } - } + } */ + } void Heap::create_table()