예제 #1
0
DUK_INTERNAL duk_bool_t duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags) {
	duk_hthread *thr;
	duk_size_t count_keep_obj;
	duk_size_t count_keep_str;
#if defined(DUK_USE_VOLUNTARY_GC)
	duk_size_t tmp;
#endif

	/* XXX: thread selection for mark-and-sweep is currently a hack.
	 * If we don't have a thread, the entire mark-and-sweep is now
	 * skipped (although we could just skip finalizations).
	 */

	/* If thr != NULL, the thr may still be in the middle of
	 * initialization.
	 * XXX: Improve the thread viability test.
	 */
	thr = duk__get_temp_hthread(heap);
	if (thr == NULL) {
		DUK_D(DUK_DPRINT("gc skipped because we don't have a temp thread"));

		/* reset voluntary gc trigger count */
#if defined(DUK_USE_VOLUNTARY_GC)
		heap->mark_and_sweep_trigger_counter = DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP;
#endif
		return 0;  /* OK */
	}

	/* If debugger is paused, garbage collection is disabled by default. */
	/* XXX: will need a force flag if garbage collection is triggered
	 * explicitly during paused state.
	 */
#if defined(DUK_USE_DEBUGGER_SUPPORT)
	if (DUK_HEAP_IS_PAUSED(heap)) {
		/* Checking this here rather that in memory alloc primitives
		 * reduces checking code there but means a failed allocation
		 * will go through a few retries before giving up.  That's
		 * fine because this only happens during debugging.
		 */
		DUK_D(DUK_DPRINT("gc skipped because debugger is paused"));
		return 0;
	}
#endif

	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08lx, effective flags: 0x%08lx",
	                 (unsigned long) flags, (unsigned long) (flags | heap->mark_and_sweep_base_flags)));

	flags |= heap->mark_and_sweep_base_flags;

	/*
	 *  Assertions before
	 */

#if defined(DUK_USE_ASSERTIONS)
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
	duk__assert_heaphdr_flags(heap);
#if defined(DUK_USE_REFERENCE_COUNTING)
	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
	 * finalizer may trigger a mark-and-sweep.
	 */
	duk__assert_valid_refcounts(heap);
#endif  /* DUK_USE_REFERENCE_COUNTING */
#endif  /* DUK_USE_ASSERTIONS */

	/*
	 *  Begin
	 */

	DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap);

	/*
	 *  Mark roots, hoping that recursion limit is not normally hit.
	 *  If recursion limit is hit, run additional reachability rounds
	 *  starting from "temproots" until marking is complete.
	 *
	 *  Marking happens in two phases: first we mark actual reachability
	 *  roots (and run "temproots" to complete the process).  Then we
	 *  check which objects are unreachable and are finalizable; such
	 *  objects are marked as FINALIZABLE and marked as reachability
	 *  (and "temproots" is run again to complete the process).
	 *
	 *  The heap finalize_list must also be marked as a reachability root.
	 *  There may be objects on the list from a previous round if the
	 *  previous run had finalizer skip flag.
	 */

	duk__mark_roots_heap(heap);               /* main reachability roots */
#if defined(DUK_USE_REFERENCE_COUNTING)
	duk__mark_refzero_list(heap);             /* refzero_list treated as reachability roots */
#endif
	duk__mark_temproots_by_heap_scan(heap);   /* temproots */

#if defined(DUK_USE_FINALIZER_SUPPORT)
	duk__mark_finalizable(heap);              /* mark finalizable as reachability roots */
	duk__mark_finalize_list(heap);            /* mark finalizer work list as reachability roots */
#endif
	duk__mark_temproots_by_heap_scan(heap);   /* temproots */

	/*
	 *  Sweep garbage and remove marking flags, and move objects with
	 *  finalizers to the finalizer work list.
	 *
	 *  Objects to be swept need to get their refcounts finalized before
	 *  they are swept.  In other words, their target object refcounts
	 *  need to be decreased.  This has to be done before freeing any
	 *  objects to avoid decref'ing dangling pointers (which may happen
	 *  even without bugs, e.g. with reference loops)
	 *
	 *  Because strings don't point to other heap objects, similar
	 *  finalization is not necessary for strings.
	 */

	/* XXX: more emergency behavior, e.g. find smaller hash sizes etc */

#if defined(DUK_USE_REFERENCE_COUNTING)
	duk__finalize_refcounts(heap);
#endif
	duk__sweep_heap(heap, flags, &count_keep_obj);
	duk__sweep_stringtable(heap, &count_keep_str);
#if defined(DUK_USE_REFERENCE_COUNTING)
	duk__clear_refzero_list_flags(heap);
#endif
#if defined(DUK_USE_FINALIZER_SUPPORT)
	duk__clear_finalize_list_flags(heap);
#endif

	/*
	 *  Object compaction (emergency only).
	 *
	 *  Object compaction is a separate step after sweeping, as there is
	 *  more free memory for it to work with.  Also, currently compaction
	 *  may insert new objects into the heap allocated list and the string
	 *  table which we don't want to do during a sweep (the reachability
	 *  flags of such objects would be incorrect).  The objects inserted
	 *  are currently:
	 *
	 *    - a temporary duk_hbuffer for a new properties allocation
	 *    - if array part is abandoned, string keys are interned
	 *
	 *  The object insertions go to the front of the list, so they do not
	 *  cause an infinite loop (they are not compacted).
	 */

	if ((flags & DUK_MS_FLAG_EMERGENCY) &&
	    !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) {
		duk__compact_objects(heap);
	}

	/*
	 *  String table resize check.
	 *
	 *  This is mainly useful in emergency GC: if the string table load
	 *  factor is really low for some reason, we can shrink the string
	 *  table to a smaller size and free some memory in the process.
	 *  Only execute in emergency GC.  String table has internal flags
	 *  to protect against recursive resizing if this mark-and-sweep pass
	 *  was triggered by a string table resize.
	 */

	if (flags & DUK_MS_FLAG_EMERGENCY) {
		DUK_D(DUK_DPRINT("stringtable resize check in emergency gc"));
		duk_heap_strtable_force_resize(heap);
	}

	/*
	 *  Finalize objects in the finalization work list.  Finalized
	 *  objects are queued back to heap_allocated with FINALIZED set.
	 *
	 *  Since finalizers may cause arbitrary side effects, they are
	 *  prevented during string table and object property allocation
	 *  resizing using the DUK_MS_FLAG_NO_FINALIZERS flag in
	 *  heap->mark_and_sweep_base_flags.  In this case the objects
	 *  remain in the finalization work list after mark-and-sweep
	 *  exits and they may be finalized on the next pass.
	 *
	 *  Finalization currently happens inside "MARKANDSWEEP_RUNNING"
	 *  protection (no mark-and-sweep may be triggered by the
	 *  finalizers).  As a side effect:
	 *
	 *    1) an out-of-memory error inside a finalizer will not
	 *       cause a mark-and-sweep and may cause the finalizer
	 *       to fail unnecessarily
	 *
	 *    2) any temporary objects whose refcount decreases to zero
	 *       during finalization will not be put into refzero_list;
	 *       they can only be collected by another mark-and-sweep
	 *
	 *  This is not optimal, but since the sweep for this phase has
	 *  already happened, this is probably good enough for now.
	 */

#if defined(DUK_USE_FINALIZER_SUPPORT)
#if defined(DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE)
	/* Cannot simulate individual finalizers because finalize_list only
	 * contains objects with actual finalizers.  But simulate side effects
	 * from finalization by doing a bogus function call and resizing the
	 * stacks.
	 */
	if (flags & DUK_MS_FLAG_NO_FINALIZERS) {
		DUK_D(DUK_DPRINT("skip mark-and-sweep torture finalizer, DUK_MS_FLAG_NO_FINALIZERS is set"));
	} else if (!(thr->valstack != NULL && thr->callstack != NULL && thr->catchstack != NULL)) {
		DUK_D(DUK_DPRINT("skip mark-and-sweep torture finalizer, thread not yet viable"));
	} else {
		DUK_D(DUK_DPRINT("run mark-and-sweep torture finalizer"));
		duk__markandsweep_torture_finalizer(thr);
	}
#endif  /* DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE */

	if (flags & DUK_MS_FLAG_NO_FINALIZERS) {
		DUK_D(DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set"));
	} else {
		duk__run_object_finalizers(heap, flags);
	}
#endif  /* DUK_USE_FINALIZER_SUPPORT */

	/*
	 *  Finish
	 */

	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);

	/*
	 *  Assertions after
	 */

#if defined(DUK_USE_ASSERTIONS)
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
	duk__assert_heaphdr_flags(heap);
#if defined(DUK_USE_REFERENCE_COUNTING)
	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
	 * finalizer may trigger a mark-and-sweep.
	 */
	duk__assert_valid_refcounts(heap);
#endif  /* DUK_USE_REFERENCE_COUNTING */
#endif  /* DUK_USE_ASSERTIONS */

	/*
	 *  Reset trigger counter
	 */

#if defined(DUK_USE_VOLUNTARY_GC)
	tmp = (count_keep_obj + count_keep_str) / 256;
	heap->mark_and_sweep_trigger_counter = (duk_int_t) (
	    (tmp * DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT) +
	    DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD);
	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, trigger reset to %ld",
	                 (long) count_keep_obj, (long) count_keep_str, (long) heap->mark_and_sweep_trigger_counter));
#else
	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %ld objects kept, %ld strings kept, no voluntary trigger",
	                 (long) count_keep_obj, (long) count_keep_str));
#endif

	return 0;  /* OK */
}
예제 #2
0
int duk_heap_mark_and_sweep(duk_heap *heap, int flags) {
	duk_size_t count_keep_obj;
	duk_size_t count_keep_str;
	duk_size_t tmp;

	/* FIXME: thread selection for mark-and-sweep is currently a hack.
	 * If we don't have a thread, the entire mark-and-sweep is now
	 * skipped (although we could just skip finalizations).
	 */
	if (duk__get_temp_hthread(heap) == NULL) {
		DUK_D(DUK_DPRINT("temporary hack: gc skipped because we don't have a temp thread"));

		/* reset voluntary gc trigger count */
#ifdef DUK_USE_VOLUNTARY_GC
		heap->mark_and_sweep_trigger_counter = DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP;
#endif
		return 0;  /* OK */
	}

	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08x, effective flags: 0x%08x",
	                 flags, flags | heap->mark_and_sweep_base_flags));

	flags |= heap->mark_and_sweep_base_flags;

	/*
	 *  Assertions before
	 */

#ifdef DUK_USE_ASSERTIONS
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
	duk__assert_heaphdr_flags(heap);
#ifdef DUK_USE_REFERENCE_COUNTING
	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
	 * finalizer may trigger a mark-and-sweep.
	 */
	duk__assert_valid_refcounts(heap);
#endif  /* DUK_USE_REFERENCE_COUNTING */
#endif  /* DUK_USE_ASSERTIONS */

	/*
	 *  Begin
	 */

	DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap);

	/*
	 *  Mark roots, hoping that recursion limit is not normally hit.
	 *  If recursion limit is hit, run additional reachability rounds
	 *  starting from "temproots" until marking is complete.
	 *
	 *  Marking happens in two phases: first we mark actual reachability
	 *  roots (and run "temproots" to complete the process).  Then we
	 *  check which objects are unreachable and are finalizable; such
	 *  objects are marked as FINALIZABLE and marked as reachability
	 *  (and "temproots" is run again to complete the process).
	 */

	duk__mark_roots_heap(heap);               /* main reachability roots */
#ifdef DUK_USE_REFERENCE_COUNTING
	duk__mark_refzero_list(heap);             /* refzero_list treated as reachability roots */
#endif
	duk__mark_temproots_by_heap_scan(heap);   /* temproots */

	duk__mark_finalizable(heap);              /* mark finalizable as reachability roots */
	duk__mark_temproots_by_heap_scan(heap);   /* temproots */

	/*
	 *  Sweep garbage and remove marking flags, and move objects with
	 *  finalizers to the finalizer work list.
	 *
	 *  Objects to be swept need to get their refcounts finalized before
	 *  they are swept.  In other words, their target object refcounts
	 *  need to be decreased.  This has to be done before freeing any
	 *  objects to avoid decref'ing dangling pointers (which may happen
	 *  even without bugs, e.g. with reference loops)
	 *
	 *  Because strings don't point to other heap objects, similar
	 *  finalization is not necessary for strings.
	 */

	/* XXX: more emergency behavior, e.g. find smaller hash sizes etc */

#ifdef DUK_USE_REFERENCE_COUNTING
	duk__finalize_refcounts(heap);
#endif
	duk__sweep_heap(heap, flags, &count_keep_obj);
	duk__sweep_stringtable(heap, &count_keep_str);
#ifdef DUK_USE_REFERENCE_COUNTING
	duk__clear_refzero_list_flags(heap);
