Branch: Tag:

2000-06-12

2000-06-12 03:21:11 by Martin Stjernholm <mast@lysator.liu.se>

Better logic in cycle check pass to ensure that no thing is recursed more
than twice.

Rev: src/gc.c:1.91
Rev: src/gc.h:1.47

29:      #include "block_alloc.h"    - RCSID("$Id: gc.c,v 1.90 2000/06/11 11:59:47 mast Exp $"); + RCSID("$Id: gc.c,v 1.91 2000/06/12 03:21:11 mast Exp $");      /* Run garbage collect approximately every time    * 20 percent of all arrays, objects and programs is
98:   struct pike_queue gc_mark_queue;   time_t last_gc;    - static struct marker rec_list = { -  0, 0, 0, 0, 0, - #ifdef PIKE_DEBUG -  0, 0, - #endif -  0, GC_RECURSING - }; + static struct marker rec_list = {0, 0, 0};   struct marker *gc_rec_last = &rec_list;   static struct marker *kill_list = 0;   static unsigned last_cycle;    -  + /* 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. +  * +  * 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 given the GC_DONT_POP flag 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. +  * +  * Markers 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 +  * flag to things that have been found to be referenced from live +  * objects. rec_list is not touched at all in this extra round. +  */ +    static double objects_alloced = 0.0;   static double objects_freed = 0.0;   
729:    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_RECURSING|GC_LIVE_RECURSE|GC_WEAK_REF|GC_STRONG_REF)) +  else if (m->flags & (GC_RECURSING|GC_LIVE_RECURSE|GC_DONT_POP| +  GC_WEAK_REF|GC_STRONG_REF))    gc_fatal(a, 2, "Marker still got flag from recurse list.\n");    else if (m->flags & GC_REFERENCED)    return;
1027: Inside #if defined(GC_CYCLE_DEBUG)
     #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, "", \ +  (TXT), (M)->data, gc_rec_last->data); \ +  describe_marker(M); \ + } while (0) + #else + #define CYCLE_DEBUG_MSG(M, TXT) do {} while (0)   #endif      static void break_cycle (struct marker *beg, struct marker *pos)   {    /* There's a cycle from beg to gc_rec_last which should be broken at -  * pos. Do it by removing the things from beg down to pos, to let -  * them be handled again after gc_rec_last. (It's possible to be -  * smarter here and put those things after gc_rec_last to avoid -  * recursing through them again, but then it becomes tricky to know -  * where the "stack top" is.) */ -  +  * pos. Do it by switching places of the markers before and after pos. */    struct marker *p, *q; - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sbreak cycle: %8p, [%8p] ", -  gc_cycle_indent, "", beg->data, gc_rec_last); -  describe_marker(beg); - #endif +  CYCLE_DEBUG_MSG(pos, "break_cycle");   #ifdef PIKE_DEBUG    if (beg == pos)    gc_fatal(beg->data, 0, "Cycle already broken at requested position.\n");
1055:    gc_fatal(pos->data, 0, "Same cycle on both sides of broken link.\n");   #endif    for (p = &rec_list; p->link->cycle != beg->cycle; p = p->link) {} +  beg = p->link;    }    else    for (p = &rec_list; p->link != beg; p = p->link) {}    -  q = p->link; +     p->link = pos;    -  while (q != pos) { -  q->flags &= ~(GC_CYCLE_CHECKED|GC_RECURSING| -  GC_WEAK_REF|GC_STRONG_REF|GC_FOLLOWED_NONSTRONG); -  q->cycle = 0; - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sreset marker: " -  "%8p, ", gc_cycle_indent, "", q->data); -  describe_marker(q); - #endif - #ifdef PIKE_DEBUG -  if (q->flags & (GC_GOT_DEAD_REF|GC_GOT_EXTRA_REF)) -  gc_fatal(q->data, 0, "Didn't expect an extra ref at reset.\n"); -  p = q->link; -  q->link = (struct marker *) -1; -  q = p; - #else -  q = q->link; - #endif +  for (p = beg; p->link != pos; p = p->link) {} +  +  for (q = pos;; q = q->link) { +  q->flags |= GC_DONT_POP; +  CYCLE_DEBUG_MSG(q, "break_cycle, mark for don't pop"); +  if (q == gc_rec_last) break;    } -  +  +  { +  struct marker *m = q->link; +  q->link = beg; +  p->link = m;    } -  + }      int gc_cycle_push(void *x, struct marker *m, int weak)   {
1134:   #endif    goto live_recurse;    } - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, no live recurse: %8p, [%8p] ", -  gc_cycle_indent, "", x, gc_rec_last); -  describe_marker(m); - #endif +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, no live recurse");    }       else {    /* Nothing more to do. Unwind the live recursion. */    int flags; - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, live rec done: %8p, [%8p] ", -  gc_cycle_indent, "", x, gc_rec_last); -  describe_marker(m); - #endif +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, live rec done");    do {    gc_rec_last->flags &= ~GC_LIVE_RECURSE;   #ifdef GC_CYCLE_DEBUG    gc_cycle_indent -= 2; -  fprintf(stderr, "%*sgc_cycle_push, unwinding live: " -  "%8p, ", gc_cycle_indent, "", gc_rec_last->data); -  describe_marker(gc_rec_last); +  CYCLE_DEBUG_MSG(gc_rec_last, "gc_cycle_push, unwinding live");   #endif    gc_rec_last = (struct marker *)    dequeue_lifo(&gc_mark_queue, (queue_call) gc_set_rec_last);
1164:    fatal("Expected a gc_set_rec_last entry in gc_mark_queue.\n");   #endif    } while (gc_rec_last->flags & GC_LIVE_RECURSE); -  if (!dequeue_lifo(&gc_mark_queue, (queue_call) gc_cycle_pop_object)) { +  if (!dequeue_lifo(&gc_mark_queue, (queue_call) gc_cycle_pop)) {   #ifdef PIKE_DEBUG -  fatal("Expected a gc_cycle_pop_object entry in gc_mark_queue.\n"); +  fatal("Expected a gc_cycle_pop entry in gc_mark_queue.\n");   #endif    }    }
1174:    return 0;    }    -  if (!(gc_rec_last->flags & GC_RECURSING)) -  /* The upward thing has been removed from rec_list, so we should -  * ignore it and not do any recursion from it. */ -  return 0; -  +    #ifdef PIKE_DEBUG    if (weak < 0 && gc_rec_last->flags & GC_FOLLOWED_NONSTRONG)    gc_fatal(x, 0, "Followed strong link too late.\n");
1219:    }    }    -  if (p) { /* It was a backward reference. */ +  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). */ - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, weak break: %8p, [%8p] ", -  gc_cycle_indent, "", weak_ref->data, gc_rec_last); -  describe_marker(weak_ref); - #endif +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, weak break");    break_cycle (m, weak_ref); -  goto normal_recurse; +     }       else if (weak < 0) {    /* The backward link is strong. Must break the cycle at the    * last nonstrong link. */ - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, nonstrong break: %8p, [%8p] ", -  gc_cycle_indent, "", nonstrong_ref->data, gc_rec_last); -  describe_marker(nonstrong_ref); - #endif +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, nonstrong break");    break_cycle (m, nonstrong_ref);    if (m->flags & GC_STRONG_REF) nonstrong_ref->flags |= GC_STRONG_REF; -  goto normal_recurse; +     }       else if (nonstrong_ref) {
1252:    * cycle with a nonweak link in it. Break before the first    * link that's stronger than the others. */    if (nonstrong_ref != m) { - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, weaker break: %8p, [%8p] ", -  gc_cycle_indent, "", nonstrong_ref->data, gc_rec_last); -  describe_marker(nonstrong_ref); - #endif +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, weaker break");    break_cycle (m, nonstrong_ref); -  goto normal_recurse; +     }    }   
1266:    /* A normal or weak cycle which will be destructed in    * arbitrary order. */    unsigned cycle = m->cycle ? m->cycle : ++last_cycle; - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, cycle: %8p, [%8p] ", -  gc_cycle_indent, "", x, gc_rec_last); -  describe_marker(m); - #endif -  for (p = m; p; p = p->link) { +  if (cycle == gc_rec_last->cycle) +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, old cycle"); +  else { +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, cycle"); +  for (p = m;; p = p->link) {    p->cycle = cycle; - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, mark cycle: " -  "%8p, ", gc_cycle_indent, "", p->data); -  describe_marker(p); - #endif -  }}}}} /* Mmm.. lisp ;) */ +  CYCLE_DEBUG_MSG(p, "gc_cycle_push, mark cycle"); +  if (p == gc_rec_last) break; +  }}}} /* Mmm.. lisp ;) */    -  +  else { /* A forward reference. */ +  /* It might be a reference to a marker that has been swapped +  * further down the list by break_cycle(). In that case we +  * must mark this path to stay on the list. */ +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, forward ref"); +  for (p = rec_list.link; !(p->flags & GC_DONT_POP); p = p->link) +  if (p == gc_rec_last) goto dont_stay_on_list; +  for (;; p = p->link) { +  p->flags |= GC_DONT_POP; +  CYCLE_DEBUG_MSG(p, "gc_cycle_push, mark for don't pop"); +  if (p == gc_rec_last) break; +  } +  dont_stay_on_list: +  } +  } +  } +     else    if (!(m->flags & GC_CYCLE_CHECKED)) {    struct marker *p; -  normal_recurse: +     m->flags |= gc_rec_last->flags & GC_LIVE;    if (weak) {    if (weak > 0) m->flags |= GC_WEAK_REF;
1294: Inside #if defined(PIKE_DEBUG)
   if (m->flags & GC_LIVE_RECURSE)    gc_fatal(x, 0, "GC_LIVE_RECURSE set in normal recursion.\n");   #endif -  m->cycle = 0; -  m->flags |= GC_CYCLE_CHECKED|GC_RECURSING; -  m->link = 0; +     -  /* Must add another variable to push and pop on gc_mark_queue to -  * avoid this loop, and that could give more overhead than this. -  * The linked list normally doesn't get very long anyway. */ -  for (p = gc_rec_last; p->link; p = p->link) {} +  p = gc_rec_last; +  if (gc_rec_last->cycle) +  for (; p->link && p->link->cycle == gc_rec_last->cycle; p = p->link) {} +  m->link = p->link;    p->link = m; -  +  m->flags |= GC_CYCLE_CHECKED|GC_RECURSING; +  m->cycle = 0;      #ifdef GC_CYCLE_DEBUG -  fprintf(stderr,"%*sgc_cycle_push, recurse%s %8p, [%8p] ", -  gc_cycle_indent, "", -  weak > 0 ? " weak: " : (weak < 0 ? " strong:" : ": "), -  x, gc_rec_last); -  describe_marker(m); +  if (weak > 0) CYCLE_DEBUG_MSG(m, "gc_cycle_push, recurse weak"); +  else if (weak < 0) CYCLE_DEBUG_MSG(m, "gc_cycle_push, recurse strong"); +  else CYCLE_DEBUG_MSG(m, "gc_cycle_push, recurse");    gc_cycle_indent += 2;   #endif    gc_rec_last = m;
1319:    /* 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) { - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, no recurse: %8p, [%8p] ", -  gc_cycle_indent, "", x, gc_rec_last); -  describe_marker(m); - #endif +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, no recurse");    return 0;    }   
1347:       /* Recurse without linking m onto rec_list. */   #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_push, live recurse: %8p, [%8p] ", -  gc_cycle_indent, "", x, gc_rec_last); -  describe_marker(m); +  CYCLE_DEBUG_MSG(m, "gc_cycle_push, live recurse");    gc_cycle_indent += 2;   #endif    gc_rec_last = m;
1370:    M->flags |= GC_GOT_DEAD_REF; \    }    - static void pop_cycle_to_kill_list() + void gc_cycle_pop(void *a)   { -  struct marker *base, *p, *q; +  struct marker *m = find_marker(a), *p, *q;    -  for (base = gc_rec_last; -  base->cycle == base->link->cycle; -  base = base->link) {} -  p = base; -  -  while ((q = p->link)) { +    #ifdef PIKE_DEBUG -  if (q == (struct marker *) -1) -  gc_fatal(p->data, 0, "Followed link to oblivion.\n"); -  if (q->cycle != base->link->cycle) -  gc_fatal(q->data, 0, "Popping more than one cycle from rec_list.\n"); -  if (!(q->flags & GC_RECURSING)) -  gc_fatal(q->data, 0, "Marker being cycle popped doesn't have GC_RECURSING.\n"); -  if (q->flags & GC_GOT_DEAD_REF) -  gc_fatal(q->data, 0, "Didn't expect a dead extra ref.\n"); - #endif -  q->flags &= ~GC_RECURSING; -  -  if (q->flags & GC_LIVE_OBJ) { -  /* This extra ref is taken away in the kill pass. */ -  gc_add_extra_ref(q->data); - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*spop_cycle_to_kill_list: %8p, [%8p] ", -  gc_cycle_indent, "", q->data, base); -  describe_marker(q); - #endif -  p = q; -  } -  else { -  ADD_REF_IF_DEAD(q); - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*spop_cycle_to_kill_list, ignore: %8p, [%8p] ", -  gc_cycle_indent, "", q->data, base); -  describe_marker(q); - #endif -  p->link = q->link; -  } -  } -  -  p->link = kill_list; -  kill_list = base->link; -  base->link = 0; - } -  - INLINE int gc_cycle_pop(void *a) - { -  struct marker *m = find_marker(a); -  - #ifdef PIKE_DEBUG +     if (Pike_in_gc != GC_PASS_CYCLE)    fatal("GC cycle pop attempted in invalid pass.\n");    if (!(m->flags & GC_CYCLE_CHECKED))
1439:       if (!(m->flags & GC_RECURSING) || m->flags & GC_LIVE_RECURSE) {    m->flags &= ~GC_LIVE_RECURSE; - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_pop, pop ignored: %8p, [%8p] ", -  gc_cycle_indent, "", a, gc_rec_last); -  describe_marker(m); - #endif -  return 0; +  CYCLE_DEBUG_MSG(m, "gc_cycle_pop, pop ignored"); +  return;    }      #ifdef PIKE_DEBUG
1452:    gc_fatal(a, 0, "Didn't expect a dead extra ref.\n");   #endif    -  if (m->cycle) { -  /* Part of a cycle. Leave for now so we pop the whole cycle at once. */ -  m->flags &= ~(GC_WEAK_REF|GC_STRONG_REF); - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_pop, in cycle: %8p, [%8p] ", -  gc_cycle_indent, "", a, gc_rec_last); -  describe_marker(m); +  if (m->flags & GC_DONT_POP) { +  CYCLE_DEBUG_MSG(m, "gc_cycle_pop, keep on stack"); +  return; +  } +  +  q = gc_rec_last; +  while (1) { +  p = q->link; + #ifdef PIKE_DEBUG +  if (p->flags & GC_LIVE_RECURSE) +  gc_fatal(p->data, 0, "Marker still got GC_LIVE_RECURSE at pop.\n");   #endif -  if (!(gc_rec_last->flags & GC_RECURSING)) -  for (gc_rec_last = &rec_list; -  gc_rec_last->link != m && gc_rec_last->link->cycle != m->cycle; -  gc_rec_last = gc_rec_last->link) {} -  if (gc_rec_last->cycle != m->cycle) -  /* Time to pop the cycle. */ -  pop_cycle_to_kill_list(); -  return 0; +  +  if (!p->cycle && !(p->flags & GC_LIVE_OBJ)) { +  ADD_REF_IF_DEAD(p); +  p->flags &= ~(GC_RECURSING|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 { -  struct marker *p; -  ADD_REF_IF_DEAD(m); -  m->flags &= ~(GC_RECURSING|GC_WEAK_REF|GC_STRONG_REF); -  if (gc_rec_last->flags & GC_RECURSING) p = gc_rec_last; -  else p = &rec_list; -  for (; p->link != m; p = p->link) { +  p->flags &= ~(GC_DONT_POP|GC_WEAK_REF|GC_STRONG_REF); +  q = p; +  } +  if (p == m) break; +  } +  +  if (gc_rec_last != q) { +  if (!q->cycle || gc_rec_last->cycle != q->cycle) { +  /* If the thing(s) are part of a cycle that we aren't leaving, +  * we let them stay on the list so the whole cycle is popped at +  * once. Otherwise it's time to move live objects to the kill +  * list. */ +  struct marker *base;   #ifdef PIKE_DEBUG -  if (!p->link || m->link) -  gc_fatal(a, 0, "Thing not in cycle isn't last on rec_list.\n"); +  int cycle = q->cycle;   #endif -  } -  p->link = 0; - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_pop: %8p, [%8p] ", -  gc_cycle_indent, "", a, gc_rec_last); -  describe_marker(m); +  +  base = gc_rec_last; +  if (gc_rec_last->cycle) +  for (; base->link->cycle == gc_rec_last->cycle; base = base->link) {} +  +  q = base; +  while ((p = q->link)) { + #ifdef PIKE_DEBUG +  if (p->cycle && cycle && p->cycle != cycle) +  gc_fatal(p->data, 0, "Popping more than one cycle from rec_list.\n"); +  if (!(p->flags & GC_RECURSING)) +  gc_fatal(p->data, 0, "Marker being cycle popped doesn't have GC_RECURSING.\n"); +  if (p->flags & GC_GOT_DEAD_REF) +  gc_fatal(p->data, 0, "Didn't expect a dead extra ref.\n");   #endif -  return 1; +  p->flags &= ~GC_RECURSING; +  +  if (p->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");    } -  +  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");    } -  +  }    - void gc_cycle_pop_object(struct object *o) - { -  struct marker *m; -  if (gc_cycle_pop(o) && (m = find_marker(o))->flags & GC_LIVE_OBJ) { -  gc_add_extra_ref(o); /* This extra ref is taken away in the kill pass. */ -  m->link = kill_list; -  kill_list = m; - #ifdef GC_CYCLE_DEBUG -  fprintf(stderr, "%*sgc_cycle_pop_object, for kill: %8p, [%8p] ", -  gc_cycle_indent, "", o, gc_rec_last); -  describe_marker(m); - #endif +  q->link = kill_list; +  kill_list = base->link; +  base->link = p;    } -  +  else +  CYCLE_DEBUG_MSG(m, "gc_cycle_pop, keep in cycle");    } -  + }      void gc_set_rec_last(struct marker *m)   {