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:
parent
4136b74e1b
commit
26819cafdc
161
gc.c
161
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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue