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$ +    */      #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:21:   #include "pike_macros.h"   #include "pike_rusage.h"   #include "pike_types.h"   #include "time_stuff.h"   #include "constants.h"   #include "interpret.h"   #include "bignum.h"   #include "pike_threadlib.h"   #include "gc.h"   #include "main.h" + #include "builtin_functions.h" + #include "block_allocator.h"      #include <math.h>      #include "block_alloc.h"      int gc_enabled = 1;      /* 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;   double gc_min_time_ratio = 1.0/10000.0; /* Martys constant. */      /* 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;    -  + /* High-level callbacks. +  * NB: These are initialized from builtin.cmod. +  */ + /* Callback called when gc() starts. */ + struct svalue gc_pre_cb; +  + /* Callback called when the mark and sweep phase of the gc() is done. */ + struct svalue gc_post_cb; +  + /* Callback called for each object that is to be destructed explicitly +  * by the gc(). +  */ + struct svalue gc_destruct_cb; +  + /* Callback called when the gc() is about to exit. */ + struct svalue gc_done_cb; +    /* The gc will free all things with no external nonweak references    * that isn't referenced by live objects. An object is considered    * "live" if it contains code that must be executed when it is    * destructed; see gc_object_is_live for details. 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
pike.git/src/gc.c:88:    * gc pass will be freed. That's done before the live object destruct    * pass. Internal weak references are however still intact.    *    * Note: Keep the doc for lfun::destroy up-to-date with the above.    */      /* #define GC_DEBUG */   /* #define GC_VERBOSE */   /* #define GC_CYCLE_DEBUG */   /* #define GC_STACK_DEBUG */ + /* #define GC_INTERVAL_DEBUG */      #if defined(GC_VERBOSE) && !defined(PIKE_DEBUG)   #undef GC_VERBOSE   #endif   #ifdef GC_VERBOSE   #define GC_VERBOSE_DO(X) X   #else   #define GC_VERBOSE_DO(X)   #endif   
pike.git/src/gc.c:111:   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;    + ALLOC_COUNT_TYPE saved_alloc_threshold; + /* Used to backup alloc_threshold if the gc is disabled, so that it +  * can be restored when it's enabled again. This is to not affect the +  * gc interval if it's disabled only for a short duration. +  * alloc_threshold is set to GC_MAX_ALLOC_THRESHOLD while it's +  * disabled, to avoid complicating the test in GC_ALLOC(). */ +    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_real_time = -1;   cpu_time_t auto_gc_time = 0;   cpu_time_t auto_gc_real_time = 0;      struct link_frame /* See cycle checking blurb below. */   {    void *data;
pike.git/src/gc.c:406: Inside #if defined(PIKE_DEBUG)
  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) + struct block_allocator gc_rec_frame_allocator = +  BA_INIT_PAGES(sizeof(struct gc_rec_frame), 2);    - BLOCK_ALLOC_FILL_PAGES (gc_rec_frame, 2) + static void really_free_gc_rec_frame(struct gc_rec_frame * f) { + #ifdef PIKE_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; + #endif +  rec_frames--; +  ba_free(&gc_rec_frame_allocator, f); + }    -  + void count_memory_in_gc_rec_frames(size_t *num, size_t * size) { +  ba_count_all(&gc_rec_frame_allocator, num, size); + } +    /* Link and free_extra frames are approximately the same size, so let    * them share block_alloc area. */   struct ba_mixed_frame   {    union {    struct link_frame link;    struct free_extra_frame free_extra;    struct ba_mixed_frame *next; /* For block_alloc internals. */    } u;   };    - #undef BLOCK_ALLOC_NEXT - #define BLOCK_ALLOC_NEXT u.next - #undef INIT_BLOCK - #define INIT_BLOCK(f) - #undef EXIT_BLOCK - #define EXIT_BLOCK(f) + static struct block_allocator ba_mixed_frame_allocator +  = BA_INIT_PAGES(sizeof(struct ba_mixed_frame), 2);    - BLOCK_ALLOC_FILL_PAGES (ba_mixed_frame, 2) + void count_memory_in_ba_mixed_frames(size_t *num, size_t * size) { +  ba_count_all(&ba_mixed_frame_allocator, num, size); + }      static INLINE struct link_frame *alloc_link_frame()   { -  struct ba_mixed_frame *f = alloc_ba_mixed_frame(); +  struct ba_mixed_frame *f = ba_alloc(&ba_mixed_frame_allocator);    if (++link_frames > max_link_frames)    max_link_frames = link_frames;    return (struct link_frame *) f;   }      static INLINE struct free_extra_frame *alloc_free_extra_frame()   { -  struct ba_mixed_frame *f = alloc_ba_mixed_frame(); +  struct ba_mixed_frame *f = ba_alloc(&ba_mixed_frame_allocator);    free_extra_frames++;    return (struct free_extra_frame *) f;   }      static INLINE void really_free_link_frame (struct link_frame *f)   {    link_frames--; -  really_free_ba_mixed_frame ((struct ba_mixed_frame *) f); +  ba_free(&ba_mixed_frame_allocator, f);   }      static INLINE void really_free_free_extra_frame (struct free_extra_frame *f)   {    free_extra_frames--; -  really_free_ba_mixed_frame ((struct ba_mixed_frame *) f); +  ba_free(&ba_mixed_frame_allocator, f);   }      /* 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, GARBAGE_MAX_INTERVAL   } last_garbage_strategy = GARBAGE_RATIO_LOW;      struct callback_list gc_callbacks;   
pike.git/src/gc.c:508:   #ifdef PIKE_DEBUG   #define INIT_BLOCK(X) \    (X)->flags=(X)->refs=(X)->weak_refs=(X)->xrefs=0; \    (X)->saved_refs=-1; \    (X)->frame = 0;   #else   #define INIT_BLOCK(X) \    (X)->flags=(X)->refs=(X)->weak_refs=0; \    (X)->frame = 0;   #endif + #undef EXIT_BLOCK + #define EXIT_BLOCK(f)      #undef get_marker   #define get_marker debug_get_marker   #undef find_marker   #define find_marker debug_find_marker      PTR_HASH_ALLOC_FIXED_FILL_PAGES(marker,2)      #undef get_marker   #define get_marker(X) ((struct marker *) debug_malloc_pass(debug_get_marker(X)))
pike.git/src/gc.c:532:   {    return debug_get_marker (p);   }      PMOD_EXPORT struct marker *pmod_find_marker (void *p)   {    return debug_find_marker (p);   }      #if defined (PIKE_DEBUG) || defined (GC_MARK_DEBUG) - void *gc_found_in = NULL; - int gc_found_in_type = PIKE_T_UNKNOWN; - const char *gc_found_place = NULL; + PMOD_EXPORT void *gc_found_in = NULL; + PMOD_EXPORT int gc_found_in_type = PIKE_T_UNKNOWN; + PMOD_EXPORT 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)   
pike.git/src/gc.c:560: Inside #if defined (PIKE_DEBUG) || defined (GC_CYCLE_DEBUG)
   f->cycle_id, f->cycle_piece, f->u.link_top);   }      /* If p* isn't NULL then p*_name will be written out next to the    * matching frame in the stack, if any is found. */   static void describe_rec_stack (struct gc_rec_frame *p1, const char *p1_name,    struct gc_rec_frame *p2, const char *p2_name,    struct gc_rec_frame *p3, const char *p3_name)   {    struct gc_rec_frame *l, *cp; -  size_t longest; +  size_t longest = 0;       if (p1) longest = strlen (p1_name);    if (p2) {size_t l = strlen (p2_name); if (l > longest) longest = l;}    if (p3) {size_t l = strlen (p3_name); if (l > longest) longest = l;}    longest++;       /* Note: Stack is listed from top to bottom, but cycle piece lists    * are lists from first to last, i.e. reverse order. */       for (l = stack_top; l != &sentinel_frame; l = l->prev) {
pike.git/src/gc.c:688:    * really be printed..    */   void describe_location(void *real_memblock,    int type,    void *location,    int indent,    int depth,    int flags)   {    struct program *p; -  void *memblock=0, *descblock, *inblock; +  void *memblock=0, *descblock, *inblock = NULL;    if(!location) return;   /* fprintf(stderr,"**Location of (short) svalue: %p\n",location); */       if(type!=-1 && real_memblock != (void *) -1) memblock=real_memblock;      #ifdef DEBUG_MALLOC    if(memblock == 0 || type == -1)    {    extern void *dmalloc_find_memblock_base(void *);    memblock=dmalloc_find_memblock_base(location);
pike.git/src/gc.c:871:    break;       case T_MULTISET:    descblock = ((struct multiset *) memblock)->msd;    /* FALL THROUGH */       case T_MULTISET_DATA: {    struct multiset_data *msd = (struct multiset_data *) descblock;    union msnode *node = low_multiset_first (msd);    struct svalue ind; -  int indval = msd->flags & MULTISET_INDVAL; +     for (; node; node = low_multiset_next (node)) {    if (&node->i.ind == (struct svalue *) location) {    fprintf (stderr, "%*s **In index ", indent, "");    safe_print_svalue (stderr, low_use_multiset_index (node, ind));    fputc ('\n', stderr);    break;    } -  else if (indval && &node->iv.val == (struct svalue *) location) { -  fprintf(stderr, "%*s **In value with index ", indent, ""); -  safe_print_svalue (stderr, low_use_multiset_index (node, ind)); -  fputc('\n', stderr); -  break; +     } -  } +     break;    }       case T_ARRAY:    {    struct array *a=(struct array *)descblock;    struct svalue *s=(struct svalue *)location;       if(location == (void *)&a->next)    fprintf(stderr,"%*s **In a->next\n",indent,"");
pike.git/src/gc.c:961: 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   }    + #ifdef GC_STACK_DEBUG   static void describe_link_frame (struct link_frame *f)   {    fprintf (stderr, "data=%p prev=%p checkfn=%p weak=%d",    f->data, f->prev, f->checkfn, f->weak);   } -  + #endif      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",    m, (long) m->flags, m->refs, m->weak_refs,    m->xrefs, m->saved_refs, m->frame);    if (m->frame) {    fputs(" [", stderr);
pike.git/src/gc.c:987:    putc(']', stderr);    }    putc('\n', stderr);    }    else    fprintf(stderr, "no marker\n");   }      #endif /* PIKE_DEBUG */    - static void debug_gc_fatal_va (void *a, int type, int flags, + static void debug_gc_fatal_va (void *DEBUGUSED(a), int DEBUGUSED(type), int DEBUGUSED(flags),    const char *fmt, va_list args)   {    int orig_gc_pass = Pike_in_gc;    -  (void) VFPRINTF(stderr, fmt, args); +  (void) vfprintf(stderr, fmt, args);      #ifdef PIKE_DEBUG    if (a) { -  void *inblock; +  void *inblock = NULL;    /* Temporarily jumping out of gc to avoid being caught in debug    * checks in describe(). */    Pike_in_gc = 0;    if (type == PIKE_T_UNKNOWN)    type = attempt_to_identify (a, &inblock);    describe_something (a, type, 0, 0, 0, inblock);    if (flags & 1) locate_references(a);    Pike_in_gc = orig_gc_pass;    }   
pike.git/src/gc.c:1033:   }      void debug_gc_fatal_2 (void *a, int type, int flags, const char *fmt, ...)   {    va_list args;    va_start (args, fmt);    debug_gc_fatal_va (a, type, flags, fmt, args);    va_end (args);   }    - static void dloc_gc_fatal (const char *file, int line, + #ifdef PIKE_DEBUG +  + static void dloc_gc_fatal (const char *file, INT_TYPE line,    void *a, int flags, const char *fmt, ...)   {    va_list args; -  fprintf (stderr, "%s:%d: GC fatal:\n", file, line); +  fprintf (stderr, "%s:%ld: GC fatal:\n", file, (long)line);    va_start (args, fmt);    debug_gc_fatal_va (a, PIKE_T_UNKNOWN, flags, fmt, args);    va_end (args);   }    - static void rec_stack_fatal (struct gc_rec_frame *err, const char *err_name, -  struct gc_rec_frame *p1, const char *p1n, -  struct gc_rec_frame *p2, const char *p2n, -  const char *file, int line, + static void rec_stack_fatal (struct gc_rec_frame *DEBUGUSED(err), +  const char *DEBUGUSED(err_name), +  struct gc_rec_frame *DEBUGUSED(p1), +  const char *DEBUGUSED(p1n), +  struct gc_rec_frame *DEBUGUSED(p2), +  const char *DEBUGUSED(p2n), +  const char *file, INT_TYPE line,    const char *fmt, ...)   {    va_list args;    va_start (args, fmt);    fprintf (stderr, msg_fatal_error, file, line); -  (void) VFPRINTF (stderr, fmt, args); +  (void) vfprintf (stderr, fmt, args);   #if defined (PIKE_DEBUG) || defined (GC_CYCLE_DEBUG)    fputs ("Recursion stack:\n", stderr);    describe_rec_stack (err, err_name, p1, p1n, p2, p2n);    if (err) {    fprintf (stderr, "Describing frame %p: ", err);    describe_rec_frame (err);    fputc ('\n', stderr);    }   #endif    d_flag = 0; /* The instruction backlog is never of any use here. */    debug_fatal (NULL);    va_end (args);   }    - #ifdef PIKE_DEBUG -  - static void gdb_gc_stop_here(void *a, int weak) + static void gdb_gc_stop_here(void *UNUSED(a), int weak)   {    found_ref_count++;    fprintf(stderr,"***One %sref found%s. ",    weak ? "weak " : "",    gc_found_place ? gc_found_place : "");    if (gc_found_in) {    if (gc_svalue_location)    describe_location(gc_found_in , gc_found_in_type, gc_svalue_location,0,1,    DESCRIBE_SHORT);    else {
pike.git/src/gc.c:1173: Inside #if defined(PIKE_DEBUG)
   }       if (p == pike_trampoline_program && ((struct object *) a)->refs > 0) {    /* Special hack to get something useful out of trampolines.    * Ought to have an event hook for this sort of thing. */    struct pike_trampoline *t =    (struct pike_trampoline *) ((struct object *) a)->storage;    struct object *o = t->frame->current_object;    struct program *p = o->prog;    struct identifier *id; -  INT32 line; +  INT_TYPE line;    struct pike_string *file;       fprintf (stderr, "%*s**The object is a trampoline.\n", indent, "");       if (!p) {    fprintf (stderr, "%*s**The trampoline function's object "    "is destructed.\n", indent, "");    p = id_to_program (o->program_id);    }   
pike.git/src/gc.c:1300: Inside #if defined(PIKE_DEBUG)
   fprintf (stderr, "%*s**Object got a parent.\n", indent, "");    }else{    fprintf(stderr,"%*s**There is no parent (any longer?)\n",indent,"");    }    }    break;       case T_PROGRAM:    {    char *tmp; -  INT32 line; +  INT_TYPE line;    ptrdiff_t id_idx, id_count = 0;    struct inherit *inh = p->inherits, *next_inh = p->inherits + 1;    ptrdiff_t inh_id_end = p->num_identifier_references;       fprintf(stderr,"%*s**Program id: %ld, flags: %x, parent id: %d\n",    indent,"", (long)(p->id), p->flags,    p->parent ? p->parent->id : -1);       if(p->flags & PROGRAM_HAS_C_METHODS)    {
pike.git/src/gc.c:1549:    }       case T_PIKE_FRAME: {    struct pike_frame *f = (struct pike_frame *) a;    do {    if (f->refs <= 0) break;    if (f->current_object) {    struct program *p = f->current_object->prog;    if (p) {    struct identifier *id = ID_FROM_INT(p, f->fun); -  INT32 line; +  INT_TYPE line;    struct pike_string *file;    if (IDENTIFIER_IS_PIKE_FUNCTION(id->identifier_flags) &&    id->func.offset >= 0 &&    (file = get_line(p->program + id->func.offset, p, &line))) {    fprintf(stderr, "%*s**Function %s at %s:%ld\n",    indent, "", id->name->str, file->str, (long) line);    free_string(file);    }    else    fprintf(stderr, "%*s**Function %s at unknown location.\n",
pike.git/src/gc.c:1634:    d_flag=tmp;   }      PMOD_EXPORT void describe(void *x)   {    void *inblock;    int type = attempt_to_identify(x, &inblock);    describe_something(x, type, 0, 0, 0, inblock);   }    - void debug_describe_svalue(struct svalue *s) + PMOD_EXPORT void debug_describe_svalue(struct svalue *s)   {    fprintf(stderr,"Svalue at %p is:\n",s);    switch(TYPEOF(*s))    {    case T_INT:    fprintf(stderr," %"PRINTPIKEINT"d (subtype %d)\n",s->u.integer,    SUBTYPEOF(*s));    return;       case T_FLOAT:
pike.git/src/gc.c:1672:    }    }else{    fprintf(stderr," Function name: %s\n",    ID_FROM_INT(s->u.object->prog, SUBTYPEOF(*s))->name->str);    }    }    }    describe_something(s->u.refs, TYPEOF(*s), 0, 1, 0, 0);   }    - void gc_watch(void *a) + PMOD_EXPORT void gc_watch(void *a)   {    struct marker *m;    init_gc();    m = get_marker(a);    if (!(m->flags & GC_WATCHED)) {    m->flags |= GC_WATCHED;    fprintf(stderr, "## Watching thing %p.\n", a);    gc_is_watching++;    }    else
pike.git/src/gc.c:1724:   {    struct gc_queue_block *next;    int used;    struct gc_queue_entry entries[GC_QUEUE_ENTRIES];   };      struct gc_queue_block *gc_mark_first = NULL, *gc_mark_last = NULL;      #define CHECK_MARK_QUEUE_EMPTY() assert (!gc_mark_first)    - void gc_mark_run_queue() + void gc_mark_run_queue(void)   {    struct gc_queue_block *b;       while((b=gc_mark_first))    {    int e;    for(e=0;e<b->used;e++)    {    debug_malloc_touch(b->entries[e].data);    b->entries[e].call(b->entries[e].data);    }       gc_mark_first=b->next;    free((char *)b);    }    gc_mark_last=0;   }    - void gc_mark_discard_queue() + void gc_mark_discard_queue(void)   {    struct gc_queue_block *b = gc_mark_first;    while (b)    {    struct gc_queue_block *next = b->next;    free((char *) b);    b = next;    }    gc_mark_first = gc_mark_last = 0;   }
pike.git/src/gc.c:2025:      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_rec_frame_blocks(); -  free_all_ba_mixed_frame_blocks(); +  ba_free_all(&gc_rec_frame_allocator); +  ba_free_all(&ba_mixed_frame_allocator);      #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    - PMOD_EXPORT void gc_check_zapped (void *a, TYPE_T type, const char *file, int line) + PMOD_EXPORT void gc_check_zapped (void *a, TYPE_T type, const char *file, INT_TYPE line)   {    struct marker *m = find_marker (a);    if (m && (m->flags & GC_CLEANUP_LEAKED)) -  fprintf (stderr, "Free of leaked %s %p from %s:%d, %d refs remaining\n", -  get_name_of_type (type), a, file, line, *(INT32 *)a - 1); +  fprintf (stderr, "Free of leaked %s %p from %s:%ld, %d refs remaining\n", +  get_name_of_type (type), a, file, (long)line, *(INT32 *)a - 1);   }      /* This function marks some known externals. The rest are handled by    * callbacks added with add_gc_callback. */   static void mark_externals (void)   {    struct mapping *constants;    if (master_object)    gc_mark_external (master_object, " as master_object");    if ((constants = get_builtin_constants()))    gc_mark_external (constants, " as global constants mapping");   }    - void locate_references(void *a) + PMOD_EXPORT void locate_references(void *a)   {    int tmp, orig_in_gc = Pike_in_gc;    const char *orig_gc_found_place = gc_found_place;    int i=0; -  if(!marker_blocks) +  if(!marker_hash_table)    {    i=1;    init_gc();    }    Pike_in_gc = GC_PASS_LOCATE;    gc_found_place = NULL;       /* Disable debug, this may help reduce recursion bugs */    tmp=d_flag;    d_flag=0;
pike.git/src/gc.c:2249:    fprintf (stderr, "Cycle id frame %p is freed. It is: ", f->cycle_id); \    describe_rec_frame (f->cycle_id); \    fputc ('\n', stderr); \    dloc_gc_fatal (file, line, f->data, 0, "Cycle id frame is freed.\n"); \    } \    } while (0)      static void check_rec_stack_frame (struct gc_rec_frame *f,    struct gc_rec_frame *p1, const char *p1n,    struct gc_rec_frame *p2, const char *p2n, -  const char *file, int line) +  const char *file, INT_TYPE line)   {    /* To allow this function to be used after a stack rotation but    * before cycle_id markup, there are no checks here for cycle_id    * consistency wrt other frames on the rec stack. */    LOW_CHECK_REC_FRAME (f, file, line);    if (f->rf_flags & (GC_ON_CYCLE_PIECE_LIST|GC_ON_KILL_LIST))    rec_stack_fatal (f, "err", p1, p1n, p2, p2n, file, line,    "Frame %p is not on the rec stack (according to flags).\n",    f);    if (!f->prev)
pike.git/src/gc.c:2296:    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, f);   }   #define CHECK_REC_STACK_FRAME(f) \    do check_rec_stack_frame ((f), NULL, NULL, NULL, NULL, __FILE__, __LINE__); \    while (0)      static void check_cycle_piece_frame (struct gc_rec_frame *f, -  const char *file, int line) +  const char *file, INT_TYPE line)   {    LOW_CHECK_REC_FRAME (f, file, line);    if ((f->rf_flags & (GC_ON_CYCLE_PIECE_LIST|GC_ON_KILL_LIST)) !=    GC_ON_CYCLE_PIECE_LIST)    dloc_gc_fatal (file, line, f->data, 0,    "Frame is not on a cycle piece list "    "(according to flags).\n");    if (f->prev)    dloc_gc_fatal (file, line, f->data, 0,    "Prev pointer set for frame on cycle piece list.\n");   }   #define CHECK_CYCLE_PIECE_FRAME(f) \    do check_cycle_piece_frame ((f), __FILE__, __LINE__); while (0)      static void check_kill_list_frame (struct gc_rec_frame *f, -  const char *file, int line) +  const char *file, INT_TYPE line)   {    LOW_CHECK_REC_FRAME (f, file, line);    if ((f->rf_flags & (GC_ON_CYCLE_PIECE_LIST|GC_ON_KILL_LIST)) !=    GC_ON_KILL_LIST)    dloc_gc_fatal (file, line, f->data, 0,    "Frame is not on kill list (according to flags).\n");    if (f->prev)    dloc_gc_fatal (file, line, f->data, 0,    "Prev pointer set for frame on kill list.\n");   }   #define CHECK_KILL_LIST_FRAME(f) \    do check_kill_list_frame ((f), __FILE__, __LINE__); while (0)      static void check_rec_stack (struct gc_rec_frame *p1, const char *p1n,    struct gc_rec_frame *p2, const char *p2n,    const char *file, int line)   {    /* This debug check is disabled during the final cleanup since this    * is O(n^2) on the stack size, and the stack gets a lot larger then. */    if (gc_debug && !gc_destruct_everything) { -  struct gc_rec_frame *l, *last_cycle_id; +  struct gc_rec_frame *l, *last_cycle_id = NULL;    for (l = &sentinel_frame; l != stack_top;) {    l = l->next;    check_rec_stack_frame (l, p1, p1n, p2, p2n, file, line);    if (l->cycle_id == l)    last_cycle_id = l;    else if (l->cycle_id != last_cycle_id)    rec_stack_fatal (l, "err", p1, p1n, p2, p2n, file, line,    "Unexpected cycle id for frame %p.\n", l);    else if (l->rf_flags & GC_PREV_WEAK)    rec_stack_fatal (l, "err", p1, p1n, p2, p2n, file, line,
pike.git/src/gc.c:2622: Inside #if defined(GC_STACK_DEBUG)
  #ifdef GC_STACK_DEBUG    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->u.link_top = l;   }      static struct gc_rec_frame *gc_cycle_enqueue_rec (void *data)   { -  struct gc_rec_frame *r = alloc_gc_rec_frame(); +  struct gc_rec_frame *r = +  (struct gc_rec_frame*)ba_alloc(&gc_rec_frame_allocator); +  if (++rec_frames > max_rec_frames) max_rec_frames = rec_frames;   #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    r->data = data;    r->u.link_top = NULL;    r->prev = stack_top;    r->cycle_id = r;    r->cycle_piece = NULL;
pike.git/src/gc.c:3323: Inside #if defined(PIKE_DEBUG)
   if(!gc_destruct_everything &&    (m->flags & (GC_MARKED|GC_XREFERENCED)) == GC_XREFERENCED)    gc_fatal(a, 1, "Thing with external reference missed in gc mark pass.\n");    if ((m->flags & (GC_DO_FREE|GC_LIVE)) == GC_LIVE) live_ref++;    m->flags |= GC_DO_FREE;   #endif       return !(m->flags & GC_LIVE);   }    + #if 0   static void free_obj_arr(void *oa)   {    struct array *obj_arr = *((struct array **)oa);       if (obj_arr) free_array(obj_arr);    free(oa);   } -  + #endif      /*! @class MasterObject    */      /*! @decl void runtime_warning(string subsystem, string msg, mixed|void data)    *!    *! Called by the Pike runtime to warn about data inconsistencies.    *!    *! @param subsystem    *! Runtime subsystem where the warning was generated.
pike.git/src/gc.c:3372:   /*! @endclass    */      static void warn_bad_cycles(void)   {    /* The reason for the extra level of indirection, is that it might    * be clobbered by the longjump() in SET_ONERROR otherwise.    * (On some architectures longjump() might restore obj_arr's original    * value (eg if obj_arr is in a register)).    */ + #if 0    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);    }    p = p->next;    if (p ? ((unsigned)(p->cycle != cycle)) : cycle) {    if ((*obj_arr_)->size >= 2) { -  push_constant_text("gc"); -  push_constant_text("bad_cycle"); +  push_static_text("gc"); +  push_static_text("bad_cycle");    push_array(*obj_arr_);    *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); -  + #endif   }    - size_t do_gc(void *ignored, int explicit_call) + size_t do_gc(void *UNUSED(ignored), int explicit_call)   {    ALLOC_COUNT_TYPE start_allocs;    size_t start_num_objs, unreferenced;    cpu_time_t gc_start_time, gc_start_real_time;    ptrdiff_t objs, pre_kill_objs;   #if defined (PIKE_DEBUG) || defined (DO_PIKE_CLEANUP)    unsigned destroy_count;   #endif   #ifdef PIKE_DEBUG    unsigned obj_count;    ONERROR uwp;   #endif       if(Pike_in_gc) return 0;       if (gc_enabled <= 0 && (gc_enabled < 0 || !explicit_call)) { -  +  /* If this happens then the gc has been disabled for a very long +  * time and num_allocs > GC_MAX_ALLOC_THRESHOLD. Have to reset +  * num_allocs, but then we also reset saved_alloc_threshold to +  * GC_MIN_ALLOC_THRESHOLD so that a gc is run quickly if it ever +  * is enabled again. */ + #ifdef GC_INTERVAL_DEBUG +  fprintf (stderr, "GC disabled: num_allocs %"PRINT_ALLOC_COUNT_TYPE", " +  ", alloc_threshold %"PRINT_ALLOC_COUNT_TYPE"\n", +  num_allocs, alloc_threshold); + #endif    num_allocs = 0; -  alloc_threshold = GC_MAX_ALLOC_THRESHOLD; +  saved_alloc_threshold = GC_MIN_ALLOC_THRESHOLD;    if (gc_evaluator_callback) {    remove_callback (gc_evaluator_callback);    gc_evaluator_callback = NULL;    }    return 0;    }      #ifdef DEBUG_MALLOC    if(debug_options & GC_RESET_DMALLOC)    reset_debug_malloc();   #endif    init_gc();    gc_generation++;    Pike_in_gc=GC_PASS_PREPARE; -  +  +  if (!SAFE_IS_ZERO(&gc_pre_cb)) { +  safe_apply_svalue(&gc_pre_cb, 0, 1); +  pop_stack(); +  } +     gc_start_time = get_cpu_time();    gc_start_real_time = get_real_time();   #ifdef GC_DEBUG    gc_debug = (GC_DEBUG + 0) || 1;   #else    gc_debug = d_flag;   #endif   #ifdef PIKE_DEBUG    SET_ONERROR(uwp, fatal_on_error, "Shouldn't get an exception inside the gc.\n");    if (gc_is_watching)
pike.git/src/gc.c:3480:    fprintf (stderr, "Destructing all objects... ");    else    fprintf(stderr,"Garbage collecting... ");    GC_VERBOSE_DO(fprintf(stderr, "\n"));    }   #ifdef PIKE_DEBUG    if(num_objects < 0)    Pike_fatal("Panic, less than zero objects!\n");   #endif    -  last_gc=TIME(0); +  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;    mark_live = frame_rot = link_search = 0;   #endif
pike.git/src/gc.c:3760:    pre_kill_objs = num_objects;    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    +  if (!SAFE_IS_ZERO(&gc_post_cb)) { +  safe_apply_svalue(&gc_post_cb, 0, 1); +  pop_stack(); +  } +     {    enum object_destruct_reason reason =   #ifdef DO_PIKE_CLEANUP    gc_destruct_everything ? DESTRUCT_CLEANUP :   #endif    DESTRUCT_GC;      #ifdef PIKE_DEBUG    {    struct gc_rec_frame *r;
pike.git/src/gc.c:3809: Inside #if defined(PIKE_DEBUG)
   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; +  INT_TYPE 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);    ); -  +  if (!SAFE_IS_ZERO(&gc_destruct_cb)) { +  ref_push_object(o); +  push_int(reason); +  push_int(o->refs - 1); +  safe_apply_svalue(&gc_destruct_cb, 3, 1); +  pop_stack(); +  }       destruct_object (o, reason);    free_object(o);    gc_free_extra_ref(o);   #if defined (PIKE_DEBUG) || defined (DO_PIKE_CLEANUP)    destroy_count++;   #endif    really_free_gc_rec_frame (kill_list);    kill_list = next;    }
pike.git/src/gc.c:3877: Inside #if defined(PIKE_DEBUG)
   "Done searching for marker(s) with extra refs.\n");    Pike_fatal("Lost track of %d extra refs to things in gc.\n", gc_extra_refs);    }    if(fatal_after_gc) Pike_fatal("%s", fatal_after_gc);   #endif       /* Calculate the next alloc_threshold. */    {    double multiplier, new_threshold;    cpu_time_t last_non_gc_time, last_gc_time; + #ifdef GC_INTERVAL_DEBUG +  double tmp_dbl1, tmp_dbl2; + #endif       /* If we're at an automatic and timely gc then start_allocs ==    * alloc_threshold and we're using gc_average_slowness in the    * decaying average calculation. Otherwise this is either an    * explicit call (start_allocs < alloc_threshold) or the gc has    * been delayed past its due time (start_allocs >    * alloc_threshold), and in those cases we adjust the multiplier    * to give the appropriate weight to this last instance. */    multiplier=pow(gc_average_slowness,    (double) start_allocs / (double) alloc_threshold);    -  + #ifdef GC_INTERVAL_DEBUG +  if (GC_VERBOSE_DO(1 ||) gc_trace) fputc ('\n', stderr); +  fprintf (stderr, "IN: GC start @ %"PRINT_CPU_TIME" "CPU_TIME_UNIT"\n" +  " avg slow %g, start_allocs %"PRINT_ALLOC_COUNT_TYPE", " +  "alloc_threshold %"PRINT_ALLOC_COUNT_TYPE" -> mult %g\n", +  gc_start_real_time, +  gc_average_slowness, start_allocs, alloc_threshold, multiplier); +  tmp_dbl1 = non_gc_time; +  tmp_dbl2 = gc_time; + #endif +     /* Comparisons to avoid that overflows mess up the statistics. */    if (last_gc_end_real_time != -1 &&    gc_start_real_time > last_gc_end_real_time) {    last_non_gc_time = gc_start_real_time - last_gc_end_real_time;    non_gc_time = non_gc_time * multiplier +    last_non_gc_time * (1.0 - multiplier);    }    else last_non_gc_time = (cpu_time_t) -1;    last_gc_end_real_time = get_real_time();    if (last_gc_end_real_time > gc_start_real_time) {    gc_time = gc_time * multiplier +    (last_gc_end_real_time - gc_start_real_time) * (1.0 - multiplier);    } -  +  + #ifdef GC_INTERVAL_DEBUG +  fprintf (stderr, +  " non_gc_time: %13"PRINT_CPU_TIME" "CPU_TIME_UNIT", " +  "%.12g -> %.12g\n" +  " gc_time: %13"PRINT_CPU_TIME" "CPU_TIME_UNIT", " +  "%.12g -> %.12g\n", +  last_non_gc_time, tmp_dbl1, non_gc_time, +  last_gc_end_real_time > gc_start_real_time ? +  last_gc_end_real_time - gc_start_real_time : (cpu_time_t) -1, +  tmp_dbl2, gc_time); +  tmp_dbl1 = objects_alloced; +  tmp_dbl2 = objects_freed; + #endif +     {    cpu_time_t gc_end_time = get_cpu_time();    if (gc_end_time > gc_start_time)    last_gc_time = gc_end_time - gc_start_time;    else    last_gc_time = (cpu_time_t) -1;    }       /* At this point, unreferenced contains the number of things that    * were without external references during the check and mark
pike.git/src/gc.c:3923:    * Therefore we use that figure instead of the difference between    * the number of allocated things to measure the amount of    * garbage. */    last_garbage_ratio = (double) unreferenced / start_num_objs;       objects_alloced = objects_alloced * multiplier +    start_allocs * (1.0 - multiplier);    objects_freed = objects_freed * multiplier +    unreferenced * (1.0 - multiplier);    + #ifdef GC_INTERVAL_DEBUG +  fprintf (stderr, +  " objects_alloced: %9"PRINT_ALLOC_COUNT_TYPE" allocs, " +  "%.12g -> %.12g\n" +  " objects_freed: %9"PRINT_ALLOC_COUNT_TYPE" unrefd, " +  "%.12g -> %.12g\n", +  start_allocs, tmp_dbl1, objects_alloced, +  unreferenced, tmp_dbl2, objects_freed); + #endif +     if (last_non_gc_time == (cpu_time_t) -1 ||    gc_time / non_gc_time <= gc_time_ratio) {    /* Calculate the new threshold by adjusting the average    * threshold (objects_alloced) with the ratio between the wanted    * garbage at the next gc (gc_garbage_ratio_low *    * start_num_objs) and the actual average garbage    * (objects_freed). (Where the +1.0's come from I don't know.    * Perhaps they're to avoid division by zero. /mast) */    new_threshold = (objects_alloced+1.0) *    (gc_garbage_ratio_low * start_num_objs) / (objects_freed+1.0);    last_garbage_strategy = GARBAGE_RATIO_LOW; -  + #ifdef GC_INTERVAL_DEBUG +  fprintf (stderr, " strategy: low ratio %g, objs %"PRINTSIZET"u, " +  "new threshold -> %.12g\n", +  gc_garbage_ratio_low, start_num_objs, new_threshold); + #endif    }    else {    new_threshold = (objects_alloced+1.0) *    (gc_garbage_ratio_high * start_num_objs) / (objects_freed+1.0);    last_garbage_strategy = GARBAGE_RATIO_HIGH; -  + #ifdef GC_INTERVAL_DEBUG +  fprintf (stderr, " strategy: high ratio %g, objs %"PRINTSIZET"u, " +  "new threshold -> %.12g\n", +  gc_garbage_ratio_high, start_num_objs, new_threshold); + #endif    }       if (non_gc_time > 0.0 && gc_min_time_ratio > 0.0) {    /* Upper limit on the new threshold based on gc_min_time_ratio. */    double max_threshold = (objects_alloced+1.0) *    gc_time / (gc_min_time_ratio * non_gc_time); -  + #ifdef GC_INTERVAL_DEBUG +  fprintf (stderr, " max interval? min time ratio %g, " +  "max threshold %.12g -> %s\n", +  gc_min_time_ratio, max_threshold, +  max_threshold < new_threshold ? "yes" : "no"); + #endif    if (max_threshold < new_threshold) {    new_threshold = max_threshold;    last_garbage_strategy = GARBAGE_MAX_INTERVAL;    }    }      #if 0    /* Afaics this is to limit the growth of the threshold to avoid    * that a single sudden allocation spike causes a very long gc    * interval the next time. Now when the bug in the decaying
pike.git/src/gc.c:3968:    new_threshold = (double)(alloc_threshold + start_allocs);   #endif       if(new_threshold < GC_MIN_ALLOC_THRESHOLD)    alloc_threshold = GC_MIN_ALLOC_THRESHOLD;    else if(new_threshold > GC_MAX_ALLOC_THRESHOLD)    alloc_threshold = GC_MAX_ALLOC_THRESHOLD;    else    alloc_threshold = (ALLOC_COUNT_TYPE) new_threshold;    + #ifdef GC_INTERVAL_DEBUG +  fprintf (stderr, "OUT: GC end @ %"PRINT_CPU_TIME" "CPU_TIME_UNIT", " +  "new capped threshold %"PRINT_ALLOC_COUNT_TYPE"\n", +  last_gc_end_real_time, alloc_threshold); + #endif +     if (!explicit_call) {    auto_gc_real_time += get_real_time() - gc_start_real_time;       if (last_gc_time != (cpu_time_t) -1) {   #ifdef CPU_TIME_MIGHT_BE_THREAD_LOCAL    if (cpu_time_is_thread_local   #ifdef PIKE_DEBUG    /* At high debug levels, the gc may get called before    * the threads are initialized.    */
pike.git/src/gc.c:4032: Inside #if defined(ALWAYS_GC)
  #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;   #endif +  +  if (!SAFE_IS_ZERO(&gc_done_cb)) { +  push_int(unreferenced); +  safe_apply_svalue(&gc_done_cb, 1, 1); +  pop_stack(); +  } +     return unreferenced;   }      /*! @decl mapping(string:int|float) gc_status()    *! @belongs Debug    *!    *! Get statistics from the garbage collector.    *!    *! @returns    *! A mapping with the following content will be returned:
