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 */ }
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 */ }