diff --git a/src/gc.c b/src/gc.c
index c787d00d9730ad8e9522bfffc01d31b870ed5a17..0bb4d3f1ac9c2e0a73476d2b1225ccb9e6477d76 100644
--- a/src/gc.c
+++ b/src/gc.c
@@ -2,7 +2,7 @@
 || 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.310 2008/05/04 04:48:32 mast Exp $
+|| $Id: gc.c,v 1.311 2008/05/11 02:31:57 mast Exp $
 */
 
 #include "global.h"
@@ -26,10 +26,11 @@ struct callback *gc_evaluator_callback=0;
 #include "interpret.h"
 #include "bignum.h"
 #include "pike_threadlib.h"
-
 #include "gc.h"
 #include "main.h"
+
 #include <math.h>
+#include <assert.h>
 
 #include "block_alloc.h"
 
@@ -110,7 +111,6 @@ PMOD_EXPORT int Pike_in_gc = 0;
 int gc_generation = 0;
 time_t last_gc;
 int gc_trace = 0, gc_debug = 0;
-PMOD_EXPORT size_t gc_counted_bytes;
 #ifdef DO_PIKE_CLEANUP
 int gc_destruct_everything = 0;
 #endif
@@ -189,7 +189,7 @@ static struct gc_rec_frame *kill_list = &sentinel_frame;
  * 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
+ * gc_cycle_check_* functions 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
@@ -519,7 +519,7 @@ static void gc_cycle_pop();
 #undef find_marker
 #define find_marker debug_find_marker
 
-PTR_HASH_ALLOC_FILL_PAGES(marker,2)
+PTR_HASH_ALLOC_FIXED_FILL_PAGES(marker,2)
 
 #undef get_marker
 #define get_marker(X) ((struct marker *) debug_malloc_pass(debug_get_marker(X)))
@@ -1786,7 +1786,7 @@ static INLINE struct marker *gc_check_debug(void *a, int weak)
 	   gc_found_place ? gc_found_place : "");
 #endif
 
-  if (Pike_in_gc != GC_PASS_CHECK && Pike_in_gc != GC_PASS_COUNT_MEMORY)
+  if (Pike_in_gc != GC_PASS_CHECK)
     Pike_fatal("gc check attempted in invalid pass.\n");
 
   m = get_marker(a);
@@ -2372,10 +2372,9 @@ int gc_mark(void *a)
   struct marker *m;
 
 #ifdef PIKE_DEBUG
-  if ((Pike_in_gc == GC_PASS_ZAP_WEAK || Pike_in_gc == GC_PASS_COUNT_MEMORY) &&
-      !find_marker (a))
+  if (Pike_in_gc == GC_PASS_ZAP_WEAK && !find_marker (a))
     gc_fatal (a, 0, "gc_mark() called for for thing without marker "
-	      "in pass %d.\n", Pike_in_gc);
+	      "in zap weak pass.\n");
 #endif
 
   m = get_marker (a);
@@ -2389,72 +2388,54 @@ int gc_mark(void *a)
     gc_watched_found (m, "gc_mark()");
   }
   if (!a) Pike_fatal("Got null pointer.\n");
-  if (Pike_in_gc != GC_PASS_MARK && Pike_in_gc != GC_PASS_ZAP_WEAK &&
-      Pike_in_gc != GC_PASS_COUNT_MEMORY)
+  if (Pike_in_gc != GC_PASS_MARK && Pike_in_gc != GC_PASS_ZAP_WEAK)
     Pike_fatal("GC mark attempted in invalid pass.\n");
   if (!*(INT32 *) a)
     gc_fatal(a, 0, "Marked a thing without refs.\n");
-  if (m->weak_refs < 0 && Pike_in_gc != GC_PASS_COUNT_MEMORY)
+  if (m->weak_refs < 0)
     gc_fatal(a, 0, "Marking thing scheduled for weak free.\n");
 #endif
 
-  switch (Pike_in_gc) {
-    case GC_PASS_ZAP_WEAK:
-      /* Things are visited in the zap weak pass through the mark
-       * functions to free refs to internal things that only got weak
-       * external references. That happens only when a thing also have
-       * internal cyclic nonweak refs. */
+  if (Pike_in_gc == GC_PASS_ZAP_WEAK) {
+    /* Things are visited in the zap weak pass through the mark
+     * functions to free refs to internal things that only got weak
+     * external references. That happens only when a thing also have
+     * internal cyclic nonweak refs. */
 #ifdef PIKE_DEBUG
-      if (!(m->flags & GC_MARKED))
-	gc_fatal(a, 0, "gc_mark() called for thing in zap weak pass "
-		 "that wasn't marked before.\n");
+    if (!(m->flags & GC_MARKED))
+      gc_fatal(a, 0, "gc_mark() called for thing in zap weak pass "
+	       "that wasn't marked before.\n");
 #endif
-      if (m->flags & GC_FREE_VISITED) {
-	debug_malloc_touch (a);
-	return 0;
-      }
-      else {
-	debug_malloc_touch (a);
-	m->flags |= GC_FREE_VISITED;
-	return 1;
-      }
-
-    case GC_PASS_COUNT_MEMORY:
-      if (m->flags & GC_NOT_REFERENCED) {
-	/* All refs to this thing are internal, so return true to get
-	 * it counted and recursed. */
-	debug_malloc_touch (a);
-	m->flags = (m->flags & ~GC_NOT_REFERENCED) | GC_MARKED;
-	DO_IF_DEBUG(marked++);
-	return 1;
-      }
-
-      else {
-	debug_malloc_touch (a);
-	return 0;
-      }
+    if (m->flags & GC_FREE_VISITED) {
+      debug_malloc_touch (a);
+      return 0;
+    }
+    else {
+      debug_malloc_touch (a);
+      m->flags |= GC_FREE_VISITED;
+      return 1;
+    }
+  }
 
-    default:
-      if (m->flags & GC_MARKED) {
-	debug_malloc_touch (a);
+  else if (m->flags & GC_MARKED) {
+    debug_malloc_touch (a);
 #ifdef PIKE_DEBUG
-	if (m->weak_refs != 0)
-	  gc_fatal (a, 0, "weak_refs changed in marker "
-		    "already visited by gc_mark().\n");
+    if (m->weak_refs != 0)
+      gc_fatal (a, 0, "weak_refs changed in marker "
+		"already visited by gc_mark().\n");
 #endif
-	return 0;
-      }
+    return 0;
+  }
 
-      else {
-	debug_malloc_touch (a);
-	if (m->weak_refs) {
-	  gc_ext_weak_refs -= m->weak_refs;
-	  m->weak_refs = 0;
-	}
-	m->flags = (m->flags & ~GC_NOT_REFERENCED) | GC_MARKED;
-	DO_IF_DEBUG(marked++);
-	return 1;
-      }
+  else {
+    debug_malloc_touch (a);
+    if (m->weak_refs) {
+      gc_ext_weak_refs -= m->weak_refs;
+      m->weak_refs = 0;
+    }
+    m->flags = (m->flags & ~GC_NOT_REFERENCED) | GC_MARKED;
+    DO_IF_DEBUG(marked++);
+    return 1;
   }
 }
 
@@ -4023,7 +4004,568 @@ void dump_gc_info(void)
   fprintf(stderr,"in_gc                      : %d\n", Pike_in_gc);
 }
 
-/*! @decl int count_memory (zero flags, @
+void cleanup_gc(void)
+{
+#ifdef 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;
+
+/* 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,
+  /* 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,
+};
+
+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;
+}
+
+/* Memory count mode
+ *
+ * This mode is used by f_count_memory, and it's recognized by a
+ * nonzero value in mc_pass. The cycle check functions are reused but
+ * work completely differently when mc_pass is set, and it takes
+ * precedence over Pike_in_gc.
+ *
+ * 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
+ * up to the refcount for a thing, that thing is known to have only
+ * internal refs, and so it's memory counted and then all its refs are
+ * followed too.
+ *
+ * To cope with internal cyclic refs, there's a "lookahead" algorithm
+ * which recurses through more things in the hope of finding cycles
+ * that otherwise would make us miss internal refs. This lookahead is
+ * limited by mc_lookahead and mc_block_lookahead.
+ *
+ * All things are categorized as follows:
+ *
+ * o  Internal things: These are known to have only internal
+ *    references and are memory counted. The things given to
+ *    f_count_memory as starting points are initially asserted to be
+ *    internal regardless of how many refs they got.
+ *
+ * o  Lookahead things: A lookahead thing is one that has been found
+ *    by following refs from an internal thing.
+ *
+ *    Every lookahead thing has a distance which is the number of refs
+ *    that were followed from an internal thing to reach it. If a
+ *    lookahead thing later on is found through another path with
+ *    fewer refs, its distance is lowered so that it eventually
+ *    reflects the shortest path. The traversal stops when the
+ *    distance reaches mc_lookahead (or if the type is blocked by
+ *    mc_block_lookahead).
+ *
+ *    Lookahead things are further divided into three categories:
+ *
+ *    o  Incomplete: Things whose refcounts (still) are higher than
+ *       all refs coming to them from both internal and lookahead
+ *       things.
+ *    o  Complete: Things whose refs from internal and lookahead
+ *       things equal their refcounts. I.e. we've found all refs going
+ *       to these.
+ *    o  Indirectly incomplete: In MC_PASS_MARK_EXTERNAL, these are
+ *       all the complete things found to be referenced by incomplete
+ *       things.
+ *
+ *    These sets are tracked through three double linked lists,
+ *    mc_incomplete, mc_complete, and mc_indirect respectively.
+ *
+ * o  Unvisited things: Everything else that hasn't been visited yet.
+ *
+ * For every visited thing we record the number of refs from internal
+ * things (int_refs) and from lookahead things (la_refs).
+ *
+ * The basic algorithm for finding all internal things works like
+ * this:
+ *
+ * First the starting point things are labelled internal and put into
+ * the work list (mc_work_list).
+ *
+ * mc_pass is set to MC_PASS_LOOKAHEAD:
+ *
+ * We do a breadth-first recursion through the things in the work list
+ * until we reach the mc_lookahead distance.
+ *
+ * Every time we visit something we calculate its distance as either
+ * the same as the distance of the source thing (if the target is
+ * found to be internal or if the followed ref is REF_TYPE_INTERNAL),
+ * or the next greater distance (otherwise). If the distance is lower
+ * than mc_lookahead and the thing is either new or its current
+ * distance is greater, it's added to the work list.
+ *
+ * Since new things always are added to the work list at the same or
+ * the next greater distance, it will never hold things at more than
+ * two distances at once. Thus we can simply enqueue things at the
+ * same distance first and those at the next distance last.
+ *
+ * We might however need to dequeue something at the greater distance
+ * to be able to reenqueue it at the same distance. mc_work_list is
+ * double linked to be able to dequeue quickly.
+ *
+ * int_refs and la_refs are updated when things are visited. They
+ * become internal if int_refs add up to the refcount. Otherwise they
+ * are put in the incomplete or complete sets as appropriate.
+ *
+ * mc_pass is set to MC_PASS_MARK_EXTERNAL:
+ *
+ * At this point the set of lookahead things is complete (as far as we
+ * are concerned), and it's divided into complete and incomplete
+ * lookahead things. All references in the incomplete list are
+ * followed to build up the set of indirectly incomplete things. The
+ * incomplete and indirectly incomplete things are referenced
+ * externally and should not be memory counted.
+ *
+ * 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
+ * at distance zero, move the indirectly incomplete list back to
+ * complete and repeat MC_PASS_LOOKAHEAD. Otherwise we're done.
+ */
+
+/* #define MEMORY_COUNT_DEBUG */
+
+PMOD_EXPORT int mc_pass;
+PMOD_EXPORT size_t mc_counted_bytes;
+
+static unsigned mc_lookahead;
+static TYPE_FIELD mc_block_lookahead;
+static TYPE_FIELD mc_block_lookahead_default = BIT_PROGRAM|BIT_STRING;
+/* Strings are blocked because they don't contain refs. */
+
+static unsigned mc_ext_toggle_bias = 0;
+
+#define MC_PASS_LOOKAHEAD 1
+#define MC_PASS_MARK_EXTERNAL 2
+
+/* Set in when the refs emanating from a thing have been counted. */
+#define MC_FLAG_REFCOUNTED	0x01
+
+/* Set when a thing has become internal. */
+#define MC_FLAG_INTERNAL	0x02
+
+/* Set when a thing has been called with VISIT_COUNT_BYTES (after the
+ * visit function has returned). */
+#define MC_FLAG_MEMCOUNTED	0x04
+
+/* A toggle flag to mark external (i.e. incomplete and indirectly
+ * incomplete) things in MC_PASS_MARK_EXTERNAL so that we don't
+ * recurse them repeatedly. If mc_ext_toggle_bias is zero then it's
+ * external if this is set. If mc_ext_toggle_bias is one then it's
+ * external if this is cleared. mc_ext_toggle_bias toggles every time
+ * we leave MC_PASS_MARK_EXTERNAL, thus we avoid the work to go
+ * through the externals clear the flag for the next round. */
+#define MC_FLAG_EXT_TOGGLE	0x08
+
+#define IS_EXTERNAL(M)							\
+  (((M)->flags ^ mc_ext_toggle_bias) & MC_FLAG_EXT_TOGGLE)
+#define INIT_CLEARED_EXTERNAL(M) do {					\
+    struct mc_marker *_m = (M);						\
+    if (mc_ext_toggle_bias) _m->flags |= MC_FLAG_EXT_TOGGLE;		\
+  } while (0)
+#define FLAG_EXTERNAL(M) do {						\
+    struct mc_marker *_m = (M);						\
+    assert (!IS_EXTERNAL (_m));						\
+    _m->flags ^= MC_FLAG_EXT_TOGGLE;					\
+  } while (0)
+#define TOGGLE_EXT_FLAGS() do {						\
+    mc_ext_toggle_bias ^= MC_FLAG_EXT_TOGGLE;				\
+  } while (0)
+
+struct mc_marker
+{
+  struct mc_marker *hash_next;	/* Used by PTR_HASH_ALLOC. */
+  struct mc_marker *wl_prev;	/* Work list pointers. wl_next is NULL for */
+  struct mc_marker *wl_next;	/*   things not on the work list. */
+  struct mc_marker *dl_prev;	/* For the mc_incomplete, mc_complete and */
+  struct mc_marker *dl_next;	/*   mc_indirect lists. Used iff not internal.*/
+  void *thing;			/* Referenced thing. */
+  visit_thing_fn *visit_fn;	/* Visit function for it */
+  void *extra;			/*   and its extra data. */
+  INT32 int_refs;		/* These refcounts are bogus */
+  INT32 la_refs;		/*   for internal things. */
+  unsigned INT32 dist;		/* Distance. */
+  unsigned INT16 flags;
+};
+
+#undef BLOCK_ALLOC_NEXT
+#undef BLOCK_ALLOC_NEXT
+#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 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->wl_next = NULL;
+#ifdef PIKE_DEBUG
+  m->wl_prev = m->dl_prev = m->dl_next = (void *) (ptrdiff_t) -1;
+  m->dist = MAX_UINT32;
+#endif
+  return m;
+}
+
+#if defined (PIKE_DEBUG) || defined (MEMORY_COUNT_DEBUG)
+static void describe_mc_marker (struct mc_marker *m)
+{
+  fprintf (stderr, "%s %p: refs %d, int %d, la %d, dist %d",
+	   get_name_of_type (type_from_visit_fn (m->visit_fn)),
+	   m->thing, *(INT32 *) m->thing, m->int_refs, m->la_refs, m->dist);
+  if (m->flags & MC_FLAG_REFCOUNTED) fputs (", RC", stderr);
+  if (m->flags & MC_FLAG_INTERNAL) fputs (", INT", stderr);
+  if (m->flags & MC_FLAG_MEMCOUNTED) fputs (", MC", stderr);
+  if (IS_EXTERNAL (m)) fputs (", EXT", stderr);
+  if (m->wl_next) fputs (", on wl", stderr);
+}
+#endif
+
+static struct mc_marker mc_work_list = {
+  /* Sentinel. The work list starts at work_list.wl_next. */
+  (void *) (ptrdiff_t) -1,
+  &mc_work_list, &mc_work_list,
+  (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1,
+  (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1,
+  -1, -1, -1, -1
+};
+
+#define WL_ADD_FIRST(M) do {						\
+    struct mc_marker *_m = (M);						\
+    struct mc_marker *_list_next = mc_work_list.wl_next;		\
+    DO_IF_DEBUG (assert (_m->wl_prev == (void *) (ptrdiff_t) -1));	\
+    assert (_m->wl_next == NULL);					\
+    _m->wl_prev = &mc_work_list;					\
+    _m->wl_next = _list_next;						\
+    mc_work_list.wl_next = _list_next->wl_prev = _m;			\
+  } while (0)
+
+#define WL_ADD_LAST(M) do {						\
+    struct mc_marker *_m = (M);						\
+    struct mc_marker *_list_prev = mc_work_list.wl_prev;		\
+    DO_IF_DEBUG (assert (_m->wl_prev == (void *) (ptrdiff_t) -1));	\
+    assert (_m->wl_next == NULL);					\
+    _m->wl_prev = _list_prev;						\
+    _m->wl_next = &mc_work_list;					\
+    mc_work_list.wl_prev = _list_prev->wl_next = _m;			\
+  } while (0)
+
+#define WL_REMOVE(M) do {						\
+    struct mc_marker *_m = (M);						\
+    struct mc_marker *_list_prev = _m->wl_prev;				\
+    struct mc_marker *_list_next = _m->wl_next;				\
+    assert (_m->wl_prev != (void *) (ptrdiff_t) -1);			\
+    assert (_m->wl_next != (void *) (ptrdiff_t) -1);			\
+    _list_prev->wl_next = _list_next;					\
+    _list_next->wl_prev = _list_prev;					\
+    DO_IF_DEBUG (_m->wl_prev = (void *) (ptrdiff_t) -1);		\
+    _m->wl_next = NULL;							\
+  } while (0)
+
+static INLINE void mc_wl_enqueue_first (struct mc_marker *m)
+{
+  if (m->wl_next) WL_REMOVE (m);
+  WL_ADD_FIRST (m);
+}
+
+static INLINE void mc_wl_enqueue_last (struct mc_marker *m)
+{
+  /* Note: Does not try to remove from the queue first. */
+  WL_ADD_LAST (m);
+}
+
+static INLINE struct mc_marker *mc_wl_dequeue()
+{
+  struct mc_marker *m = mc_work_list.wl_next;
+  if (m != &mc_work_list) {
+    WL_REMOVE (m);
+    return m;
+  }
+  return NULL;
+}
+
+static struct mc_marker mc_incomplete = {
+  /* Sentinel for the incomplete lookaheads list. */
+  (void *) (ptrdiff_t) -1,
+  (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1,
+  &mc_incomplete, &mc_incomplete,
+  (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1,
+  -1, -1, -1, -1
+};
+
+static struct mc_marker mc_complete = {
+  /* Sentinel for the complete lookaheads list. */
+  (void *) (ptrdiff_t) -1,
+  (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1,
+  &mc_complete, &mc_complete,
+  (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1,
+  -1, -1, -1, -1
+};
+
+static struct mc_marker mc_indirect = {
+  /* Sentinel for the indirectly incomplete lookaheads list. */
+  (void *) (ptrdiff_t) -1,
+  (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1,
+  &mc_indirect, &mc_indirect,
+  (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1, (void *) (ptrdiff_t) -1,
+  -1, -1, -1, -1
+};
+
+#define DL_IS_EMPTY(LIST) (LIST.dl_next == &LIST)
+
+#define DL_ADD(LIST, M) do {						\
+    struct mc_marker *_m = (M);						\
+    struct mc_marker *_list_next = LIST.dl_next;			\
+    DO_IF_DEBUG (							\
+      assert (_m->dl_prev == (void *) (ptrdiff_t) -1);			\
+      assert (_m->dl_next == (void *) (ptrdiff_t) -1);			\
+    );									\
+    _m->dl_prev = &LIST;						\
+    _m->dl_next = _list_next;						\
+    LIST.dl_next = _list_next->dl_prev = _m;				\
+  } while (0)
+
+#define DL_REMOVE(M) do {						\
+    struct mc_marker *_m = (M);						\
+    struct mc_marker *_list_prev = _m->dl_prev;				\
+    struct mc_marker *_list_next = _m->dl_next;				\
+    assert (_m->dl_prev != (void *) (ptrdiff_t) -1);			\
+    assert (_m->dl_next != (void *) (ptrdiff_t) -1);			\
+    _list_prev->dl_next = _list_next;					\
+    _list_next->dl_prev = _list_prev;					\
+    DO_IF_DEBUG (_m->dl_prev = _m->dl_next = (void *) (ptrdiff_t) -1);	\
+  } while (0)
+
+#define DL_MOVE(FROM_LIST, TO_LIST) do {				\
+    assert (TO_LIST.dl_prev == &TO_LIST);				\
+    assert (TO_LIST.dl_next == &TO_LIST);				\
+    if (FROM_LIST.dl_next != &FROM_LIST) {				\
+      TO_LIST.dl_prev = FROM_LIST.dl_prev;				\
+      TO_LIST.dl_next = FROM_LIST.dl_next;				\
+      FROM_LIST.dl_prev->dl_next = &TO_LIST;				\
+      FROM_LIST.dl_next->dl_prev = &TO_LIST;				\
+      FROM_LIST.dl_prev = FROM_LIST.dl_next = &FROM_LIST;		\
+    }									\
+  } while (0)
+
+#define DL_MAKE_EMPTY(LIST) do {					\
+    LIST.dl_prev = LIST.dl_next = &LIST;				\
+  } while (0)
+
+static struct mc_marker *mc_ref_from = (void *) (ptrdiff_t) -1;
+
+#ifdef MEMORY_COUNT_DEBUG
+static void MC_DEBUG_MSG (struct mc_marker *m, const char *msg)
+{
+  switch (mc_pass) {
+    case MC_PASS_LOOKAHEAD: fputs ("LA ", stderr); break;
+    case MC_PASS_MARK_EXTERNAL: fputs ("ME ", stderr); break;
+  }
+  if (m) {
+    if (mc_ref_from != (void *) (ptrdiff_t) -1) fputs ("  [", stderr);
+    else fputs ("[", stderr);
+    describe_mc_marker (m);
+    fprintf (stderr, "] %s\n", msg);
+  }
+  else if (mc_ref_from != (void *) (ptrdiff_t) -1) {
+    fputs ("{", stderr);
+    describe_mc_marker (mc_ref_from);
+    fprintf (stderr, "} %s\n", msg);
+  }
+}
+#else
+#define MC_DEBUG_MSG(m, msg) do {} while (0)
+#endif
+
+/* memory_count_enter gets called both from the gc_check_* calls we
+ * make for each thing in the work list, and also from the calls made
+ * through the gc_check_* functions for each reference inside the
+ * things. We use mc_has_entered_thing to figure out which. */
+
+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);
+  int ref_to_is_new = !ref_to;
+  unsigned cur_dist;
+  int ref_from_flags;
+
+  assert (mc_pass == MC_PASS_LOOKAHEAD);
+#ifdef PIKE_DEBUG
+  assert (mc_ref_from != (void *) (ptrdiff_t) -1);
+#endif
+
+  cur_dist = mc_ref_from->dist;
+  ref_from_flags = mc_ref_from->flags;
+
+  if (ref_to_is_new) {
+    ref_to = my_make_mc_marker (thing, visit_fn, extra);
+    MC_DEBUG_MSG (ref_to, "visiting new thing");
+    assert (!(ref_from_flags & MC_FLAG_REFCOUNTED));
+  }
+  else if (ref_to->flags & MC_FLAG_INTERNAL) {
+    /* Ignore refs to internal things. Can't treat them like other
+     * things anyway since the int_refs aren't valid for the starting
+     * points. */
+    MC_DEBUG_MSG (ref_to, "ignored internal");
+    return;
+  }
+  else
+    MC_DEBUG_MSG (ref_to, "visiting old thing");
+
+  if (ref_from_flags & MC_FLAG_INTERNAL) {
+    if (!(ref_from_flags & MC_FLAG_REFCOUNTED))
+      ref_to->int_refs++;
+    else {
+      /* mc_ref_from is a former lookahead thing that has become internal. */
+      assert (ref_to->la_refs > 0);
+      ref_to->la_refs--;
+      ref_to->int_refs++;
+    }
+
+    assert (ref_to->int_refs + ref_to->la_refs <= *(INT32 *) thing);
+
+    if (ref_to->int_refs == *(INT32 *) thing) {
+      /* Found a new internal thing. */
+      ref_to->flags |= MC_FLAG_INTERNAL;
+      ref_to->dist = 0;
+      if (!ref_to_is_new) DL_REMOVE (ref_to);
+      mc_wl_enqueue_first (ref_to);
+      MC_DEBUG_MSG (ref_to, "made internal and enqueued");
+      return;
+    }
+    else
+      MC_DEBUG_MSG (ref_to, "added internal ref");
+  }
+
+  else {
+    if (!(ref_from_flags & MC_FLAG_REFCOUNTED)) {
+      ref_to->la_refs++;
+      MC_DEBUG_MSG (ref_to, "added lookahead ref");
+    }
+
+    assert (ref_to->int_refs + ref_to->la_refs <= *(INT32 *) thing);
+  }
+
+  if (!(ref_from_flags & MC_FLAG_REFCOUNTED)) {
+    if (ref_to->int_refs + ref_to->la_refs == *(INT32 *) thing) {
+      /* The thing has become complete. */
+      if (!ref_to_is_new) DL_REMOVE (ref_to);
+      DL_ADD (mc_complete, ref_to);
+      MC_DEBUG_MSG (ref_to, "refs are complete");
+    }
+    else
+      if (ref_to_is_new) DL_ADD (mc_incomplete, ref_to);
+  }
+
+  {
+    /* Internal refs don't count as any distance, so the distance to
+     * ref_to is the same as cur_dist in that case. */
+    unsigned ref_to_dist =
+      ref_type & REF_TYPE_INTERNAL ? cur_dist : cur_dist + 1;
+
+    if (ref_to_is_new || ref_to->dist > ref_to_dist) {
+      unsigned type;
+
+      ref_to->dist = ref_to_dist;
+#ifdef MEMORY_COUNT_DEBUG
+      if (ref_to_dist == cur_dist)
+	MC_DEBUG_MSG (ref_to, "lowered to dist + 0");
+      else
+	MC_DEBUG_MSG (ref_to, "lowered to dist + 1");
+#endif
+
+      type = type_from_visit_fn (visit_fn);
+      if (mc_block_lookahead & (1 << type))
+	MC_DEBUG_MSG (ref_to, "type is blocked - not enqueued");
+      else if (ref_to_dist < mc_lookahead) {
+	if (ref_to_dist == cur_dist)
+	  mc_wl_enqueue_first (ref_to);
+	else
+	  mc_wl_enqueue_last (ref_to);
+	MC_DEBUG_MSG (ref_to, "enqueued");
+      }
+      else
+	MC_DEBUG_MSG (ref_to, "at max distance - not enqueued");
+    }
+  }
+}
+
+static void pass_mark_external_visit_ref (void *thing, int ref_type,
+					  visit_thing_fn *visit_fn, void *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_INTERNAL)) {
+      /* Only interested in existing lookahead things. */
+
+      if (!IS_EXTERNAL (ref_to)) {
+	assert (ref_to->int_refs + ref_to->la_refs == *(INT32 *) thing);
+	DL_REMOVE (ref_to);
+	FLAG_EXTERNAL (ref_to);
+	DL_ADD (mc_indirect, ref_to);
+	mc_wl_enqueue_last (ref_to);
+	MC_DEBUG_MSG (ref_to, "marked external");
+      }
+      else
+	MC_DEBUG_MSG (ref_to, "already external");
+    }
+    else
+      MC_DEBUG_MSG (ref_to, "ignored internal");
+  }
+}
+
+PMOD_EXPORT int mc_count_bytes (void *thing)
+{
+  if (mc_pass == MC_PASS_LOOKAHEAD) {
+    struct mc_marker *m = find_mc_marker (thing);
+    if ((m->flags & (MC_FLAG_INTERNAL|MC_FLAG_MEMCOUNTED)) == MC_FLAG_INTERNAL)
+      return 1;
+  }
+  return 0;
+}
+
+/*! @decl int count_memory (int(0..)|mapping(string:int) options, @
  *!         array|multiset|mapping|object|program|string|type|int... things)
  *! @appears Pike.count_memory
  *!
@@ -4032,8 +4574,8 @@ void dump_gc_info(void)
  *! would be freed if all those things would lose their references at
  *! the same time. I.e. it not only counts the memory in the things
  *! themselves, but also in all the things that are directly and
- *! indirectly referenced from those things, and only from those
- *! things.
+ *! indirectly referenced from those things and not from anywhere
+ *! else.
  *!
  *! The memory counted is only that which is directly occupied by the
  *! things in question, including any overallocation for mappings,
@@ -4052,12 +4594,79 @@ void dump_gc_info(void)
  *! function returns the same size, there should be essentially no
  *! increase in the size of the pike process (some increase might
  *! occur due to internal fragmentation and memory pooling, but it
- *! should be small overall).
+ *! should be small in general and over time).
  *!
- *! @param flags
- *!   Reserved for future use to be able to pass various flags to
- *!   control the counting. Right now this argument should always be
- *!   zero.
+ *! The search for things only referenced from the arguments can
+ *! handle limited cyclic structures. That is done by doing a
+ *! "lookahead", i.e. searching through things that apparently have
+ *! other outside references. You can control how long this lookahead
+ *! should be through @[options] (see below). If the lookahead is too
+ *! short to cover the cycles in a structure then a too low value is
+ *! returned. If the lookahead is made gradually longer then the
+ *! returned value will eventually become accurate and not increase
+ *! anymore. If the lookahead is too long then unnecessary time might
+ *! be spent searching through things that really have external
+ *! references.
+ *!
+ *! @param options
+ *!   If this is an integer, it specifies the maximum lookahead
+ *!   distance. To e.g. cover cycles of length 2 (i.e. where two
+ *!   things point at each other), this has to be 3.
+ *!
+ *!   However, the lookahead is by default blocked by programs, i.e.
+ *!   it never follows references emanating from programs. That since
+ *!   programs seldom are part of dynamic data structures, and they
+ *!   also typically contain a lot of references to global data which
+ *!   would add a lot of work to the lookahead search.
+ *!
+ *!   To control the search in more detail, @[options] can be a
+ *!   mapping instead:
+ *!
+ *!   @mapping
+ *!     @member int lookahead
+ *!       The maximum lookahead distance.
+ *!     @member int block_arrays
+ *!     @member int block_mappings
+ *!     @member int block_multisets
+ *!     @member int block_objects
+ *!     @member int block_programs
+ *!       When any of these are given with a nonzero value, the
+ *!       corresponding type is blocked in when lookahead references
+ *!       are followed. They are unblocked if the flag is given with a
+ *!       zero value.
+ *!     @member int collect_stats
+ *!       If this is nonzero then the mapping is extended with more
+ *!       elements containing statistics from the search; see below.
+ *!   @endmapping
+ *!
+ *!   When the @expr{collect_stats@} flag is set, the mapping is
+ *!   extended with these elements:
+ *!
+ *!   @mapping
+ *!     @member int internal
+ *!       Number of things that were marked internal and hence memory
+ *!       counted. It includes the things given as arguments.
+ *!     @member int cyclic
+ *!       Number of things that were marked internal only after
+ *!       resolving cycles through the lookahead.
+ *!     @member int external
+ *!       Number of things that were visited through the lookahead but
+ *!       were found to be external.
+ *!     @member int visits
+ *!       Number of times things were visited in total.
+ *!     @member int rounds
+ *!       Number of search rounds. Whenever the lookahead discovers a
+ *!       cycle, another round has to be made to search for more
+ *!       internal things referenced from the cycle.
+ *!     @member int max_distance
+ *!       The maximum distance in the cyclic things that were
+ *!       eventually marked internal. This gives a measure on how much
+ *!       the lookahead can be lowered while still handling the same
+ *!       cycles.
+ *!     @member int size
+ *!       The memory occupied by the internal things. This is the same
+ *!       as the return value, but it's put here too for convenience.
+ *!   @endmapping
  *!
  *! @param things
  *!   One or more things to count memory size for. Only things passed
@@ -4080,41 +4689,80 @@ void dump_gc_info(void)
  *! @note
  *! It's possible that a string that is referenced still isn't
  *! counted, because strings are always shared in Pike and the same
- *! string might be used in some unrelated part of the program.
+ *! string might be in use in some unrelated part of the program.
+ *!
+ *! @note
+ *! Things (normally programs) that are blocked in the lookahead
+ *! search are still recursed and memory counted properly if they are
+ *! given as arguments or only got internal references.
  */
 void f_count_memory (INT32 args)
 {
+  struct mapping *opts = NULL;
+  unsigned count_internal, count_cyclic, count_visited;
+  unsigned count_visits, count_rounds, max_dist;
+
   if (args < 1)
     SIMPLE_TOO_FEW_ARGS_ERROR ("count_memory", 1);
-  if (Pike_sp[-args].type != T_INT || Pike_sp[-args].u.integer)
-    SIMPLE_ARG_TYPE_ERROR ("count_memory", 1, "zero");
 
-  if (Pike_in_gc)
-    /* FIXME: This can happen if the function gets called in
-     * GC_PASS_FREE..GC_PASS_DESTRUCT. The markers should be cleaned
-     * up before GC_PASS_FREE. */
-    Pike_error ("This function cannot work while the gc is running.\n");
+  mc_block_lookahead = mc_block_lookahead_default;
+
+  if (Pike_sp[-args].type == T_MAPPING) {
+    struct mapping *m = Pike_sp[-args].u.mapping;
+    struct pike_string *ind;
+    struct svalue *val;
+
+    MAKE_CONST_STRING (ind, "lookahead");
+    if ((val = low_mapping_string_lookup (m, ind))) {
+      if (val->type != T_INT || val->u.integer < 0)
+	SIMPLE_ARG_ERROR ("count_memory", 1,
+			  "\"lookahead\" is not a non-negative integer.");
+#if MAX_INT_TYPE > UINT_MAX
+      if (Pike_sp[-args].u.integer > UINT_MAX)
+	mc_lookahead = UINT_MAX;
+      else
+#endif
+	mc_lookahead = val->u.integer;
+    }
+
+#define CHECK_BLOCK_FLAG(NAME, TYPE_BIT) do {				\
+      MAKE_CONST_STRING (ind, NAME);					\
+      if ((val = low_mapping_string_lookup (m, ind))) {			\
+	if (UNSAFE_IS_ZERO (val))					\
+	  mc_block_lookahead &= ~TYPE_BIT;				\
+	else								\
+	  mc_block_lookahead |= TYPE_BIT;				\
+      }									\
+    } while (0)
+    CHECK_BLOCK_FLAG ("block_arrays", BIT_ARRAY);
+    CHECK_BLOCK_FLAG ("block_mappings", BIT_MAPPING);
+    CHECK_BLOCK_FLAG ("block_multisets", BIT_MULTISET);
+    CHECK_BLOCK_FLAG ("block_objects", BIT_OBJECT);
+    CHECK_BLOCK_FLAG ("block_programs", BIT_PROGRAM);
+
+    MAKE_CONST_STRING (ind, "collect_stats");
+    if ((val = low_mapping_string_lookup (m, ind)) && !UNSAFE_IS_ZERO (val))
+      opts = m;
+  }
 
