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.
This commit is contained in:
Jesse D. McDonald 2009-11-09 09:47:36 -06:00
parent 4136b74e1b
commit 26819cafdc
1 changed files with 106 additions and 55 deletions

145
gc.c
View File

@ -342,6 +342,8 @@ void gc_init(size_t min_size, size_t max_size)
static inline void *_gc_alloc(size_t nbytes) static inline void *_gc_alloc(size_t nbytes)
{ {
void *p = gc_free_ptr; void *p = gc_free_ptr;
assert(nbytes == gc_align(nbytes));
assert(nbytes <= gc_free_space());
gc_free_ptr += nbytes; gc_free_ptr += nbytes;
return p; return p;
} }
@ -361,15 +363,11 @@ static void transfer_object(value_t *value)
{ {
if (is_object(*value)) if (is_object(*value))
{ {
object_t *obj; object_t *obj = _get_object(*value);
size_t nbytes; size_t nbytes;
void *newobj; void *newobj;
value_t new_value;
assert(gc_range_of(obj) != gc_current_range); assert(gc_range_of(obj) != gc_current_range);
assert(is_object(*value));
obj = _get_object(*value);
if (obj->tag == BROKEN_HEART) if (obj->tag == BROKEN_HEART)
{ {
@ -396,27 +394,26 @@ static void transfer_object(value_t *value)
nbytes = sizeof(will_t); nbytes = sizeof(will_t);
break; break;
case TYPE_TAG_BOX: case TYPE_TAG_BOX:
nbytes = sizeof(box_t);
break;
default: /* pair */ default: /* pair */
nbytes = sizeof(pair_t); nbytes = sizeof(pair_t);
break; break;
} }
newobj = _gc_alloc(gc_align(nbytes)); newobj = _gc_alloc(gc_align(nbytes));
memcpy(newobj, obj, nbytes); memcpy(newobj, obj, nbytes);
/* Keep the original tag bits (pair or object) */ /* Keep the original tag bits (pair or object) */
new_value = object_value(newobj) | (*value & 2);
obj->tag = BROKEN_HEART; obj->tag = BROKEN_HEART;
obj->forward = new_value; *value = obj->forward = (object_value(newobj) & ~2) | (*value & 2);
*value = new_value;
} }
} }
static size_t transfer_vector(vector_t *vec) static size_t transfer_vector(vector_t *vec)
{ {
assert(vec->tag == TYPE_TAG_VECTOR);
for (size_t i = 0; i < vec->size; ++i) for (size_t i = 0; i < vec->size; ++i)
transfer_object(&vec->elements[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) static size_t transfer_struct(struct_t *s)
{ {
assert(s->tag == TYPE_TAG_STRUCT);
transfer_object(&s->type); transfer_object(&s->type);
for (size_t i = 0; i < s->nslots; ++i) 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) static size_t transfer_will(will_t *w)
{ {
assert(w->tag == TYPE_TAG_WILL);
transfer_object(&w->finalizer); transfer_object(&w->finalizer);
/* Weak boxes are discarded when there are no other references, /* 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) 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))
{
/* The box itself is reachable; need to update 'next' pointer to new location */
weak_box_t *box; weak_box_t *box;
*wb = _get_object(*wb)->forward; if (is_broken_heart(wb))
assert(is_weak_box(*wb)); {
box = _get_weak_box(*wb); /* 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 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)) if (is_broken_heart(box->value))
{ {
/* The value in the box is also reachable; update w/ new location. */ /* The value in the box is reachable; update w/ new location. */
box->value = _get_object(box->value)->forward; box->value = _get_object(box->value)->forward;
} }
else if (is_object(box->value)) else if (is_object(box->value))
{ {
/* The value in the box is an unreachable object; change to #f */ /* 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; box->value = FALSE_VALUE;
} }
/* Move on to this box's 'next' pointer */ /* Move on to this box's 'next' pointer */
wb = &_get_weak_box(*wb)->next; wb = box->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;
}
} }
} }
@ -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) #define GC_DEFLATE_SIZE (64*1024)
static void update_soft_limit(size_t min_free) 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 min_limit = bytes_used + min_free;
size_t new_limit = (4 * min_limit) / 3; 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) if (new_limit > gc_max_size)
new_limit = gc_max_size; new_limit = gc_max_size;
#if 1
else if (new_limit < gc_min_size) else if (new_limit < gc_min_size)
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; 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 */ /* Update end of range to reflect new limit */
gc_range_end = gc_ranges[gc_current_range] + gc_soft_limit; gc_range_end = gc_ranges[gc_current_range] + gc_soft_limit;
#ifndef NO_STATS
if (gc_soft_limit > gc_stats.high_water) if (gc_soft_limit > gc_stats.high_water)
{ {
gc_stats.high_water = gc_soft_limit; gc_stats.high_water = gc_soft_limit;
} }
#endif
} }
static void _collect_garbage(size_t min_free) static void _collect_garbage(size_t min_free)
{ {
if (gc_enabled) if (gc_enabled)
{ {
struct timespec start_time;
char *object_ptr; char *object_ptr;
#ifndef NO_STATS
#ifndef NO_TIMING_STATS
struct timespec start_time;
clock_gettime(CLOCK_MONOTONIC, &start_time); clock_gettime(CLOCK_MONOTONIC, &start_time);
#endif
gc_stats.total_freed -= gc_free_space(); gc_stats.total_freed -= gc_free_space();
++gc_stats.collections; ++gc_stats.collections;
#endif
//debug(("Collecting garbage...\n")); //debug(("Collecting garbage...\n"));
/* Swap ranges; new "current" range is initially empty, old one is full */
swap_gc_ranges(); swap_gc_ranges();
/* New "current" range is initially empty, old one is full */
object_ptr = gc_free_ptr; object_ptr = gc_free_ptr;
/* Prime the pump */
transfer_roots(); transfer_roots();
/* Keep transferring until no more objects in the new range refer to the old one, /* 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)); 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_weak_boxes();
process_wills(); process_wills();
/* Keep transferring until no more objects in the new range refer to the old one. /* 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 * 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. */ * be finalized in any order, and that any weak references have already been cleared. */
while (object_ptr < gc_free_ptr) while (object_ptr < gc_free_ptr)
{ {
object_ptr += gc_align(transfer_children((object_t*)object_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)); //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; struct timespec end_time;
nsec_t nsec; nsec_t nsec;
@ -662,12 +711,15 @@ static void _collect_garbage(size_t min_free)
nsec += (end_time.tv_nsec - start_time.tv_nsec); nsec += (end_time.tv_nsec - start_time.tv_nsec);
gc_stats.total_ns += nsec; gc_stats.total_ns += nsec;
gc_stats.total_freed += gc_free_space();
if (nsec > gc_stats.max_ns) if (nsec > gc_stats.max_ns)
gc_stats.max_ns = nsec; gc_stats.max_ns = nsec;
} }
} }
#endif
gc_stats.total_freed += gc_free_space();
#endif
update_soft_limit(min_free); update_soft_limit(min_free);
@ -679,10 +731,9 @@ static void _collect_garbage(size_t min_free)
void collect_garbage(size_t min_free) void collect_garbage(size_t min_free)
{ {
bool was_enabled = gc_enabled; bool was_enabled = set_gc_enabled(true);
gc_enabled = true;
_collect_garbage(min_free); _collect_garbage(min_free);
gc_enabled = was_enabled; set_gc_enabled(was_enabled);
} }
bool set_gc_enabled(bool enable) bool set_gc_enabled(bool enable)