pike.git/src/gc.c:4094:    *!    *! @seealso    *! @[gc()], @[Pike.gc_parameters()], @[Pike.implicit_gc_real_time]    */   void f__gc_status(INT32 args)   {    int size = 0;       pop_n_elems(args);    -  push_constant_text("num_objects"); +  push_static_text("num_objects");    push_int(num_objects);    size++;    -  push_constant_text("num_allocs"); +  push_static_text("num_allocs");    push_int64(num_allocs);    size++;    -  push_constant_text("alloc_threshold"); +  push_static_text("alloc_threshold");    push_int64(alloc_threshold);    size++;    -  push_constant_text("projected_garbage"); -  push_float(DO_NOT_WARN((FLOAT_TYPE)(objects_freed * (double) num_allocs / -  (double) alloc_threshold))); +  push_static_text("projected_garbage"); +  push_float((FLOAT_TYPE)(objects_freed * (double) num_allocs / +  (double) alloc_threshold));    size++;    -  push_constant_text("objects_alloced"); -  push_int64(DO_NOT_WARN((INT64)objects_alloced)); +  push_static_text("objects_alloced"); +  push_int64((INT64)objects_alloced);    size++;    -  push_constant_text("objects_freed"); -  push_int64(DO_NOT_WARN((INT64)objects_freed)); +  push_static_text("objects_freed"); +  push_int64((INT64)objects_freed);    size++;    -  push_constant_text("last_garbage_ratio"); -  push_float(DO_NOT_WARN((FLOAT_TYPE) last_garbage_ratio)); +  push_static_text("last_garbage_ratio"); +  push_float((FLOAT_TYPE) last_garbage_ratio);    size++;    -  push_constant_text("non_gc_time"); -  push_int64(DO_NOT_WARN((INT64) non_gc_time)); +  push_static_text("non_gc_time"); +  push_int64((INT64) non_gc_time);    size++;    -  push_constant_text("gc_time"); -  push_int64(DO_NOT_WARN((INT64) gc_time)); +  push_static_text("gc_time"); +  push_int64((INT64) gc_time);    size++;    -  push_constant_text ("last_garbage_strategy"); +  push_static_text ("last_garbage_strategy");    switch (last_garbage_strategy) {    case GARBAGE_RATIO_LOW: -  push_constant_text ("garbage_ratio_low"); break; +  push_static_text ("garbage_ratio_low"); break;    case GARBAGE_RATIO_HIGH: -  push_constant_text ("garbage_ratio_high"); break; +  push_static_text ("garbage_ratio_high"); break;    case GARBAGE_MAX_INTERVAL: -  push_constant_text ("garbage_max_interval"); break; +  push_static_text ("garbage_max_interval"); break;   #ifdef PIKE_DEBUG    default:    Pike_fatal ("Unknown last_garbage_strategy %d\n", last_garbage_strategy);   #endif    }    size++;    -  push_constant_text("last_gc"); +  push_static_text("last_gc");    push_int64(last_gc);    size++;    -  push_constant_text ("total_gc_cpu_time"); +  push_static_text ("total_gc_cpu_time");    push_int64 (auto_gc_time);   #ifndef LONG_CPU_TIME    push_int (1000000000 / CPU_TIME_TICKS);    o_multiply();   #endif    size++;    -  push_constant_text ("total_gc_real_time"); +  push_static_text ("total_gc_real_time");    push_int64 (auto_gc_real_time);   #ifndef LONG_CPU_TIME    push_int (1000000000 / CPU_TIME_TICKS);    o_multiply();   #endif    size++;      #ifdef PIKE_DEBUG -  push_constant_text ("max_rec_frames"); -  push_int64 (DO_NOT_WARN ((INT64) tot_max_rec_frames)); +  push_static_text ("max_rec_frames"); +  push_int64 ((INT64) tot_max_rec_frames);    size++;    -  push_constant_text ("max_link_frames"); -  push_int64 (DO_NOT_WARN ((INT64) tot_max_link_frames)); +  push_static_text ("max_link_frames"); +  push_int64 ((INT64) tot_max_link_frames);    size++;    -  push_constant_text ("max_free_extra_frames"); -  push_int64 (DO_NOT_WARN ((INT64) tot_max_free_extra_frames)); +  push_static_text ("max_free_extra_frames"); +  push_int64 ((INT64) tot_max_free_extra_frames);    size++;   #endif       f_aggregate_mapping(size * 2);   }      /*! @decl int implicit_gc_real_time (void|int nsec)    *! @belongs Pike    *!    *! Returns the total amount of real time that has been spent in
pike.git/src/gc.c:4219:    push_int64 (auto_gc_real_time);    push_int (1000000 / CPU_TIME_TICKS);    o_multiply();   #endif    }   }      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", +  fprintf(stderr,"Allocations since last gc : %"PRINT_ALLOC_COUNT_TYPE"\n",    num_allocs); -  fprintf(stderr,"Threshold for next gc : "PRINT_ALLOC_COUNT_TYPE"\n", +  fprintf(stderr,"Threshold for next gc : %"PRINT_ALLOC_COUNT_TYPE"\n",    alloc_threshold);    fprintf(stderr,"Projected current garbage : %f\n",    objects_freed * (double) num_allocs / (double) alloc_threshold);       fprintf(stderr,"Avg allocs between gc : %f\n",objects_alloced);    fprintf(stderr,"Avg frees per gc : %f\n",objects_freed);    fprintf(stderr,"Garbage ratio in last gc : %f\n", last_garbage_ratio);       fprintf(stderr,"Avg "CPU_TIME_UNIT" between gc : %f\n", non_gc_time);    fprintf(stderr,"Avg "CPU_TIME_UNIT" in gc : %f\n", gc_time);
pike.git/src/gc.c:4266: Inside #if defined(PIKE_DEBUG)
   if (gc_evaluator_callback) {    remove_callback(gc_evaluator_callback);    gc_evaluator_callback = NULL;    }   #endif /* PIKE_DEBUG */   }      /* Visit things API */      PMOD_EXPORT visit_ref_cb *visit_ref = NULL; + PMOD_EXPORT visit_enter_cb *visit_enter = NULL; + PMOD_EXPORT visit_leave_cb *visit_leave = NULL;      /* Be careful if extending this with internal types like    * T_MAPPING_DATA and T_MULTISET_DATA; there's code that assumes    * type_from_visit_fn only returns types that fit in a TYPE_FIELD. */ - PMOD_EXPORT visit_thing_fn *const visit_fn_from_type[MAX_REF_TYPE + 1] = { -  (visit_thing_fn *) &visit_array, -  (visit_thing_fn *) &visit_mapping, -  (visit_thing_fn *) &visit_multiset, -  (visit_thing_fn *) &visit_object, + PMOD_EXPORT visit_thing_fn *const visit_fn_from_type[MAX_TYPE + 1] = { +  (visit_thing_fn *) (ptrdiff_t) -1, +  (visit_thing_fn *) (ptrdiff_t) -1, +  (visit_thing_fn *) (ptrdiff_t) -1, +  (visit_thing_fn *) (ptrdiff_t) -1, +  (visit_thing_fn *) (ptrdiff_t) -1, +  (visit_thing_fn *) (ptrdiff_t) -1, +  (visit_thing_fn *) (ptrdiff_t) -1, +  (visit_thing_fn *) (ptrdiff_t) -1, +  (visit_thing_fn *)&visit_array, +  (visit_thing_fn *)&visit_mapping, +  (visit_thing_fn *)&visit_multiset, +  (visit_thing_fn *)&visit_object,    /* visit_function must be called with a whole svalue, so it's not    * included here. */    (visit_thing_fn *) (ptrdiff_t) -1, -  (visit_thing_fn *) &visit_program, -  (visit_thing_fn *) &visit_string, -  (visit_thing_fn *) &visit_type, +  (visit_thing_fn *)&visit_program, +  (visit_thing_fn *)&visit_string, +  (visit_thing_fn *)&visit_type,   };      PMOD_EXPORT TYPE_T type_from_visit_fn (visit_thing_fn *fn)   {    /* Since the array to search is so small, linear search is probably    * fastest. */    unsigned t;    for (t = 0; t < NELEM (visit_fn_from_type); t++)    if (visit_fn_from_type[t] == fn)    return (TYPE_T) t;    return PIKE_T_UNKNOWN;   }      PMOD_EXPORT TYPE_FIELD real_visit_svalues (struct svalue *s, size_t num, -  int ref_type) +  int ref_type, void *extra)   {    for (; num; num--, s++) -  visit_svalue (s, ref_type); +  visit_svalue (s, ref_type, extra);    return 0;   }      /* Memory counting    *    * This mode is used by f_count_memory, and it's recognized by a    * nonzero value in mc_pass.    *    * The basic idea is to follow and count all refs from the starting    * point things given to f_count_memory. Whenever the counted refs add
pike.git/src/gc.c:4432:    * If there's anything left in the complete list then it's internal    * cyclic stuff. In that case we put those things into the work list,    * move the indirectly incomplete list back to complete and repeat    * MC_PASS_LOOKAHEAD. Otherwise we're done.    */      /* #define MEMORY_COUNT_DEBUG */      #define MC_WQ_START_SIZE 1024    + static IMUTEX_T mc_mutex; +    PMOD_EXPORT int mc_pass;   PMOD_EXPORT size_t mc_counted_bytes;      static int mc_lookahead, mc_block_pike_cycle_depth;   static TYPE_FIELD mc_block_lookahead;   static TYPE_FIELD mc_block_lookahead_default = BIT_PROGRAM|BIT_STRING|BIT_TYPE;   /* Strings are blocked because they don't contain refs. Types are    * blocked because they are acyclic and don't contain refs to anything    * but strings and other types. */    -  + static INLINE int mc_lookahead_blocked(unsigned INT16 type) { +  if (type < sizeof(TYPE_FIELD)*8) { +  return !!(mc_block_lookahead & ((TYPE_FIELD)1 << type)); +  } +  +  return 0; + } +    static int mc_block_strings;      static int mc_enqueued_noninternal;   /* Set whenever something is enqueued in MC_PASS_LOOKAHEAD that isn't    * internal already. This is used to detect whether another    * MC_PASS_MARK_EXTERNAL is necessary. */      static unsigned mc_ext_toggle_bias = 0;      #define MC_PASS_LOOKAHEAD 1
pike.git/src/gc.c:4535:   #define BLOCK_ALLOC_NEXT hash_next   #undef PTR_HASH_ALLOC_DATA   #define PTR_HASH_ALLOC_DATA thing   #undef INIT_BLOCK   #define INIT_BLOCK(f)   #undef EXIT_BLOCK   #define EXIT_BLOCK(f)      PTR_HASH_ALLOC_FILL_PAGES (mc_marker, 2)    + static void start_mc(void) + { +  LOCK_IMUTEX(&mc_mutex); +  init_mc_marker_hash(); + } +  + static void stop_mc(void) + { +  exit_mc_marker_hash(); +  UNLOCK_IMUTEX(&mc_mutex); + } +    static struct mc_marker *my_make_mc_marker (void *thing,    visit_thing_fn *visit_fn,    void *extra)   {    struct mc_marker *m = make_mc_marker (thing);    assert (thing);    assert (visit_fn);    m->thing = thing;    m->visit_fn = visit_fn;    m->extra = extra;    m->int_refs = m->la_refs = m->flags = 0;    INIT_CLEARED_EXTERNAL (m);    m->queuepos = MAX_UINT32;   #ifdef PIKE_DEBUG    m->dl_prev = m->dl_next = (void *) (ptrdiff_t) -1;    m->la_count = ((unsigned INT16) -1) >> 1;   #endif    return m;   }    - #if defined (PIKE_DEBUG) || defined (MEMORY_COUNT_DEBUG) + #ifdef MEMORY_COUNT_DEBUG   static void describe_mc_marker (struct mc_marker *m)   {    fprintf (stderr, "%s %p: refs %d, int %d, la %d, cnt %d",    get_name_of_type (type_from_visit_fn (m->visit_fn)),    m->thing, *(INT32 *) m->thing, m->int_refs, m->la_refs, m->la_count);    if (m->queuepos != MAX_UINT32) fprintf (stderr, ", wq %u", m->queuepos);    if (m->flags & MC_FLAG_INTERNAL) fputs (", I", stderr);    if (m->flags & MC_FLAG_INT_VISITED) fputs (", IV", stderr);    if (m->flags & MC_FLAG_LA_VISITED) fputs (", LAV", stderr);    if (m->flags & MC_FLAG_CANDIDATE) fputs (", C", stderr);
pike.git/src/gc.c:4799:   {    struct program *p = o->prog;    struct svalue val;       if (!p) return 0; /* No need to look ahead in destructed objects. */       object_index_no_free2 (&val, o, 0, &pike_cycle_depth_str);       if (TYPEOF(val) != T_INT) {    int i = find_shared_string_identifier (pike_cycle_depth_str.u.string, p); -  INT32 line; +  INT_TYPE line;    struct pike_string *file = get_identifier_line (p, i, &line); -  make_error ("Object got non-integer pike_cycle_depth %O at %S:%d.\n", -  &val, file, line); +  make_error ("Object got non-integer pike_cycle_depth %O at %S:%ld.\n", +  &val, file, (long)line);    free_svalue (&val);    free_svalue (&throw_value);    move_svalue (&throw_value, --Pike_sp);    return -1;    }       if (SUBTYPEOF(val) == NUMBER_UNDEFINED)    return -1;       if (val.u.integer > (unsigned INT16) -1)    return (unsigned INT16) -1;       if (val.u.integer < 0) {    int i = find_shared_string_identifier (pike_cycle_depth_str.u.string, p); -  INT32 line; +  INT_TYPE line;    struct pike_string *file = get_identifier_line (p, i, &line); -  make_error ("Object got negative pike_cycle_depth at %S:%d.\n", -  &val, file, line); +  make_error ("Object got negative pike_cycle_depth at %S:%ld.\n", +  &val, file, (long)line);    free_svalue (&throw_value);    move_svalue (&throw_value, --Pike_sp);    return -1;    }       return val.u.integer;   }      static void pass_lookahead_visit_ref (void *thing, int ref_type,    visit_thing_fn *visit_fn, void *extra)   { -  struct mc_marker *ref_to = find_mc_marker (thing); +  struct mc_marker *ref_to;    int ref_from_flags, ref_to_flags, old_la_count, ref_to_la_count;    int ref_added = 0, check_new_candidate = 0, la_count_handled = 0;       assert (mc_lookahead >= 0);    assert (mc_pass == MC_PASS_LOOKAHEAD);   #ifdef PIKE_DEBUG    assert (mc_ref_from != (void *) (ptrdiff_t) -1);    assert (mc_ref_from->la_count != ((unsigned INT16) -1) >> 1);   #endif       if (mc_block_strings > 0 &&    visit_fn == (visit_thing_fn *) &visit_string) { -  + #ifdef MEMORY_COUNT_DEBUG +  ref_to = find_mc_marker (thing); + #endif    MC_DEBUG_MSG (ref_to, "ignored string");    return;    }    -  +  ref_to = find_mc_marker (thing);    ref_from_flags = mc_ref_from->flags;       /* Create mc_marker if necessary. */       if (!ref_to) {    ref_to = my_make_mc_marker (thing, visit_fn, extra);    MC_DEBUG_MSG (ref_to, "visiting new thing");    assert (!(ref_from_flags & (MC_FLAG_INT_VISITED | MC_FLAG_LA_VISITED)));    ref_to_la_count = old_la_count = 0;    }
pike.git/src/gc.c:4989:    (ref_to_flags & MC_FLAG_CANDIDATE_REF) &&    ref_to->int_refs + ref_to->la_refs == *(INT32 *) thing) {    assert (!(ref_to_flags & MC_FLAG_CANDIDATE));    assert (ref_to->la_refs > 0);    ref_to_flags |= MC_FLAG_CANDIDATE;    MC_DEBUG_MSG (ref_to, "made candidate");       ref_to->flags = ref_to_flags;    ref_to->la_count = ref_to_la_count;    -  if (mc_block_lookahead & (1 << type_from_visit_fn (visit_fn))) { +  if (mc_lookahead_blocked(type_from_visit_fn (visit_fn))) {    MC_DEBUG_MSG (ref_to, "type is blocked - not enqueued");    return;    }       if (ref_to_la_count > 0) {    /* Always enqueue if the count allows it, even if it hasn't    * increased. That since MC_FLAG_CANDIDATE_REF must be propagated. */    if (ref_to->queuepos != MAX_UINT32 && old_la_count == ref_to_la_count)    MC_DEBUG_MSG (ref_to, "already in queue");    else {
pike.git/src/gc.c:5013:    mc_enqueued_noninternal = 1;    }    }    else    MC_DEBUG_MSG (ref_to, "candidate not enqueued due to zero count");    return;    }       /* Normal handling. */    -  if (mc_block_lookahead & (1 << type_from_visit_fn (visit_fn))) { +  if (mc_lookahead_blocked(type_from_visit_fn (visit_fn))) {    ref_to->flags = ref_to_flags;    ref_to->la_count = ref_to_la_count;    MC_DEBUG_MSG (ref_to, "type is blocked - not enqueued");    return;    }       if (!la_count_handled && !(ref_to_flags & MC_FLAG_LA_COUNT_FIXED)) {    int cycle_depth;    int count_from_source = ref_type & REF_TYPE_INTERNAL ?    mc_ref_from->la_count : mc_ref_from->la_count - 1;
pike.git/src/gc.c:5057:    assert (ref_to->la_count >= old_la_count);    if (ref_to->la_count > old_la_count) {    mc_wq_enqueue (ref_to);    MC_DEBUG_MSG (ref_to, "enqueued");    mc_enqueued_noninternal = 1;    }    else    MC_DEBUG_MSG (ref_to, "not enqueued");   }    - static void pass_mark_external_visit_ref (void *thing, int ref_type, -  visit_thing_fn *visit_fn, void *extra) + static void pass_mark_external_visit_ref (void *thing, int UNUSED(ref_type), +  visit_thing_fn *UNUSED(visit_fn), void *UNUSED(extra))   {    struct mc_marker *ref_to = find_mc_marker (thing);       assert (mc_pass == MC_PASS_MARK_EXTERNAL);       if (ref_to) {    if ((ref_to->flags & (MC_FLAG_INT_VISITED | MC_FLAG_LA_VISITED)) ==    MC_FLAG_LA_VISITED) {    /* Only interested in existing lookahead things, except those on    * the "fringe" that haven't been visited. */
pike.git/src/gc.c:5092:    "ignored internal" : "ignored fringe thing");    }   }      static void current_only_visit_ref (void *thing, int ref_type,    visit_thing_fn *visit_fn, void *extra)   /* This is used when count_memory has a negative lookahead. It only    * recurses through REF_TYPE_INTERNAL references. Note that most    * fields in mc_marker aren't used. */   { -  struct mc_marker *ref_to = find_mc_marker (thing); +     int ref_from_flags; -  +  struct mc_marker *ref_to;       assert (mc_pass);    assert (mc_lookahead < 0);   #ifdef PIKE_DEBUG    assert (mc_ref_from != (void *) (ptrdiff_t) -1);   #endif       ref_from_flags = mc_ref_from->flags;    assert (ref_from_flags & MC_FLAG_INTERNAL);    assert (!(ref_from_flags & MC_FLAG_INT_VISITED));    -  + #ifndef MEMORY_COUNT_DEBUG +  if (!(ref_type & REF_TYPE_INTERNAL)) { +  /* Return before lookup (or much worse, allocation) in the +  mc_marker hash table. The only reason to allocate a marker in +  this case is, AFAICS, to get the tracing right with +  MEMORY_COUNT_DEBUG enabled. That case is handled below. */ +  return; +  } + #endif +  +  ref_to = find_mc_marker (thing); +     if (!ref_to) {    ref_to = my_make_mc_marker (thing, visit_fn, extra);    MC_DEBUG_MSG (ref_to, "got new thing");    }    else if (ref_to->flags & MC_FLAG_INTERNAL) {    /* Ignore refs to the starting points. Can't treat them like other    * things anyway since the int_refs aren't valid. */    MC_DEBUG_MSG (ref_to, "ignored starting point");    return;    }    else    MC_DEBUG_MSG (ref_to, "got old thing");    -  + #ifdef MEMORY_COUNT_DEBUG    if (!(ref_type & REF_TYPE_INTERNAL)) {    MC_DEBUG_MSG (ref_to, "ignored non-internal ref");    return;    } -  + #endif       ref_to->int_refs++;    MC_DEBUG_MSG (ref_to, "added really internal ref");    assert (ref_to->int_refs <= *(INT32 *) thing);       if (ref_to->int_refs == *(INT32 *) thing) {    ref_to->flags |= MC_FLAG_INTERNAL;    mc_wq_enqueue (ref_to);    MC_DEBUG_MSG (ref_to, "enqueued internal");    }   }    -  + static void ignore_visit_enter(void *UNUSED(thing), int UNUSED(type), void *UNUSED(extra)) + { + } +  + static void ignore_visit_leave(void *UNUSED(thing), int UNUSED(type), void *UNUSED(extra)) + { + } +    PMOD_EXPORT int mc_count_bytes (void *thing)   {    if (mc_pass == MC_PASS_LOOKAHEAD) {    struct mc_marker *m = find_mc_marker (thing);   #ifdef PIKE_DEBUG    if (!m) Pike_fatal ("mc_marker not found for %p.\n", thing);   #endif    if ((m->flags & (MC_FLAG_INTERNAL|MC_FLAG_INT_VISITED)) == MC_FLAG_INTERNAL)    return 1;    }
pike.git/src/gc.c:5440:       else {    if (TYPEOF(Pike_sp[-args]) != T_INT)    SIMPLE_ARG_TYPE_ERROR ("count_memory", 1, "int|mapping(string:int)");    mc_lookahead =    Pike_sp[-args].u.integer > (unsigned INT16) -1 ? (unsigned INT16) -1 :    Pike_sp[-args].u.integer < 0 ? -1 :    Pike_sp[-args].u.integer;    }    -  init_mc_marker_hash(); +  start_mc();       if (TYPEOF(pike_cycle_depth_str) == PIKE_T_FREE) {    SET_SVAL_TYPE(pike_cycle_depth_str, T_STRING);    MAKE_CONST_STRING (pike_cycle_depth_str.u.string, "pike_cycle_depth");    }       assert (mc_work_queue == NULL);    mc_work_queue = malloc (MC_WQ_START_SIZE * sizeof (mc_work_queue[0]));    if (!mc_work_queue) { -  exit_mc_marker_hash(); +  stop_mc();    SIMPLE_OUT_OF_MEMORY_ERROR ("Pike.count_memory",    MC_WQ_START_SIZE * sizeof (mc_work_queue[0]));    }    mc_wq_size = MC_WQ_START_SIZE;    mc_work_queue--; /* Compensate for 1-based indexing. */    mc_wq_used = 1;       assert (!mc_pass); -  +  assert (visit_enter == NULL);    assert (visit_ref == NULL); -  +  assert (visit_leave == NULL);       free_svalue (&throw_value);    mark_free_svalue (&throw_value);       {    int i;    for (i = -args + 1; i < 0; i++) {    struct svalue *s = Pike_sp + i;       if (TYPEOF(*s) == T_INT)    continue;    -  else if (TYPEOF(*s) > MAX_REF_TYPE) { -  exit_mc_marker_hash(); +  else if (!REFCOUNTED_TYPE(TYPEOF(*s))) {    free (mc_work_queue + 1);    mc_work_queue = NULL; -  +  stop_mc();    SIMPLE_ARG_TYPE_ERROR (    "count_memory", i + args + 1,    "array|multiset|mapping|object|program|string|type|int");    }       else {    if (TYPEOF(*s) == T_FUNCTION) {    struct svalue s2;    if (!(s2.u.program = program_from_function (s))) { -  exit_mc_marker_hash(); +     free (mc_work_queue + 1);    mc_work_queue = NULL; -  +  stop_mc();    SIMPLE_ARG_TYPE_ERROR (    "count_memory", i + args + 1,    "array|multiset|mapping|object|program|string|type|int");    }    add_ref (s2.u.program);    SET_SVAL_TYPE(s2, T_PROGRAM);    free_svalue (s);    move_svalue (s, &s2);    }   
pike.git/src/gc.c:5509:    /* The user passed the same thing several times. Ignore it. */    }       else {    struct mc_marker *m =    my_make_mc_marker (s->u.ptr, visit_fn_from_type[TYPEOF(*s)], NULL);    m->flags |= MC_FLAG_INTERNAL;    if (!mc_block_pike_cycle_depth && TYPEOF(*s) == T_OBJECT) {    int cycle_depth = mc_cycle_depth_from_obj (s->u.object);    if (TYPEOF(throw_value) != PIKE_T_FREE) { -  exit_mc_marker_hash(); +     free (mc_work_queue + 1);    mc_work_queue = NULL; -  +  stop_mc();    throw_severity = THROW_ERROR;    pike_throw();    }    m->la_count = cycle_depth >= 0 ? cycle_depth : mc_lookahead;    }    else    m->la_count = mc_lookahead;    mc_wq_enqueue (m);    MC_DEBUG_MSG (m, "enqueued starting point");    }
pike.git/src/gc.c:5544:    assert (mc_complete.dl_prev == &mc_complete);    assert (mc_complete.dl_next == &mc_complete);   #ifdef PIKE_DEBUG    assert (mc_ref_from == (void *) (ptrdiff_t) -1);   #endif       mc_counted_bytes = 0;    count_internal = count_cyclic = count_visited = 0;    count_visits = count_revisits = count_rounds = 0;    +  visit_enter = ignore_visit_enter;    visit_ref = mc_lookahead < 0 ?    current_only_visit_ref : pass_lookahead_visit_ref; -  +  visit_leave = ignore_visit_leave;       do {    count_rounds++;    mc_enqueued_noninternal = 0;      #ifdef MEMORY_COUNT_DEBUG    fprintf (stderr, "[%d] MC_PASS_LOOKAHEAD\n", count_rounds);   #endif    mc_pass = MC_PASS_LOOKAHEAD;       while ((mc_ref_from = mc_wq_dequeue())) {    int action;       assert (!(mc_ref_from->flags & MC_FLAG_INT_VISITED));       if (mc_ref_from->flags & MC_FLAG_INTERNAL) {    action = VISIT_COUNT_BYTES; /* Memory count this. */    MC_DEBUG_MSG (NULL, "enter with byte counting"); -  +  if (mc_lookahead < 0) { +  MC_DEBUG_MSG (NULL, "VISIT_NO_REFS mode"); +  action |= VISIT_NO_REFS; +  }       mc_ref_from->visit_fn (mc_ref_from->thing, action, mc_ref_from->extra);    count_visits++;       if (mc_ref_from->flags & MC_FLAG_LA_VISITED) {    count_revisits++;    DL_REMOVE (mc_ref_from);    MC_DEBUG_MSG (NULL, "leave - removed from list");    }    else {
pike.git/src/gc.c:5632:    MC_DEBUG_MSG (NULL, "leave - added to incomplete list");    }    else {    DL_ADD_LAST (mc_complete, mc_ref_from);    MC_DEBUG_MSG (NULL, "leave - added to complete list");    }    }    }       if (TYPEOF(throw_value) != PIKE_T_FREE) { -  exit_mc_marker_hash(); +     free (mc_work_queue + 1);    mc_work_queue = NULL; -  +  stop_mc();    throw_severity = THROW_ERROR;    pike_throw();    }    }   #if defined (PIKE_DEBUG) || defined (MEMORY_COUNT_DEBUG)    mc_ref_from = (void *) (ptrdiff_t) -1;   #endif       /* If no things that might be indirectly incomplete have been    * enqueued then there's no need to do another mark external pass. */
pike.git/src/gc.c:5684:    list = &mc_incomplete;    while (1) {    /* First go through the incomplete list to visit externals,    * then the indirectly incomplete list where all the new    * indirect externals appear. */    for (m = list->dl_next; m != list; m = m->dl_next) {    TYPE_T type = type_from_visit_fn (m->visit_fn);    assert (!(m->flags & MC_FLAG_INTERNAL));    assert (m->flags & MC_FLAG_LA_VISITED);    assert (list != &mc_incomplete || !(m->flags & MC_FLAG_CANDIDATE)); -  if (mc_block_lookahead & (1 << type)) +  if (mc_lookahead_blocked(type))    MC_DEBUG_MSG (m, "type is blocked - not visiting");    else {   #ifdef MEMORY_COUNT_DEBUG    mc_ref_from = m;    MC_DEBUG_MSG (NULL, "visiting external");   #endif    count_visits++;    count_revisits++;    m->visit_fn (m->thing, VISIT_NORMAL, m->extra);    }
pike.git/src/gc.c:5727:       {    /* We've found some internal cyclic stuff. Put it in the work    * list for the next round. */    struct mc_marker *m = mc_complete.dl_next;    assert (m != &mc_complete);    do {    assert (!(m->flags & (MC_FLAG_INTERNAL | MC_FLAG_INT_VISITED)));    m->flags |= MC_FLAG_INTERNAL;    assert (m->flags & (MC_FLAG_CANDIDATE | MC_FLAG_LA_VISITED)); -  assert (!(mc_block_lookahead & -  (1 << type_from_visit_fn (m->visit_fn)))); +  assert (!(mc_lookahead_blocked(type_from_visit_fn (m->visit_fn))));    /* The following assertion implies that the lookahead count    * already has been raised as it should. */    assert (m->flags & MC_FLAG_CANDIDATE_REF);    mc_wq_enqueue (m);    if (collect_stats && type_from_visit_fn (m->visit_fn) <= MAX_TYPE)    count_cyclic++;    MC_DEBUG_MSG (m, "enqueued cyclic internal");    m = m->dl_next;    } while (m != &mc_complete);    }
pike.git/src/gc.c:5866:    DO_AGGREGATE_ARRAY (120);    }    }    } END_AGGREGATE_ARRAY;    args++;    mapping_string_insert (opts, ind, Pike_sp - 1);    }    }       mc_pass = 0; +  visit_enter = NULL;    visit_ref = NULL; -  +  visit_leave = NULL;       DL_MAKE_EMPTY (mc_incomplete);    DL_MAKE_EMPTY (mc_indirect);   #ifdef DO_PIKE_CLEANUP    {    size_t e;    for (e = 0; e < mc_marker_hash_table_size; e++)    while (mc_marker_hash_table[e])    remove_mc_marker (mc_marker_hash_table[e]->thing);    }   #endif -  exit_mc_marker_hash(); +        assert (mc_wq_used == 1);    free (mc_work_queue + 1);    mc_work_queue = NULL; -  +  stop_mc();       pop_n_elems (args);    push_ulongest (return_count ? count_internal : mc_counted_bytes);   } -  +  + static struct mapping *identify_loop_reverse = NULL; +  + void identify_loop_visit_enter(void *thing, int type, void *UNUSED(extra)) + { +  if (type < T_VOID) { +  /* Valid svalue type. */ +  SET_SVAL(*Pike_sp, type, 0, refs, thing); +  add_ref(((struct array *)thing)); +  Pike_sp++; +  } + } +  + void identify_loop_visit_ref(void *dst, int UNUSED(ref_type), +  visit_thing_fn *visit_dst, +  void *extra) + { +  int type = type_from_visit_fn(visit_dst); +  struct mc_marker *ref_to = find_mc_marker(dst); +  if (ref_to) { +  /* Already visited or queued for visiting. */ +  return; +  } +  +  ref_to = my_make_mc_marker(dst, visit_dst, extra); +  +  if (type != PIKE_T_UNKNOWN) { +  struct svalue s; +  SET_SVAL(s, type, 0, refs, dst); +  low_mapping_insert(identify_loop_reverse, &s, Pike_sp-1, 0); +  +  mc_wq_enqueue(ref_to); +  } else { +  /* Not a valid svalue type. +  * +  * Probably T_MAPPING_DATA or T_MULTISET_DATA or similar. +  * +  * Recurse directly while we have the containing thing on the stack. +  */ +  ref_to->flags |= MC_FLAG_INT_VISITED; +  visit_dst(dst, VISIT_COMPLEX_ONLY, extra); +  } + } +  + void identify_loop_visit_leave(void *UNUSED(thing), int type, void *UNUSED(extra)) + { +  if (type < T_VOID) { +  /* Valid svalue type. */ +  pop_stack(); +  } + } +  + /*! @decl array(mixed) identify_cycle(mixed x) +  *! @belongs Pike +  *! +  *! Identify reference cycles in Pike datastructures. +  *! +  *! This function is typically used to identify why certain +  *! datastructures need the @[gc] to run to be freed. +  *! +  *! @param x +  *! Value that is believed to be involved in a reference cycle. +  *! +  *! @returns +  *! @mixed +  *! @type zero +  *! Returns @expr{UNDEFINED@} if @[x] is not member of a reference cycle. +  *! @type array(mixed) +  *! Otherwise returns an array identifying a cycle with @[x] as the first +  *! element, and where the elements refer to each other in order, and the +  *! last element refers to the first. +  *! @endmixed +  */ + void f_identify_cycle(INT32 args) + { +  struct svalue *s; +  struct mc_marker *m; +  struct svalue *k; +  +  if (args < 1) { +  SIMPLE_TOO_FEW_ARGS_ERROR("identify_loops", 1); +  } +  +  if (args > 1) pop_n_elems(args-1); +  args = 1; +  +  s = Pike_sp - 1; +  +  if (!REFCOUNTED_TYPE(TYPEOF(*s))) { +  SIMPLE_ARG_TYPE_ERROR("identify_loops", 1, +  "array|multiset|mapping|object|program|string|type"); +  } +  if (TYPEOF(*s) == T_FUNCTION) { +  if (SUBTYPEOF(*s) == FUNCTION_BUILTIN) { +  SIMPLE_ARG_TYPE_ERROR("identify_loops", 1, +  "array|multiset|mapping|object|program|string|type"); +  } +  SET_SVAL_TYPE(*s, T_OBJECT); +  } +  +  start_mc(); +  +  if (TYPEOF(pike_cycle_depth_str) == PIKE_T_FREE) { +  SET_SVAL_TYPE(pike_cycle_depth_str, T_STRING); +  MAKE_CONST_STRING (pike_cycle_depth_str.u.string, "pike_cycle_depth"); +  } +  +  assert (mc_work_queue == NULL); +  mc_work_queue = malloc (MC_WQ_START_SIZE * sizeof (mc_work_queue[0])); +  if (!mc_work_queue) { +  stop_mc(); +  SIMPLE_OUT_OF_MEMORY_ERROR ("Pike.count_memory", +  MC_WQ_START_SIZE * sizeof (mc_work_queue[0])); +  } +  /* NB: 1-based indexing in mc_work_queue. */ +  mc_work_queue--; +  mc_wq_size = MC_WQ_START_SIZE; +  mc_wq_used = 1; +  mc_lookahead = -1; +  +  assert (!mc_pass); +  assert (visit_enter == NULL); +  assert (visit_ref == NULL); +  assert (visit_leave == NULL); +  +  /* There's a fair chance of there being lots of stuff being referenced, +  * so preallocate a reasonable initial size. +  */ +  identify_loop_reverse = allocate_mapping(1024); +  +  visit_enter = identify_loop_visit_enter; +  visit_ref = identify_loop_visit_ref; +  visit_leave = identify_loop_visit_leave; +  +  /* NB: This initial call will botstrap the wq_queue. */ +  visit_fn_from_type[TYPEOF(*s)](s->u.ptr, VISIT_COMPLEX_ONLY, NULL); +  + #ifdef PIKE_DEBUG +  assert (mc_ref_from == (void *) (ptrdiff_t) -1); + #endif +  +  while ((mc_ref_from = mc_wq_dequeue())) { +  if (mc_ref_from->flags & MC_FLAG_INT_VISITED) continue; +  +  mc_ref_from->flags |= MC_FLAG_INT_VISITED; +  mc_ref_from->visit_fn(mc_ref_from->thing, VISIT_COMPLEX_ONLY, NULL); +  } +  + #if defined (PIKE_DEBUG) || defined (MEMORY_COUNT_DEBUG) +  mc_ref_from = (void *) (ptrdiff_t) -1; + #endif +  +  /* NB: 1-based indexing in mc_work_queue. */ +  mc_work_queue++; +  free(mc_work_queue); +  mc_work_queue = NULL; +  +  visit_enter = NULL; +  visit_ref = NULL; +  visit_leave = NULL; +  + #ifdef PIKE_DEBUG +  if (s != Pike_sp-1) { +  Pike_fatal("Stack error in identify_loops.\n"); +  } + #endif +  +  while ((k = low_mapping_lookup(identify_loop_reverse, Pike_sp-1))) { +  /* NB: Since we entered this loop, we know that there's a +  * reference loop involving s, as s otherwise wouldn't +  * have been in the mapping. +  */ +  push_svalue(k); +  if (k->u.refs == s->u.refs) { +  /* Found! */ +  break; +  } +  } +  +  free_mapping(identify_loop_reverse); +  +  stop_mc(); +  +  if (!k) { +  push_undefined(); +  } else { +  /* NB: We push s an extra time last above, to simplify the +  * reversing below. +  */ +  f_aggregate(Pike_sp - (s + 1)); +  f_reverse(1); +  } + } +  + void init_mc(void) + { +  init_interleave_mutex(&mc_mutex); + } +  + void exit_mc(void) + { +  exit_interleave_mutex(&mc_mutex); + }