-  /* Not calling init_gc here since we don't want a hash table big
-   * enough to handle a marker for every thing in memory. */
-  init_marker_hash();
+  else {
+    if (Pike_sp[-args].type != T_INT || Pike_sp[-args].u.integer < 0)
+      SIMPLE_ARG_TYPE_ERROR ("count_memory", 1, "int(0..)");
 
-  Pike_in_gc = GC_PASS_COUNT_MEMORY;
-  gc_counted_bytes = 0;
-#ifdef PIKE_DEBUG
-  checked = marked = 0;
+#if MAX_INT_TYPE > UINT_MAX
+    if (Pike_sp[-args].u.integer > UINT_MAX)
+      mc_lookahead = UINT_MAX;
+    else
 #endif
+      mc_lookahead = Pike_sp[-args].u.integer;
+  }
+
+  init_mc_marker_hash();
 
-  /* In GC_PASS_COUNT_MEMORY mode, gc_mark only returns true once for
-   * each thing which have all refs accounted for by earlier gc check
-   * calls (i.e. have GC_NOT_REFERENCED set). The
-   * gc_mark_*_as_referenced functions (whose names are highly
-   * confusing in this case) then add the size to gc_counted_bytes and
-   * do a gc check before continuing with the mark calls as usual.
-   *
-   * That way gc checking is used to count all internal references
-   * from each "unreferenced" thing. If they add up to the real
-   * refcount then the last gc mark call recurses. */
+  assert (!mc_pass);
+  assert (visit_ref == NULL);
+  assert (mc_work_list.wl_prev == &mc_work_list);
+  assert (mc_work_list.wl_next == &mc_work_list);
 
   {
     int i;
@@ -4124,85 +4772,212 @@ void f_count_memory (INT32 args)
       if (s->type == T_INT)
 	continue;
 
-      else if (s->type > MAX_REF_TYPE) {
-	Pike_in_gc = 0;
-	cleanup_markers();
+      else if (s->type > MAX_REF_TYPE)
 	SIMPLE_ARG_TYPE_ERROR (
 	  "count_memory", i + args + 1,
 	  "array|multiset|mapping|object|program|string|type|int");
-      }
 
       else {
-	struct marker *m;
-
 	if (s->type == T_FUNCTION) {
 	  struct svalue s2;
-	  if (!(s2.u.program = program_from_function (s))) {
-	    Pike_in_gc = 0;
-	    cleanup_markers();
+	  if (!(s2.u.program = program_from_function (s)))
 	    SIMPLE_ARG_TYPE_ERROR (
 	      "count_memory", i + args + 1,
 	      "array|multiset|mapping|object|program|string|type|int");
-	  }
 	  add_ref (s2.u.program);
 	  s2.type = T_PROGRAM;
 	  free_svalue (s);
 	  move_svalue (s, &s2);
 	}
 
-	m = get_marker (s->u.ptr);
-	if (!(m->flags & GC_MARKED)) {
-	  m->flags |= GC_NOT_REFERENCED; /* Make gc_mark return true for it. */
-
-	  switch (s->type) {
-	    case T_ARRAY:
-	      gc_mark_array_as_referenced (s->u.array); break;
-	    case T_MULTISET:
-	      gc_mark_multiset_as_referenced (s->u.multiset); break;
-	    case T_MAPPING:
-	      gc_mark_mapping_as_referenced (s->u.mapping); break;
-	    case T_PROGRAM:
-	      gc_mark_program_as_referenced (s->u.program); break;
-	    case T_OBJECT:
-	      gc_mark_object_as_referenced (s->u.object); break;
-	    case T_STRING:
-	      gc_mark_string_as_referenced (s->u.string); break;
-	    case T_TYPE:
-	      gc_mark_type_as_referenced (s->u.type); break;
-	  }
+	if (find_mc_marker (s->u.ptr)) {
+	  /* The user passed the same thing several times. Ignore it. */
+	}
+
+	else if (s->type == T_ARRAY &&
+		 (s->u.array == &empty_array ||
+		  s->u.array == &weak_empty_array)) {
+	  /* Special cases for statically allocated things. */
+	}
+
+	else {
+	  struct mc_marker *m =
+	    my_make_mc_marker (s->u.ptr, visit_fn_from_type[s->type], NULL);
+	  m->dist = 0;
+	  m->flags = MC_FLAG_INTERNAL;
+	  mc_wl_enqueue_last (m);
+	  MC_DEBUG_MSG (m, "enqueued starting point");
 	}
       }
     }
   }
 
-  gc_mark_run_queue();
+  assert (mc_incomplete.dl_prev == &mc_incomplete);
+  assert (mc_incomplete.dl_next == &mc_incomplete);
+  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_rounds = max_dist = 0;
+
+  do {
+    count_rounds++;
+
+#ifdef MEMORY_COUNT_DEBUG
+    fputs ("MC_PASS_LOOKAHEAD\n", stderr);
+#endif
+    mc_pass = MC_PASS_LOOKAHEAD;
+    visit_ref = pass_lookahead_visit_ref;
+
+    while ((mc_ref_from = mc_wl_dequeue())) {
+      int action;
+
+      if ((mc_ref_from->flags & (MC_FLAG_INTERNAL|MC_FLAG_MEMCOUNTED)) ==
+	  MC_FLAG_INTERNAL) {
+	action = VISIT_COUNT_BYTES; /* Memory count this. */
+	count_internal++;
+	MC_DEBUG_MSG (NULL, "enter with byte counting");
+      }
+      else {
+	action = VISIT_NORMAL;
+	MC_DEBUG_MSG (NULL, "enter");
+      }
+
+      count_visits++;
+      mc_ref_from->visit_fn (mc_ref_from->thing, action, mc_ref_from->extra);
+
+      if (!(mc_ref_from->flags & MC_FLAG_REFCOUNTED)) {
+	mc_ref_from->flags |= MC_FLAG_REFCOUNTED;
+	count_visited++;
+      }
+
+      if (mc_ref_from->flags & MC_FLAG_INTERNAL)
+	mc_ref_from->flags |= MC_FLAG_MEMCOUNTED;
+      MC_DEBUG_MSG (NULL, "leave");
+    }
+#if defined (PIKE_DEBUG) || defined (MEMORY_COUNT_DEBUG)
+    mc_ref_from = (void *) (ptrdiff_t) -1;
+#endif
+
+#ifdef MEMORY_COUNT_DEBUG
+    fputs ("MC_PASS_MARK_EXTERNAL\n", stderr);
+#endif
+    mc_pass = MC_PASS_MARK_EXTERNAL;
+    visit_ref = pass_mark_external_visit_ref;
+
+    assert (mc_indirect.dl_next == &mc_indirect);
+    assert (mc_indirect.dl_prev == &mc_indirect);
+
+    {
+      struct mc_marker *m;
+      for (m = mc_incomplete.dl_next; m != &mc_incomplete; m = m->dl_next) {
+	int type = type_from_visit_fn (m->visit_fn);
+	FLAG_EXTERNAL (m);
+	if (mc_block_lookahead & (1 << type))
+	  MC_DEBUG_MSG (m, "type blocked - not enqueued");
+	else {
+	  mc_wl_enqueue_last (m);
+	  MC_DEBUG_MSG (m, "enqueued external");
+	}
+      }
+
+      while ((m = mc_wl_dequeue())) {
+	MC_DEBUG_MSG (m, "visiting external");
+	count_visits++;
+	m->visit_fn (m->thing, VISIT_NORMAL, m->extra);
+      }
+    }
+
+    if (DL_IS_EMPTY (mc_complete)) break;
+
+    {
+      /* 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 {
+	DL_REMOVE (m);
+	if (m->dist > max_dist) max_dist = m->dist;
+	m->dist = 0;
+	m->flags |= MC_FLAG_INTERNAL;
+	mc_wl_enqueue_last (m);
+	count_cyclic++;
+	MC_DEBUG_MSG (m, "enqueued cyclic internal");
+	m = mc_complete.dl_next;
+      } while (m != &mc_complete);
+    }
+
+    DL_MOVE (mc_indirect, mc_complete);
+
+    TOGGLE_EXT_FLAGS();
+
+#ifdef PIKE_DEBUG
+    if (d_flag) {
+      struct mc_marker *m;
+      for (m = mc_incomplete.dl_next; m != &mc_incomplete; m = m->dl_next)
+	assert (!IS_EXTERNAL (m));
+      for (m = mc_complete.dl_next; m != &mc_complete; m = m->dl_next)
+	assert (!IS_EXTERNAL (m));
+    }
+#endif
+  } while (1);
+
+#ifdef MEMORY_COUNT_DEBUG
+  fputs ("memory counting done\n", stderr);
+#endif
 
 #if 0
+  fprintf (stderr, "count_memory stats: %u internal, %u cyclic, %u external\n"
+	   "count_memory stats: %u visits, %u rounds, %u max distance\n",
+	   count_internal, count_cyclic,
+	   count_visited - count_internal,
+	   count_visits, count_rounds, max_dist);
 #ifdef PIKE_DEBUG
   {
     size_t num, size;
-    count_memory_in_markers (&num, &size);
-    fprintf (stderr,
-	     "count_memory checked %u refs, visited %u things and "
-	     "used %"PRINTSIZET"u bytes for %"PRINTSIZET"u markers.\n",
-	     checked, marked, size, num);
+    count_memory_in_mc_markers (&num, &size);
+    fprintf (stderr, "count_memory used %"PRINTSIZET"u bytes "
+	     "for %"PRINTSIZET"u markers.\n", size, num);
   }
 #endif
 #endif
 
-  Pike_in_gc = 0;
-  cleanup_markers();
+  if (opts) {
+#define INSERT_STAT(NAME, VALUE) do {					\
+      struct pike_string *ind;						\
+      push_ulongest (VALUE);						\
+      MAKE_CONST_STRING (ind, NAME);					\
+      mapping_string_insert (opts, ind, Pike_sp - 1);			\
+      pop_stack();							\
+    } while (0)
+    INSERT_STAT ("internal", count_internal);
+    INSERT_STAT ("cyclic", count_cyclic);
+    INSERT_STAT ("external", count_visited - count_internal);
+    INSERT_STAT ("visits", count_visits);
+    INSERT_STAT ("rounds", count_rounds);
+    INSERT_STAT ("max_distance", max_dist);
+    INSERT_STAT ("size", mc_counted_bytes);
+  }
 
-  pop_n_elems (args);
-  push_ulongest (gc_counted_bytes);
-}
+  mc_pass = 0;
+  visit_ref = NULL;
 
-void cleanup_gc(void)
-{
-#ifdef PIKE_DEBUG
-  if (gc_evaluator_callback) {
-    remove_callback(gc_evaluator_callback);
-    gc_evaluator_callback = 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 /* PIKE_DEBUG */
+#endif
+  exit_mc_marker_hash();
+
+  pop_n_elems (args);
+  push_ulongest (mc_counted_bytes);
 }
diff --git a/src/gc.h b/src/gc.h
index d7e0a5357ee15371391111be6e98826522dc9bc4..b6f902914a742d02eb39807f5578860c00f2217b 100644
--- a/src/gc.h
+++ b/src/gc.h
@@ -2,7 +2,7 @@
 || 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.h,v 1.132 2008/05/06 19:19:10 mast Exp $
+|| $Id: gc.h,v 1.133 2008/05/11 02:31:57 mast Exp $
 */
 
 #ifndef GC_H
@@ -72,7 +72,6 @@ extern ALLOC_COUNT_TYPE num_allocs, alloc_threshold;
 PMOD_EXPORT extern int Pike_in_gc;
 extern int gc_generation;
 extern int gc_trace, gc_debug;
-PMOD_EXPORT extern size_t gc_counted_bytes;
 #ifdef CPU_TIME_MIGHT_NOT_BE_THREAD_LOCAL
 extern cpu_time_t auto_gc_time;
 #endif
@@ -284,7 +283,6 @@ extern size_t gc_ext_weak_refs;
 
 typedef void gc_cycle_check_cb (void *data, int weak);
 
-/* Prototypes begin here */
 struct gc_frame;
 struct callback *debug_add_gc_callback(callback_func call,
 				 void *arg,
@@ -478,9 +476,11 @@ static INLINE int debug_gc_check_weak (void *a, const char *place)
    gc_cycle_check_weak_short_svalue((U), (T)) : gc_mark_weak_short_svalue((U), (T)))
 
 #define GC_RECURSE_THING(V, T)						\
-  (DMALLOC_TOUCH_MARKER(V, Pike_in_gc == GC_PASS_CYCLE) ?		\
-   PIKE_CONCAT(gc_cycle_check_, T)(V, 0) :				\
-   PIKE_CONCAT3(gc_mark_, T, _as_referenced)(V))
+  (mc_pass ?								\
+   PIKE_CONCAT3 (visit_,T,_ref) (debug_malloc_pass (V), REF_TYPE_NORMAL) : \
+   (DMALLOC_TOUCH_MARKER(V, Pike_in_gc == GC_PASS_CYCLE) ?		\
+    PIKE_CONCAT(gc_cycle_check_, T)(V, 0) :				\
+    PIKE_CONCAT3(gc_mark_, T, _as_referenced)(V)))
 #define gc_recurse_array(V) GC_RECURSE_THING((V), array)
 #define gc_recurse_mapping(V) GC_RECURSE_THING((V), mapping)
 #define gc_recurse_multiset(V) GC_RECURSE_THING((V), multiset)
@@ -517,8 +517,7 @@ static INLINE int debug_gc_check_weak (void *a, const char *place)
 #define GC_PASS_DESTRUCT	500
 
 #define GC_PASS_LOCATE -1
-#define GC_PASS_COUNT_MEMORY -2
-#define GC_PASS_DISABLED -3
+#define GC_PASS_DISABLED -2
 
 #ifdef PIKE_DEBUG
 extern int gc_in_cycle_check;
@@ -568,4 +567,103 @@ extern int gc_in_cycle_check;
   }									\
 } while (0)
 
+/* Generic API to follow refs in arbitrary structures (experimental).
+ *
+ * The check/mark/cycle check callbacks in the gc might be converted
+ * to this. Global state is fair play by design; the intention is to
+ * use thread local storage when the time comes. */
+
+/* A visit_thing_fn is made for every type of refcounted block. An
+ * INT32 refcounter is assumed to be first in every block. The
+ * visit_thing_fn should call visit_ref exactly once for every
+ * refcounted ref inside src_thing, in a stable order. dst_thing is
+ * then the target of the ref, ref_type describes the type of the ref
+ * itself (REF_TYPE_*), visit_dst is the visit_thing_fn for the target
+ * block, and extra is an arbitrary value passed along to visit_dst.
+ *
+ * action identifies some action that the visit_thing_fn should take
+ * (VISIT_*). Also, visit_ref_cb is likely to get a return value that
+ * identifies an action that should be taken on the ref.
+ *
+ * visit_thing_fn's must be reentrant. visit_dst might be called
+ * immediately, queued and called later, or not called at all. */
+
+typedef void visit_thing_fn (void *src_thing, int action, void *extra);
+typedef void visit_ref_cb (void *dst_thing, int ref_type,
+			   visit_thing_fn *visit_dst, void *extra);
+PMOD_EXPORT extern visit_ref_cb *visit_ref;
+
+#define REF_TYPE_STRENGTH 0x03	/* Bits for normal/weak/strong. */
+#define REF_TYPE_NORMAL	0x00	/* Normal (nonweak and nonstrong) ref. */
+#define REF_TYPE_WEAK	0x01	/* Weak ref. */
+#define REF_TYPE_STRONG	0x02	/* Strong ref. Note restrictions above. */
+
+#define REF_TYPE_INTERNAL 0x04	/* "Internal" ref. */
+/* An "internal" ref is one that should be considered internal within
+ * a structure that is treated as one unit on the pike level. E.g. the
+ * refs from mappings and multisets to their data blocks are internal.
+ * In general, if the target block isn't one of the eight referenced
+ * pike types then the ref is internal. Internal refs must not make up
+ * a cycle. They don't count towards the lookahead distance in
+ * f_count_memory. */
+
+#define VISIT_NORMAL 0
+/* Alias for zero which indicates normal (no) action: Visit all refs
+ * to all (refcounted) blocks and do nothing else. */
+
+#define VISIT_COMPLEX_ONLY 0x01
+/* Bit flag. If this is set, only follow refs to blocks that might
+ * contain more refs. */
+
+#define VISIT_COUNT_BYTES 0x02
+/* Add the number of bytes allocated for the block to
+ * mc_counted_bytes, then visit the refs. Never combined with
+ * VISIT_COMPLEX_ONLY. */
+
+/* Map between type and visit function for the standard ref types. */
+PMOD_EXPORT extern visit_thing_fn *const visit_fn_from_type[MAX_REF_TYPE + 1];
+PMOD_EXPORT TYPE_T type_from_visit_fn (visit_thing_fn *fn);
+
+static INLINE void real_visit_short_svalue (const union anything *u, TYPE_T t,
+					    int ref_type)
+{
+  check_short_svalue (u, t);
+  if (t <= MAX_REF_TYPE)
+    visit_ref (u->ptr, ref_type, visit_fn_from_type[t], NULL);
+}
+#define visit_short_svalue(U, T, REF_TYPE) \
+  (real_visit_short_svalue (debug_malloc_pass ((U)->ptr), (T), (REF_TYPE)))
+
+#ifdef DEBUG_MALLOC
+static INLINE void dmalloc_visit_svalue (struct svalue *s,
+					 int ref_type, char *l)
+{
+  int t = s->type;
+  check_svalue (s);
+  dmalloc_check_svalue (s, l);
+  if (t <= MAX_REF_TYPE) {
+    if (t == PIKE_T_FUNCTION) visit_function (s, ref_type);
+    else visit_ref (s->u.ptr, ref_type, visit_fn_from_type[t], NULL);
+  }
+}
+#define visit_svalue(S, REF_TYPE) \
+  dmalloc_visit_svalue ((S), (REF_TYPE), DMALLOC_LOCATION())
+#else
+static INLINE void visit_svalue (struct svalue *s, int ref_type)
+{
+  int t = s->type;
+  check_svalue (s);
+  if (t <= MAX_REF_TYPE) {
+    if (t == PIKE_T_FUNCTION) visit_function (s, ref_type);
+    else visit_ref (s->u.ptr, ref_type, visit_fn_from_type[t], NULL);
+  }
+}
+#endif
+
+/* Memory counting */
+
+PMOD_EXPORT extern int mc_pass;
+PMOD_EXPORT extern size_t mc_counted_bytes;
+PMOD_EXPORT int mc_count_bytes (void *thing);
+
 #endif