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 */ }
DUK_INTERNAL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h) { duk_heap *heap; DUK_ASSERT(thr != NULL); DUK_ASSERT(h != NULL); heap = thr->heap; DUK_DDD(DUK_DDDPRINT("refzero %p: %!O", (void *) h, (duk_heaphdr *) h)); /* * Refzero handling is skipped entirely if (1) mark-and-sweep is * running or (2) execution is paused in the debugger. The objects * are left in the heap, and will be freed by mark-and-sweep or * eventual heap destruction. * * This is necessary during mark-and-sweep because refcounts are also * updated during the sweep phase (otherwise objects referenced by a * swept object would have incorrect refcounts) which then calls here. * This could be avoided by using separate decref macros in * mark-and-sweep; however, mark-and-sweep also calls finalizers which * would use the ordinary decref macros anyway and still call this * function. * * This check must be enabled also when mark-and-sweep support has been * disabled: the flag is also used in heap destruction when running * finalizers for remaining objects, and the flag prevents objects from * being moved around in heap linked lists. */ /* XXX: ideally this would be just one flag (maybe a derived one) so * that a single bit test is sufficient to check the condition. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) if (DUK_UNLIKELY(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap) || DUK_HEAP_IS_PAUSED(heap))) { #else if (DUK_UNLIKELY(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap))) { #endif DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h)); return; } switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: /* * Strings have no internal references but do have "weak" * references in the string cache. Also note that strings * are not on the heap_allocated list like other heap * elements. */ duk_heap_strcache_string_remove(heap, (duk_hstring *) h); duk_heap_string_remove(heap, (duk_hstring *) h); duk_heap_free_heaphdr_raw(heap, h); break; case DUK_HTYPE_OBJECT: /* * Objects have internal references. Must finalize through * the "refzero" work list. */ duk_heap_remove_any_from_heap_allocated(heap, h); duk__queue_refzero(heap, h); duk__refzero_free_pending(thr); break; case DUK_HTYPE_BUFFER: /* * Buffers have no internal references. However, a dynamic * buffer has a separate allocation for the buffer. This is * freed by duk_heap_free_heaphdr_raw(). */ duk_heap_remove_any_from_heap_allocated(heap, h); duk_heap_free_heaphdr_raw(heap, h); break; default: DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h))); DUK_UNREACHABLE(); } } #if !defined(DUK_USE_FAST_REFCOUNT_DEFAULT) DUK_INTERNAL void duk_tval_incref(duk_tval *tv) { DUK_ASSERT(tv != NULL); if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv)) { duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h)); DUK_ASSERT_DISABLE(h->h_refcount >= 0); DUK_HEAPHDR_PREINC_REFCOUNT(h); } }