2000-07-18
2000-07-18 05:48:20 by Martin Stjernholm <mast@lysator.liu.se>
-
45d87e3f47369de81ac6a7b01c06302558578902
(770 lines)
(+486/-284)
[
Show
| Annotate
]
Branch: 7.9
Fixed tricky gc problems by implementing a gc-tailored call stack that can
be rotated. That also made the whole thing quite a bit less hairy.
Rev: src/array.c:1.78
Rev: src/array.h:1.21
Rev: src/gc.c:1.107
Rev: src/gc.h:1.55
Rev: src/mapping.c:1.93
Rev: src/mapping.h:1.27
Rev: src/multiset.c:1.25
Rev: src/multiset.h:1.13
Rev: src/object.c:1.136
Rev: src/object.h:1.49
Rev: src/program.c:1.251
Rev: src/program.h:1.96
Rev: src/svalue.c:1.84
Rev: src/testsuite.in:1.314
29:
#include "block_alloc.h"
- RCSID("$Id: gc.c,v 1.106 2000/07/11 04:42:37 mast Exp $");
+ RCSID("$Id: gc.c,v 1.107 2000/07/18 05:48:20 mast Exp $");
/* Run garbage collect approximately every time
* 20 percent of all arrays, objects and programs is
41:
#define MAX_ALLOC_THRESHOLD 10000000
#define MULTIPLIER 0.9
#define MARKER_CHUNK_SIZE 1023
- #define REF_CYCLE_CHUNK_SIZE 32
+ #define GC_LINK_CHUNK_SIZE 31
/* The gc will free all things with no external references that isn't
* referenced by undestructed objects with destroy() lfuns (known as
82:
/* #define GC_VERBOSE */
/* #define GC_CYCLE_DEBUG */
+ /* #define GC_STACK_DEBUG */
+
#if defined(GC_VERBOSE) && !defined(PIKE_DEBUG)
#undef GC_VERBOSE
#endif
98:
struct pike_queue gc_mark_queue;
time_t last_gc;
- static struct marker rec_list = {0, 0, 0};
- struct marker *gc_rec_last = &rec_list;
- static struct marker *kill_list = 0;
+ struct gc_frame
+ {
+ struct gc_frame *back; /* Previous stack frame. */
+ void *data;
+ union {
+ struct { /* Pop frame. */
+ struct gc_frame *prev; /* Previous frame in rec_list. */
+ struct gc_frame *next; /* Next pointer in rec_list and kill_list. */
+ unsigned INT16 cycle; /* Cycle id number. */
+ } pop;
+ struct { /* Link frame. */
+ gc_cycle_check_cb *checkfn;
+ int weak;
+ } link;
+ } u;
+ unsigned INT16 frameflags;
+ };
+
+ #define GC_POP_FRAME 0x01
+ #define GC_WEAK_REF 0x02
+ #define GC_STRONG_REF 0x04
+ #define GC_OFF_STACK 0x08
+ #ifdef PIKE_DEBUG
+ #define GC_LINK_FREED 0x10
+ #define GC_FOLLOWED_NONSTRONG 0x20
+ #endif
+
+ #undef BLOCK_ALLOC_NEXT
+ #define BLOCK_ALLOC_NEXT back
+
+ BLOCK_ALLOC(gc_frame,GC_LINK_CHUNK_SIZE)
+
+ #define PREV(frame) ((frame)->u.pop.prev)
+ #define NEXT(frame) ((frame)->u.pop.next)
+ #define CYCLE(frame) ((frame)->u.pop.cycle)
+
+ #ifdef PIKE_DEBUG
+ #define CHECK_POP_FRAME(frame) do { \
+ if ((frame)->frameflags & GC_LINK_FREED) \
+ gc_fatal((frame)->data, 0, "Accessing freed gc_frame.\n"); \
+ if (!((frame)->frameflags & GC_POP_FRAME)) \
+ gc_fatal((frame)->data, 0, #frame " is not a pop frame.\n"); \
+ if (NEXT(PREV(frame)) != (frame)) \
+ gc_fatal((frame)->data, 0, \
+ "Pop frame pointers are inconsistent.\n"); \
+ } while (0)
+ #else
+ #define CHECK_POP_FRAME(frame) do {} while (0)
+ #endif
+
+ static struct gc_frame rec_list = {0, 0, {{0, 0, 0}}, GC_POP_FRAME};
+ static struct gc_frame *gc_rec_last = &rec_list, *gc_rec_top = 0;
+ static struct gc_frame *kill_list = 0;
+
static unsigned last_cycle;
size_t gc_ext_weak_refs;
- /* rec_list is a linked list of the markers currently being recursed
- * through in the cycle check pass. gc_rec_last points at the
- * innermost marker being visited. A new marker is linked in after
- * gc_rec_last, except when that's inside a cycle, in which case it's
- * linked in after that cycle. A cycle is always treated as one atomic
- * unit, e.g. it's either popped whole or not at all.
+ /* gc_frame objects are used as frames in a recursion stack during the
+ * cycle check pass. gc_rec_top points to the current top of the
+ * stack. When a thing is recursed, a pop frame is first pushed on the
+ * stack and then the gc_cycle_check_* function fills in with link
+ * frames for every reference the thing contains.
*
- * Two ranges of markers next to each other may swap places to break a
- * cyclic reference at a chosen point. Some markers thus get a place
- * earlier on the list even though their corresponding stack frames
- * are later than some other markers they have references to. These
- * markers are flagged to make them stay on the list when they're
- * popped from the stack. They'll get popped eventually since all
- * markers in the gap between the top one and it's previous
- * gc_rec_last point are popped.
+ * rec_list is a double linked list of the pop frames on the stack,
+ * and that list represents the current prospective destruct order.
+ * gc_rec_last points at the last frame in the list and new frames are
+ * linked in after it. A cycle is always treated as one atomic unit,
+ * e.g. it's either popped whole or not at all. That means that
+ * rec_list may contain frames that are no longer on the stack.
*
- * Markers for live objects are linked into the beginning of kill_list
+ * A range of frames which always ends at the end of the list, may be
+ * rotated a number of slots to break a cyclic reference at a chosen
+ * point. The stack of link frames are rotated simultaneously.
+ *
+ * Frames for live objects are linked into the beginning of kill_list
* when they're popped from rec_list.
*
* The cycle check functions might recurse another round through the
- * markers that have been recursed already, to propagate the GC_LIVE
+ * frames that have been recursed already, to propagate the GC_LIVE
* flag to things that have been found to be referenced from live
* objects. rec_list is not touched at all in this extra round.
*/
141:
return add_to_callback(&gc_callbacks, call, arg, free_func);
}
+ static void gc_cycle_pop(void *a);
-
+
+ #undef BLOCK_ALLOC_NEXT
+ #define BLOCK_ALLOC_NEXT next
+
#undef INIT_BLOCK
#ifdef PIKE_DEBUG
#define INIT_BLOCK(X) \
(X)->flags=(X)->refs=(X)->weak_refs=(X)->xrefs=0; \
(X)->saved_refs=-1; \
- (X)->cycle = (unsigned INT16) -1; \
- (X)->link = (struct marker *) -1;
+ (X)->frame = 0;
#else
#define INIT_BLOCK(X) \
- (X)->flags=(X)->refs=(X)->weak_refs=0;
+ (X)->flags=(X)->refs=(X)->weak_refs=0; \
+ (X)->frame = 0;
#endif
PTR_HASH_ALLOC(marker,MARKER_CHUNK_SIZE)
160: Inside #if defined(PIKE_DEBUG)
int gc_in_cycle_check = 0;
static unsigned weak_freed, checked, marked, cycle_checked, live_ref;
+ static unsigned max_gc_frames, num_gc_frames = 0;
static unsigned gc_extra_refs = 0;
void dump_gc_info(void)
407:
#endif
}
+ static void describe_gc_frame(struct gc_frame *l)
+ {
+ if (l->frameflags & GC_POP_FRAME)
+ fprintf(stderr, "back=%p, prev=%p, next=%p, data=%p, cycle=%u, flags=0x%02x",
+ l->back, PREV(l), NEXT(l), l->data, CYCLE(l), l->frameflags);
+ else
+ fprintf(stderr, "LINK back=%p, data=%p, weak=%d, flags=0x%02x",
+ l->back, l->data, l->u.link.weak, l->frameflags);
+ }
+
static void describe_marker(struct marker *m)
{
- if (m)
+ if (m) {
fprintf(stderr, "marker at %p: flags=0x%06x, refs=%d, weak=%d, "
- "xrefs=%d, saved=%d, cycle=%d, link=%p\n",
+ "xrefs=%d, saved=%d, frame=%p",
m, m->flags, m->refs, m->weak_refs,
- m->xrefs, m->saved_refs, m->cycle, m->link);
+ m->xrefs, m->saved_refs, m->frame);
+ #ifdef PIKE_DEBUG
+ if (m->frame) {
+ fputs(" [", stderr);
+ describe_gc_frame(m->frame);
+ putc(']', stderr);
+ }
+ #endif
+ putc('\n', stderr);
+ }
else
fprintf(stderr, "no marker\n");
}
749:
if (!(m->flags & GC_TOUCHED))
gc_fatal(a, 2, "An existing but untouched marker found "
"for object in linked lists.\n");
- else if (m->flags & (GC_ON_STACK|GC_IN_REC_LIST|GC_DONT_POP|
- GC_LIVE_RECURSE|GC_WEAK_REF|GC_STRONG_REF))
- gc_fatal(a, 2, "Marker still got flag from recurse list.\n");
+ else if (m->flags & GC_LIVE_RECURSE ||
+ (m->frame && m->frame->frameflags & (GC_WEAK_REF|GC_STRONG_REF)))
+ gc_fatal(a, 2, "Thing still got flag from recurse list.\n");
else if (m->flags & GC_MARKED)
return;
else if (!(m->flags & GC_NOT_REFERENCED) || m->flags & GC_XREFERENCED)
866:
static void init_gc(void)
{
init_marker_hash();
+ get_marker(rec_list.data); /* Used to simplify fencepost conditions. */
}
static void exit_gc(void)
878:
remove_marker(marker_hash_table[e]->data);
#endif
exit_marker_hash();
+ free_all_gc_frame_blocks();
+ #ifdef GC_VERBOSE
+ num_gc_frames = 0;
+ #endif
}
#ifdef PIKE_DEBUG
1030: Inside #if defined(PIKE_DEBUG)
if (!a) fatal("Got null pointer.\n");
if (Pike_in_gc != GC_PASS_MARK && Pike_in_gc != GC_PASS_CYCLE &&
- Pike_in_gc != GC_PASS_FREE)
+ Pike_in_gc != GC_PASS_ZAP_WEAK)
fatal("gc_do_weak_free() called in invalid gc pass.\n");
if (gc_debug) {
if (!(m = find_marker(a)))
1042: Inside #if defined(PIKE_DEBUG)
if (m->weak_refs > m->refs)
gc_fatal(a, 0, "More weak references than internal references.\n");
- if (Pike_in_gc != GC_PASS_FREE) {
+ if (Pike_in_gc != GC_PASS_ZAP_WEAK) {
if (m->weak_refs == -1) {
gc_ext_weak_refs--;
return 1;
1060: Inside #if defined(PIKE_DEBUG)
return 0;
}
+ void debug_really_free_gc_frame(struct gc_frame *l)
+ {
+ if (l->frameflags & GC_LINK_FREED)
+ gc_fatal(l->data, 0, "Freeing freed gc_frame.\n");
+ l->frameflags |= GC_LINK_FREED;
+ l->back = PREV(l) = NEXT(l) = (struct gc_frame *) -1;
+ really_free_gc_frame(l);
+ #ifdef GC_VERBOSE
+ num_gc_frames--;
+ #endif
+ }
+
+ #else /* PIKE_DEBUG */
+
+ #define debug_really_free_gc_frame(l) really_free_gc_frame(l)
+
#endif /* PIKE_DEBUG */
int gc_mark(void *a)
1068: Inside #if defined(PIKE_DEBUG)
#ifdef PIKE_DEBUG
if (!a) fatal("Got null pointer.\n");
- if (Pike_in_gc != GC_PASS_MARK && Pike_in_gc != GC_PASS_FREE)
+ if (Pike_in_gc != GC_PASS_MARK && Pike_in_gc != GC_PASS_ZAP_WEAK)
fatal("gc mark attempted in invalid pass.\n");
if (!*(INT32 *) a)
gc_fatal(a, 0, "Marked a thing without refs.\n");
if (m->weak_refs == -1)
gc_fatal(a, 0, "Marking thing scheduled for weak free.\n");
- if (Pike_in_gc == GC_PASS_FREE && !(m->flags & GC_MARKED))
- gc_fatal(a, 0, "gc_mark() called for thing in free pass "
+ if (Pike_in_gc == GC_PASS_ZAP_WEAK && !(m->flags & GC_MARKED))
+ gc_fatal(a, 0, "gc_mark() called for thing in zap weak pass "
"that wasn't marked before.\n");
#endif
- if (Pike_in_gc == GC_PASS_FREE)
- /* Things are visited in the free pass through the mark functions
- * to free refs to internal things that only got weak external
- * references. That happens only when a thing also have internal
- * cyclic non-weak refs. */
+ if (Pike_in_gc == GC_PASS_ZAP_WEAK)
+ /* Things are visited in the zap weak pass through the mark
+ * functions to free refs to internal things that only got weak
+ * external references. That happens only when a thing also have
+ * internal cyclic non-weak refs. */
if (m->flags & GC_FREE_VISITED)
return 0;
else {
1109:
}
}
+ void gc_cycle_enqueue(gc_cycle_check_cb *checkfn, void *data, int weak)
+ {
+ struct gc_frame *l = alloc_gc_frame();
+ #ifdef GC_VERBOSE
+ if (++num_gc_frames > max_gc_frames) max_gc_frames = num_gc_frames;
+ #endif
+ l->data = data;
+ l->u.link.checkfn = checkfn;
+ l->u.link.weak = weak;
+ l->frameflags = 0;
+ l->back = gc_rec_top;
+ #ifdef GC_STACK_DEBUG
+ fprintf(stderr, "enqueue %p [%p]: ", l, gc_rec_top);
+ describe_gc_frame(l);
+ fputc('\n', stderr);
+ #endif
+ gc_rec_top = l;
+ }
+
+ static struct gc_frame *gc_cycle_enqueue_pop(void *data)
+ {
+ struct gc_frame *l = alloc_gc_frame();
+ #ifdef GC_VERBOSE
+ if (++num_gc_frames > max_gc_frames) max_gc_frames = num_gc_frames;
+ #endif
+ l->data = data;
+ PREV(l) = gc_rec_last;
+ NEXT(l) = 0;
+ CYCLE(l) = 0;
+ l->frameflags = GC_POP_FRAME;
+ l->back = gc_rec_top;
+ #ifdef GC_STACK_DEBUG
+ fprintf(stderr, "enqueue %p [%p]: ", l, gc_rec_top);
+ describe_gc_frame(l);
+ fputc('\n', stderr);
+ #endif
+ gc_rec_top = l;
+ return l;
+ }
+
+ void gc_cycle_run_queue()
+ {
+ while (gc_rec_top) {
+ #ifdef GC_STACK_DEBUG
+ fprintf(stderr, "dequeue %p [%p]: ", gc_rec_top, gc_rec_top->back);
+ describe_gc_frame(gc_rec_top);
+ fputc('\n', stderr);
+ #endif
+ if (gc_rec_top->frameflags & GC_POP_FRAME) {
+ struct gc_frame *l = gc_rec_top->back;
+ gc_cycle_pop(gc_rec_top->data);
+ gc_rec_top = l;
+ } else {
+ struct gc_frame l = *gc_rec_top;
+ #ifdef PIKE_DEBUG
+ if (l.frameflags & GC_LINK_FREED)
+ gc_fatal(l.data, 0, "Accessing freed gc_frame.\n");
+ #endif
+ debug_really_free_gc_frame(gc_rec_top);
+ gc_rec_top = l.back;
+ l.u.link.checkfn(l.data, l.u.link.weak);
+ }
+ }
+ }
+
#ifdef GC_CYCLE_DEBUG
static int gc_cycle_indent = 0;
#define CYCLE_DEBUG_MSG(M, TXT) do { \
- fprintf(stderr, "%*s%-33s %p [%p] ", gc_cycle_indent, "", \
+ fprintf(stderr, "%*s%-35s %p [%p] ", gc_cycle_indent, "", \
(TXT), (M) ? (M)->data : 0, gc_rec_last->data); \
describe_marker(M); \
} while (0)
1120:
#define CYCLE_DEBUG_MSG(M, TXT) do {} while (0)
#endif
- static struct marker *break_cycle (struct marker *beg, struct marker *pos)
+ static void rotate_rec_list (struct gc_frame *beg, struct gc_frame *pos)
+ /* Rotates the marker list and the cycle stack so the bit from pos
+ * down to the end gets before the bit from beg down to pos. */
{
- /* There's a cycle from beg to gc_rec_last which should be broken at
- * pos. Do it by switching places of the markers before and after pos. */
- struct marker *p, *q;
+ struct gc_frame *l;
+
#ifdef PIKE_DEBUG
-
+ CHECK_POP_FRAME(beg);
+ CHECK_POP_FRAME(pos);
if (beg == pos)
gc_fatal(beg->data, 0, "Cycle already broken at requested position.\n");
-
+ if (NEXT(gc_rec_last))
+ gc_fatal(gc_rec_last->data, 0, "gc_rec_last not at end.\n");
#endif
- if (beg->cycle) {
- for (q = &rec_list; q->link->cycle != beg->cycle; q = q->link) {}
- beg = q->link;
- if (beg->cycle == pos->cycle) {
+ #ifdef GC_STACK_DEBUG
+ fprintf(stderr,"Stack before:\n");
+ for (l = gc_rec_top; l; l = l->back) {
+ fprintf(stderr, " %p ", l);
+ describe_gc_frame(l);
+ fputc('\n', stderr);
+ }
+ #endif
+
+ if (CYCLE(beg)) {
+ for (l = beg; CYCLE(PREV(l)) == CYCLE(beg); l = PREV(l))
+ CHECK_POP_FRAME(l);
+ CHECK_POP_FRAME(l);
+ if (CYCLE(l) == CYCLE(pos)) {
/* Breaking something previously marked as a cycle. Clear it
* since we're no longer sure it's an ambigious cycle. */
- unsigned cycle = beg->cycle;
- for (p = beg; p && p->cycle == cycle; p = p->link)
- p->cycle = 0;
+ unsigned cycle = CYCLE(l);
+ for (; l && CYCLE(l) == cycle; l = NEXT(l)) {
+ CHECK_POP_FRAME(l);
+ #ifdef GC_CYCLE_DEBUG
+ if (CYCLE(l))
+ CYCLE_DEBUG_MSG(find_marker(l->data), "> rotate_rec_list, clear cycle");
+ #endif
+ CYCLE(l) = 0;
}
}
- else
- for (q = &rec_list; q->link != beg; q = q->link) {}
+ else beg = l; /* Keep the cycle continuous. */
+ }
- q->link = pos;
+ CYCLE_DEBUG_MSG(find_marker(beg->data), "> rotate_rec_list, begin at");
- CYCLE_DEBUG_MSG(beg, "> break_cycle, begin at");
-
- for (p = beg; p->link != pos; p = p->link) {}
-
- for (q = pos;; q = q->link) {
- q->flags |= GC_MOVED_BACK|GC_DONT_POP;
- CYCLE_DEBUG_MSG(q, "> break_cycle, mark for don't pop");
- if (q == gc_rec_last) break;
+ {
+ struct gc_frame *b = beg, *p = pos, *old_rec_top;
+ while (b->frameflags & GC_OFF_STACK) {
+ if ((b = NEXT(b)) == pos) goto done;
+ CHECK_POP_FRAME(b);
}
-
+ while (p->frameflags & GC_OFF_STACK) {
+ if (!(p = NEXT(p))) goto done;
+ CHECK_POP_FRAME(p);
+ }
+ old_rec_top = gc_rec_top;
+ gc_rec_top = p->back;
+ p->back = b->back;
+ b->back = old_rec_top;
+ }
+ done:
{
- struct marker *m = q->link;
- q->link = beg;
- p->link = m;
+ struct gc_frame *new_rec_last = PREV(pos);
+ NEXT(PREV(beg)) = pos;
+ PREV(pos) = PREV(beg);
+ NEXT(gc_rec_last) = beg;
+ PREV(beg) = gc_rec_last;
+ gc_rec_last = new_rec_last;
+ NEXT(gc_rec_last) = 0;
}
- if (beg->flags & GC_WEAK_REF) {
- beg->flags &= ~GC_WEAK_REF;
- pos->flags |= GC_WEAK_REF;
- CYCLE_DEBUG_MSG(pos, "> break_cycle, moved weak flag");
+ if (beg->frameflags & GC_WEAK_REF) {
+ beg->frameflags &= ~GC_WEAK_REF;
+ pos->frameflags |= GC_WEAK_REF;
+ CYCLE_DEBUG_MSG(get_marker(pos->data), "> rotate_rec_list, moved weak flag");
}
- return beg;
+ #ifdef GC_STACK_DEBUG
+ fprintf(stderr,"Stack after:\n");
+ for (l = gc_rec_top; l; l = l->back) {
+ fprintf(stderr, " %p ", l);
+ describe_gc_frame(l);
+ fputc('\n', stderr);
}
-
+ #endif
+ }
int gc_cycle_push(void *x, struct marker *m, int weak)
{
-
+ struct marker *last = find_marker(gc_rec_last->data);
+
#ifdef PIKE_DEBUG
if (!x) fatal("Got null pointer.\n");
if (m->data != x) fatal("Got wrong marker.\n");
1208:
}
#endif
- if (gc_rec_last->flags & GC_LIVE_RECURSE) {
+ if (last->flags & GC_LIVE_RECURSE) {
#ifdef PIKE_DEBUG
- if (!(gc_rec_last->flags & GC_LIVE))
+ if (!(last->flags & GC_LIVE))
gc_fatal(x, 0, "Doing live recursion from a dead thing.\n");
#endif
1232:
int flags;
CYCLE_DEBUG_MSG(m, "gc_cycle_push, live rec done");
do {
- gc_rec_last->flags &= ~GC_LIVE_RECURSE;
+ last->flags &= ~GC_LIVE_RECURSE;
#ifdef GC_CYCLE_DEBUG
gc_cycle_indent -= 2;
- CYCLE_DEBUG_MSG(gc_rec_last, "> gc_cycle_push, unwinding live");
+ CYCLE_DEBUG_MSG(find_marker(gc_rec_last->data),
+ "> gc_cycle_push, unwinding live");
#endif
- gc_rec_last = (struct marker *)
- dequeue_lifo(&gc_mark_queue, (queue_call) gc_set_rec_last);
+ while (1) {
+ struct gc_frame *l = gc_rec_top;
#ifdef PIKE_DEBUG
- if (!gc_rec_last)
- fatal("Expected a gc_set_rec_last entry in gc_mark_queue.\n");
+ if (!gc_rec_top)
+ fatal("Expected a gc_cycle_pop entry in gc_rec_top.\n");
#endif
- } while (gc_rec_last->flags & GC_LIVE_RECURSE);
- if (!dequeue_lifo(&gc_mark_queue, (queue_call) gc_cycle_pop)) {
- #ifdef PIKE_DEBUG
- fatal("Expected a gc_cycle_pop entry in gc_mark_queue.\n");
- #endif
+ gc_rec_top = l->back;
+ if (l->frameflags & GC_POP_FRAME) {
+ gc_rec_last = PREV(l);
+ debug_really_free_gc_frame(l);
+ break;
}
-
+ debug_really_free_gc_frame(l);
}
-
+ last = find_marker(gc_rec_last->data);
+ } while (last->flags & GC_LIVE_RECURSE);
+ }
return 0;
}
#ifdef PIKE_DEBUG
- if (weak >= 0 && gc_rec_last->flags & GC_FOLLOWED_STRONG)
- gc_fatal(x, 0, "Followed strong link too early.\n");
- if (weak < 0) gc_rec_last->flags |= GC_FOLLOWED_STRONG;
+ if (weak < 0 && gc_rec_last->frameflags & GC_FOLLOWED_NONSTRONG)
+ gc_fatal(x, 0, "Followed strong link too late.\n");
+ if (weak >= 0) gc_rec_last->frameflags |= GC_FOLLOWED_NONSTRONG;
#endif
if (weak > 0) {
1269:
gc_ext_weak_refs--;
}
- if (m->flags & GC_IN_REC_LIST) { /* A cyclic reference is found. */
+ if (m->frame && !(m->frame->frameflags & GC_OFF_STACK)) {
+ /* A cyclic reference is found. */
#ifdef PIKE_DEBUG
- if (m == &rec_list || gc_rec_last == &rec_list)
+ if (gc_rec_last == &rec_list)
gc_fatal(x, 0, "Cyclic ref involves dummy rec_list marker.\n");
-
+ CHECK_POP_FRAME(gc_rec_last);
+ CHECK_POP_FRAME(m->frame);
#endif
- if (m != gc_rec_last) {
- struct marker *p, *weak_ref = 0, *nonstrong_ref = 0;
+ if (m != last) {
+ struct gc_frame *p, *weak_ref = 0, *nonstrong_ref = 0;
if (!weak) {
- struct marker *q;
+ struct gc_frame *q;
CYCLE_DEBUG_MSG(m, "gc_cycle_push, search normal");
- for (q = m, p = m->link; p; q = p, p = p->link) {
- if (p->flags & (GC_WEAK_REF|GC_STRONG_REF)) {
- if (p->flags & GC_WEAK_REF) weak_ref = p;
+ for (q = m->frame, p = NEXT(q);; q = p, p = NEXT(p)) {
+ CHECK_POP_FRAME(p);
+ if (p->frameflags & (GC_WEAK_REF|GC_STRONG_REF)) {
+ if (p->frameflags & GC_WEAK_REF) weak_ref = p;
else if (!nonstrong_ref) nonstrong_ref = q;
}
if (p == gc_rec_last) break;
1291:
else if (weak < 0) {
CYCLE_DEBUG_MSG(m, "gc_cycle_push, search strong");
- for (p = m->link; p; p = p->link) {
- if (p->flags & GC_WEAK_REF) weak_ref = p;
- if (!(p->flags & GC_STRONG_REF)) nonstrong_ref = p;
+ for (p = NEXT(m->frame);; p = NEXT(p)) {
+ CHECK_POP_FRAME(p);
+ if (p->frameflags & GC_WEAK_REF) weak_ref = p;
+ if (!(p->frameflags & GC_STRONG_REF)) nonstrong_ref = p;
if (p == gc_rec_last) break;
}
#ifdef PIKE_DEBUG
1303:
}
else {
- struct marker *q;
+ struct gc_frame *q;
CYCLE_DEBUG_MSG(m, "gc_cycle_push, search weak");
- for (q = m, p = m->link; p; q = p, p = p->link) {
- if (!(p->flags & GC_WEAK_REF) && !nonstrong_ref) nonstrong_ref = q;
+ for (q = m->frame, p = NEXT(q);; q = p, p = NEXT(p)) {
+ CHECK_POP_FRAME(p);
+ if (!(p->frameflags & GC_WEAK_REF) && !nonstrong_ref)
+ nonstrong_ref = q;
if (p == gc_rec_last) break;
}
}
- if (p == gc_rec_last) { /* It was a backward reference. */
+
if (weak_ref) {
/* The backward link is normal or strong and there are one
* or more weak links in the cycle. Let's break it at the
* last one (to ensure that a sequence of several weak links
* are broken at the last one). */
- CYCLE_DEBUG_MSG(weak_ref, "gc_cycle_push, weak break");
- break_cycle (m, weak_ref);
+ CYCLE_DEBUG_MSG(find_marker(weak_ref->data),
+ "gc_cycle_push, weak break");
+ rotate_rec_list(m->frame, weak_ref);
}
else if (weak < 0) {
/* The backward link is strong. Must break the cycle at the
* last nonstrong link. */
- if (m->flags & GC_STRONG_REF)
- nonstrong_ref->flags =
- (nonstrong_ref->flags & ~GC_WEAK_REF) | GC_STRONG_REF;
+ if (m->frame->frameflags & GC_STRONG_REF)
+ nonstrong_ref->frameflags =
+ (nonstrong_ref->frameflags & ~GC_WEAK_REF) | GC_STRONG_REF;
else
- m->flags = (m->flags & ~GC_WEAK_REF) | GC_STRONG_REF;
- CYCLE_DEBUG_MSG(nonstrong_ref, "gc_cycle_push, nonstrong break");
- break_cycle (m, nonstrong_ref);
+ m->frame->frameflags =
+ (m->frame->frameflags & ~GC_WEAK_REF) | GC_STRONG_REF;
+ CYCLE_DEBUG_MSG(find_marker(nonstrong_ref->data),
+ "gc_cycle_push, nonstrong break");
+ rotate_rec_list(m->frame, nonstrong_ref);
}
else if (nonstrong_ref) {
/* Either a nonweak cycle with a strong link in it or a weak
* cycle with a nonweak link in it. Break before the first
* link that's stronger than the others. */
- if (nonstrong_ref != m) {
- CYCLE_DEBUG_MSG(nonstrong_ref, "gc_cycle_push, weaker break");
- break_cycle (m, nonstrong_ref);
+ if (nonstrong_ref != m->frame) {
+ CYCLE_DEBUG_MSG(find_marker(nonstrong_ref->data),
+ "gc_cycle_push, weaker break");
+ rotate_rec_list(m->frame, nonstrong_ref);
}
}
1347:
/* A normal cycle which will be destructed in arbitrary
* order. For reasons having to do with strong links we
* can't mark weak cycles this way. */
- unsigned cycle = m->cycle ? m->cycle : ++last_cycle;
- if (cycle == gc_rec_last->cycle)
+ unsigned cycle = CYCLE(m->frame) ? CYCLE(m->frame) : ++last_cycle;
+ if (cycle == CYCLE(gc_rec_last))
CYCLE_DEBUG_MSG(m, "gc_cycle_push, old cycle");
else {
- unsigned replace_cycle = gc_rec_last->cycle;
+
CYCLE_DEBUG_MSG(m, "gc_cycle_push, cycle");
- for (p = m;; p = p->link) {
- p->cycle = cycle;
- CYCLE_DEBUG_MSG(p, "> gc_cycle_push, mark cycle");
+ for (p = m->frame;; p = NEXT(p)) {
+ CYCLE(p) = cycle;
+ CYCLE_DEBUG_MSG(find_marker(p->data), "> gc_cycle_push, mark cycle");
if (p == gc_rec_last) break;
- }
- if (replace_cycle && replace_cycle != cycle)
- for (p = p->link; p && p->cycle == replace_cycle; p = p->link) {
- p->cycle = cycle;
- CYCLE_DEBUG_MSG(p, "> gc_cycle_push, re-mark cycle");
- }}}} /* Mmm.. lisp ;) */
+ }}}}} /* Mmm.. lisp ;) */
- else { /* A forward reference. */
- CYCLE_DEBUG_MSG(m, "gc_cycle_push, forward ref");
- if (m->flags & GC_ON_STACK &&
- !(gc_rec_last->cycle && gc_rec_last->cycle == m->cycle)) {
- /* Since it's not part of the same cycle it's a reference to
- * a marker that has been swapped further down the list by
- * break_cycle(). In that case we must mark gc_rec_last to
- * stay on the list. */
- gc_rec_last->flags |= GC_DONT_POP;
- CYCLE_DEBUG_MSG(gc_rec_last, "gc_cycle_push, mark for don't pop");
- }
- }
- }
- }
-
+
else
if (!(m->flags & GC_CYCLE_CHECKED)) {
- struct marker *p;
- m->flags |= gc_rec_last->flags & GC_LIVE;
- if (weak) {
- if (weak > 0) m->flags |= GC_WEAK_REF;
- else m->flags |= GC_STRONG_REF;
- }
+ struct gc_frame *l, *prev_rec_last = gc_rec_last;
#ifdef PIKE_DEBUG
cycle_checked++;
- if (m->flags & GC_ON_STACK)
- gc_fatal(x, 0, "Recursing twice into thing.\n");
- if (m->flags & GC_LIVE_RECURSE)
- gc_fatal(x, 0, "GC_LIVE_RECURSE set in normal recursion.\n");
+ if (m->frame)
+ gc_fatal(x, 0, "Marker already got a frame.\n");
+ if (NEXT(gc_rec_last))
+ gc_fatal(gc_rec_last->data, 0, "Not at end of list.\n");
#endif
- p = gc_rec_last;
- if (p->cycle)
- for (; p->link && p->link->cycle == p->cycle; p = p->link) {}
- m->link = p->link;
- p->link = m;
- m->flags |= GC_CYCLE_CHECKED|GC_ON_STACK|GC_IN_REC_LIST;
- m->cycle = 0;
+ NEXT(gc_rec_last) = m->frame = l = gc_cycle_enqueue_pop(x);
+ m->flags |= GC_CYCLE_CHECKED | (last->flags & GC_LIVE);
+ if (weak) {
+ if (weak > 0) l->frameflags |= GC_WEAK_REF;
+ else l->frameflags |= GC_STRONG_REF;
+ }
#ifdef GC_CYCLE_DEBUG
if (weak > 0) CYCLE_DEBUG_MSG(m, "gc_cycle_push, recurse weak");
1409: Inside #if defined(GC_CYCLE_DEBUG)
else CYCLE_DEBUG_MSG(m, "gc_cycle_push, recurse");
gc_cycle_indent += 2;
#endif
- gc_rec_last = m;
+ gc_rec_last = l;
return 1;
}
/* Should normally not recurse now, but got to do that anyway if we
* must mark live things. */
- if (!(gc_rec_last->flags & GC_LIVE) || m->flags & GC_LIVE) {
+ if (!(last->flags & GC_LIVE) || m->flags & GC_LIVE) {
CYCLE_DEBUG_MSG(m, "gc_cycle_push, no recurse");
return 0;
}
1438:
}
}
- /* Recurse without linking m onto rec_list. */
+ {
+ /* Recurse without linking onto rec_list. */
+ struct gc_frame *l = gc_cycle_enqueue_pop(x);
#ifdef GC_CYCLE_DEBUG
CYCLE_DEBUG_MSG(m, "gc_cycle_push, live recurse");
gc_cycle_indent += 2;
#endif
- gc_rec_last = m;
- return 1;
+ gc_rec_last = l;
}
- /* Add an extra ref when a dead thing is popped. It's taken away in
- * the free pass. This is done to not refcount garb the cycles
- * themselves recursively, which in bad cases can consume a lot of C
- * stack. */
- #define ADD_REF_IF_DEAD(M) \
- if (!(M->flags & GC_LIVE)) { \
- DO_IF_DEBUG( \
- if (M->flags & GC_GOT_DEAD_REF) \
- gc_fatal(M->data, 0, "A thing already got an extra dead cycle ref.\n"); \
- ); \
- gc_add_extra_ref(M->data); \
- M->flags |= GC_GOT_DEAD_REF; \
+ return 1;
}
- void gc_cycle_pop(void *a)
+ static void gc_cycle_pop(void *a)
{
- struct marker *m = find_marker(a), *p, *q, *base;
+ struct marker *m = find_marker(a);
+ struct gc_frame *here, *base, *p;
#ifdef PIKE_DEBUG
-
+ if (!a) fatal("Got null pointer.\n");
if (Pike_in_gc != GC_PASS_CYCLE)
fatal("GC cycle pop attempted in invalid pass.\n");
if (!(m->flags & GC_CYCLE_CHECKED))
1483:
if (m->flags & GC_LIVE_RECURSE) {
m->flags &= ~GC_LIVE_RECURSE;
- CYCLE_DEBUG_MSG(m, "gc_cycle_pop, live pop");
+ CYCLE_DEBUG_MSG(m, "gc_cycle_pop_live");
+ gc_rec_last = PREV(gc_rec_top);
+ debug_really_free_gc_frame(gc_rec_top);
return;
}
-
+ here = m->frame;
#ifdef PIKE_DEBUG
- if (!(m->flags & GC_ON_STACK))
+ if (!here || here->data != a)
+ gc_fatal(a, 0, "Marker being popped has no or invalid frame.\n");
+ CHECK_POP_FRAME(here);
+ CHECK_POP_FRAME(gc_rec_last);
+ if (here->frameflags & GC_OFF_STACK)
gc_fatal(a, 0, "Marker being popped isn't on stack.\n");
- if (m->flags & GC_GOT_DEAD_REF)
- gc_fatal(a, 0, "Didn't expect a dead extra ref.\n");
+ here->back = (struct gc_frame *) -1;
#endif
- m->flags &= ~GC_ON_STACK;
+ here->frameflags |= GC_OFF_STACK;
- if (m->flags & GC_DONT_POP) {
- #ifdef PIKE_DEBUG
- if (!m->link)
- gc_fatal(a, 0, "Found GC_DONT_POP marker on top of list.\n");
- #endif
- CYCLE_DEBUG_MSG(m, "gc_cycle_pop, keep in list");
- if (!(m->flags & GC_MOVED_BACK)) {
- gc_rec_last->flags |= GC_DONT_POP;
- CYCLE_DEBUG_MSG(gc_rec_last, "gc_cycle_pop, propagate don't pop");
- }
- return;
- }
-
- for (base = gc_rec_last, p = base->link;; base = p, p = p->link) {
- if (base == m) {
- #ifdef GC_CYCLE_DEBUG
+ for (base = PREV(here), p = here;; base = p, p = NEXT(p)) {
+ if (base == here) {
+ /* Part of a cycle; wait until the cycle is complete before
+ * unlinking it from rec_list. */
+ DO_IF_DEBUG(m->frame->back = (struct gc_frame *) -1);
CYCLE_DEBUG_MSG(m, "gc_cycle_pop, keep cycle");
- #endif
+
return;
}
- if (!(p->cycle && p->cycle == base->cycle))
+ CHECK_POP_FRAME(p);
+ if (!(CYCLE(p) && CYCLE(p) == CYCLE(base)))
break;
}
- q = base;
- while (1) {
- p = q->link;
+ gc_rec_last = base;
+ while ((p = NEXT(base))) {
+ struct marker *pm = find_marker(p->data);
#ifdef PIKE_DEBUG
- if (p->flags & (GC_ON_STACK|GC_LIVE_RECURSE))
- gc_fatal(p->data, 0, "Marker still got an on-stack flag at pop.\n");
- #endif
-
- if (!p->cycle && !(p->flags & GC_LIVE_OBJ)) {
- ADD_REF_IF_DEAD(p);
- p->flags &= ~(GC_IN_REC_LIST|GC_DONT_POP|GC_WEAK_REF|GC_STRONG_REF);
- q->link = p->link;
- DO_IF_DEBUG(p->link = (struct marker *) -1);
- CYCLE_DEBUG_MSG(p, "gc_cycle_pop, pop off");
- }
- else q = p;
-
- if (p == m) break;
- }
-
- if (base != q) {
- q = base;
- while ((p = q->link)) {
- #ifdef PIKE_DEBUG
- if (!(p->flags & GC_IN_REC_LIST))
- gc_fatal(p->data, 0, "Marker being cycle popped doesn't have GC_IN_REC_LIST.\n");
- if (p->flags & GC_GOT_DEAD_REF)
+ if (pm->frame != p)
+ gc_fatal(p->data, 0, "Bogus marker for thing being popped.\n");
+ if (pm->flags & GC_GOT_DEAD_REF)
gc_fatal(p->data, 0, "Didn't expect a dead extra ref.\n");
#endif
- p->flags &= ~GC_IN_REC_LIST;
-
- p->flags &= ~(GC_DONT_POP|GC_WEAK_REF|GC_STRONG_REF);
- if (p->flags & GC_LIVE_OBJ) {
+ p->frameflags &= ~(GC_WEAK_REF|GC_STRONG_REF);
+ if (pm->flags & GC_LIVE_OBJ) {
/* This extra ref is taken away in the kill pass. */
gc_add_extra_ref(p->data);
- q = p;
- CYCLE_DEBUG_MSG(p, "gc_cycle_pop, put on kill list");
+ base = p;
+ DO_IF_DEBUG(PREV(p) = (struct gc_frame *) -1);
+ CYCLE_DEBUG_MSG(pm, "gc_cycle_pop, put on kill list");
}
else {
- ADD_REF_IF_DEAD(p);
- q->link = p->link;
- DO_IF_DEBUG(p->link = (struct marker *) -1);
- CYCLE_DEBUG_MSG(p, "gc_cycle_pop, cycle pop off");
+ if (!(pm->flags & GC_LIVE)) {
+ /* Add an extra ref which is taken away in the free pass. This
+ * is done to not refcount garb the cycles themselves
+ * recursively, which in bad cases can consume a lot of C
+ * stack. */
+ #ifdef PIKE_DEBUG
+ if (pm->flags & GC_GOT_DEAD_REF)
+ gc_fatal(pm->data, 0,
+ "A thing already got an extra dead cycle ref.\n");
+ #endif
+ gc_add_extra_ref(pm->data);
+ pm->flags |= GC_GOT_DEAD_REF;
}
-
+ NEXT(base) = NEXT(p);
+ CYCLE_DEBUG_MSG(pm, "gc_cycle_pop, pop off");
+ pm->frame = 0;
+ debug_really_free_gc_frame(p);
}
-
+ }
- q->link = kill_list;
- kill_list = base->link;
- base->link = p;
+ if (base != gc_rec_last) {
+ NEXT(base) = kill_list;
+ kill_list = NEXT(gc_rec_last);
+ NEXT(gc_rec_last) = 0;
}
}
- void gc_set_rec_last(struct marker *m)
- {
- gc_rec_last = m;
- }
-
+
void do_gc_recurse_svalues(struct svalue *s, int num)
{
gc_recurse_svalues(s, num);
1622:
struct array *obj_arr = 0;
if (!SETJMP(uwp)) {
- struct marker *p;
+ struct gc_frame *p;
unsigned cycle = 0;
obj_arr = allocate_array(0);
for (p = kill_list; p;) {
- if ((cycle = p->cycle)) {
+ if ((cycle = CYCLE(p))) {
push_object((struct object *) p->data);
obj_arr = append_array(obj_arr, --sp);
}
- p = p->link;
- if (p ? p->cycle != cycle : cycle) {
+ p = NEXT(p);
+ if (p ? CYCLE(p) != cycle : cycle) {
if (obj_arr->size >= 2) {
push_constant_text("gc");
push_constant_text("bad_cycle");
1786: Inside #if defined(PIKE_DEBUG)
Pike_in_gc=GC_PASS_CYCLE;
#ifdef PIKE_DEBUG
obj_count = num_objects;
+ max_gc_frames = 0;
#endif
/* Now find all cycles in the internal structures */
1799:
gc_cycle_check_all_programs();
#ifdef PIKE_DEBUG
- if (gc_mark_queue.first)
- fatal("gc_mark_queue not empty at end of cycle check pass.\n");
- if (rec_list.link || gc_rec_last != &rec_list)
+ if (gc_rec_top)
+ fatal("gc_rec_top not empty at end of cycle check pass.\n");
+ if (NEXT(&rec_list) || gc_rec_last != &rec_list || gc_rec_top)
fatal("Recurse list not empty or inconsistent after cycle check pass.\n");
#endif
GC_VERBOSE_DO(fprintf(stderr,
"| cycle: %u internal things visited, %u cycle ids used,\n"
- "| %u weak references freed, %d things really freed\n",
- cycle_checked, last_cycle, weak_freed, obj_count - num_objects));
+ "| %u weak references freed, %d things really freed,\n"
+ "| space for %u gc frames used\n",
+ cycle_checked, last_cycle, weak_freed, obj_count - num_objects,
+ max_gc_frames));
-
+ if (gc_ext_weak_refs) {
+ size_t to_free = gc_ext_weak_refs;
+ #ifdef PIKE_DEBUG
+ obj_count = num_objects;
+ #endif
+ Pike_in_gc = GC_PASS_ZAP_WEAK;
+ /* Zap weak references from external to internal things. That
+ * doesn't occur very often; only when something have both
+ * external weak refs and nonweak cyclic refs from internal
+ * things. */
+ gc_zap_ext_weak_refs_in_arrays();
+ /* Multisets handled as arrays. */
+ if (gc_ext_weak_refs) {
+ gc_zap_ext_weak_refs_in_mappings();
+ if (gc_ext_weak_refs) {
+ gc_zap_ext_weak_refs_in_programs();
+ if (gc_ext_weak_refs)
+ gc_zap_ext_weak_refs_in_objects();
+ }
+ }
+ GC_VERBOSE_DO(
+ fprintf(stderr,
+ "| zap weak: freed %u external weak refs, %d things really freed\n",
+ to_free - gc_ext_weak_refs, obj_count - num_objects));
+ }
+
/* Thread switches, object alloc/free and reference changes are
* allowed again now. */
1851: Inside #if defined(PIKE_DEBUG)
#ifdef PIKE_DEBUG
destroy_count = 0;
#endif
- for (; kill_list; kill_list = kill_list->link) {
+ for (; kill_list; kill_list = NEXT(kill_list)) {
struct object *o = (struct object *) kill_list->data;
#ifdef PIKE_DEBUG
- if ((kill_list->flags & (GC_LIVE|GC_LIVE_OBJ)) != (GC_LIVE|GC_LIVE_OBJ))
+ if ((get_marker(kill_list->data)->flags & (GC_LIVE|GC_LIVE_OBJ)) !=
+ (GC_LIVE|GC_LIVE_OBJ))
gc_fatal(o, 0, "Invalid thing in kill list.\n");
-
+ if (o->parent && !o->parent->prog &&
+ get_marker(o->parent)->flags & GC_LIVE_OBJ)
+ gc_fatal(o, 0, "GC destructed parent prematurely.\n");
#endif
GC_VERBOSE_DO(fprintf(stderr, "| Killing %p with %d refs\n",
o, o->refs));