diff --git a/runtime/gc.c b/runtime/gc.c index fb6d3ffca..313f850f9 100644 --- a/runtime/gc.c +++ b/runtime/gc.c @@ -238,10 +238,62 @@ bool is_valid_pointer(const size_t *p) { return !UNBOXED(p); } +static inline void queue_enqueue(heap_iterator *tail_iter, void *obj) { + void *tail = tail_iter->current; + void *tail_content = get_object_content_ptr(tail); + set_forward_address(tail_content, (size_t) obj); + make_enqueued(obj); + heap_next_obj_iterator(tail_iter); +} + +static inline void *queue_dequeue(heap_iterator *head_iter) { + void *head = head_iter->current; + void *head_content = get_object_content_ptr(head); + void *value = (void*) get_forward_address(head_content); + make_dequeued(value); + heap_next_obj_iterator(head_iter); + return value; +} + void mark(void *obj) { if (!is_valid_heap_pointer(obj)) { return; } + + if (is_marked(obj)) { + return; + } + + // TL;DR: [q_head_iter, q_tail_iter) q_head_iter -- current dequeue's victim, q_tail_iter -- place for next enqueue + // in forward_address of corresponding element we store address of element to be removed after dequeue operation + heap_iterator q_head_iter = heap_begin_iterator(); + // iterator where we will write address of the element that is going to be enqueued + heap_iterator q_tail_iter = q_head_iter; + queue_enqueue(&q_tail_iter, obj); + + // invariant: queue contains only objects that are valid heap pointers (each corresponding to content of unmarked object) + // also each object is in queue only once + while (q_head_iter.current != q_tail_iter.current) { // means the queue is not empty + void *cur_obj = queue_dequeue(&q_head_iter); + mark_object(cur_obj); + void *header_ptr = get_obj_header_ptr(cur_obj, get_type_row_ptr(cur_obj)); + for ( + obj_field_iterator ptr_field_it = ptr_field_begin_iterator(header_ptr); + !field_is_done_iterator(&ptr_field_it); + obj_next_ptr_field_iterator(&ptr_field_it) + ) { + void *field_value = * (void **) ptr_field_it.cur_field; + if (!is_valid_heap_pointer(field_value) || is_marked(field_value) || is_enqueued(field_value)) { + continue; + } + // if we came to this point it must be true that field_value is unmarked and not currently in queue + // thus, we maintain the invariant + queue_enqueue(&q_tail_iter, field_value); + } + } +/* if (!is_valid_heap_pointer(obj)) { + return; + } if (is_marked(obj)) { return; } @@ -253,7 +305,7 @@ void mark(void *obj) { obj_next_ptr_field_iterator(&ptr_field_it) ) { mark(* (void **) ptr_field_it.cur_field); - } + }*/ } void scan_extra_roots(void) { @@ -422,6 +474,21 @@ void unmark_object(void *obj) { RESET_MARK_BIT(d->forward_address); } +bool is_enqueued(void *obj) { + data *d = TO_DATA(obj); + return IS_ENQUEUED(d->forward_address) != 0; +} + +void make_enqueued(void *obj) { + data *d = TO_DATA(obj); + MAKE_ENQUEUED(d->forward_address); +} + +void make_dequeued(void *obj) { + data *d = TO_DATA(obj); + MAKE_DEQUEUED(d->forward_address); +} + heap_iterator heap_begin_iterator() { heap_iterator it = {.current=heap.begin}; return it; diff --git a/runtime/gc.h b/runtime/gc.h index c104d1fda..957f239e4 100644 --- a/runtime/gc.h +++ b/runtime/gc.h @@ -5,9 +5,12 @@ # define GET_MARK_BIT(x) (((int) (x)) & 1) # define SET_MARK_BIT(x) (x = (((int) (x)) | 1)) +# define IS_ENQUEUED(x) (((int) (x)) & 2) +# define MAKE_ENQUEUED(x) (x = (((int) (x)) | 2)) +# define MAKE_DEQUEUED(x) (x = (((int) (x)) & (~2))) # define RESET_MARK_BIT(x) (x = (((int) (x)) & (~1))) -# define GET_FORWARD_ADDRESS(x) (((size_t) (x)) & (~1)) // since last bit is used as mark-bit and due to correct alignment we can expect that last bit doesn'test_small_tree_compaction influence address (it should always be zero) -# define SET_FORWARD_ADDRESS(x, addr) (x = (GET_MARK_BIT(x) | ((int) (addr)))) +# define GET_FORWARD_ADDRESS(x) (((size_t) (x)) & (~3)) // since last 2 bits are used for mark-bit and enqueued-bit and due to correct alignment we can expect that last 2 bits don't influence address (they should always be zero) +# define SET_FORWARD_ADDRESS(x, addr) (x = ((x & 3) | ((int) (addr)))) // take the last two bits as they are and make all others zero # define EXTRA_ROOM_HEAP_COEFFICIENT 2 // TODO: tune this parameter #ifdef DEBUG_VERSION # define MINIMUM_HEAP_CAPACITY (8) @@ -134,6 +137,15 @@ void mark_object(void *obj); // takes a pointer to an object content as an argument, marks the object as dead void unmark_object(void *obj); +// takes a pointer to an object content as an argument, returns whether this object was enqueued to the queue (which is used in mark phase) +bool is_enqueued(void *obj); + +// takes a pointer to an object content as an argument, marks object as enqueued +void make_enqueued(void *obj); + +// takes a pointer to an object content as an argument, unmarks object as enqueued +void make_dequeued(void *obj); + // returns iterator to an object with the lowest address heap_iterator heap_begin_iterator(); void heap_next_obj_iterator(heap_iterator *it); diff --git a/runtime/test_main.c b/runtime/test_main.c index f38227f24..558abbf80 100644 --- a/runtime/test_main.c +++ b/runtime/test_main.c @@ -226,7 +226,7 @@ size_t generate_random_obj_forest(virt_stack *st, int cnt, int seed) { void run_stress_test_random_obj_forest(int seed) { virt_stack *st = init_test(); - const int SZ = 10000; + const int SZ = 100000; size_t expectedAlive = generate_random_obj_forest(st, SZ, seed); @@ -244,6 +244,8 @@ void run_stress_test_random_obj_forest(int seed) { #endif +#include + int main(int argc, char ** argv) { #ifdef DEBUG_VERSION no_gc_tests(); @@ -257,9 +259,15 @@ int main(int argc, char ** argv) { test_alive_are_not_reclaimed(); test_small_tree_compaction(); + time_t start, end; + double diff; + time(&start); // stress test for (int s = 0; s < 100; ++s) { run_stress_test_random_obj_forest(s); } + time(&end); + diff = difftime(end, start); + printf ("Stress tests took %.2lf seconds to complete\n", diff); #endif } \ No newline at end of file