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)
|
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))
|
weak_box_t *box;
|
||||||
|
|
||||||
|
if (is_broken_heart(wb))
|
||||||
{
|
{
|
||||||
/* The box itself is reachable; need to update 'next' pointer to new location */
|
/* Box has been moved; get a pointer to the new location, but don't update list yet. */
|
||||||
weak_box_t *box;
|
value_t fw = _get_object(wb)->forward;
|
||||||
|
assert(is_weak_box(fw));
|
||||||
*wb = _get_object(*wb)->forward;
|
box = _get_weak_box(fw);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Box is no longer reachable; remove it from the list by updating 'next' pointer. */
|
/* Box hasn't been moved yet, but may live on as the value of a will. */
|
||||||
assert(is_weak_box(*wb));
|
assert(is_weak_box(wb));
|
||||||
*wb = _get_weak_box(*wb)->next;
|
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)
|
#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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue