From f7021ddf82e85b7ea093a367df02028779fc6041 Mon Sep 17 00:00:00 2001 From: chibash Date: Thu, 19 Sep 2024 01:02:34 +0900 Subject: [PATCH] add critical sections to gc_write_barrier() to let an interrupt handler safely run during garbage collection. --- microcontroller/core/src/c-runtime.c | 77 ++++++++++++++++++++-- microcontroller/core/test/c-runtime-test.c | 49 ++++++++++++++ 2 files changed, 120 insertions(+), 6 deletions(-) diff --git a/microcontroller/core/src/c-runtime.c b/microcontroller/core/src/c-runtime.c index 4f09a89..f0e6bcc 100644 --- a/microcontroller/core/src/c-runtime.c +++ b/microcontroller/core/src/c-runtime.c @@ -93,6 +93,18 @@ pointer_t gc_heap_pointer(pointer_t ptr) { } #endif +#ifdef TEST64 + +#define ATOMIC_ENTER_CRITICAL +#define ATOMIC_EXIT_CRITICAL + +#else + +#include +#include + +#endif + // runtime error handling static jmp_buf long_jump_buffer; @@ -1178,12 +1190,15 @@ struct gc_root_set* gc_root_set_head = NULL; #define CLEAR_GRAY_BIT(ptr) ((ptr)->header &= ~0b10) #define SET_GRAY_BIT(ptr) ((ptr)->header |= 0b10) - #define STACK_SIZE (HEAP_SIZE / 65) static pointer_t gc_stack[STACK_SIZE]; static uint32_t gc_stack_top = 0; static bool gc_stack_overflowed = false; +#define ISTACK_SIZE (STACK_SIZE / 2) +static pointer_t gc_intr_stack[ISTACK_SIZE]; // used by interrupt handlers +static uint32_t gc_intr_stack_top = 0; + static void push_object_to_stack(pointer_t obj, uint32_t mark) { WRITE_MARK_BIT(obj, mark); SET_GRAY_BIT(obj); @@ -1199,12 +1214,41 @@ void gc_write_barrier(pointer_t obj, value_t value) { uint32_t mark = current_no_mark ? 0 : 1; pointer_t ptr = value_to_ptr(value); if (IS_WHITE(ptr, mark) && (obj == NULL || IS_BLACK(obj, mark))) { - push_object_to_stack(ptr, mark); + ATOMIC_ENTER_CRITICAL + if (gc_intr_stack_top < ISTACK_SIZE) + gc_intr_stack[gc_intr_stack_top++] = ptr; + else { + WRITE_MARK_BIT(ptr, mark); + SET_GRAY_BIT(ptr); + gc_stack_overflowed = true; + } + ATOMIC_EXIT_CRITICAL } } } } +static void copy_from_intr_stack(uint32_t mark) { + uint32_t num = gc_intr_stack_top; + uint32_t i = 0; + int failure; + do { + while (i < num) + push_object_to_stack(gc_intr_stack[i++], mark); + + ATOMIC_ENTER_CRITICAL + if (num == gc_intr_stack_top) { + failure = 0; + gc_intr_stack_top = 0; + } + else { + num = gc_intr_stack_top; + failure = 1; + } + ATOMIC_EXIT_CRITICAL + } while (failure); +} + static void trace_from_an_object(uint32_t mark) { while (gc_stack_top > 0) { pointer_t obj = gc_stack[--gc_stack_top]; @@ -1240,6 +1284,9 @@ static void scan_and_mark_objects(uint32_t mark) { if (IS_GRAY(obj)) { gc_stack[0] = obj; gc_stack_top = 1; + if (gc_intr_stack_top > 0) + copy_from_intr_stack(mark); + trace_from_an_object(mark); } start += real_objsize(size); @@ -1268,6 +1315,9 @@ static void mark_objects(struct gc_root_set* root_set, uint32_t mark) { SET_GRAY_BIT(rootp); gc_stack[0] = rootp; gc_stack_top = 1; + if (gc_intr_stack_top > 0) + copy_from_intr_stack(mark); + trace_from_an_object(mark); } } @@ -1276,10 +1326,18 @@ static void mark_objects(struct gc_root_set* root_set, uint32_t mark) { root_set = root_set->next; } - while (gc_stack_overflowed) { - gc_stack_overflowed = false; - scan_and_mark_objects(mark); - } + do { + while (gc_stack_overflowed) { + gc_stack_overflowed = false; + scan_and_mark_objects(mark); + } + + if (gc_intr_stack_top > 0) { + gc_stack_top = 0; + copy_from_intr_stack(mark); + trace_from_an_object(mark); + } + } while (gc_stack_overflowed || gc_intr_stack_top > 0); } static void sweep_objects(uint32_t mark) { @@ -1348,6 +1406,13 @@ void gc_run() { gc_is_running = false; } +#ifdef TEST64 +uint32_t gc_test_run() { + gc_is_running = true; + return current_no_mark ? 0 : 1; +} +#endif + void gc_init_rootset(struct gc_root_set* set, uint32_t length) { set->next = gc_root_set_head; if (length > 0) { diff --git a/microcontroller/core/test/c-runtime-test.c b/microcontroller/core/test/c-runtime-test.c index dfec88b..bb46c05 100644 --- a/microcontroller/core/test/c-runtime-test.c +++ b/microcontroller/core/test/c-runtime-test.c @@ -448,6 +448,54 @@ void test_gc_sweep() { gc_run(); } +extern uint32_t gc_test_run(); + +static void write_mark_bit(value_t obj, uint32_t mark) { + pointer_t ptr = value_to_ptr(obj); + if (mark) + ptr->header |= 1; + else + ptr->header &= ~1; +} + +void test_gc_write_barrier() { + gc_initialize(); + ROOT_SET(root_set, 5); + value_t obj, obj1, obj2; + + obj = gc_new_string("test"); + obj1 = gc_new_string("int"); + + uint32_t mark = gc_test_run(); + interrupt_handler_start(); + + write_mark_bit(obj, mark); + write_mark_bit(obj1, !mark); + gc_write_barrier(value_to_ptr(obj), obj1); + + interrupt_handler_end(); + + gc_new_string("test1"); + root_set.values[0] = gc_new_string("test2"); + root_set.values[1] = gc_new_string("test3"); + root_set.values[2] = gc_new_string("test4"); + gc_new_string("test5"); + gc_new_vector2(2); + gc_new_vector2(3); + root_set.values[3] = obj = gc_new_vector2(4); + obj2 = gc_new_vector2(3); + gc_vector_setter(obj, 0, obj2); + + gc_run(); + Assert_true(is_live_object(obj1)); + Assert_true(is_live_object(obj2)); + + gc_run(); + Assert_true(!is_live_object(obj1)); + + DELETE_ROOT_SET(root_set); +} + void test_main() { test_converters(); test_string(); @@ -461,6 +509,7 @@ void test_main() { test_gc_liveness(); test_gc_liveness2(); test_gc_sweep(); + test_gc_write_barrier(); puts("done"); }