pike.git / src / gc.c

version» Context lines:

pike.git/src/gc.c:1:   /*   || This file is part of Pike. For copyright information see COPYRIGHT.   || Pike is distributed under GPL, LGPL and MPL. See the file COPYING   || for more information. - || $Id: gc.c,v 1.280 2007/04/25 21:58:33 mast Exp $ + || $Id: gc.c,v 1.281 2007/05/13 14:55:26 mast Exp $   */      #include "global.h"      struct callback *gc_evaluator_callback=0;      #include "array.h"   #include "multiset.h"   #include "mapping.h"   #include "object.h"
pike.git/src/gc.c:37:      /* These defaults are only guesses and hardly tested at all. Please improve. */   double gc_garbage_ratio_low = 0.2;   double gc_time_ratio = 0.05;   double gc_garbage_ratio_high = 0.5;      /* This slowness factor approximately corresponds to the average over    * the last ten gc rounds. (0.9 == 1 - 1/10) */   double gc_average_slowness = 0.9;    - #define GC_LINK_CHUNK_SIZE 64 -  +    /* The gc will free all things with no external references that isn't    * referenced by undestructed objects with destroy() lfuns (known as    * "live" objects). Live objects without external references are then    * destructed and garbage collected with normal refcount garbing    * (which might leave dead garbage around for the next gc). These live    * objects are destructed in an order that tries to be as well defined    * as possible using several rules:    *    * o If an object A references B single way, then A is destructed    * before B.    * o If A and B are in a cycle, and there is a reference somewhere    * from B to A that is weaker than any reference from A to B, then -  * A is destructed before B. +  * the cycle is resolved by disregarding the weaker reference, and +  * A is therefore destructed before B. +  * o If a cycle is resolved through disregarding a weaker reference +  * according to the preceding rule, and there is another cycle +  * without weak references which also gets resolved through +  * disregarding the same reference, then the other cycle won't be +  * resolved by disregarding some other reference.    * o Weak references are considered weaker than normal ones, and both    * are considered weaker than strong references.    * o Strong references are used in special cases like parent object    * references. There can never be a cycle consisting only of strong    * references. (This means the gc will never destruct a parent    * object before all children have been destructed.)    *    * The gc tries to detect and warn about cases where there are live    * objects with no well defined order between them. There are cases    * that are missed by this detection, though.
pike.git/src/gc.c:102:   int num_objects = 2; /* Account for *_empty_array. */   ALLOC_COUNT_TYPE num_allocs =0;   ALLOC_COUNT_TYPE alloc_threshold = GC_MIN_ALLOC_THRESHOLD;   PMOD_EXPORT int Pike_in_gc = 0;   int gc_generation = 0;   time_t last_gc;   int gc_trace = 0, gc_debug = 0;   #ifdef DO_PIKE_CLEANUP   int gc_destruct_everything = 0;   #endif + size_t gc_ext_weak_refs;    - /* Shared fields between link and pop frames. */ - #define GC_STACK_COMMON_FIELDS \ -  void *data; \ -  struct gc_stack_frame *s_prev; /* Previous stack frame. */ \ -  unsigned INT16 frameflags + static double objects_alloced = 0.0; + static double objects_freed = 0.0; + static double gc_time = 0.0, non_gc_time = 0.0; + static cpu_time_t last_gc_end_time = 0; + #if CPU_TIME_IS_THREAD_LOCAL == PIKE_NO + cpu_time_t auto_gc_time = 0; + #endif    - struct gc_stack_frame + struct link_frame /* See cycle checking blurb below. */   { -  GC_STACK_COMMON_FIELDS; +  void *data; +  struct link_frame *prev; /* Previous frame in the link stack. */ +  gc_cycle_check_cb *checkfn; /* Function to call to recurse the thing. */ +  int weak; /* Weak flag to checkfn. */   };    - struct gc_link_frame + struct gc_rec_frame /* See cycle checking blurb below. */   { -  GC_STACK_COMMON_FIELDS; -  INT16 weak; /* Weak flag to checkfn. */ -  gc_cycle_check_cb *checkfn; +  void *data; +  int rf_flags; +  struct gc_rec_frame *prev; /* The previous frame in the recursion +  * stack. NULL for frames not in the stack. */ +  struct gc_rec_frame *next; /* The next frame in the recursion +  * stack or the kill list. */ +  struct gc_rec_frame *cycle_id;/* The cycle identifier frame. +  * Initially points to self. */ +  struct gc_rec_frame *cycle_piece;/* The start of the cycle piece list +  * for frames on the recursion stack, +  * or the next frame in the list for +  * frames in cycle piece lists. */ +  union { +  struct link_frame *link_top;/* The top of the link stack for +  * frames on the recursion stack. */ +  struct gc_rec_frame *last_cycle_piece;/* In the first frame on a +  * cycle piece list, this is used to +  * point to the last frame in the list. */ +  } u;   };    - struct gc_pop_frame - { -  GC_STACK_COMMON_FIELDS; -  unsigned INT16 cycle; /* Cycle id number. */ -  struct gc_pop_frame *prev; /* Previous frame in the frame list. */ -  struct gc_pop_frame *next; /* Next pointer in the frame list and in kill_list. */ + /* rf_flags bits. */ + #define GC_PREV_WEAK 0x01 + #define GC_PREV_STRONG 0x02 + #define GC_PREV_BROKEN 0x04 + #define GC_MARK_LIVE 0x08 + #define GC_ON_KILL_LIST 0x10 + #ifdef PIKE_DEBUG + #define GC_ON_CYCLE_PIECE_LIST 0x20 + #define GC_FRAME_FREED 0x40 + #define GC_FOLLOWED_NONSTRONG 0x80 + #endif +  + static struct gc_rec_frame sentinel_frame = { +  (void *) (ptrdiff_t) -1, +  0, +  (struct gc_rec_frame *) (ptrdiff_t) -1, +  (struct gc_rec_frame *) (ptrdiff_t) -1, +  &sentinel_frame, /* Recognize as cycle id frame. */ +  (struct gc_rec_frame *) (ptrdiff_t) -1, +  {(struct link_frame *) (ptrdiff_t) -1}   }; -  + static struct gc_rec_frame *stack_top = &sentinel_frame; + static struct gc_rec_frame *kill_list = &sentinel_frame;    - struct gc_free_extra_frame /* Used on free_extra_list. See gc_delayed_free. */ + /* Cycle checking +  * +  * When a thing is recursed into, a gc_rec_frame is pushed onto the +  * recursion stack whose top pointer is stack_top. After that the +  * links emanating from that thing are collected through the +  * gc_cycle_check_* function and pushed as link_frames onto a link +  * stack that is specific to the rec frame. gc_rec_frame.u.link_top is +  * the top pointer of that stack. The link frames are then popped off +  * again one by one. If the thing that the link points to hasn't been +  * visited already then it's recursed, which means that the link frame +  * is popped off the link stack and a new rec frame is pushed onto the +  * main stack instead. +  * +  * When a reference is followed to a thing which has a rec frame +  * (either on the stack or on a cycle piece list - see below), we have +  * a cycle. However, if that reference is weak (or becomes weak after +  * rotation - see below), it's still not regarded as a cycle since +  * weak refs always are eligible to be broken to resolve cycles. +  * +  * A cycle is marked by setting gc_rec_frame.cycle_id in the rec +  * frames on the stack that are part of the cycle to the first +  * (deepest) one of those frames. That frame is called the "cycle +  * identifier frame" since all frames in the same cycle will end up +  * there if the cycle pointers are followed transitively. The cycle_id +  * pointer in the cycle identifier frame points to itself. Every frame +  * is initially treated as a cycle containing only itself. +  * +  * When the recursion leaves a thing, the rec frame is popped off the +  * stack. If the frame is part of a cycle that isn't finished at that +  * point, it's not freed but instead linked onto the cycle piece list +  * in gc_rec_frame.cycle_piece of the parent rec frame (which +  * necessarily is part of the same cycle). That is done to detect +  * cyclic refs that end up at the popped frame later on. +  * +  * The cycle_id pointers for frames on cycle piece lists point back +  * towards the rec frame that still is on the stack, but not past it +  * to the cycle id frame (which might be further back in the stack). +  * Whenever cycle_id pointer chains are traversed to find the root of +  * a cycle piece list, they are compacted to avoid O(n) complexity. +  * +  * The current tentative destruct order is described by the order on +  * the stack and the attached cycle piece lists: The thing that's +  * deepest in the stack is destructed first and the cycle piece list +  * has precedence over the next frame on the recursion stack. To +  * illustrate: +  * ,- stack_top +  * v +  * t1 <=> t4 <=> ... <=> t7 +  * | | `-> t8 -> ... -> t9 +  * | `-> t5 -> ... -> t6 +  * `-> t2 -> ... -> t3 +  * +  * Here <=> represents links on the recursion stack and -> links in +  * the cycle piece lists. The tentative destruct order for these +  * things is the same as the numbering above. +  * +  * Since we strive to keep the refs intact during destruction, the +  * above means that the refs which have priority to be kept intact +  * should point towards the top of the stack and towards the end of +  * the cycle piece lists. +  * +  * To allow rotations, the recursion stack is a double linked list +  * using gc_rec_frame.prev and gc_rec_frame.next. Rotations are the +  * operation used to manipulate the order to avoid getting a +  * prioritized link pointing in the wrong direction: +  * ,- stack_top +  * weak v +  * t1 <=> ... <=> t2 <=> ... <=> t3 <-> t4 <=> ... <=> t5 +  * +  * If a non-weak backward pointer from t5 to t2 is encountered here, +  * we should prefer to break(*) the weak ref between t3 and t4. The +  * stack is therefore rotated to become: +  * ,- stack_top +  * broken v +  * t1 <=> ... <#> t4 <=> ... <=> t5 <=> t2 <=> ... <=> t3 +  * +  * The section to rotate always ends at the top of the stack. +  * +  * The strength of the refs along the stack links are represented as +  * follows: +  * +  * o Things with a strong ref between them are kept next to each +  * other, and the second (the one being referenced by the strong +  * ref) has the GC_PREV_STRONG bit set. A rotation never breaks the +  * list inside a sequence of strong refs. +  * +  * o The GC_PREV_WEAK bit is set in the next frame for every link on +  * the stack where no preceding frame reference any following frame +  * with anything but weak refs. +  * +  * o GC_PREV_BROKEN is set in frames that are rotated back, i.e. t4 +  * in the example above. This is used to break later cycles in the +  * same position when they can't be broken at a weak link. +  * +  * Several separate cycles may be present on the stack simultaneously. +  * That happens when a subcycle which is referenced one way from an +  * earlier cycle is encountered. E.g. +  * +  * L--. L--. +  * t1 t2 -> t3 t4 +  * `--7 `--7 +  * +  * where the visit order is t1, t2, t3 and then t4. Because of the +  * stack which causes a subcycle to always be added to the top, it can +  * be handled independently of the earlier cycles, and those can also +  * be extended later on when the subcycle has been popped off. If a +  * ref from the subcycle to an earlier cycle is found, that means that +  * both are really the same cycle, and the frames in the former +  * subcycle will instead become a cycle piece list on a frame in the +  * former preceding cycle. +  * +  * Since the link frames are kept in substacks attached to the rec +  * frames, they get rotated with the rec frames. This has the effect +  * that the links from the top rec frame on the stack always are +  * tested first. That is necessary to avoid clobbering weak ref +  * partitions. Example: +  * +  * weak weak +  * t1 <=> t2 <-> t3 <=> t4 <-> t5 +  * +  * A nonweak ref is found from t5 to t2. We get this after rotation: +  * +  * broken weak +  * t1 <#> t5 <=> t2 <-> t3 <=> t4 +  * +  * Now, if we would continue to follow the links from t5 and encounter +  * a new thing t7, we'd have to add it to the top. If that ref isn't +  * weak we'd have to blank out the weak flag which could be used in +  * other rotations above t5 (e.g. if a normal ref from t4 to t2 is +  * encountered). To avoid this we do the t4 links instead and continue +  * with t5 when t4, t3 and t2 are done. +  * +  * As said earlier, rec frames are moved to cycle piece lists when +  * they are popped off while being part of unfinished cycles. Since +  * there are no more outgoing refs at that point, there can be no more +  * rotations that affect the order between the rec frame and its +  * predecessor. Therefore the order on a cycle piece list is optimal +  * (in as far as the gc destruct order policy goes). Any further +  * rotations can move the predecessor around, but it can always be +  * treated as one unit together with its cycle piece list. Remaining +  * weak refs inside the cycle piece list are no longer relevant since +  * they don't apply to the links that remain to be followed for the +  * predecessor (i.e. the root of the cycle piece list, still on the +  * rec frame stack). +  * +  * If the preceding frame already has a cycle piece list when a rec +  * frame should be added to it, the rec frame (and its attached cycle +  * piece list) is linked in before that list. That since the rec frame +  * might have refs to the earlier cycle piece list, but the opposite +  * can't happen. +  * +  * When a cycle identifier frame is popped off the stack, the frame +  * together with its cycle piece list represent the complete cycle, +  * and the list holds an optimal order for destructing it. The frames +  * are freed at that point, except for the ones which correspond to +  * live objects, which instead are linked in order into the beginning +  * of the kill list. That list, whose beginning is pointed to by +  * kill_list, holds the final destruct order for all live objects. +  * +  * Note that the complete cycle has to be added to the kill list at +  * once since all live objects that are referenced single way from the +  * cycle should be destructed later and must therefore be put on the +  * kill list before the cycle. +  * +  * The cycle check functions might recurse another round through the +  * frames that have been recursed already, to propagate the GC_LIVE +  * flag to things that have been found to be referenced from live +  * objects. In this mode a single dummy rec frame with the +  * GC_MARK_LIVE bit is pushed on the recursion stack, and all link +  * frames are stacked in it, regardless of the things they originate +  * from. +  * +  * *) Here "breaking" a ref doesn't mean that it actually gets +  * zeroed out. It's only disregarded to resolve the cycle to +  * produce an optimal destruct order. I.e. it will still be intact +  * when the first object in the cycle is destructed, and it will +  * only be zeroed when the thing it points to has been destructed. +  */ +  + /* The free extra list. See note in gc_delayed_free. */ + struct free_extra_frame   {    void *data; -  struct gc_free_extra_frame *fe_next; /* Next pointer. */ +  struct free_extra_frame *next; /* Next pointer. */    int type; /* The type of the thing. */   }; -  + static struct free_extra_frame *free_extra_list = NULL;    - struct gc_frame + #ifdef PIKE_DEBUG + static unsigned delayed_freed, weak_freed, checked, marked, cycle_checked, live_ref; + static unsigned mark_live, frame_rot, link_search; + static unsigned gc_extra_refs = 0; + static unsigned tot_cycle_checked = 0, tot_mark_live = 0, tot_frame_rot = 0; + static unsigned gc_rec_frame_seq_max; + #endif +  + static unsigned rec_frames, link_frames, free_extra_frames; + static unsigned max_rec_frames, max_link_frames; + static unsigned tot_max_rec_frames = 0, tot_max_link_frames = 0, tot_max_free_extra_frames = 0; +  + #undef INIT_BLOCK + #define INIT_BLOCK(f) do { \ +  if (++rec_frames > max_rec_frames) \ +  max_rec_frames = rec_frames; \ +  } while (0) + #undef EXIT_BLOCK + #define EXIT_BLOCK(f) do { \ +  DO_IF_DEBUG ({ \ +  if (f->rf_flags & GC_FRAME_FREED) \ +  gc_fatal (f->data, 0, "Freeing gc_rec_frame twice.\n"); \ +  f->rf_flags |= GC_FRAME_FREED; \ +  f->u.link_top = (struct link_frame *) (ptrdiff_t) -1; \ +  f->prev = f->next = f->cycle_id = f->cycle_piece = \ +  (struct gc_rec_frame *) (ptrdiff_t) -1; \ +  }); \ +  rec_frames--; \ +  } while (0) +  + BLOCK_ALLOC_FILL_PAGES (gc_rec_frame, 2) +  + #ifdef PIKE_DEBUG + #define LOW_CHECK_REC_FRAME(f, extra) do { \ +  struct gc_rec_frame *f_ = (f); \ +  if (f_->rf_flags & GC_FRAME_FREED) \ +  gc_fatal (f_->data, 0, "Accessing freed gc_stack_frame %p.\n", f_); \ +  if (f_->cycle_id->rf_flags & GC_FRAME_FREED) { \ +  fprintf (stderr, "Cycle id frame is freed. It is: "); \ +  describe_rec_frame (f_->cycle_id); \ +  fputc ('\n', stderr); \ +  gc_fatal (f_->data, 0, "Cycle id frame is freed.\n"); \ +  } \ +  {extra;} \ +  } while (0) + #define CHECK_REC_STACK_FRAME(f) LOW_CHECK_REC_FRAME (f, { \ +  if (f_->rf_flags & (GC_ON_CYCLE_PIECE_LIST|GC_ON_KILL_LIST)) \ +  gc_fatal (f_->data, 0, "Frame is not on the rec stack.\n"); \ +  if (!f_->prev) \ +  gc_fatal (f_->data, 0, "Prev pointer not set for rec stack frame.\n"); \ +  if (f_->prev->next != f_) \ +  gc_fatal (f_->data, 0, "Rec stack pointers are inconsistent.\n"); \ +  if (f_->cycle_id && \ +  f_->cycle_id->rf_flags & (GC_ON_CYCLE_PIECE_LIST|GC_ON_KILL_LIST)) \ +  gc_fatal (f_->data, 0, "Cycle id frame not on the rec stack.\n"); \ +  if (f_->cycle_piece && \ +  (!f_->cycle_piece->u.last_cycle_piece || \ +  f_->cycle_piece->u.last_cycle_piece->cycle_piece)) \ +  gc_fatal (f_->data, 0, "Bogus last_cycle_piece %p is %p in %p.\n", \ +  f_->cycle_piece->u.last_cycle_piece, \ +  f_->cycle_piece->u.last_cycle_piece ? \ +  f_->cycle_piece->u.last_cycle_piece->cycle_piece : NULL, \ +  f_->cycle_piece); \ +  if ((f_->rf_flags & GC_PREV_STRONG) && \ +  (f_->rf_flags & (GC_PREV_WEAK|GC_PREV_BROKEN))) \ +  gc_fatal (f_->data, 0, "GC_PREV_STRONG set together with " \ +  "GC_PREV_WEAK or GC_PREV_BROKEN.\n"); \ +  }) + #define CHECK_CYCLE_PIECE_FRAME(f) LOW_CHECK_REC_FRAME (f, { \ +  if ((f_->rf_flags & (GC_ON_CYCLE_PIECE_LIST|GC_ON_KILL_LIST)) != \ +  GC_ON_CYCLE_PIECE_LIST) \ +  gc_fatal (f_->data, 0, "Frame is not on a cycle piece list.\n"); \ +  if (f_->prev) \ +  gc_fatal (f_->data, 0, "Prev pointer set for frame on cycle piece list.\n"); \ +  }) + #define CHECK_KILL_LIST_FRAME(f) LOW_CHECK_REC_FRAME (f, { \ +  if ((f_->rf_flags & (GC_ON_CYCLE_PIECE_LIST|GC_ON_KILL_LIST)) != \ +  GC_ON_KILL_LIST) \ +  gc_fatal (f_->data, 0, "Frame is not on kill list.\n"); \ +  if (f_->prev) \ +  gc_fatal (f_->data, 0, "Prev pointer set for frame on kill list.\n"); \ +  }) + #else + #define CHECK_REC_STACK_FRAME(f) do {} while (0) + #define CHECK_CYCLE_PIECE_FRAME(f) do {} while (0) + #define CHECK_KILL_LIST_FRAME(f) do {} while (0) + #endif +  + /* Link and free_extra frames are approximately the same size, so let +  * them share block_alloc area. */ + struct ba_mixed_frame   {    union { -  struct gc_link_frame link; -  struct gc_pop_frame pop; -  struct gc_free_extra_frame free_extra; -  struct gc_frame *next; /* For block_alloc. */ +  struct link_frame link; +  struct free_extra_frame free_extra; +  struct ba_mixed_frame *next; /* For block_alloc internals. */    } u;   };    - /* frameflags bits. */ - #define GC_POP_FRAME 0x01 - #define GC_PREV_WEAK 0x02 /* Only applicable in pop frames. */ - #define GC_PREV_STRONG 0x04 /* Only applicable in pop frames. */ - #define GC_OFF_STACK 0x08 /* Only applicable in pop frames. */ - #define GC_ON_KILL_LIST 0x10 /* Only applicable in pop frames. */ - #ifdef PIKE_DEBUG - #define GC_FRAME_FREED 0x20 - #define GC_FOLLOWED_NONSTRONG 0x40 - #endif -  +    #undef BLOCK_ALLOC_NEXT   #define BLOCK_ALLOC_NEXT u.next -  + #undef INIT_BLOCK + #define INIT_BLOCK(f) + #undef EXIT_BLOCK + #define EXIT_BLOCK(f)    - BLOCK_ALLOC(gc_frame,GC_LINK_CHUNK_SIZE) + BLOCK_ALLOC_FILL_PAGES (ba_mixed_frame, 2)    - static INLINE struct gc_pop_frame *alloc_pop_frame() + static INLINE struct link_frame *alloc_link_frame()   { -  struct gc_frame *f = alloc_gc_frame(); -  f->u.pop.frameflags = GC_POP_FRAME; -  return (struct gc_pop_frame *) f; +  struct ba_mixed_frame *f = alloc_ba_mixed_frame(); +  if (++link_frames > max_link_frames) +  max_link_frames = link_frames; +  return (struct link_frame *) f;   }    - static INLINE struct gc_link_frame *alloc_link_frame() + static INLINE struct free_extra_frame *alloc_free_extra_frame()   { -  struct gc_frame *f = alloc_gc_frame(); -  f->u.link.frameflags = 0; -  return (struct gc_link_frame *) f; +  struct ba_mixed_frame *f = alloc_ba_mixed_frame(); +  free_extra_frames++; +  return (struct free_extra_frame *) f;   }    - static INLINE struct gc_free_extra_frame *alloc_free_extra_frame() + static INLINE void really_free_link_frame (struct link_frame *f)   { -  return (struct gc_free_extra_frame *) alloc_gc_frame(); +  link_frames--; +  really_free_ba_mixed_frame ((struct ba_mixed_frame *) f);   }    - #ifdef PIKE_DEBUG -  - static unsigned num_gc_stack_frames = 0; -  - void debug_free_gc_stack_frame (struct gc_stack_frame *f) + static INLINE void really_free_free_extra_frame (struct free_extra_frame *f)   { -  if (f->frameflags & GC_FRAME_FREED) -  gc_fatal (f->data, 0, "Freeing freed gc_stack_frame.\n"); -  f->frameflags |= GC_FRAME_FREED; -  f->s_prev = (struct gc_stack_frame *) (ptrdiff_t) -1; -  if (f->frameflags & GC_POP_FRAME) { -  struct gc_pop_frame *p = (struct gc_pop_frame *) f; -  p->prev = p->next = (struct gc_pop_frame *)(ptrdiff_t) -1; +  free_extra_frames--; +  really_free_ba_mixed_frame ((struct ba_mixed_frame *) f);   } -  really_free_gc_frame ((struct gc_frame *) f); - #ifdef GC_VERBOSE -  num_gc_stack_frames--; - #endif - } +     - static INLINE void FREE_POP_FRAME (struct gc_pop_frame *f) {debug_free_gc_stack_frame ((struct gc_stack_frame *) f);} - static INLINE void FREE_LINK_FRAME (struct gc_link_frame *f) {debug_free_gc_stack_frame ((struct gc_stack_frame *) f);} - static INLINE void FREE_FREE_EXTRA_FRAME (struct gc_free_extra_frame *f) {really_free_gc_frame ((struct gc_frame *) f);} - static INLINE struct gc_stack_frame *POP2STACK (struct gc_pop_frame *f) {return (struct gc_stack_frame *) f;} - static INLINE struct gc_stack_frame *LINK2STACK (struct gc_link_frame *f) {return (struct gc_stack_frame *) f;} - static INLINE struct gc_pop_frame *STACK2POP (struct gc_stack_frame *f) - { -  if (!(f->frameflags & GC_POP_FRAME)) fatal ("Pop frame expected.\n"); -  return (struct gc_pop_frame *) f; - } - static INLINE struct gc_link_frame *STACK2LINK (struct gc_stack_frame *f) - { -  if (f->frameflags & GC_POP_FRAME) fatal ("Link frame expected.\n"); -  return (struct gc_link_frame *) f; - } -  - #else -  - #define FREE_POP_FRAME(f) really_free_gc_frame ((struct gc_frame *) f) - #define FREE_LINK_FRAME(f) really_free_gc_frame ((struct gc_frame *) f) - #define FREE_FREE_EXTRA_FRAME(f) really_free_gc_frame ((struct gc_frame *) f) - #define POP2STACK(f) ((struct gc_stack_frame *) f) - #define LINK2STACK(f) ((struct gc_stack_frame *) f) - #define STACK2POP(f) ((struct gc_pop_frame *) f) - #define STACK2LINK(f) ((struct gc_link_frame *) f) -  - #endif -  - #define gc_frame gc_foo_frame -  - #ifdef PIKE_DEBUG - #define CHECK_POP_FRAME(frame) do { \ -  struct gc_pop_frame *f_ = (frame); \ -  if (f_->frameflags & GC_FRAME_FREED) \ -  gc_fatal (f_->data, 0, "Accessing freed gc_stack_frame.\n"); \ -  if (!(f_->frameflags & GC_POP_FRAME)) \ -  gc_fatal(f_->data, 0, #frame " is not a pop frame.\n"); \ -  if (f_->prev->next != f_) \ -  gc_fatal(f_->data, 0, "Pop frame pointers are inconsistent.\n"); \ - } while (0) - #else - #define CHECK_POP_FRAME(frame) do {} while (0) - #endif -  - static struct gc_pop_frame sentinel_frame = {NULL, NULL, GC_POP_FRAME, 0, NULL, NULL}; - static struct gc_pop_frame *list_end = &sentinel_frame; - static struct gc_stack_frame *stack_top = (struct gc_stack_frame *) &sentinel_frame; - static struct gc_pop_frame *stack_top_pop = &sentinel_frame; - static struct gc_pop_frame *kill_list = NULL; - static struct gc_free_extra_frame *free_extra_list = NULL; /* See note in gc_delayed_free. */ -  - static unsigned last_cycle; - size_t gc_ext_weak_refs; -  - /* gc_frame objects are used as frames in a recursion stack during the -  * cycle check pass. stack_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. -  * -  * rec_list is a double linked list of the pop frames on the stack, -  * and that list represents the current prospective destruct order. -  * list_end 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, -  * i.e. it's either popped whole or not at all. That means that -  * rec_list might contain frames that are no longer on the stack. -  * -  * 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 -  * frames that have been recursed already, to propagate the GC_LIVE -  * flag to things that have been found to be referenced from live -  * objects. The frame list is not touched at all in this extra round. -  */ -  - static double objects_alloced = 0.0; - static double objects_freed = 0.0; - static double gc_time = 0.0, non_gc_time = 0.0; - static cpu_time_t last_gc_end_time = 0; - #if CPU_TIME_IS_THREAD_LOCAL == PIKE_NO - cpu_time_t auto_gc_time = 0; - #endif -  +    /* These are only collected for the sake of gc_status. */   static double last_garbage_ratio = 0.0;   static enum {    GARBAGE_RATIO_LOW, GARBAGE_RATIO_HIGH   } last_garbage_strategy = GARBAGE_RATIO_LOW;      struct callback_list gc_callbacks;      /* These callbacks are run early in the check pass of the gc and when    * locate_references is called. They are typically used to mark    * external references (using gc_mark_external) for debug purposes. */   struct callback *debug_add_gc_callback(callback_func call,    void *arg,    callback_func free_func)   {    return add_to_callback(&gc_callbacks, call, arg, free_func);   }      static void init_gc(void); - static void gc_cycle_pop(void *a); + static void gc_cycle_pop();      #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)->frame = 0;
pike.git/src/gc.c:359: Inside #if defined (PIKE_DEBUG) || defined (GC_MARK_DEBUG)
  int gc_found_in_type = PIKE_T_UNKNOWN;   const char *gc_found_place = NULL;   #endif      #ifdef DO_PIKE_CLEANUP   /* To keep the markers after the gc. Only used for the leak report at exit. */   int gc_keep_markers = 0;   PMOD_EXPORT int gc_external_refs_zapped = 0;   #endif    + #if defined (PIKE_DEBUG) || defined (GC_CYCLE_DEBUG) + static void describe_rec_frame (struct gc_rec_frame *f) + { +  fprintf (stderr, "data=%p rf_flags=0x%02x prev=%p next=%p " +  "cycle_id=%p cycle_piece=%p link_top/last_cycle_piece=%p", +  f->data, f->rf_flags, f->prev, f->next, +  f->cycle_id, f->cycle_piece, f->u.link_top); + } + #endif +    #ifdef PIKE_DEBUG      int gc_in_cycle_check = 0; - static unsigned delayed_freed, weak_freed, checked, marked, cycle_checked, live_ref; - static unsigned max_gc_stack_frames, live_rec, frame_rot, link_search; - static unsigned gc_extra_refs = 0; +     - static unsigned max_tot_gc_stack_frames = 0; - static unsigned tot_cycle_checked = 0, tot_live_rec = 0, tot_frame_rot = 0; -  +    static int gc_is_watching = 0;      int attempt_to_identify(void *something, void **inblock)   {    size_t i;    struct array *a;    struct object *o;    struct program *p;    struct mapping *m;    struct multiset *mu;
pike.git/src/gc.c:725: Inside #if defined(DEBUG_MALLOC)
   /* FIXME: Is the following call correct?    * Shouldn't the second argument be an offset?    */    /* dmalloc_describe_location(descblock, location, indent); */    /* My attempt to fix it, although I'm not really sure: /mast */    if (memblock)    dmalloc_describe_location(memblock, (char *) location - (char *) memblock, indent);   #endif   }    - static void describe_gc_stack_frame (struct gc_stack_frame *f) + static void describe_link_frame (struct link_frame *f)   { -  if (f->frameflags & GC_POP_FRAME) { -  struct gc_pop_frame *p = STACK2POP (f); -  fprintf (stderr, "s_prev=%p, data=%p, flags=0x%02x, prev=%p, next=%p, cycle=%u", -  p->s_prev, p->data, p->frameflags, p->prev, p->next, p->cycle); +  fprintf (stderr, "data=%p prev=%p checkfn=%p weak=%d", +  f->data, f->prev, f->checkfn, f->weak);   } -  else { -  struct gc_link_frame *l = STACK2LINK (f); -  fprintf (stderr, "s_prev=%p, data=%p, flags=0x%02x, weak=%d", -  l->s_prev, l->data, l->frameflags, l->weak); -  } - } +       static void describe_marker(struct marker *m)   {    if (m) { -  fprintf(stderr, "marker at %p: flags=0x%05lx, refs=%d, weak=%d, " -  "xrefs=%d, saved=%d, frame=%p", +  fprintf(stderr, "marker at %p: flags=0x%05lx refs=%d weak=%d " +  "xrefs=%d saved=%d frame=%p",    m, (long) m->flags, m->refs, m->weak_refs,    m->xrefs, m->saved_refs, m->frame); - #ifdef PIKE_DEBUG +     if (m->frame) {    fputs(" [", stderr); -  describe_gc_stack_frame (POP2STACK (m->frame)); +  describe_rec_frame (m->frame);    putc(']', stderr);    } - #endif +     putc('\n', stderr);    }    else    fprintf(stderr, "no marker\n");   }      #endif /* PIKE_DEBUG */      void debug_gc_fatal(void *a, int flags, const char *fmt, ...)   {
pike.git/src/gc.c:1474:       case GC_PASS_MIDDLETOUCH: {   #ifdef PIKE_DEBUG    int extra_ref;   #endif    m = find_marker(a);    if (!m)    gc_fatal(a, 1, "Found a thing without marker.\n");    else if (!(m->flags & GC_PRETOUCHED))    gc_fatal(a, 1, "Thing got an existing but untouched marker.\n"); +  if (gc_destruct_everything && (m->flags & GC_MARKED)) +  gc_fatal (a, 1, "Thing got marked in gc_destruct_everything mode.\n");   #ifdef PIKE_DEBUG    extra_ref = (m->flags & GC_GOT_EXTRA_REF) == GC_GOT_EXTRA_REF;    if (m->saved_refs + extra_ref < *(INT32 *) a)    if (m->flags & GC_WEAK_FREED)    gc_fatal(a, 1, "Something failed to remove weak reference(s) to thing, "    "or it has gotten more references since gc start.\n");    else    gc_fatal(a, 1, "Thing has gotten more references since gc start.\n");    else    if (m->weak_refs > m->saved_refs)
pike.git/src/gc.c:1501: Inside #if 0
   /* Disabled since we can't assume any correlation between the    * marks and the actual blocks in or after GC_PASS_FREE. */    case GC_PASS_POSTTOUCH:    m = find_marker(a);    if (!*(INT32 *) a)    gc_fatal(a, 1, "Found a thing without refs.\n");    if (m) {    if (!(m->flags & (GC_PRETOUCHED|GC_MIDDLETOUCHED)))    gc_fatal(a, 2, "An existing but untouched marker found "    "for object in linked lists.\n"); -  else if (m->flags & GC_LIVE_RECURSE || -  (m->frame && m->frame->frameflags & (GC_PREV_WEAK|GC_PREV_STRONG))) -  gc_fatal(a, 2, "Thing still got flag from recurse list.\n"); +    #ifdef PIKE_DEBUG    else if (m->flags & GC_MARKED)    return;    else if (gc_destruct_everything)    return;    else if (!(m->flags & GC_NOT_REFERENCED) || m->flags & GC_XREFERENCED)    gc_fatal(a, 3, "A thing with external references "    "got missed by mark pass.\n");    else if (!(m->flags & GC_CYCLE_CHECKED))    gc_fatal(a, 2, "A thing was missed by "
pike.git/src/gc.c:1688: Inside #if defined(PIKE_DEBUG)
  #ifdef PIKE_DEBUG    if (!gc_is_watching) {   #endif   #if defined (PIKE_DEBUG) || defined (DO_PIKE_CLEANUP)    /* The marker hash table is left around after a previous gc if    * gc_keep_markers is set. */    if (marker_hash_table) cleanup_markers();    if (!marker_hash_table)   #endif    low_init_marker_hash(num_objects); -  get_marker(sentinel_frame.data); /* Used to simplify fencepost conditions. */ +    #ifdef PIKE_DEBUG    }   #endif   }      void exit_gc(void)   {    if (gc_evaluator_callback) {    remove_callback(gc_evaluator_callback);    gc_evaluator_callback = NULL;    }    if (!gc_keep_markers)    cleanup_markers();    -  free_all_gc_frame_blocks(); +  free_all_gc_rec_frame_blocks(); +  free_all_ba_mixed_frame_blocks();    - #ifdef GC_VERBOSE -  num_gc_stack_frames = 0; - #endif -  +    #ifdef PIKE_DEBUG    if (gc_is_watching) {    fprintf(stderr, "## Exiting gc and resetting watches for %d things.\n",    gc_is_watching);    gc_is_watching = 0;    }   #endif   }      #ifdef PIKE_DEBUG
pike.git/src/gc.c:2017:    if (m->flags & GC_MARKED) {    /* Note that we can get marked things here, e.g. if the index in a    * mapping with weak indices is removed in the zap weak pass, the    * value will be zapped too, but it will still have a mark from    * the mark pass. This means that the stuff referenced by the    * value will only be refcount garbed, which can leave cyclic    * garbage for the next gc round.    *    * Since the value has been marked we won't find it in the free    * pass, so we have to keep special track of it. :P */ -  struct gc_free_extra_frame *l = alloc_free_extra_frame(); +  struct free_extra_frame *l = alloc_free_extra_frame();    l->data = a;    l->type = type; -  l->fe_next = free_extra_list; +  l->next = free_extra_list;    free_extra_list = l;    }       gc_add_extra_ref(a);    m->flags |= GC_GOT_DEAD_REF;   }      int gc_mark(void *a)   {    struct marker *m;
pike.git/src/gc.c:2114: Inside #if defined(PIKE_DEBUG)
   if (!m) Pike_fatal ("Have no marker for old block %p.\n", old);    if (find_marker (new))    Pike_fatal ("New block %p already got a marker.\n", new);   #endif       move_marker (m, debug_malloc_pass (new));   }      PMOD_EXPORT void gc_cycle_enqueue(gc_cycle_check_cb *checkfn, void *data, int weak)   { -  struct gc_link_frame *l = alloc_link_frame(); +  struct link_frame *l = alloc_link_frame();   #ifdef PIKE_DEBUG    {    struct marker *m;    if (gc_is_watching && (m = find_marker(data)) && m->flags & GC_WATCHED) {    /* This is useful to set breakpoints on. */    gc_watched_found (m, "gc_cycle_enqueue()");    }    }    if (Pike_in_gc != GC_PASS_CYCLE)    gc_fatal(data, 0, "Use of the gc frame stack outside the cycle check pass.\n"); -  +  if (stack_top == &sentinel_frame) +  gc_fatal (data, 0, "No thing on rec stack to follow links from.\n");   #endif - #ifdef GC_VERBOSE -  if (++num_gc_stack_frames > max_gc_stack_frames) -  max_gc_stack_frames = num_gc_stack_frames; - #endif +     l->data = data;    l->checkfn = checkfn;    l->weak = weak; -  l->s_prev = stack_top; +  l->prev = stack_top->u.link_top;   #ifdef GC_STACK_DEBUG -  fprintf(stderr, "enqueue %p [%p]: ", l, stack_top); -  describe_gc_stack_frame (LINK2STACK (l)); +  fprintf (stderr, "push link %p [%p in %p]: ", l, stack_top->u.link_top, stack_top); +  describe_link_frame (l);    fputc('\n', stderr);   #endif -  stack_top = LINK2STACK (l); +  stack_top->u.link_top = l;   }    - static struct gc_pop_frame *gc_cycle_enqueue_pop(void *data) + static struct gc_rec_frame *gc_cycle_enqueue_rec (void *data)   { -  struct gc_pop_frame *p = alloc_pop_frame(); +  struct gc_rec_frame *r = alloc_gc_rec_frame();   #ifdef PIKE_DEBUG    if (Pike_in_gc != GC_PASS_CYCLE)    gc_fatal(data, 0, "Use of the gc frame stack outside the cycle check pass.\n"); -  +  r->next = (struct gc_rec_frame *) (ptrdiff_t) -1;   #endif - #ifdef GC_VERBOSE -  if (++num_gc_stack_frames > max_gc_stack_frames) -  max_gc_stack_frames = num_gc_stack_frames; - #endif -  p->data = data; - #ifdef PIKE_DEBUG -  p->prev = p->next = (struct gc_pop_frame *) (ptrdiff_t) -1; - #endif -  p->cycle = 0; -  p->s_prev = stack_top; +  r->data = data; +  r->u.link_top = NULL; +  r->prev = stack_top; +  r->cycle_id = r; +  r->cycle_piece = NULL;   #ifdef GC_STACK_DEBUG -  fprintf(stderr, "enqueue %p [%p]: ", p, stack_top); -  describe_gc_stack_frame (POP2STACK (p)); +  fprintf (stderr, "push rec %p [%p]: ", r, stack_top); +  describe_rec_frame (r);    fputc('\n', stderr);   #endif -  stack_top = POP2STACK (p); -  stack_top_pop = p; -  return p; +  stack_top->next = r; +  stack_top = r; +  return r;   }      void gc_cycle_run_queue()   {   #ifdef PIKE_DEBUG    if (Pike_in_gc != GC_PASS_CYCLE)    Pike_fatal("Use of the gc frame stack outside the cycle check pass.\n");   #endif    -  while (stack_top != POP2STACK (&sentinel_frame)) { +  while (stack_top != &sentinel_frame) { +  while (stack_top->u.link_top) { +  struct link_frame l = *stack_top->u.link_top;   #ifdef GC_STACK_DEBUG -  fprintf(stderr, "dequeue %p [%p]: ", stack_top, stack_top->s_prev); -  describe_gc_stack_frame(stack_top); -  fputc('\n', stderr); +  fprintf (stderr, "pop link %p [%p in %p]: ", +  stack_top->u.link_top, l.prev, stack_top); +  describe_link_frame (stack_top->u.link_top); +  fputc ('\n', stderr);   #endif -  -  if (stack_top->frameflags & GC_POP_FRAME) { -  struct gc_stack_frame *f = stack_top->s_prev; -  gc_cycle_pop (stack_top->data); -  stack_top = f; -  -  /* Use separate pointer chain instead? */ -  for (; !(f->frameflags & GC_POP_FRAME); f = f->s_prev) {} -  stack_top_pop = STACK2POP (f); +  really_free_link_frame (stack_top->u.link_top); +  stack_top->u.link_top = l.prev; +  l.checkfn (l.data, l.weak); /* Might change stack_top. */    }    -  else { -  struct gc_link_frame l = *STACK2LINK (stack_top); + #ifdef GC_STACK_DEBUG +  fprintf (stderr, "pop rec %p [%p]: ", stack_top, stack_top->prev); +  describe_rec_frame (stack_top); +  fputc ('\n', stderr); + #endif +  CHECK_REC_STACK_FRAME (stack_top); +    #ifdef PIKE_DEBUG -  if (l.frameflags & GC_FRAME_FREED) -  gc_fatal(l.data, 0, "Accessing freed gc_frame.\n"); +  { +  struct gc_rec_frame *old_stack_top = stack_top; +  gc_cycle_pop(); +  if (stack_top == old_stack_top) +  fatal ("gc_cycle_pop didn't pop the stack.\n"); +  } + #else +  gc_cycle_pop();   #endif -  FREE_LINK_FRAME (STACK2LINK (stack_top)); -  stack_top = l.s_prev; -  l.checkfn (l.data, l.weak); +     }   } - } +       #ifdef GC_CYCLE_DEBUG   static int gc_cycle_indent = 0; - #define CYCLE_DEBUG_MSG(M, TXT) do { \ -  fprintf(stderr, "%*s%-35s %p [stack:%p, list:%p] ", gc_cycle_indent, "", \ -  (TXT), (M) ? (M)->data : 0, stack_top->data, list_end->data); \ -  describe_marker(M); \ + #define CYCLE_DEBUG_MSG(REC, TXT) do { \ +  struct gc_rec_frame *r_ = (REC); \ +  fprintf (stderr, "%*s%-35s %p [%p] ", gc_cycle_indent, "", \ +  (TXT), r_ ? r_->data : NULL, stack_top->data); \ +  if (r_) describe_rec_frame (r_); \ +  putc ('\n', stderr); \    } while (0)   #else - #define CYCLE_DEBUG_MSG(M, TXT) do {} while (0) + #define CYCLE_DEBUG_MSG(REC, TXT) do {} while (0)   #endif    - static void rotate_frame_list (struct gc_pop_frame *beg, struct gc_pop_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. The beg -  * pos might be moved further down the stack to avoid mixing cycles or -  * breaking strong link sequences. */ + static struct gc_rec_frame *rotate_rec_stack (struct gc_rec_frame *beg, +  struct gc_rec_frame *pos) + /* Performs a rotation of the recursion stack so the part from pos +  * down to the end gets before the part from beg down to pos. The beg +  * pos might be moved further back the list to avoid breaking strong +  * link sequences. Returns the actual beg pos. Example: +  * +  * strong +  * a1 <=> ... <=> a2 <=> b1 <*> b2 <=> ... <=> b3 <=> c1 <=> ... <=> c2 +  * ^ beg ^ pos +  * +  * becomes +  * +  * broken strong +  * a1 <=> ... <=> a2 <#> c1 <=> ... <=> c2 <=> b1 <*> b2 <=> ... <=> b3 +  * ^ pos ^ ^ beg +  * returned +  * +  * Note: The part from pos down to the end is assumed to not contain +  * any weak refs. (If it does they must be cleared, unless the link +  * before beg is weak.) +  */   { - #ifdef GC_STACK_DEBUG -  struct gc_stack_frame *l; - #endif -  CYCLE_DEBUG_MSG(find_marker(beg->data), "> rotate_frame_list, asked to begin at"); +  CYCLE_DEBUG_MSG (beg, "> rotate_rec_stack, requested beg");      #ifdef PIKE_DEBUG    if (Pike_in_gc != GC_PASS_CYCLE)    Pike_fatal("Use of the gc frame stack outside the cycle check pass.\n"); -  CHECK_POP_FRAME(beg); -  CHECK_POP_FRAME(pos); +  CHECK_REC_STACK_FRAME (beg); +  CHECK_REC_STACK_FRAME (pos);    if (beg == pos) -  gc_fatal(beg->data, 0, "Cycle already broken at requested position.\n"); -  if (list_end->next) -  gc_fatal(list_end->data, 0, "list_end not at end.\n"); +  gc_fatal (beg->data, 0, "Cycle already broken at requested position.\n");   #endif      #ifdef GC_STACK_DEBUG    fprintf(stderr,"Stack before:\n"); -  for (l = stack_top; l != &sentinel_frame; l = l->s_prev) { -  fprintf(stderr, " %p ", l); -  describe_gc_stack_frame(l); -  fputc('\n', stderr); +  { +  struct gc_rec_frame *l; +  for (l = stack_top; l != &sentinel_frame; l = l->prev) { +  fprintf (stderr, " %p%s ", l, +  l == beg ? " (beg)" : l == pos ? " (pos)" : ""); +  describe_rec_frame (l); +  fputc ('\n', stderr);    } -  +  }   #endif       /* Always keep chains of strong refs continuous, or else we risk    * breaking the order in a later rotation. */ -  for (; beg->frameflags & GC_PREV_STRONG; beg = beg->prev) {} +  for (; beg->rf_flags & GC_PREV_STRONG; beg = beg->prev) +  CYCLE_DEBUG_MSG (beg, "> rotate_rec_stack, skipping strong"); + #ifdef PIKE_DEBUG +  if (beg == &sentinel_frame) fatal ("Strong ref chain ended up off stack.\n"); + #endif +  CYCLE_DEBUG_MSG (beg, "> rotate_rec_stack, actual beg");    -  CYCLE_DEBUG_MSG(find_marker(beg->data), "> rotate_frame_list, begin at"); -  +     { -  struct gc_pop_frame *b = beg, *p = pos; -  struct gc_stack_frame *old_stack_top; -  while (b->frameflags & GC_OFF_STACK) { -  if ((b = b->next) == pos) goto done; -  CHECK_POP_FRAME(b); -  DO_IF_DEBUG(frame_rot++); -  } -  while (p->frameflags & GC_OFF_STACK) { -  if (!(p = p->next)) goto done; -  CHECK_POP_FRAME(p); -  DO_IF_DEBUG(frame_rot++); -  } -  old_stack_top = stack_top; -  stack_top = p->s_prev; -  p->s_prev = b->s_prev; -  b->s_prev = old_stack_top; -  } - done: -  DO_IF_DEBUG(frame_rot++); +  struct gc_rec_frame *new_stack_top = pos->prev;    -  { -  struct gc_pop_frame *new_list_end = pos->prev; +     beg->prev->next = pos;    pos->prev = beg->prev; -  list_end->next = beg; -  beg->prev = list_end; -  list_end = new_list_end; -  list_end->next = 0; +  +  stack_top->next = beg; +  beg->prev = stack_top; +  +  stack_top = new_stack_top; + #ifdef PIKE_DEBUG +  stack_top->next = (struct gc_rec_frame *) (ptrdiff_t) -1; + #endif    }    -  + #ifdef PIKE_DEBUG +  frame_rot++; + #endif +  +  pos->rf_flags |= GC_PREV_BROKEN; +    #ifdef GC_STACK_DEBUG    fprintf(stderr,"Stack after:\n"); -  for (l = stack_top; l != sentinel_frame; l = l->s_prev) { -  fprintf(stderr, " %p ", l); -  describe_gc_stack_frame(l); -  fputc('\n', stderr); +  { +  struct gc_rec_frame *l; +  for (l = stack_top; l != &sentinel_frame; l = l->prev) { +  fprintf (stderr, " %p%s ", l, +  l == beg ? " (beg)" : l == pos ? " (pos)" : ""); +  describe_rec_frame (l); +  fputc ('\n', stderr);    } -  +  }   #endif -  +  +  return beg;   }    - int gc_cycle_push(void *x, struct marker *m, int weak) + int gc_cycle_push(void *data, struct marker *m, int weak)   { -  struct marker *top = find_marker (stack_top_pop->data); +  struct marker *pm;    -  /* FIXME: Should use top instead? */ -  struct marker *last = find_marker (list_end->data); -  +    #ifdef PIKE_DEBUG    if (gc_is_watching && m && m->flags & GC_WATCHED) {    /* This is useful to set breakpoints on. */    gc_watched_found (m, "gc_cycle_push()");    }    -  debug_malloc_touch(x); +  debug_malloc_touch (data);    -  if (!x) Pike_fatal("Got null pointer.\n"); -  if (m->data != x) Pike_fatal("Got wrong marker.\n"); +  if (!data) Pike_fatal ("Got null pointer.\n"); +  if (m->data != data) Pike_fatal ("Got wrong marker.\n");    if (Pike_in_gc != GC_PASS_CYCLE)    Pike_fatal("GC cycle push attempted in invalid pass.\n");    if (gc_debug && !(m->flags & GC_PRETOUCHED)) -  gc_fatal(x, 0, "gc_cycle_push() called for untouched thing.\n"); +  gc_fatal (data, 0, "gc_cycle_push() called for untouched thing.\n");    if (!gc_destruct_everything) {    if ((!(m->flags & GC_NOT_REFERENCED) || m->flags & GC_MARKED) && -  *(INT32 *) x) -  gc_fatal(x, 1, "Got a referenced marker to gc_cycle_push.\n"); +  *(INT32 *) data) +  gc_fatal (data, 1, "Got a referenced marker to gc_cycle_push.\n");    if (m->flags & GC_XREFERENCED) -  gc_fatal(x, 1, "Doing cycle check in externally referenced thing " +  gc_fatal (data, 1, "Doing cycle check in externally referenced thing "    "missed in mark pass.\n");    } -  if (weak && list_end == &sentinel_frame) -  gc_fatal(x, 1, "weak is %d when on top of stack.\n", weak); +  if (weak && stack_top == &sentinel_frame) +  gc_fatal (data, 1, "weak is %d when stack is empty.\n", weak);    if (gc_debug > 1) {    struct array *a;    struct object *o;    struct program *p;    struct mapping *m;    struct multiset *l;    for(a = gc_internal_array; a; a = a->next) -  if(a == (struct array *) x) goto on_gc_internal_lists; +  if(a == (struct array *) data) goto on_gc_internal_lists;    for(o = gc_internal_object; o; o = o->next) -  if(o == (struct object *) x) goto on_gc_internal_lists; +  if(o == (struct object *) data) goto on_gc_internal_lists;    for(p = gc_internal_program; p; p = p->next) -  if(p == (struct program *) x) goto on_gc_internal_lists; +  if(p == (struct program *) data) goto on_gc_internal_lists;    for(m = gc_internal_mapping; m; m = m->next) -  if(m == (struct mapping *) x) goto on_gc_internal_lists; +  if(m == (struct mapping *) data) goto on_gc_internal_lists;    for(l = gc_internal_multiset; l; l = l->next) -  if(l == (struct multiset *) x) goto on_gc_internal_lists; -  gc_fatal(x, 0, "gc_cycle_check() called for thing not on gc_internal lists.\n"); +  if(l == (struct multiset *) data) goto on_gc_internal_lists; +  gc_fatal (data, 0, "gc_cycle_check() called for thing not on gc_internal lists.\n");    on_gc_internal_lists:    ; /* We must have a least one expression after a label! - Hubbe */    }   #endif    -  if (top->flags & GC_LIVE_RECURSE) { - #ifdef PIKE_DEBUG -  if (!(top->flags & GC_LIVE)) -  gc_fatal(x, 0, "Doing live recursion from a dead thing.\n"); - #endif -  -  if (m->flags & GC_CYCLE_CHECKED) { -  if (!(m->flags & GC_LIVE)) { +  if (stack_top->rf_flags & GC_MARK_LIVE) {    /* Only recurse through things already handled; we'll get to the    * other later in the normal recursion. */ - #ifdef PIKE_DEBUG -  if (m->flags & GC_LIVE_RECURSE) -  gc_fatal(x, 0, "Mark live recursion attempted twice into thing.\n"); - #endif -  goto live_recurse; +  if (m->flags & GC_CYCLE_CHECKED && !(m->flags & GC_LIVE)) { +  CYCLE_DEBUG_MSG (m->frame, "gc_cycle_push, mark live"); +  goto mark_live;    } -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, no live recurse"); +  CYCLE_DEBUG_MSG (m->frame, "gc_cycle_push, no mark live"); +  return 0;    }    -  +  if (stack_top == &sentinel_frame) +  pm = NULL;    else { -  /* We'll get here eventually in the normal recursion. Pop off -  * the remaining live recurse frames for the last thing. */ -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, no live recurse"); -  top->flags &= ~GC_LIVE_RECURSE; -  while (1) { -  struct gc_stack_frame *l = stack_top, *f; +  pm = find_marker (stack_top->data);   #ifdef PIKE_DEBUG -  if (stack_top == POP2STACK (&sentinel_frame)) -  Pike_fatal ("Stack shouldn't be empty here.\n"); +  if (!pm) +  gc_fatal (stack_top->data, 0, "No marker for thing on top of the stack.\n");   #endif -  stack_top = l->s_prev; -  -  /* Use separate pointer chain instead? */ -  for (f = stack_top; !(f->frameflags & GC_POP_FRAME); f = f->s_prev) {} -  stack_top_pop = STACK2POP (f); -  -  if (l->frameflags & GC_POP_FRAME) { -  FREE_POP_FRAME (STACK2POP (l)); -  break; +     } -  FREE_LINK_FRAME (STACK2LINK (l)); -  } - #ifdef GC_CYCLE_DEBUG -  gc_cycle_indent -= 2; -  CYCLE_DEBUG_MSG(m, "> gc_cycle_push, unwound live rec"); - #endif -  } +     -  return 0; -  } -  +    #ifdef PIKE_DEBUG -  if (weak < 0 && stack_top->frameflags & GC_FOLLOWED_NONSTRONG) -  gc_fatal(x, 0, "Followed strong link too late.\n"); -  if (weak >= 0) stack_top->frameflags |= GC_FOLLOWED_NONSTRONG; +  if (weak < 0 && stack_top->rf_flags & GC_FOLLOWED_NONSTRONG) +  gc_fatal (data, 0, "Followed strong link too late.\n"); +  if (weak >= 0) stack_top->rf_flags |= GC_FOLLOWED_NONSTRONG;   #endif    -  if (m->frame && !(m->frame->frameflags & GC_ON_KILL_LIST)) { -  /* A cyclic reference is found. */ +  if (m->frame) { +  /* A cyclic ref or a ref to something on the kill list is found. */ +  struct gc_rec_frame *cycle_frame = m->frame; +  +  if (cycle_frame->rf_flags & GC_ON_KILL_LIST) +  CYCLE_DEBUG_MSG (cycle_frame, "gc_cycle_push, ref to kill list"); +  else if (cycle_frame == stack_top) +  CYCLE_DEBUG_MSG (cycle_frame, "gc_cycle_push, self-ref"); +  else if (weak > 0) +  /* Ignore weak refs since they always are eligible to be broken anyway. */ +  CYCLE_DEBUG_MSG (cycle_frame, "gc_cycle_push, weak cyclic ref"); +  +  else { +  struct gc_rec_frame *weakly_refd = NULL; +  struct gc_rec_frame *brokenly_refd = NULL; +  struct gc_rec_frame *nonstrongly_refd = NULL;   #ifdef PIKE_DEBUG -  if (list_end == &sentinel_frame) -  gc_fatal(x, 0, "Cyclic ref involves dummy sentinel_frame marker.\n"); -  CHECK_POP_FRAME(list_end); -  CHECK_POP_FRAME(m->frame); +  if (stack_top == &sentinel_frame) +  gc_fatal (data, 0, "Cyclic ref involves dummy sentinel frame.\n"); +  CHECK_REC_STACK_FRAME (stack_top);   #endif    -  if (m != last) { -  struct gc_pop_frame *p, *weak_ref = 0, *nonstrong_ref = 0; +  CYCLE_DEBUG_MSG (cycle_frame, "gc_cycle_push, cyclic ref"); +  +  /* Find the corresponding frame still on the stack and compress +  * indirect cycle_id links. */ +  { +  struct gc_rec_frame *r; +  for (r = cycle_frame; !r->prev; r = r->cycle_id) +  CHECK_CYCLE_PIECE_FRAME (r); +  while (cycle_frame != r) { +  struct gc_rec_frame *next = cycle_frame->cycle_id; +  cycle_frame->cycle_id = r; +  cycle_frame = next; +  } +  CHECK_REC_STACK_FRAME (cycle_frame); +  } +     if (!weak) { -  struct gc_pop_frame *q; -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, search normal"); -  /* Find the last weakly linked thing and the one before the -  * first strongly linked thing. */ -  for (q = m->frame, p = q->next;; q = p, p = p->next) { -  CYCLE_DEBUG_MSG (find_marker (p->data), "> gc_cycle_push, search"); -  CHECK_POP_FRAME(p); +  struct gc_rec_frame *r; +  CYCLE_DEBUG_MSG (cycle_frame, "gc_cycle_push, search normal"); +  /* Find the last weakly linked thing and the last thing whose +  * normal ref already has been broken. */ +  for (r = stack_top; r != cycle_frame; r = r->prev) { +  CHECK_REC_STACK_FRAME (r);    DO_IF_DEBUG (link_search++); -  if (p->frameflags & (GC_PREV_WEAK|GC_PREV_STRONG)) { -  if (p->frameflags & GC_PREV_WEAK) weak_ref = p; -  else if (!nonstrong_ref) nonstrong_ref = q; +  if (r->rf_flags & GC_PREV_WEAK) { +  CYCLE_DEBUG_MSG (r, "> gc_cycle_push, found weak"); +  weakly_refd = r; +  break;    } -  if (p == list_end) break; +  if (!brokenly_refd && (r->rf_flags & GC_PREV_BROKEN)) { +  CYCLE_DEBUG_MSG (r, "> gc_cycle_push, found broken"); +  brokenly_refd = r;    } -  +  else +  CYCLE_DEBUG_MSG (r, "> gc_cycle_push, search");    } -  +  }    -  else if (weak < 0) { -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, search strong"); +  else { /* weak < 0 */ +  struct gc_rec_frame *r; +  CYCLE_DEBUG_MSG (cycle_frame, "gc_cycle_push, search strong");    /* Find the last weakly linked thing and the last one which    * isn't strongly linked. */ -  for (p = m->frame->next;; p = p->next) { -  CYCLE_DEBUG_MSG (find_marker (p->data), "> gc_cycle_push, search"); -  CHECK_POP_FRAME(p); +  for (r = stack_top; r != cycle_frame; r = r->prev) { +  CHECK_REC_STACK_FRAME (r);    DO_IF_DEBUG (link_search++); -  if (p->frameflags & GC_PREV_WEAK) weak_ref = p; -  if (!(p->frameflags & GC_PREV_STRONG)) nonstrong_ref = p; -  if (p == list_end) break; +  if (r->rf_flags & GC_PREV_WEAK) { +  CYCLE_DEBUG_MSG (r, "> gc_cycle_push, found weak"); +  weakly_refd = r; +  break;    } -  +  if (!nonstrongly_refd && !(r->rf_flags & GC_PREV_STRONG)) { +  nonstrongly_refd = r; +  CYCLE_DEBUG_MSG (r, "> gc_cycle_push, found nonstrong"); +  } +  if (!brokenly_refd && (r->rf_flags & GC_PREV_BROKEN)) { +  CYCLE_DEBUG_MSG (r, "> gc_cycle_push, found broken"); +  brokenly_refd = r; +  } + #ifdef GC_CYCLE_DEBUG +  else if (r != nonstrongly_refd) +  CYCLE_DEBUG_MSG (r, "> gc_cycle_push, search"); + #endif +  }   #ifdef PIKE_DEBUG -  if (p == list_end && !nonstrong_ref) { +  if (weak && r == cycle_frame && !nonstrongly_refd) {    fprintf(stderr, "Only strong links in cycle:\n"); -  for (p = m->frame;; p = p->next) { -  describe(p->data); -  locate_references(p->data); -  if (p == list_end) break; +  for (r = cycle_frame;; r = r->next) { +  describe (r->data); +  locate_references (r->data); +  if (r == stack_top) break;    fprintf(stderr, "========= next =========\n");    }    gc_fatal(0, 0, "Only strong links in cycle.\n");    }   #endif    }    -  else { -  struct gc_pop_frame *q; -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, search weak"); -  /* Find the thing before the first strongly linked one. */ -  for (q = m->frame, p = q->next;; q = p, p = p->next) { -  CYCLE_DEBUG_MSG (find_marker (p->data), "> gc_cycle_push, search"); -  CHECK_POP_FRAME(p); -  DO_IF_DEBUG (link_search++); -  if (!(p->frameflags & GC_PREV_WEAK) && !nonstrong_ref) -  nonstrong_ref = q; -  if (p == list_end) break; +  if (weakly_refd) { +  /* 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 avoid having to clobber the others after the +  * rotation). */ +  CYCLE_DEBUG_MSG (weakly_refd, "gc_cycle_push, weak break"); +  rotate_rec_stack (cycle_frame, weakly_refd);    } -  } +     -  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(find_marker(weak_ref->data), -  "gc_cycle_push, weak break"); -  rotate_frame_list (m->frame, weak_ref); -  } +  else { +  struct gc_rec_frame *cycle_id = cycle_frame->cycle_id; +  struct gc_rec_frame *break_pos;    -  else if (weak < 0) { +  if (brokenly_refd) { +  /* Found a link that already has been broken once, so we +  * prefer to break at it again. */ +  CYCLE_DEBUG_MSG (brokenly_refd, "gc_cycle_push, break at broken"); +  break_pos = brokenly_refd; +  } +  else if (!weak) { +  CYCLE_DEBUG_MSG (cycle_frame, "gc_cycle_push, no break spot found"); +  break_pos = NULL; +  } +  else { /* weak < 0 */    /* The backward link is strong. Must break the cycle at the    * last nonstrong link. */ -  CYCLE_DEBUG_MSG(find_marker(nonstrong_ref->data), -  "gc_cycle_push, nonstrong break"); -  rotate_frame_list (m->frame, nonstrong_ref); -  nonstrong_ref->next->frameflags = -  (nonstrong_ref->next->frameflags & ~GC_PREV_WEAK) | GC_PREV_STRONG; +  CYCLE_DEBUG_MSG (nonstrongly_refd, "gc_cycle_push, nonstrong break"); +  break_pos = nonstrongly_refd;    }    -  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->frame) { -  CYCLE_DEBUG_MSG(find_marker(nonstrong_ref->data), -  "gc_cycle_push, weaker break"); -  rotate_frame_list (m->frame, nonstrong_ref); +  if (break_pos) { +  struct gc_rec_frame *rot_beg; +  rot_beg = rotate_rec_stack (cycle_frame, break_pos); +  cycle_frame->rf_flags = +  (cycle_frame->rf_flags & ~(GC_PREV_WEAK|GC_PREV_BROKEN)) | GC_PREV_STRONG; +  +  if (rot_beg->cycle_id != break_pos->prev->cycle_id) +  /* Ensure that the cycle id frame is kept deepest in the +  * stack: Since there's no already marked cycle that +  * continues past the beginning of the rotated portion +  * (rot_beg and break_pos->prev were previously next to +  * each other), break_pos is now the deepest frame in the +  * cycle. */ +  cycle_id = break_pos;    } -  } +     -  else if (!weak) { -  /* 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->frame->cycle ? m->frame->cycle : ++last_cycle; -  if (cycle == list_end->cycle) -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, old cycle"); -  else { -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, cycle"); -  for (p = m->frame;; p = p->next) { -  p->cycle = cycle; -  CYCLE_DEBUG_MSG(find_marker(p->data), "> gc_cycle_push, mark cycle"); -  if (p == list_end) break; +  /* Mark the cycle. NB: This causes O(n^2) complexity for some +  * kinds of data structures. */ +  CHECK_REC_STACK_FRAME (cycle_id); +  { +  struct gc_rec_frame *r, *bottom = break_pos ? break_pos : cycle_frame; +  CYCLE_DEBUG_MSG (cycle_id, "gc_cycle_push, cycle"); +  for (r = stack_top;; r = r->prev) { +  CHECK_REC_STACK_FRAME (r); +  r->cycle_id = cycle_id; +  CYCLE_DEBUG_MSG (r, "> gc_cycle_push, mark cycle 1"); +  if (r == bottom) break;    }}}}} /* Mmm.. lisp ;) */       else    if (!(m->flags & GC_CYCLE_CHECKED)) { -  struct gc_pop_frame *l; +  struct gc_rec_frame *r;   #ifdef PIKE_DEBUG    cycle_checked++;    if (m->frame) -  gc_fatal(x, 0, "Marker already got a frame.\n"); -  if (list_end->next) -  gc_fatal(list_end->data, 0, "Not at end of list.\n"); +  gc_fatal (data, 0, "Marker already got a frame.\n");   #endif    -  m->frame = l = gc_cycle_enqueue_pop(x); -  l->next = NULL; -  l->prev = list_end; -  list_end->next = l; -  m->flags |= GC_CYCLE_CHECKED | (last->flags & GC_LIVE); -  debug_malloc_touch(x); +  m->flags |= GC_CYCLE_CHECKED | (pm ? pm->flags & GC_LIVE : 0); +  m->frame = r = gc_cycle_enqueue_rec (data); +  debug_malloc_touch (data);    if (weak) { -  if (weak > 0) l->frameflags |= GC_PREV_WEAK; -  else l->frameflags |= GC_PREV_STRONG; +  if (weak > 0) r->rf_flags = GC_PREV_WEAK; +  else r->rf_flags = GC_PREV_STRONG;    } -  +  else +  r->rf_flags = 0;      #ifdef GC_CYCLE_DEBUG -  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"); +  if (weak > 0) CYCLE_DEBUG_MSG (r, "gc_cycle_push, recurse weak"); +  else if (weak < 0) CYCLE_DEBUG_MSG (r, "gc_cycle_push, recurse strong"); +  else CYCLE_DEBUG_MSG (r, "gc_cycle_push, recurse");    gc_cycle_indent += 2;   #endif -  list_end = l; +     return 1;    }       /* Should normally not recurse now, but got to do that anyway if we -  * must mark live things. */ -  if (!(last->flags & GC_LIVE) || m->flags & GC_LIVE) { -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, no recurse"); +  * must propagate GC_LIVE flags. */ +  if (!pm || !(pm->flags & GC_LIVE) || m->flags & GC_LIVE) { +  CYCLE_DEBUG_MSG (m->frame ? m->frame : NULL, "gc_cycle_push, no recurse");    return 0;    }    - live_recurse: +  /* Initialize mark live recursion. */ +  gc_cycle_enqueue_rec (NULL)->rf_flags = GC_MARK_LIVE; + #ifdef GC_CYCLE_DEBUG +  CYCLE_DEBUG_MSG (m->frame ? m->frame : NULL, "gc_cycle_push, mark live begins"); +  gc_cycle_indent += 2; + #endif +  + mark_live:   #ifdef PIKE_DEBUG    if (m->flags & GC_LIVE) -  Pike_fatal("Shouldn't live recurse when there's nothing to do.\n"); +  Pike_fatal("Shouldn't mark live recurse when there's nothing to do.\n");   #endif -  m->flags |= GC_LIVE|GC_LIVE_RECURSE; -  debug_malloc_touch(x); +  m->flags |= GC_LIVE; +  debug_malloc_touch (data);       if (m->flags & GC_GOT_DEAD_REF) {    /* A thing previously popped as dead is now being marked live.    * Have to remove the extra ref added by gc_cycle_pop(). */ -  gc_free_extra_ref(x); -  if (!sub_ref ((struct ref_dummy *) x)) { +  gc_free_extra_ref (data); +  if (!sub_ref ((struct ref_dummy *) data)) {   #ifdef PIKE_DEBUG -  gc_fatal(x, 0, "Thing got zero refs after removing the dead gc ref.\n"); +  gc_fatal (data, 0, "Thing got zero refs after removing the dead gc ref.\n");   #endif    }    }    -  { -  /* Recurse without linking onto the frame list. */ -  struct gc_pop_frame *l = gc_cycle_enqueue_pop(x); - #ifdef GC_CYCLE_DEBUG -  CYCLE_DEBUG_MSG(m, "gc_cycle_push, live recurse"); -  gc_cycle_indent += 2; +  /* Visit links without pushing a rec frame. */ + #ifdef PIKE_DEBUG +  mark_live++;   #endif -  +  return 1;   }    -  + static void gc_cycle_pop() + {   #ifdef PIKE_DEBUG -  live_rec++; +  if (Pike_in_gc != GC_PASS_CYCLE) +  Pike_fatal("GC cycle pop attempted in invalid pass.\n"); +  if (stack_top->u.link_top) +  gc_fatal (stack_top->data, 0, "Link list not empty for popped rec frame.\n");   #endif -  return 1; + #ifdef GC_CYCLE_DEBUG +  gc_cycle_indent -= 2; + #endif +  +  if (stack_top->rf_flags & GC_MARK_LIVE) { +  struct gc_rec_frame *r = stack_top->prev; +  CYCLE_DEBUG_MSG (stack_top, "gc_cycle_pop, mark live ends"); +  really_free_gc_rec_frame (stack_top); +  stack_top = r;    }    - static void gc_cycle_pop(void *a) - { -  struct marker *m = find_marker(a); -  struct gc_pop_frame *here, *base, *p; +  else { +  struct gc_rec_frame *popped = stack_top;      #ifdef PIKE_DEBUG -  +  { +  void *data = popped->data; +  struct marker *m = find_marker (data);    if (gc_is_watching && m && m->flags & GC_WATCHED) {    /* This is useful to set breakpoints on. */    gc_watched_found (m, "gc_cycle_pop()");    } -  if (!a) Pike_fatal("Got null pointer.\n"); -  if (Pike_in_gc != GC_PASS_CYCLE) -  Pike_fatal("GC cycle pop attempted in invalid pass.\n"); +     if (!(m->flags & GC_CYCLE_CHECKED)) -  gc_fatal(a, 0, "Marker being popped doesn't have GC_CYCLE_CHECKED.\n"); +  gc_fatal (data, 0, "Marker being popped doesn't have GC_CYCLE_CHECKED.\n");    if (!gc_destruct_everything) {    if ((!(m->flags & GC_NOT_REFERENCED) || m->flags & GC_MARKED) && -  *(INT32 *) a) -  gc_fatal(a, 1, "Got a referenced marker to gc_cycle_pop.\n"); +  *(INT32 *) data) +  gc_fatal (data, 1, "Got a referenced marker to gc_cycle_pop.\n");    if (m->flags & GC_XREFERENCED) -  gc_fatal(a, 1, "Doing cycle check in externally referenced thing " +  gc_fatal (data, 1, "Doing cycle check in externally referenced thing "    "missed in mark pass.\n");    } -  +  if (popped->next != (struct gc_rec_frame *) (ptrdiff_t) -1) +  gc_fatal (data, 0, "Popped rec frame got stuff in the next pointer.\n"); +  }   #endif - #ifdef GC_CYCLE_DEBUG -  gc_cycle_indent -= 2; +  +  stack_top = popped->prev; + #ifdef PIKE_DEBUG +  if (stack_top != &sentinel_frame) CHECK_REC_STACK_FRAME (stack_top); +  CHECK_REC_STACK_FRAME (popped); +  popped->prev = NULL;   #endif    -  if (m->flags & GC_LIVE_RECURSE) { -  m->flags &= ~GC_LIVE_RECURSE; -  CYCLE_DEBUG_MSG(m, "gc_cycle_pop_live"); -  FREE_POP_FRAME (STACK2POP (stack_top)); -  /* stack_top and stack_top_pop updated by caller. */ -  return; -  } +  if (popped->cycle_id != popped) { +  /* Part of a cycle that extends further back - move to the cycle +  * piece list of the previous frame. */ +  struct gc_rec_frame *this_list_last = +  popped->cycle_piece ? popped->cycle_piece->u.last_cycle_piece : popped; + #ifdef PIKE_DEBUG +  if (this_list_last->cycle_piece) +  gc_fatal (this_list_last->data, 0, +  "This frame should be last on the cycle piece list.\n"); +  popped->rf_flags |= GC_ON_CYCLE_PIECE_LIST; +  CHECK_CYCLE_PIECE_FRAME (this_list_last); + #endif +  CYCLE_DEBUG_MSG (popped, "gc_cycle_pop, keep cycle piece");    -  here = m->frame; +  if (!stack_top->cycle_piece) +  popped->u.last_cycle_piece = this_list_last; +  else { +  /* Link in the popped frame and its cycle piece list before +  * the one that the previous frame has. */ +  struct gc_rec_frame *up_list_first = stack_top->cycle_piece; +  struct gc_rec_frame *up_list_last = up_list_first->u.last_cycle_piece;   #ifdef PIKE_DEBUG -  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(list_end); -  if (here->frameflags & GC_OFF_STACK) -  gc_fatal(a, 0, "Marker being popped isn't on stack.\n"); -  here->s_prev = (struct gc_stack_frame *)(ptrdiff_t) -1; +  CHECK_CYCLE_PIECE_FRAME (up_list_last); +  if (up_list_last->cycle_piece) +  gc_fatal (up_list_last->data, 0, +  "This frame should be last on the cycle piece list.\n");   #endif -  here->frameflags |= GC_OFF_STACK; +  CYCLE_DEBUG_MSG (up_list_first, "> gc_cycle_pop, inserted before"); +  this_list_last->cycle_piece = up_list_first; +  popped->u.last_cycle_piece = up_list_last; +  } +  stack_top->cycle_piece = popped; +  popped->cycle_id = stack_top;    -  for (base = here->prev, p = here;; base = p, p = p->next) { -  if (base == here) { -  /* Part of a cycle; wait until the cycle is complete before -  * unlinking it from the frame list. */ -  DO_IF_DEBUG(m->frame->s_prev = (struct gc_stack_frame *)(ptrdiff_t) -1); -  CYCLE_DEBUG_MSG(m, "gc_cycle_pop, keep cycle"); -  return; +  CHECK_CYCLE_PIECE_FRAME (popped); +  CHECK_REC_STACK_FRAME (stack_top);    } -  CHECK_POP_FRAME(p); -  if (!(p->cycle && p->cycle == base->cycle)) -  break; +  +  else { +  /* Free or move to the kill list the popped frame and its cycle +  * piece list. */ +  struct gc_rec_frame **kill_list_ptr = &kill_list; +  struct gc_rec_frame *cycle_id = NULL; +  + #ifdef PIKE_DEBUG +  { +  struct gc_rec_frame *r; +  for (r = popped->cycle_piece; r; r = r->cycle_piece) +  /* Can't do this while the list is being freed below. */ +  CHECK_CYCLE_PIECE_FRAME (r);    } -  + #endif    -  list_end = base; -  while ((p = base->next)) { -  struct marker *pm = find_marker(p->data); +  CYCLE_DEBUG_MSG (popped, "gc_cycle_pop, popping cycle"); +  +  do { +  struct marker *m = find_marker (popped->data); +  struct gc_rec_frame *next = popped->cycle_piece; +  +  if (m->flags & GC_LIVE_OBJ) { +  /* Move to the kill list. */   #ifdef PIKE_DEBUG -  if (pm->frame != p) -  gc_fatal(p->data, 0, "Bogus marker for thing being popped.\n"); +  popped->rf_flags &= ~GC_ON_CYCLE_PIECE_LIST; +  popped->cycle_piece = popped->u.last_cycle_piece = +  (struct gc_rec_frame *) (ptrdiff_t) -1;   #endif -  p->frameflags &= ~(GC_PREV_WEAK|GC_PREV_STRONG); -  if (pm->flags & GC_LIVE_OBJ) { +  popped->next = *kill_list_ptr; +  *kill_list_ptr = popped; +  kill_list_ptr = &popped->next; +  popped->rf_flags |= GC_ON_KILL_LIST; +  +  /* Ensure that the frames on the kill list have a valid +  * cycle id frame and that every frame is linked directly to +  * it. This is only for the sake of warn_bad_cycles. */ +  if (!cycle_id) cycle_id = popped; +  popped->cycle_id = cycle_id; +     /* This extra ref is taken away in the kill pass. Don't add one    * if it got an extra ref already due to weak free. */ -  if (!(pm->flags & GC_GOT_DEAD_REF)) -  gc_add_extra_ref(p->data); -  base = p; -  p->frameflags |= GC_ON_KILL_LIST; -  DO_IF_DEBUG(p->prev = (struct gc_pop_frame *)(ptrdiff_t) -1); -  CYCLE_DEBUG_MSG(pm, "gc_cycle_pop, put on kill list"); +  if (!(m->flags & GC_GOT_DEAD_REF)) +  gc_add_extra_ref (popped->data); +  +  CHECK_KILL_LIST_FRAME (popped); +  CYCLE_DEBUG_MSG (popped, "> gc_cycle_pop, move to kill list");    } -  +     else { -  if (!(pm->flags & GC_LIVE)) { +  if (!(m->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. */ -  if (!(pm->flags & GC_GOT_DEAD_REF)) { -  gc_add_extra_ref(pm->data); -  pm->flags |= GC_GOT_DEAD_REF; +  if (!(m->flags & GC_GOT_DEAD_REF)) { +  gc_add_extra_ref (popped->data); +  m->flags |= GC_GOT_DEAD_REF;    }    }   #ifdef PIKE_DEBUG    else -  if (pm->flags & GC_GOT_DEAD_REF) -  gc_fatal(p->data, 0, "Didn't expect a dead extra ref.\n"); +  if (m->flags & GC_GOT_DEAD_REF) +  gc_fatal (popped->data, 0, "Didn't expect a dead extra ref.\n");   #endif -  base->next = p->next; -  CYCLE_DEBUG_MSG(pm, "gc_cycle_pop, pop off"); -  pm->frame = 0; -  FREE_POP_FRAME (p); +  +  CYCLE_DEBUG_MSG (popped, "> gc_cycle_pop, free"); +  m->frame = NULL; +  really_free_gc_rec_frame (popped);    } -  } +     -  if (base != list_end) { -  base->next = kill_list; -  kill_list = list_end->next; -  list_end->next = 0; +  popped = next; +  } while (popped);    } -  +  }    -  /* stack_top and stack_top_pop updated by caller. */ + #ifdef PIKE_DEBUG +  stack_top->next = (struct gc_rec_frame *) (ptrdiff_t) -1; + #endif   }      void do_gc_recurse_svalues(struct svalue *s, int num)   {    gc_recurse_svalues(s, num);   }      void do_gc_recurse_short_svalue(union anything *u, int type)   {    gc_recurse_short_svalue(u, type);
pike.git/src/gc.c:2820:    * (On some architectures longjump() might restore obj_arr's original    * value (eg if obj_arr is in a register)).    */    struct array **obj_arr_ = (struct array **)xalloc(sizeof(struct array *));    ONERROR tmp;       *obj_arr_ = NULL;       SET_ONERROR(tmp, free_obj_arr, obj_arr_);    + #if 0    {    struct gc_pop_frame *p;    unsigned cycle = 0;    *obj_arr_ = allocate_array(0);       for (p = kill_list; p;) {    if ((cycle = p->cycle)) {    push_object((struct object *) p->data);    dmalloc_touch_svalue(Pike_sp-1);    *obj_arr_ = append_array(*obj_arr_, --Pike_sp);
pike.git/src/gc.c:2847:    *obj_arr_ = 0;    SAFE_APPLY_MASTER("runtime_warning", 3);    pop_stack();    *obj_arr_ = allocate_array(0);    }    else *obj_arr_ = resize_array(*obj_arr_, 0);    }    if (!p) break;    }    } + #endif       CALL_AND_UNSET_ONERROR(tmp);   }      size_t do_gc(void *ignored, int explicit_call)   {    ALLOC_COUNT_TYPE start_allocs;    size_t start_num_objs, unreferenced;    cpu_time_t gc_start_time;    ptrdiff_t objs, pre_kill_objs;
pike.git/src/gc.c:2901:       destruct_objects_to_destruct();       if(gc_evaluator_callback)    {    remove_callback(gc_evaluator_callback);    gc_evaluator_callback=0;    }       objs=num_objects; -  last_cycle = 0; +        if(GC_VERBOSE_DO(1 ||) gc_trace) {    if (gc_destruct_everything)    fprintf (stderr, "Destructing all objects... ");    else    fprintf(stderr,"Garbage collecting... ");    GC_VERBOSE_DO(fprintf(stderr, "\n"));    }   #ifdef PIKE_DEBUG    if(num_objects < 0)
pike.git/src/gc.c:2924:       last_gc=TIME(0);    start_num_objs = num_objects;    start_allocs = num_allocs;    num_allocs = 0;       /* Object alloc/free and any reference changes are disallowed now. */      #ifdef PIKE_DEBUG    delayed_freed = weak_freed = checked = marked = cycle_checked = live_ref = 0; -  live_rec = frame_rot = link_search = 0; +  mark_live = frame_rot = link_search = 0;   #endif -  +  rec_frames = link_frames = free_extra_frames = 0; +  max_rec_frames = max_link_frames = 0;    if (gc_debug) {    unsigned n;    Pike_in_gc = GC_PASS_PRETOUCH;    n = gc_touch_all_arrays();    n += gc_touch_all_multisets();    n += gc_touch_all_mappings();    n += gc_touch_all_programs();    n += gc_touch_all_objects();   #ifdef PIKE_DEBUG    gc_touch_all_strings();
pike.git/src/gc.c:3017:    "| mark: %u markers referenced, %u weak references freed,\n"    "| %d things to free, "    "got %"PRINTSIZET"u tricky weak refs\n",    marked, weak_freed, delayed_freed, gc_ext_weak_refs));    }       {   #ifdef PIKE_DEBUG    size_t orig_ext_weak_refs = gc_ext_weak_refs;    obj_count = delayed_freed; -  max_gc_stack_frames = 0; +    #endif    Pike_in_gc=GC_PASS_CYCLE;       /* Now find all cycles in the internal structures. Note that we can    * follow the same reference several times, just like in the mark    * pass. */    /* Note: The order between types here is normally not significant,    * but the permuting destruct order tests in the testsuite won't be    * really effective unless objects are handled first. :P */    gc_cycle_check_all_objects();
pike.git/src/gc.c:3039:    gc_cycle_check_all_multisets();    gc_cycle_check_all_mappings();    gc_cycle_check_all_programs();   #if 0   #ifdef DEBUG_MALLOC    gc_cycle_check_all_types();   #endif   #endif      #ifdef PIKE_DEBUG -  if (stack_top != POP2STACK (&sentinel_frame)) +  if (stack_top != &sentinel_frame)    Pike_fatal("Frame stack not empty at end of cycle check pass.\n"); -  if (sentinel_frame.next || list_end != &sentinel_frame) -  Pike_fatal("Frame list not empty or inconsistent after cycle check pass.\n"); +     if (gc_ext_weak_refs != orig_ext_weak_refs)    Pike_fatal("gc_ext_weak_refs changed from %"PRINTSIZET"u "    "to %"PRINTSIZET"u in cycle check pass.\n",    orig_ext_weak_refs, gc_ext_weak_refs);   #endif       GC_VERBOSE_DO(fprintf(stderr, -  "| cycle: %u internal things visited, %u cycle ids used,\n" +  "| cycle: %u internal things visited,\n"    "| %u weak references freed, %d more things to free,\n" -  "| %u live recursed frames, %u frame rotations,\n" -  "| %u links searched, space for %u gc frames used\n", -  cycle_checked, last_cycle, weak_freed, -  delayed_freed - obj_count, -  live_rec, frame_rot, link_search, max_gc_stack_frames)); +  "| %u mark live visits, %u frame rotations,\n" +  "| %u links searched, used max %u link frames,\n" +  "| %u rec frames and %u free extra frames\n", +  cycle_checked, weak_freed, delayed_freed - obj_count, +  mark_live, frame_rot, link_search, max_link_frames, +  max_rec_frames, free_extra_frames)); +  + #ifdef PIKE_DEBUG +  if (link_frames) fatal ("Leaked %u link frames.\n", link_frames); + #endif    }       if (gc_ext_weak_refs) {    size_t to_free = gc_ext_weak_refs;   #ifdef PIKE_DEBUG    obj_count = delayed_freed;   #endif    Pike_in_gc = GC_PASS_ZAP_WEAK;    /* Zap weak references from external to internal things. That    * occurs when something has both external weak refs and nonweak
pike.git/src/gc.c:3102:    n += gc_touch_all_programs();    n += gc_touch_all_objects();   #ifdef PIKE_DEBUG   #if 0    gc_touch_all_types();   #endif    gc_touch_all_strings();   #endif    if (n != (unsigned) num_objects)    Pike_fatal("Object count wrong in gc; expected %d, got %d.\n", num_objects, n); -  get_marker(sentinel_frame.data)->flags |= GC_MIDDLETOUCHED; +    #if 0 /* Temporarily disabled - Hubbe */   #ifdef PIKE_DEBUG   #ifdef DEBUG_MALLOC    PTR_HASH_LOOP(marker, i, m)    if (!(m->flags & (GC_MIDDLETOUCHED|GC_WEAK_FREED)) &&    dmalloc_is_invalid_memory_block(m->data)) {    fprintf(stderr, "Found a stray marker after middletouch pass: ");    describe_marker(m);    fprintf(stderr, "Describing marker location(s):\n");    debug_malloc_dump_references(m, 2, 1, 0);
pike.git/src/gc.c:3151:    if (gc_internal_mapping)    unreferenced += gc_free_all_unreferenced_mappings();    if (gc_internal_object)    unreferenced += gc_free_all_unreferenced_objects();    /* Note: gc_free_all_unreferenced_objects needs to have the programs    * around to handle the free (even when they aren't live). So it's    * necessary to free the objects before the programs. */    if (gc_internal_program)    unreferenced += gc_free_all_unreferenced_programs();    +  if (free_extra_frames > tot_max_free_extra_frames) +  tot_max_free_extra_frames = free_extra_frames; +    #if 0   #ifdef DEBUG_MALLOC    unreferenced += gc_free_all_unreferenced_types();   #endif   #endif       /* We might occasionally get things to gc_delayed_free that the free    * calls above won't find. They're tracked in this list. */    while (free_extra_list) { -  struct gc_free_extra_frame *next = free_extra_list->fe_next; +  struct free_extra_frame *next = free_extra_list->next;    union anything u;    u.refs = (INT32 *) free_extra_list->data;    gc_free_extra_ref (u.refs);    free_short_svalue (&u, free_extra_list->type); -  FREE_FREE_EXTRA_FRAME (free_extra_list); +  really_free_free_extra_frame (free_extra_list);    free_extra_list = next;    }    -  GC_VERBOSE_DO(fprintf(stderr, "| free: %d unreferenced, %d really freed, " -  "%u left with live references\n", + #ifdef PIKE_DEBUG +  if (free_extra_frames) fatal ("Leaked %u free extra frames.\n", free_extra_frames); + #endif +  +  GC_VERBOSE_DO(fprintf(stderr, "| free: %"PRINTSIZET"u unreferenced, " +  "%d really freed, %u left with live references\n",    unreferenced, obj_count - num_objects, live_ref));      #ifdef PIKE_DEBUG    gc_internal_array = (struct array *) (ptrdiff_t) -1;    gc_internal_multiset = (struct multiset *) (ptrdiff_t) -1;    gc_internal_mapping = (struct mapping *) (ptrdiff_t) -1;    gc_internal_program = (struct program *) (ptrdiff_t) -1;    gc_internal_object = (struct object *) (ptrdiff_t) -1;       if(fatal_after_gc) Pike_fatal("%s", fatal_after_gc);   #endif       Pike_in_gc=GC_PASS_KILL; -  +     /* Destruct the live objects in cycles, but first warn about any bad    * cycles. */    pre_kill_objs = num_objects; -  if (last_cycle && Pike_interpreter.evaluator_stack && -  !gc_destruct_everything) { +  if (Pike_interpreter.evaluator_stack && !gc_destruct_everything) {    objs -= num_objects;    warn_bad_cycles();    objs += num_objects;    }   #if defined (PIKE_DEBUG) || defined (DO_PIKE_CLEANUP)    destroy_count = 0;   #endif -  +     {    enum object_destruct_reason reason =   #ifdef DO_PIKE_CLEANUP    gc_destruct_everything ? DESTRUCT_CLEANUP :   #endif    DESTRUCT_GC; -  while (kill_list) { -  struct gc_pop_frame *next = kill_list->next; +  + #ifdef PIKE_DEBUG +  { +  struct gc_rec_frame *r; +  for (r = kill_list; r != &sentinel_frame; r = r->next) +  /* Can't do this while the list is being freed below. */ +  CHECK_KILL_LIST_FRAME (r); +  } + #endif +  +  while (kill_list != &sentinel_frame) { +  struct gc_rec_frame *next = kill_list->next;    struct object *o = (struct object *) kill_list->data; -  +    #ifdef PIKE_DEBUG -  if (!(kill_list->frameflags & GC_ON_KILL_LIST)) -  gc_fatal(o, 0, "Kill list element hasn't got the proper flag.\n"); +     if ((get_marker(kill_list->data)->flags & (GC_LIVE|GC_LIVE_OBJ)) !=    (GC_LIVE|GC_LIVE_OBJ))    gc_fatal(o, 0, "Invalid object on kill list.\n");    if (o->prog && (o->prog->flags & PROGRAM_USES_PARENT) &&    PARENT_INFO(o)->parent &&    !PARENT_INFO(o)->parent->prog &&    get_marker(PARENT_INFO(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", o, o->refs);    if (o->prog) {    INT32 line;    struct pike_string *file = get_program_line (o->prog, &line);    fprintf(stderr, ", prog %s:%d\n", file->str, line);    free_string(file);    }    else fputs(", is destructed\n", stderr);    ); -  +     destruct_object (o, reason);    free_object(o);    gc_free_extra_ref(o);   #if defined (PIKE_DEBUG) || defined (DO_PIKE_CLEANUP)    destroy_count++;   #endif -  FREE_POP_FRAME (kill_list); +  really_free_gc_rec_frame (kill_list);    kill_list = next;    }    }    -  GC_VERBOSE_DO(fprintf(stderr, "| kill: %u objects killed, %d things really freed\n", + #ifdef PIKE_DEBUG +  if (rec_frames) fatal ("Leaked %u rec frames.\n", rec_frames); + #endif +  +  GC_VERBOSE_DO(fprintf(stderr, "| kill: %u objects killed, " +  "%"PRINTSIZET"u things really freed\n",    destroy_count, pre_kill_objs - num_objects));       Pike_in_gc=GC_PASS_DESTRUCT;    /* Destruct objects on the destruct queue. */    GC_VERBOSE_DO(obj_count = num_objects);    destruct_objects_to_destruct();    GC_VERBOSE_DO(fprintf(stderr, "| destruct: %d things really freed\n",    obj_count - num_objects));      #if 0
pike.git/src/gc.c:3411:    fprintf(stderr, "done (%"PRINTSIZET"d of %"PRINTSIZET"d "    "%s unreferenced)%s\n",    unreferenced, start_num_objs,    unreferenced == 1 ? "was" : "were",    timestr);    }    }      #ifdef PIKE_DEBUG    UNSET_ONERROR (uwp); -  if (max_gc_stack_frames > max_tot_gc_stack_frames) -  max_tot_gc_stack_frames = max_gc_stack_frames; +     tot_cycle_checked += cycle_checked; -  tot_live_rec += live_rec, tot_frame_rot += frame_rot; +  tot_mark_live += mark_live, tot_frame_rot += frame_rot;   #endif -  +  if (max_rec_frames > tot_max_rec_frames) +  tot_max_rec_frames = max_rec_frames; +  if (max_link_frames > tot_max_link_frames) +  tot_max_link_frames = max_link_frames;      #ifdef ALWAYS_GC    ADD_GC_CALLBACK();   #else    if(d_flag > 3) ADD_GC_CALLBACK();   #endif      #ifdef DO_PIKE_CLEANUP    if (gc_destruct_everything)    return destroy_count;
pike.git/src/gc.c:3531: Inside #if defined(PIKE_DEBUG)
  #ifdef PIKE_DEBUG    default: Pike_fatal ("Unknown last_garbage_strategy %d\n", last_garbage_strategy);   #endif    }    size++;       push_constant_text("last_gc");    push_int64(last_gc);    size++;    + #ifdef PIKE_DEBUG +  push_constant_text ("max_rec_frames"); +  push_int64 (DO_NOT_WARN ((INT64) tot_max_rec_frames)); +  +  push_constant_text ("max_link_frames"); +  push_int64 (DO_NOT_WARN ((INT64) tot_max_link_frames)); +  +  push_constant_text ("max_free_extra_frames"); +  push_int64 (DO_NOT_WARN ((INT64) tot_max_free_extra_frames)); + #endif +     f_aggregate_mapping(size * 2);   }      void dump_gc_info(void)   {    fprintf(stderr,"Current number of things : %d\n",num_objects);    fprintf(stderr,"Allocations since last gc : "PRINT_ALLOC_COUNT_TYPE"\n",    num_allocs);    fprintf(stderr,"Threshold for next gc : "PRINT_ALLOC_COUNT_TYPE"\n",    alloc_threshold);
pike.git/src/gc.c:3558:    fprintf(stderr,"Avg cpu "CPU_TIME_UNIT" between gc : %f\n", non_gc_time);    fprintf(stderr,"Avg cpu "CPU_TIME_UNIT" in gc : %f\n", gc_time);    fprintf(stderr,"Avg time ratio in gc : %f\n", gc_time / non_gc_time);       fprintf(stderr,"Garbage strategy in last gc: %s\n",    last_garbage_strategy == GARBAGE_RATIO_LOW ? "garbage_ratio_low" :    last_garbage_strategy == GARBAGE_RATIO_HIGH ? "garbage_ratio_high" :    "???");      #ifdef PIKE_DEBUG -  fprintf(stderr,"Max used gc frames : %u\n", max_tot_gc_stack_frames); -  fprintf(stderr,"Live recursed ratio : %g\n", -  (double) tot_live_rec / tot_cycle_checked); +  fprintf(stderr,"Max used recursion frames : %u\n", tot_max_rec_frames); +  fprintf(stderr,"Max used link frames : %u\n", tot_max_link_frames); +  fprintf(stderr,"Max used free extra frames : %u\n", tot_max_free_extra_frames); +  fprintf(stderr,"Marked live ratio : %g\n", +  (double) tot_mark_live / tot_cycle_checked);    fprintf(stderr,"Frame rotation ratio : %g\n",    (double) tot_frame_rot / tot_cycle_checked);   #endif       fprintf(stderr,"in_gc : %d\n", Pike_in_gc);   }      void cleanup_gc(void)   {   #ifdef PIKE_DEBUG    if (gc_evaluator_callback) {    remove_callback(gc_evaluator_callback);    gc_evaluator_callback = NULL;    }   #endif /* PIKE_DEBUG */   }