#endif

	/*
	 *  Object compaction (emergency only).
	 *
	 *  Object compaction is a separate step after sweeping, as there is
	 *  more free memory for it to work with.  Also, currently compaction
	 *  may insert new objects into the heap allocated list and the string
	 *  table which we don't want to do during a sweep (the reachability
	 *  flags of such objects would be incorrect).  The objects inserted
	 *  are currently:
	 *
	 *    - a temporary duk_hbuffer for a new properties allocation
	 *    - if array part is abandoned, string keys are interned
	 *
	 *  The object insertions go to the front of the list, so they do not
	 *  cause an infinite loop (they are not compacted).
	 */

	if ((flags & DUK_MS_FLAG_EMERGENCY) &&
	    !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) {
		duk__compact_objects(heap);
	}

	/*
	 *  String table resize check.
	 *
	 *  Note: this may silently (and safely) fail if GC is caused by an
	 *  allocation call in stringtable resize_hash().  Resize_hash()
	 *  will prevent a recursive call to itself by setting the
	 *  DUK_MS_FLAG_NO_STRINGTABLE_RESIZE in heap->mark_and_sweep_base_flags.
	 */

	/* XXX: stringtable emergency compaction? */

#if defined(DUK_USE_MS_STRINGTABLE_RESIZE)
	if (!(flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE)) {
		DUK_DD(DUK_DDPRINT("resize stringtable: %p", (void *) heap));
		duk_heap_force_stringtable_resize(heap);
	} else {
		DUK_D(DUK_DPRINT("stringtable resize skipped because DUK_MS_FLAG_NO_STRINGTABLE_RESIZE is set"));
	}
#endif

	/*
	 *  Finalize objects in the finalization work list.  Finalized
	 *  objects are queued back to heap_allocated with FINALIZED set.
	 *
	 *  Since finalizers may cause arbitrary side effects, they are
	 *  prevented during string table and object property allocation
	 *  resizing using the DUK_MS_FLAG_NO_FINALIZERS flag in
	 *  heap->mark_and_sweep_base_flags.  In this case the objects
	 *  remain in the finalization work list after mark-and-sweep
	 *  exits and they may be finalized on the next pass.
	 *
	 *  Finalization currently happens inside "MARKANDSWEEP_RUNNING"
	 *  protection (no mark-and-sweep may be triggered by the
	 *  finalizers).  As a side effect:
	 *
	 *    1) an out-of-memory error inside a finalizer will not
	 *       cause a mark-and-sweep and may cause the finalizer
	 *       to fail unnecessarily
	 *
	 *    2) any temporary objects whose refcount decreases to zero
	 *       during finalization will not be put into refzero_list;
	 *       they can only be collected by another mark-and-sweep
	 *
	 *  This is not optimal, but since the sweep for this phase has
	 *  already happened, this is probably good enough for now.
	 */

	if (!(flags & DUK_MS_FLAG_NO_FINALIZERS)) {
		duk__run_object_finalizers(heap);
	} else {
		DUK_D(DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set"));
	}

	/*
	 *  Finish
	 */

	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);

	/*
	 *  Assertions after
	 */

#ifdef DUK_USE_ASSERTIONS
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap));
	DUK_ASSERT(heap->mark_and_sweep_recursion_depth == 0);
	duk__assert_heaphdr_flags(heap);
#ifdef DUK_USE_REFERENCE_COUNTING
	/* Note: DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap) may be true; a refcount
	 * finalizer may trigger a mark-and-sweep.
	 */
	duk__assert_valid_refcounts(heap);
#endif  /* DUK_USE_REFERENCE_COUNTING */
#endif  /* DUK_USE_ASSERTIONS */

	/*
	 *  Reset trigger counter
	 */

#ifdef DUK_USE_VOLUNTARY_GC
	tmp = (count_keep_obj + count_keep_str) / 256;
	heap->mark_and_sweep_trigger_counter =
	    (tmp * DUK_HEAP_MARK_AND_SWEEP_TRIGGER_MULT) +
	    DUK_HEAP_MARK_AND_SWEEP_TRIGGER_ADD;
	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %d objects kept, %d strings kept, trigger reset to %d",
	                 (int) count_keep_obj, (int) count_keep_str, (int) heap->mark_and_sweep_trigger_counter));
#else
	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %d objects kept, %d strings kept, no voluntary trigger",
	                 (int) count_keep_obj, (int) count_keep_str));
#endif
	return 0;  /* OK */
}