From 26819cafdc04554228ccf1682d5d448c07d65d6e Mon Sep 17 00:00:00 2001 From: Jesse McDonald Date: Mon, 9 Nov 2009 09:47:36 -0600 Subject: [PATCH] Update list of weak boxes *after* processing finalizers. It's a bit unlikely, but a finalizer could be arranged for a weak box. Also add more assertions, clear old range in debug builds, and misc. cleanup. --- gc.c | 161 +++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 106 insertions(+), 55 deletions(-) diff --git a/gc.c b/gc.c index fef2f43..ec0239d 100644 --- a/gc.c +++ b/gc.c @@ -342,6 +342,8 @@ void gc_init(size_t min_size, size_t max_size) static inline void *_gc_alloc(size_t nbytes) { void *p = gc_free_ptr; + assert(nbytes == gc_align(nbytes)); + assert(nbytes <= gc_free_space()); gc_free_ptr += nbytes; return p; } @@ -361,15 +363,11 @@ static void transfer_object(value_t *value) { if (is_object(*value)) { - object_t *obj; + object_t *obj = _get_object(*value); size_t nbytes; void *newobj; - value_t new_value; assert(gc_range_of(obj) != gc_current_range); - assert(is_object(*value)); - - obj = _get_object(*value); if (obj->tag == BROKEN_HEART) { @@ -396,27 +394,26 @@ static void transfer_object(value_t *value) nbytes = sizeof(will_t); break; case TYPE_TAG_BOX: + nbytes = sizeof(box_t); + break; default: /* pair */ nbytes = sizeof(pair_t); break; } newobj = _gc_alloc(gc_align(nbytes)); - memcpy(newobj, obj, nbytes); /* Keep the original tag bits (pair or object) */ - new_value = object_value(newobj) | (*value & 2); - obj->tag = BROKEN_HEART; - obj->forward = new_value; - - *value = new_value; + *value = obj->forward = (object_value(newobj) & ~2) | (*value & 2); } } static size_t transfer_vector(vector_t *vec) { + assert(vec->tag == TYPE_TAG_VECTOR); + for (size_t i = 0; i < vec->size; ++i) transfer_object(&vec->elements[i]); @@ -425,6 +422,8 @@ static size_t transfer_vector(vector_t *vec) static size_t transfer_struct(struct_t *s) { + assert(s->tag == TYPE_TAG_STRUCT); + transfer_object(&s->type); for (size_t i = 0; i < s->nslots; ++i) @@ -442,6 +441,8 @@ static size_t transfer_pair(pair_t *p) static size_t transfer_will(will_t *w) { + assert(w->tag == TYPE_TAG_WILL); + transfer_object(&w->finalizer); /* Weak boxes are discarded when there are no other references, @@ -497,39 +498,46 @@ static void transfer_roots(void) static void process_weak_boxes(void) { - value_t *wb = &gc_weak_box_list; + value_t wb = gc_weak_box_list; - while (!is_nil(*wb)) + while (!is_nil(wb)) { - if (is_broken_heart(*wb)) + weak_box_t *box; + + if (is_broken_heart(wb)) { - /* The box itself is reachable; need to update 'next' pointer to new location */ - weak_box_t *box; - - *wb = _get_object(*wb)->forward; - assert(is_weak_box(*wb)); - box = _get_weak_box(*wb); - - if (is_broken_heart(box->value)) - { - /* The value in the box is also reachable; update w/ new location. */ - box->value = _get_object(box->value)->forward; - } - else if (is_object(box->value)) - { - /* The value in the box is an unreachable object; change to #f */ - box->value = FALSE_VALUE; - } - - /* Move on to this box's 'next' pointer */ - wb = &_get_weak_box(*wb)->next; + /* Box has been moved; get a pointer to the new location, but don't update list yet. */ + value_t fw = _get_object(wb)->forward; + assert(is_weak_box(fw)); + box = _get_weak_box(fw); } else { - /* Box is no longer reachable; remove it from the list by updating 'next' pointer. */ - assert(is_weak_box(*wb)); - *wb = _get_weak_box(*wb)->next; + /* Box hasn't been moved yet, but may live on as the value of a will. */ + assert(is_weak_box(wb)); + box = _get_weak_box(wb); } + + if (is_broken_heart(box->value)) + { + /* The value in the box is reachable; update w/ new location. */ + box->value = _get_object(box->value)->forward; + } + else if (is_object(box->value)) + { + /* The value in the box is an unreachable object; change to #f. */ + + /* Note that an object is considered unreachable via weak box when it could be finalized, + * even though it will be kept alive until the finalizer(s) is/are removed from the 'active' + * list and the finalizer(s) itself/themselves may restore the object to a reachable state. */ + + /* This last behavior is not recommended. */ + + box->value = FALSE_VALUE; + } + + /* Move on to this box's 'next' pointer */ + wb = box->next; } } @@ -577,6 +585,30 @@ static void process_wills(void) } } +static void update_weak_box_list(void) +{ + value_t *wb = &gc_weak_box_list; + + while (!is_nil(*wb)) + { + if (is_broken_heart(*wb)) + { + /* The box itself is reachable; need to update 'next' pointer to new location */ + *wb = _get_object(*wb)->forward; + + /* Move on to next box's 'next' pointer */ + assert(is_weak_box(*wb)); + wb = &_get_weak_box(*wb)->next; + } + else + { + /* Box is no longer reachable; remove it from the list by updating 'next' pointer. */ + assert(is_weak_box(*wb)); + *wb = _get_weak_box(*wb)->next; + } + } +} + #define GC_DEFLATE_SIZE (64*1024) static void update_soft_limit(size_t min_free) @@ -585,50 +617,56 @@ static void update_soft_limit(size_t min_free) size_t min_limit = bytes_used + min_free; size_t new_limit = (4 * min_limit) / 3; + if (gc_soft_limit > GC_DEFLATE_SIZE) + { + size_t deflate_limit = gc_soft_limit - GC_DEFLATE_SIZE; + + if (new_limit < deflate_limit) + new_limit = deflate_limit; + } + if (new_limit > gc_max_size) new_limit = gc_max_size; -#if 1 else if (new_limit < gc_min_size) new_limit = gc_min_size; - if (gc_soft_limit > GC_DEFLATE_SIZE) - { - if (new_limit < (gc_soft_limit - GC_DEFLATE_SIZE)) - new_limit = gc_soft_limit - GC_DEFLATE_SIZE; - } - gc_soft_limit = new_limit; -#else - if (new_limit > gc_soft_limit) - gc_soft_limit = new_limit; -#endif /* Update end of range to reflect new limit */ gc_range_end = gc_ranges[gc_current_range] + gc_soft_limit; +#ifndef NO_STATS if (gc_soft_limit > gc_stats.high_water) { gc_stats.high_water = gc_soft_limit; } +#endif } static void _collect_garbage(size_t min_free) { if (gc_enabled) { - struct timespec start_time; char *object_ptr; +#ifndef NO_STATS +#ifndef NO_TIMING_STATS + struct timespec start_time; clock_gettime(CLOCK_MONOTONIC, &start_time); +#endif + gc_stats.total_freed -= gc_free_space(); ++gc_stats.collections; +#endif //debug(("Collecting garbage...\n")); - /* Swap ranges; new "current" range is initially empty, old one is full */ swap_gc_ranges(); + + /* New "current" range is initially empty, old one is full */ object_ptr = gc_free_ptr; + /* Prime the pump */ transfer_roots(); /* Keep transferring until no more objects in the new range refer to the old one, @@ -638,20 +676,31 @@ static void _collect_garbage(size_t min_free) object_ptr += gc_align(transfer_children((object_t*)object_ptr)); } + /* These have to be examined after normal reachability has been determined */ process_weak_boxes(); process_wills(); /* Keep transferring until no more objects in the new range refer to the old one. * This is so that values which are otherwise unreachable, but have finalizers which - * may be able to reach them, are not collected prematurely. Note that these values may + * may be able to reach them, are not collected prematurely. process_wills() transfers + * the value of any will newly placed on the active list. Note that these values may * be finalized in any order, and that any weak references have already been cleared. */ while (object_ptr < gc_free_ptr) { object_ptr += gc_align(transfer_children((object_t*)object_ptr)); } + + update_weak_box_list(); + +#ifndef NDEBUG + /* Clear old range, to make it easier to detect bugs. */ + memset(gc_ranges[1-gc_current_range], 0, gc_soft_limit); +#endif //debug(("Finished collection with %d bytes to spare (out of %d bytes).\n", gc_free_space(), gc_soft_limit)); +#ifndef NO_STATS +#ifndef NO_TIMING_STATS { struct timespec end_time; nsec_t nsec; @@ -662,12 +711,15 @@ static void _collect_garbage(size_t min_free) nsec += (end_time.tv_nsec - start_time.tv_nsec); gc_stats.total_ns += nsec; - gc_stats.total_freed += gc_free_space(); if (nsec > gc_stats.max_ns) gc_stats.max_ns = nsec; } } +#endif + + gc_stats.total_freed += gc_free_space(); +#endif update_soft_limit(min_free); @@ -679,10 +731,9 @@ static void _collect_garbage(size_t min_free) void collect_garbage(size_t min_free) { - bool was_enabled = gc_enabled; - gc_enabled = true; + bool was_enabled = set_gc_enabled(true); _collect_garbage(min_free); - gc_enabled = was_enabled; + set_gc_enabled(was_enabled); } bool set_gc_enabled(bool enable)