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)
{
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))
{
/* 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(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))
{
/* 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;
}
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;
}
/* Move on to this box's 'next' pointer */
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;
}
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)