/* * Mark-and-sweep garbage collection. */ #include "duk_internal.h" DUK_LOCAL_DECL void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h); DUK_LOCAL_DECL void duk__mark_heaphdr_nonnull(duk_heap *heap, duk_heaphdr *h); DUK_LOCAL_DECL void duk__mark_tval(duk_heap *heap, duk_tval *tv); DUK_LOCAL_DECL void duk__mark_tvals(duk_heap *heap, duk_tval *tv, duk_idx_t count); /* * Marking functions for heap types: mark children recursively. */ DUK_LOCAL void duk__mark_hstring(duk_heap *heap, duk_hstring *h) { DUK_UNREF(heap); DUK_UNREF(h); DUK_DDD(DUK_DDDPRINT("duk__mark_hstring: %p", (void *) h)); DUK_ASSERT(h); DUK_HSTRING_ASSERT_VALID(h); /* nothing to process */ } DUK_LOCAL void duk__mark_hobject(duk_heap *heap, duk_hobject *h) { duk_uint_fast32_t i; DUK_DDD(DUK_DDDPRINT("duk__mark_hobject: %p", (void *) h)); DUK_ASSERT(h); DUK_HOBJECT_ASSERT_VALID(h); /* XXX: use advancing pointers instead of index macros -> faster and smaller? */ for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) { duk_hstring *key = DUK_HOBJECT_E_GET_KEY(heap, h, i); if (key == NULL) { continue; } duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) key); if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, h, i)) { duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.get); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->a.set); } else { duk__mark_tval(heap, &DUK_HOBJECT_E_GET_VALUE_PTR(heap, h, i)->v); } } for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(h); i++) { duk__mark_tval(heap, DUK_HOBJECT_A_GET_VALUE_PTR(heap, h, i)); } /* Hash part is a 'weak reference' and does not contribute. */ duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HOBJECT_GET_PROTOTYPE(heap, h)); /* Fast path for objects which don't have a subclass struct, or have a * subclass struct but nothing that needs marking in the subclass struct. */ if (DUK_HOBJECT_HAS_FASTREFS(h)) { DUK_ASSERT(DUK_HOBJECT_ALLOWS_FASTREFS(h)); return; } DUK_ASSERT(DUK_HOBJECT_PROHIBITS_FASTREFS(h)); /* XXX: reorg, more common first */ if (DUK_HOBJECT_IS_COMPFUNC(h)) { duk_hcompfunc *f = (duk_hcompfunc *) h; duk_tval *tv, *tv_end; duk_hobject **fn, **fn_end; DUK_HCOMPFUNC_ASSERT_VALID(f); /* 'data' is reachable through every compiled function which * contains a reference. */ duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_DATA(heap, f)); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_LEXENV(heap, f)); duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_HCOMPFUNC_GET_VARENV(heap, f)); if (DUK_HCOMPFUNC_GET_DATA(heap, f) != NULL) { tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(heap, f); tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(heap, f); while (tv < tv_end) { duk__mark_tval(heap, tv); tv++; } fn = DUK_HCOMPFUNC_GET_FUNCS_BASE(heap, f); fn_end = DUK_HCOMPFUNC_GET_FUNCS_END(heap, f); while (fn < fn_end) { duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) *fn); fn++; } } else { /* May happen in some out-of-memory corner cases. */ DUK_D(DUK_DPRINT("duk_hcompfunc 'data' is NULL, skipping marking")); } } else if (DUK_HOBJECT_IS_DECENV(h)) { duk_hdecenv *e = (duk_hdecenv *) h; DUK_HDECENV_ASSERT_VALID(e); duk__mark_heaphdr(heap, (duk_heaphdr *) e->thread); duk__mark_heaphdr(heap, (duk_heaphdr *) e->varmap); } else if (DUK_HOBJECT_IS_OBJENV(h)) { duk_hobjenv *e = (duk_hobjenv *) h; DUK_HOBJENV_ASSERT_VALID(e); duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) e->target); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (DUK_HOBJECT_IS_BUFOBJ(h)) { duk_hbufobj *b = (duk_hbufobj *) h; DUK_HBUFOBJ_ASSERT_VALID(b); duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf); duk__mark_heaphdr(heap, (duk_heaphdr *) b->buf_prop); #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ } else if (DUK_HOBJECT_IS_BOUNDFUNC(h)) { duk_hboundfunc *f = (duk_hboundfunc *) (void *) h; DUK_HBOUNDFUNC_ASSERT_VALID(f); duk__mark_tval(heap, &f->target); duk__mark_tval(heap, &f->this_binding); duk__mark_tvals(heap, f->args, f->nargs); #if defined(DUK_USE_ES6_PROXY) } else if (DUK_HOBJECT_IS_PROXY(h)) { duk_hproxy *p = (duk_hproxy *) h; DUK_HPROXY_ASSERT_VALID(p); duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) p->target); duk__mark_heaphdr_nonnull(heap, (duk_heaphdr *) p->handler); #endif /* DUK_USE_ES6_PROXY */ } else if (DUK_HOBJECT_IS_THREAD(h)) { duk_hthread *t = (duk_hthread *) h; duk_activation *act; duk_tval *tv; DUK_HTHREAD_ASSERT_VALID(t); tv = t->valstack; while (tv < t->valstack_top) { duk__mark_tval(heap, tv); tv++; } for (act = t->callstack_curr; act != NULL; act = act->parent) { duk__mark_heaphdr(heap, (duk_heaphdr *) DUK_ACT_GET_FUNC(act)); duk__mark_heaphdr(heap, (duk_heaphdr *) act->var_env); duk__mark_heaphdr(heap, (duk_heaphdr *) act->lex_env); #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) duk__mark_heaphdr(heap, (duk_heaphdr *) act->prev_caller); #endif #if 0 /* nothing now */ for (cat = act->cat; cat != NULL; cat = cat->parent) { } #endif } duk__mark_heaphdr(heap, (duk_heaphdr *) t->resumer); for (i = 0; i < DUK_NUM_BUILTINS; i++) { duk__mark_heaphdr(heap, (duk_heaphdr *) t->builtins[i]); } } else { /* We may come here if the object should have a FASTREFS flag * but it's missing for some reason. Assert for never getting * here; however, other than performance, this is harmless. */ DUK_D(DUK_DPRINT("missing FASTREFS flag for: %!iO", h)); DUK_ASSERT(0); } } /* Mark any duk_heaphdr type. Recursion tracking happens only here. */ DUK_LOCAL void duk__mark_heaphdr(duk_heap *heap, duk_heaphdr *h) { DUK_DDD(DUK_DDDPRINT("duk__mark_heaphdr %p, type %ld", (void *) h, (h != NULL ? (long) DUK_HEAPHDR_GET_TYPE(h) : (long) -1))); /* XXX: add non-null variant? */ if (h == NULL) { return; } DUK_HEAPHDR_ASSERT_VALID(h); DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(h) || DUK_HEAPHDR_HAS_REACHABLE(h)); #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) if (!DUK_HEAPHDR_HAS_READONLY(h)) { h->h_assert_refcount++; /* Comparison refcount: bump even if already reachable. */ } #endif if (DUK_HEAPHDR_HAS_REACHABLE(h)) { DUK_DDD(DUK_DDDPRINT("already marked reachable, skip")); return; } #if defined(DUK_USE_ROM_OBJECTS) /* READONLY objects always have REACHABLE set, so the check above * will prevent READONLY objects from being marked here. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(h)); #endif DUK_HEAPHDR_SET_REACHABLE(h); if (heap->ms_recursion_depth >= DUK_USE_MARK_AND_SWEEP_RECLIMIT) { DUK_D(DUK_DPRINT("mark-and-sweep recursion limit reached, marking as temproot: %p", (void *) h)); DUK_HEAP_SET_MARKANDSWEEP_RECLIMIT_REACHED(heap); DUK_HEAPHDR_SET_TEMPROOT(h); return; } heap->ms_recursion_depth++; DUK_ASSERT(heap->ms_recursion_depth != 0); /* Wrap. */ switch (DUK_HEAPHDR_GET_TYPE(h)) { case DUK_HTYPE_STRING: duk__mark_hstring(heap, (duk_hstring *) h); break; case DUK_HTYPE_OBJECT: duk__mark_hobject(heap, (duk_hobject *) h); break; case DUK_HTYPE_BUFFER: /* nothing to mark */ break; default: DUK_D(DUK_DPRINT("attempt to mark heaphdr %p with invalid htype %ld", (void *) h, (long) DUK_HEAPHDR_GET_TYPE(h))); DUK_UNREACHABLE(); } DUK_ASSERT(heap->ms_recursion_depth > 0); heap->ms_recursion_depth--; } DUK_LOCAL void duk__mark_tval(duk_heap *heap, duk_tval *tv) { DUK_DDD(DUK_DDDPRINT("duk__mark_tval %p", (void *) tv)); if (tv == NULL) { return; } DUK_TVAL_ASSERT_VALID(tv); if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { duk_heaphdr *h; h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); duk__mark_heaphdr_nonnull(heap, h); } } DUK_LOCAL void duk__mark_tvals(duk_heap *heap, duk_tval *tv, duk_idx_t count) { DUK_ASSERT(count == 0 || tv != NULL); while (count-- > 0) { DUK_TVAL_ASSERT_VALID(tv); if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) { duk_heaphdr *h; h = DUK_TVAL_GET_HEAPHDR(tv); DUK_ASSERT(h != NULL); duk__mark_heaphdr_nonnull(heap, h); } tv++; } } /* Mark any duk_heaphdr type, caller guarantees a non-NULL pointer. */ DUK_LOCAL void duk__mark_heaphdr_nonnull(duk_heap *heap, duk_heaphdr *h) { /* For now, just call the generic handler. Change when call sites * are changed too. */ duk__mark_heaphdr(heap, h); } /* * Mark the heap. */ DUK_LOCAL void duk__mark_roots_heap(duk_heap *heap) { duk_small_uint_t i; DUK_DD(DUK_DDPRINT("duk__mark_roots_heap: %p", (void *) heap)); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_thread); duk__mark_heaphdr(heap, (duk_heaphdr *) heap->heap_object); for (i = 0; i < DUK_HEAP_NUM_STRINGS; i++) { duk_hstring *h = DUK_HEAP_GET_STRING(heap, i); duk__mark_heaphdr(heap, (duk_heaphdr *) h); } duk__mark_tval(heap, &heap->lj.value1); duk__mark_tval(heap, &heap->lj.value2); #if defined(DUK_USE_DEBUGGER_SUPPORT) for (i = 0; i < heap->dbg_breakpoint_count; i++) { duk__mark_heaphdr(heap, (duk_heaphdr *) heap->dbg_breakpoints[i].filename); } #endif } /* * Mark unreachable, finalizable objects. * * Such objects will be moved aside and their finalizers run later. They * have to be treated as reachability roots for their properties etc to * remain allocated. This marking is only done for unreachable values which * would be swept later. * * Objects are first marked FINALIZABLE and only then marked as reachability * roots; otherwise circular references might be handled inconsistently. */ #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__mark_finalizable(duk_heap *heap) { duk_heaphdr *hdr; duk_size_t count_finalizable = 0; DUK_DD(DUK_DDPRINT("duk__mark_finalizable: %p", (void *) heap)); DUK_ASSERT(heap->heap_thread != NULL); hdr = heap->heap_allocated; while (hdr != NULL) { /* A finalizer is looked up from the object and up its * prototype chain (which allows inherited finalizers). * The finalizer is checked for using a duk_hobject flag * which is kept in sync with the presence and callability * of a _Finalizer hidden symbol. */ if (!DUK_HEAPHDR_HAS_REACHABLE(hdr) && DUK_HEAPHDR_IS_OBJECT(hdr) && !DUK_HEAPHDR_HAS_FINALIZED(hdr) && DUK_HOBJECT_HAS_FINALIZER_FAST(heap, (duk_hobject *) hdr)) { /* heaphdr: * - is not reachable * - is an object * - is not a finalized object waiting for rescue/keep decision * - has a finalizer */ DUK_DD(DUK_DDPRINT("unreachable heap object will be " "finalized -> mark as finalizable " "and treat as a reachability root: %p", (void *) hdr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(hdr)); DUK_HEAPHDR_SET_FINALIZABLE(hdr); count_finalizable++; } hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } if (count_finalizable == 0) { return; } DUK_DD(DUK_DDPRINT("marked %ld heap objects as finalizable, now mark them reachable", (long) count_finalizable)); hdr = heap->heap_allocated; while (hdr != NULL) { if (DUK_HEAPHDR_HAS_FINALIZABLE(hdr)) { duk__mark_heaphdr_nonnull(heap, hdr); } hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } /* Caller will finish the marking process if we hit a recursion limit. */ } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Mark objects on finalize_list. */ #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__mark_finalize_list(duk_heap *heap) { duk_heaphdr *hdr; #if defined(DUK_USE_DEBUG) duk_size_t count_finalize_list = 0; #endif DUK_DD(DUK_DDPRINT("duk__mark_finalize_list: %p", (void *) heap)); hdr = heap->finalize_list; while (hdr != NULL) { duk__mark_heaphdr_nonnull(heap, hdr); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); #if defined(DUK_USE_DEBUG) count_finalize_list++; #endif } #if defined(DUK_USE_DEBUG) if (count_finalize_list > 0) { DUK_D(DUK_DPRINT("marked %ld objects on the finalize_list as reachable (previous finalizer run skipped)", (long) count_finalize_list)); } #endif } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Fallback marking handler if recursion limit is reached. * * Iterates 'temproots' until recursion limit is no longer hit. Temproots * can be in heap_allocated or finalize_list; refzero_list is now always * empty for mark-and-sweep. A temproot may occur in finalize_list now if * there are objects on the finalize_list and user code creates a reference * from an object in heap_allocated to the object in finalize_list (which is * now allowed), and it happened to coincide with the recursion depth limit. * * This is a slow scan, but guarantees that we finish with a bounded C stack. * * Note that nodes may have been marked as temproots before this scan begun, * OR they may have been marked during the scan (as we process nodes * recursively also during the scan). This is intended behavior. */ #if defined(DUK_USE_DEBUG) DUK_LOCAL void duk__handle_temproot(duk_heap *heap, duk_heaphdr *hdr, duk_size_t *count) { #else DUK_LOCAL void duk__handle_temproot(duk_heap *heap, duk_heaphdr *hdr) { #endif DUK_ASSERT(hdr != NULL); if (!DUK_HEAPHDR_HAS_TEMPROOT(hdr)) { DUK_DDD(DUK_DDDPRINT("not a temp root: %p", (void *) hdr)); return; } DUK_DDD(DUK_DDDPRINT("found a temp root: %p", (void *) hdr)); DUK_HEAPHDR_CLEAR_TEMPROOT(hdr); DUK_HEAPHDR_CLEAR_REACHABLE(hdr); /* Done so that duk__mark_heaphdr() works correctly. */ #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) hdr->h_assert_refcount--; /* Same node visited twice. */ #endif duk__mark_heaphdr_nonnull(heap, hdr); #if defined(DUK_USE_DEBUG) (*count)++; #endif } DUK_LOCAL void duk__mark_temproots_by_heap_scan(duk_heap *heap) { duk_heaphdr *hdr; #if defined(DUK_USE_DEBUG) duk_size_t count; #endif DUK_DD(DUK_DDPRINT("duk__mark_temproots_by_heap_scan: %p", (void *) heap)); while (DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)) { DUK_DD(DUK_DDPRINT("recursion limit reached, doing heap scan to continue from temproots")); #if defined(DUK_USE_DEBUG) count = 0; #endif DUK_HEAP_CLEAR_MARKANDSWEEP_RECLIMIT_REACHED(heap); hdr = heap->heap_allocated; while (hdr) { #if defined(DUK_USE_DEBUG) duk__handle_temproot(heap, hdr, &count); #else duk__handle_temproot(heap, hdr); #endif hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } #if defined(DUK_USE_FINALIZER_SUPPORT) hdr = heap->finalize_list; while (hdr) { #if defined(DUK_USE_DEBUG) duk__handle_temproot(heap, hdr, &count); #else duk__handle_temproot(heap, hdr); #endif hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } #endif #if defined(DUK_USE_DEBUG) DUK_DD(DUK_DDPRINT("temproot mark heap scan processed %ld temp roots", (long) count)); #endif } } /* * Finalize refcounts for heap elements just about to be freed. * This must be done for all objects before freeing to avoid any * stale pointer dereferences. * * Note that this must deduce the set of objects to be freed * identically to duk__sweep_heap(). */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_LOCAL void duk__finalize_refcounts(duk_heap *heap) { duk_heaphdr *hdr; DUK_ASSERT(heap->heap_thread != NULL); DUK_DD(DUK_DDPRINT("duk__finalize_refcounts: heap=%p", (void *) heap)); hdr = heap->heap_allocated; while (hdr) { if (!DUK_HEAPHDR_HAS_REACHABLE(hdr)) { /* * Unreachable object about to be swept. Finalize target refcounts * (objects which the unreachable object points to) without doing * refzero processing. Recursive decrefs are also prevented when * refzero processing is disabled. * * Value cannot be a finalizable object, as they have been made * temporarily reachable for this round. */ DUK_DDD(DUK_DDDPRINT("unreachable object, refcount finalize before sweeping: %p", (void *) hdr)); /* Finalize using heap->heap_thread; DECREF has a * suppress check for mark-and-sweep which is based * on heap->ms_running. */ duk_heaphdr_refcount_finalize_norz(heap, hdr); } hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } } #endif /* DUK_USE_REFERENCE_COUNTING */ /* * Clear (reachable) flags of finalize_list. * * We could mostly do in the sweep phase when we move objects from the * heap into the finalize_list. However, if a finalizer run is skipped * during a mark-and-sweep, the objects on the finalize_list will be marked * reachable during the next mark-and-sweep. Since they're already on the * finalize_list, no-one will be clearing their REACHABLE flag so we do it * here. (This now overlaps with the sweep handling in a harmless way.) */ #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_LOCAL void duk__clear_finalize_list_flags(duk_heap *heap) { duk_heaphdr *hdr; DUK_DD(DUK_DDPRINT("duk__clear_finalize_list_flags: %p", (void *) heap)); hdr = heap->finalize_list; while (hdr) { DUK_HEAPHDR_CLEAR_REACHABLE(hdr); #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZABLE(hdr) || \ (heap->currently_finalizing == hdr)); #endif /* DUK_HEAPHDR_FLAG_FINALIZED may be set. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr)); hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr); } } #endif /* DUK_USE_FINALIZER_SUPPORT */ /* * Sweep stringtable. */ DUK_LOCAL void duk__sweep_stringtable(duk_heap *heap, duk_size_t *out_count_keep) { duk_hstring *h; duk_hstring *prev; duk_uint32_t i; #if defined(DUK_USE_DEBUG) duk_size_t count_free = 0; #endif duk_size_t count_keep = 0; DUK_DD(DUK_DDPRINT("duk__sweep_stringtable: %p", (void *) heap)); #if defined(DUK_USE_STRTAB_PTRCOMP) if (heap->strtable16 == NULL) { #else if (heap->strtable == NULL) { #endif goto done; } for (i = 0; i < heap->st_size; i++) { #if defined(DUK_USE_STRTAB_PTRCOMP) h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]); #else h = heap->strtable[i]; #endif prev = NULL; while (h != NULL) { duk_hstring *next; next = h->hdr.h_next; if (DUK_HEAPHDR_HAS_REACHABLE((duk_heaphdr *) h)) { DUK_HEAPHDR_CLEAR_REACHABLE((duk_heaphdr *) h); count_keep++; prev = h; } else { #if defined(DUK_USE_DEBUG) count_free++; #endif /* For pinned strings the refcount has been * bumped. We could unbump it here before * freeing, but that's actually not necessary * except for assertions. */ #if 0 if (DUK_HSTRING_HAS_PINNED_LITERAL(h)) { DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) > 0U); DUK_HSTRING_DECREF_NORZ(heap->heap_thread, h); DUK_HSTRING_CLEAR_PINNED_LITERAL(h); } #endif #if defined(DUK_USE_REFERENCE_COUNTING) /* Non-zero refcounts should not happen for unreachable strings, * because we refcount finalize all unreachable objects which * should have decreased unreachable string refcounts to zero * (even for cycles). However, pinned strings have a +1 bump. */ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == DUK_HSTRING_HAS_PINNED_LITERAL(h) ? 1U : 0U); #endif /* Deal with weak references first. */ duk_heap_strcache_string_remove(heap, (duk_hstring *) h); /* Remove the string from the string table. */ duk_heap_strtable_unlink_prev(heap, (duk_hstring *) h, (duk_hstring *) prev); /* Free inner references (these exist e.g. when external * strings are enabled) and the struct itself. */ duk_free_hstring(heap, (duk_hstring *) h); /* Don't update 'prev'; it should be last string kept. */ } h = next; } } done: #if defined(DUK_USE_DEBUG) DUK_D(DUK_DPRINT("mark-and-sweep sweep stringtable: %ld freed, %ld kept", (long) count_free, (long) count_keep)); #endif *out_count_keep = count_keep; } /* * Sweep heap. */ DUK_LOCAL void duk__sweep_heap(duk_heap *heap, duk_small_uint_t flags, duk_size_t *out_count_keep) { duk_heaphdr *prev; /* last element that was left in the heap */ duk_heaphdr *curr; duk_heaphdr *next; #if defined(DUK_USE_DEBUG) duk_size_t count_free = 0; duk_size_t count_finalize = 0; duk_size_t count_rescue = 0; #endif duk_size_t count_keep = 0; DUK_DD(DUK_DDPRINT("duk__sweep_heap: %p", (void *) heap)); prev = NULL; curr = heap->heap_allocated; heap->heap_allocated = NULL; while (curr) { /* Strings and ROM objects are never placed on the heap allocated list. */ DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_STRING); DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr)); next = DUK_HEAPHDR_GET_NEXT(heap, curr); if (DUK_HEAPHDR_HAS_REACHABLE(curr)) { /* * Reachable object: * - If FINALIZABLE -> actually unreachable (but marked * artificially reachable), queue to finalize_list. * - If !FINALIZABLE but FINALIZED -> rescued after * finalizer execution. * - Otherwise just a normal, reachable object. * * Objects which are kept are queued to heap_allocated * tail (we're essentially filtering heap_allocated in * practice). */ #if defined(DUK_USE_FINALIZER_SUPPORT) if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZABLE(curr))) { DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); DUK_DD(DUK_DDPRINT("sweep; reachable, finalizable --> move to finalize_list: %p", (void *) curr)); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_HEAPHDR_PREINC_REFCOUNT(curr); /* Bump refcount so that refzero never occurs when pending a finalizer call. */ #endif DUK_HEAP_INSERT_INTO_FINALIZE_LIST(heap, curr); #if defined(DUK_USE_DEBUG) count_finalize++; #endif } else #endif /* DUK_USE_FINALIZER_SUPPORT */ { if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZED(curr))) { DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); if (flags & DUK_MS_FLAG_POSTPONE_RESCUE) { DUK_DD(DUK_DDPRINT("sweep; reachable, finalized, but postponing rescue decisions --> keep object (with FINALIZED set): %!iO", curr)); count_keep++; } else { DUK_DD(DUK_DDPRINT("sweep; reachable, finalized --> rescued after finalization: %p", (void *) curr)); #if defined(DUK_USE_FINALIZER_SUPPORT) DUK_HEAPHDR_CLEAR_FINALIZED(curr); #endif #if defined(DUK_USE_DEBUG) count_rescue++; #endif } } else { DUK_DD(DUK_DDPRINT("sweep; reachable --> keep: %!iO", curr)); count_keep++; } if (prev != NULL) { DUK_ASSERT(heap->heap_allocated != NULL); DUK_HEAPHDR_SET_NEXT(heap, prev, curr); } else { DUK_ASSERT(heap->heap_allocated == NULL); heap->heap_allocated = curr; } #if defined(DUK_USE_DOUBLE_LINKED_HEAP) DUK_HEAPHDR_SET_PREV(heap, curr, prev); #endif DUK_HEAPHDR_ASSERT_LINKS(heap, prev); DUK_HEAPHDR_ASSERT_LINKS(heap, curr); prev = curr; } /* * Shrink check for value stacks here. We're inside * ms_prevent_count protection which prevents recursive * mark-and-sweep and refzero finalizers, so there are * no side effects that would affect the heap lists. */ if (DUK_HEAPHDR_IS_OBJECT(curr) && DUK_HOBJECT_IS_THREAD((duk_hobject *) curr)) { duk_hthread *thr_curr = (duk_hthread *) curr; DUK_DD(DUK_DDPRINT("value stack shrink check for thread: %!O", curr)); duk_valstack_shrink_check_nothrow(thr_curr, flags & DUK_MS_FLAG_EMERGENCY /*snug*/); } DUK_HEAPHDR_CLEAR_REACHABLE(curr); /* Keep FINALIZED if set, used if rescue decisions are postponed. */ /* Keep FINALIZABLE for objects on finalize_list. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); } else { /* * Unreachable object: * - If FINALIZED, object was finalized but not * rescued. This doesn't affect freeing. * - Otherwise normal unreachable object. * * There's no guard preventing a FINALIZED object * from being freed while finalizers execute: the * artificial finalize_list reachability roots can't * cause an incorrect free decision (but can cause * an incorrect rescue decision). */ #if defined(DUK_USE_REFERENCE_COUNTING) /* Non-zero refcounts should not happen because we refcount * finalize all unreachable objects which should cancel out * refcounts (even for cycles). */ DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(curr) == 0); #endif DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); #if defined(DUK_USE_DEBUG) if (DUK_HEAPHDR_HAS_FINALIZED(curr)) { DUK_DD(DUK_DDPRINT("sweep; unreachable, finalized --> finalized object not rescued: %p", (void *) curr)); } else { DUK_DD(DUK_DDPRINT("sweep; not reachable --> free: %p", (void *) curr)); } #endif /* Note: object cannot be a finalizable unreachable object, as * they have been marked temporarily reachable for this round, * and are handled above. */ #if defined(DUK_USE_DEBUG) count_free++; #endif /* Weak refs should be handled here, but no weak refs for * any non-string objects exist right now. */ /* Free object and all auxiliary (non-heap) allocs. */ duk_heap_free_heaphdr_raw(heap, curr); } curr = next; } if (prev != NULL) { DUK_HEAPHDR_SET_NEXT(heap, prev, NULL); } DUK_HEAPHDR_ASSERT_LINKS(heap, prev); #if defined(DUK_USE_DEBUG) DUK_D(DUK_DPRINT("mark-and-sweep sweep objects (non-string): %ld freed, %ld kept, %ld rescued, %ld queued for finalization", (long) count_free, (long) count_keep, (long) count_rescue, (long) count_finalize)); #endif *out_count_keep = count_keep; } /* * Litcache helpers. */ #if defined(DUK_USE_LITCACHE_SIZE) DUK_LOCAL void duk__wipe_litcache(duk_heap *heap) { duk_uint_t i; duk_litcache_entry *e; e = heap->litcache; for (i = 0; i < DUK_USE_LITCACHE_SIZE; i++) { e->addr = NULL; /* e->h does not need to be invalidated: when e->addr is * NULL, e->h is considered garbage. */ e++; } } #endif /* DUK_USE_LITCACHE_SIZE */ /* * Object compaction. * * Compaction is assumed to never throw an error. */ DUK_LOCAL int duk__protected_compact_object(duk_hthread *thr, void *udata) { duk_hobject *obj; /* XXX: for threads, compact stacks? */ DUK_UNREF(udata); obj = duk_known_hobject(thr, -1); duk_hobject_compact_props(thr, obj); return 0; } #if defined(DUK_USE_DEBUG) DUK_LOCAL void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start, duk_size_t *p_count_check, duk_size_t *p_count_compact, duk_size_t *p_count_bytes_saved) { #else DUK_LOCAL void duk__compact_object_list(duk_heap *heap, duk_hthread *thr, duk_heaphdr *start) { #endif duk_heaphdr *curr; #if defined(DUK_USE_DEBUG) duk_size_t old_size, new_size; #endif duk_hobject *obj; DUK_UNREF(heap); curr = start; while (curr) { DUK_DDD(DUK_DDDPRINT("mark-and-sweep compact: %p", (void *) curr)); if (DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_OBJECT) { goto next; } obj = (duk_hobject *) curr; #if defined(DUK_USE_DEBUG) old_size = DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), DUK_HOBJECT_GET_ASIZE(obj), DUK_HOBJECT_GET_HSIZE(obj)); #endif DUK_DD(DUK_DDPRINT("compact object: %p", (void *) obj)); duk_push_hobject(thr, obj); /* XXX: disable error handlers for duration of compaction? */ duk_safe_call(thr, duk__protected_compact_object, NULL, 1, 0); #if defined(DUK_USE_DEBUG) new_size = DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), DUK_HOBJECT_GET_ASIZE(obj), DUK_HOBJECT_GET_HSIZE(obj)); #endif #if defined(DUK_USE_DEBUG) (*p_count_compact)++; (*p_count_bytes_saved) += (duk_size_t) (old_size - new_size); #endif next: curr = DUK_HEAPHDR_GET_NEXT(heap, curr); #if defined(DUK_USE_DEBUG) (*p_count_check)++; #endif } } DUK_LOCAL void duk__compact_objects(duk_heap *heap) { /* XXX: which lists should participate? to be finalized? */ #if defined(DUK_USE_DEBUG) duk_size_t count_check = 0; duk_size_t count_compact = 0; duk_size_t count_bytes_saved = 0; #endif DUK_DD(DUK_DDPRINT("duk__compact_objects: %p", (void *) heap)); DUK_ASSERT(heap->heap_thread != NULL); #if defined(DUK_USE_DEBUG) duk__compact_object_list(heap, heap->heap_thread, heap->heap_allocated, &count_check, &count_compact, &count_bytes_saved); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__compact_object_list(heap, heap->heap_thread, heap->finalize_list, &count_check, &count_compact, &count_bytes_saved); #endif #else duk__compact_object_list(heap, heap->heap_thread, heap->heap_allocated); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__compact_object_list(heap, heap->heap_thread, heap->finalize_list); #endif #endif #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always handled to completion inline in DECREF. */ #endif #if defined(DUK_USE_DEBUG) DUK_D(DUK_DPRINT("mark-and-sweep compact objects: %ld checked, %ld compaction attempts, %ld bytes saved by compaction", (long) count_check, (long) count_compact, (long) count_bytes_saved)); #endif } /* * Assertion helpers. */ #if defined(DUK_USE_ASSERTIONS) typedef void (*duk__gc_heaphdr_assert)(duk_heap *heap, duk_heaphdr *h); typedef void (*duk__gc_hstring_assert)(duk_heap *heap, duk_hstring *h); DUK_LOCAL void duk__assert_walk_list(duk_heap *heap, duk_heaphdr *start, duk__gc_heaphdr_assert func) { duk_heaphdr *curr; for (curr = start; curr != NULL; curr = DUK_HEAPHDR_GET_NEXT(heap, curr)) { func(heap, curr); } } DUK_LOCAL void duk__assert_walk_strtable(duk_heap *heap, duk__gc_hstring_assert func) { duk_uint32_t i; for (i = 0; i < heap->st_size; i++) { duk_hstring *h; #if defined(DUK_USE_STRTAB_PTRCOMP) h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]); #else h = heap->strtable[i]; #endif while (h != NULL) { func(heap, h); h = h->hdr.h_next; } } } DUK_LOCAL void duk__assert_heaphdr_flags_cb(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(h)); DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(h)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(h)); /* may have FINALIZED */ } DUK_LOCAL void duk__assert_heaphdr_flags(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__assert_heaphdr_flags_cb); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always handled to completion inline in DECREF. */ #endif /* XXX: Assertions for finalize_list? */ } DUK_LOCAL void duk__assert_validity_cb1(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); DUK_ASSERT(DUK_HEAPHDR_IS_OBJECT(h) || DUK_HEAPHDR_IS_BUFFER(h)); duk_heaphdr_assert_valid_subclassed(h); } DUK_LOCAL void duk__assert_validity_cb2(duk_heap *heap, duk_hstring *h) { DUK_UNREF(heap); DUK_ASSERT(DUK_HEAPHDR_IS_STRING((duk_heaphdr *) h)); duk_heaphdr_assert_valid_subclassed((duk_heaphdr *) h); } DUK_LOCAL void duk__assert_validity(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__assert_validity_cb1); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__assert_walk_list(heap, heap->finalize_list, duk__assert_validity_cb1); #endif #if defined(DUK_USE_REFERENCE_COUNTING) duk__assert_walk_list(heap, heap->refzero_list, duk__assert_validity_cb1); #endif duk__assert_walk_strtable(heap, duk__assert_validity_cb2); } #if defined(DUK_USE_REFERENCE_COUNTING) DUK_LOCAL void duk__assert_valid_refcounts_cb(duk_heap *heap, duk_heaphdr *h) { /* Cannot really assert much w.r.t. refcounts now. */ DUK_UNREF(heap); if (DUK_HEAPHDR_GET_REFCOUNT(h) == 0 && DUK_HEAPHDR_HAS_FINALIZED(h)) { /* An object may be in heap_allocated list with a zero * refcount if it has just been finalized and is waiting * to be collected by the next cycle. * (This doesn't currently happen however.) */ } else if (DUK_HEAPHDR_GET_REFCOUNT(h) == 0) { /* An object may be in heap_allocated list with a zero * refcount also if it is a temporary object created * during debugger paused state. It will get collected * by mark-and-sweep based on its reachability status * (presumably not reachable because refcount is 0). */ } DUK_ASSERT_DISABLE(DUK_HEAPHDR_GET_REFCOUNT(h) >= 0); /* Unsigned. */ } DUK_LOCAL void duk__assert_valid_refcounts(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__assert_valid_refcounts_cb); } DUK_LOCAL void duk__clear_assert_refcounts_cb1(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); h->h_assert_refcount = 0; } DUK_LOCAL void duk__clear_assert_refcounts_cb2(duk_heap *heap, duk_hstring *h) { DUK_UNREF(heap); ((duk_heaphdr *) h)->h_assert_refcount = 0; } DUK_LOCAL void duk__clear_assert_refcounts(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__clear_assert_refcounts_cb1); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__assert_walk_list(heap, heap->finalize_list, duk__clear_assert_refcounts_cb1); #endif #if defined(DUK_USE_REFERENCE_COUNTING) duk__assert_walk_list(heap, heap->refzero_list, duk__clear_assert_refcounts_cb1); #endif duk__assert_walk_strtable(heap, duk__clear_assert_refcounts_cb2); } DUK_LOCAL void duk__check_refcount_heaphdr(duk_heaphdr *hdr) { duk_bool_t count_ok; duk_size_t expect_refc; /* The refcount check only makes sense for reachable objects on * heap_allocated or string table, after the sweep phase. Prior to * sweep phase refcounts will include references that are not visible * via reachability roots. * * Because we're called after the sweep phase, all heap objects on * heap_allocated are reachable. REACHABLE flags have already been * cleared so we can't check them. */ /* ROM objects have intentionally incorrect refcount (1), but we won't * check them. */ DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(hdr)); expect_refc = hdr->h_assert_refcount; if (DUK_HEAPHDR_IS_STRING(hdr) && DUK_HSTRING_HAS_PINNED_LITERAL((duk_hstring *) hdr)) { expect_refc++; } count_ok = ((duk_size_t) DUK_HEAPHDR_GET_REFCOUNT(hdr) == expect_refc); if (!count_ok) { DUK_D(DUK_DPRINT("refcount mismatch for: %p: header=%ld counted=%ld --> %!iO", (void *) hdr, (long) DUK_HEAPHDR_GET_REFCOUNT(hdr), (long) hdr->h_assert_refcount, hdr)); DUK_ASSERT(0); } } DUK_LOCAL void duk__check_assert_refcounts_cb1(duk_heap *heap, duk_heaphdr *h) { DUK_UNREF(heap); duk__check_refcount_heaphdr(h); } DUK_LOCAL void duk__check_assert_refcounts_cb2(duk_heap *heap, duk_hstring *h) { DUK_UNREF(heap); duk__check_refcount_heaphdr((duk_heaphdr *) h); } DUK_LOCAL void duk__check_assert_refcounts(duk_heap *heap) { duk__assert_walk_list(heap, heap->heap_allocated, duk__check_assert_refcounts_cb1); #if defined(DUK_USE_FINALIZER_SUPPORT) duk__assert_walk_list(heap, heap->finalize_list, duk__check_assert_refcounts_cb1); #endif /* XXX: Assert anything for refzero_list? */ duk__assert_walk_strtable(heap, duk__check_assert_refcounts_cb2); } #endif /* DUK_USE_REFERENCE_COUNTING */ #if defined(DUK_USE_LITCACHE_SIZE) DUK_LOCAL void duk__assert_litcache_nulls(duk_heap *heap) { duk_uint_t i; duk_litcache_entry *e; e = heap->litcache; for (i = 0; i < DUK_USE_LITCACHE_SIZE; i++) { /* Entry addresses were NULLed before mark-and-sweep, check * that they're still NULL afterwards to ensure no pointers * were recorded through any side effects. */ DUK_ASSERT(e->addr == NULL); } } #endif /* DUK_USE_LITCACHE_SIZE */ #endif /* DUK_USE_ASSERTIONS */ /* * Stats dump. */ #if defined(DUK_USE_DEBUG) DUK_LOCAL void duk__dump_stats(duk_heap *heap) { DUK_D(DUK_DPRINT("stats executor: opcodes=%ld, interrupt=%ld, throw=%ld", (long) heap->stats_exec_opcodes, (long) heap->stats_exec_interrupt, (long) heap->stats_exec_throw)); DUK_D(DUK_DPRINT("stats call: all=%ld, tailcall=%ld, ecmatoecma=%ld", (long) heap->stats_call_all, (long) heap->stats_call_tailcall, (long) heap->stats_call_ecmatoecma)); DUK_D(DUK_DPRINT("stats safecall: all=%ld, nothrow=%ld, throw=%ld", (long) heap->stats_safecall_all, (long) heap->stats_safecall_nothrow, (long) heap->stats_safecall_throw)); DUK_D(DUK_DPRINT("stats mark-and-sweep: try_count=%ld, skip_count=%ld, emergency_count=%ld", (long) heap->stats_ms_try_count, (long) heap->stats_ms_skip_count, (long) heap->stats_ms_emergency_count)); DUK_D(DUK_DPRINT("stats stringtable: intern_hit=%ld, intern_miss=%ld, " "resize_check=%ld, resize_grow=%ld, resize_shrink=%ld, " "litcache_hit=%ld, litcache_miss=%ld, litcache_pin=%ld", (long) heap->stats_strtab_intern_hit, (long) heap->stats_strtab_intern_miss, (long) heap->stats_strtab_resize_check, (long) heap->stats_strtab_resize_grow, (long) heap->stats_strtab_resize_shrink, (long) heap->stats_strtab_litcache_hit, (long) heap->stats_strtab_litcache_miss, (long) heap->stats_strtab_litcache_pin)); DUK_D(DUK_DPRINT("stats object: realloc_props=%ld, abandon_array=%ld", (long) heap->stats_object_realloc_props, (long) heap->stats_object_abandon_array)); DUK_D(DUK_DPRINT("stats getownpropdesc: count=%ld, hit=%ld, miss=%ld", (long) heap->stats_getownpropdesc_count, (long) heap->stats_getownpropdesc_hit, (long) heap->stats_getownpropdesc_miss)); DUK_D(DUK_DPRINT("stats getpropdesc: count=%ld, hit=%ld, miss=%ld", (long) heap->stats_getpropdesc_count, (long) heap->stats_getpropdesc_hit, (long) heap->stats_getpropdesc_miss)); DUK_D(DUK_DPRINT("stats getprop: all=%ld, arrayidx=%ld, bufobjidx=%ld, " "bufferidx=%ld, bufferlen=%ld, stringidx=%ld, stringlen=%ld, " "proxy=%ld, arguments=%ld", (long) heap->stats_getprop_all, (long) heap->stats_getprop_arrayidx, (long) heap->stats_getprop_bufobjidx, (long) heap->stats_getprop_bufferidx, (long) heap->stats_getprop_bufferlen, (long) heap->stats_getprop_stringidx, (long) heap->stats_getprop_stringlen, (long) heap->stats_getprop_proxy, (long) heap->stats_getprop_arguments)); DUK_D(DUK_DPRINT("stats putprop: all=%ld, arrayidx=%ld, bufobjidx=%ld, " "bufferidx=%ld, proxy=%ld", (long) heap->stats_putprop_all, (long) heap->stats_putprop_arrayidx, (long) heap->stats_putprop_bufobjidx, (long) heap->stats_putprop_bufferidx, (long) heap->stats_putprop_proxy)); DUK_D(DUK_DPRINT("stats getvar: all=%ld", (long) heap->stats_getvar_all)); DUK_D(DUK_DPRINT("stats putvar: all=%ld", (long) heap->stats_putvar_all)); DUK_D(DUK_DPRINT("stats envrec: delayedcreate=%ld, create=%ld, newenv=%ld, oldenv=%ld, pushclosure=%ld", (long) heap->stats_envrec_delayedcreate, (long) heap->stats_envrec_create, (long) heap->stats_envrec_newenv, (long) heap->stats_envrec_oldenv, (long) heap->stats_envrec_pushclosure)); } #endif /* DUK_USE_DEBUG */ /* * Main mark-and-sweep function. * * 'flags' represents the features requested by the caller. The current * heap->ms_base_flags is ORed automatically into the flags; the base flags * mask typically prevents certain mark-and-sweep operation to avoid trouble. */ DUK_INTERNAL void duk_heap_mark_and_sweep(duk_heap *heap, duk_small_uint_t flags) { duk_size_t count_keep_obj; duk_size_t count_keep_str; #if defined(DUK_USE_VOLUNTARY_GC) duk_size_t tmp; #endif DUK_STATS_INC(heap, stats_ms_try_count); #if defined(DUK_USE_DEBUG) if (flags & DUK_MS_FLAG_EMERGENCY) { DUK_STATS_INC(heap, stats_ms_emergency_count); } #endif /* If debugger is paused, garbage collection is disabled by default. * This is achieved by bumping ms_prevent_count when becoming paused. */ DUK_ASSERT(!DUK_HEAP_HAS_DEBUGGER_PAUSED(heap) || heap->ms_prevent_count > 0); /* Prevention/recursion check as soon as possible because we may * be called a number of times when voluntary mark-and-sweep is * pending. */ if (heap->ms_prevent_count != 0) { DUK_DD(DUK_DDPRINT("reject recursive mark-and-sweep")); DUK_STATS_INC(heap, stats_ms_skip_count); return; } DUK_ASSERT(heap->ms_running == 0); /* ms_prevent_count is bumped when ms_running is set */ /* Heap_thread is used during mark-and-sweep for refcount finalization * (it's also used for finalizer execution once mark-and-sweep is * complete). Heap allocation code ensures heap_thread is set and * properly initialized before setting ms_prevent_count to 0. */ DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(heap->heap_thread->valstack != NULL); 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->ms_base_flags))); flags |= heap->ms_base_flags; #if defined(DUK_USE_FINALIZER_SUPPORT) if (heap->finalize_list != NULL) { flags |= DUK_MS_FLAG_POSTPONE_RESCUE; } #endif /* * Assertions before */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(heap->ms_prevent_count == 0); DUK_ASSERT(heap->ms_running == 0); DUK_ASSERT(!DUK_HEAP_HAS_DEBUGGER_PAUSED(heap)); DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); DUK_ASSERT(heap->ms_recursion_depth == 0); duk__assert_heaphdr_flags(heap); duk__assert_validity(heap); #if defined(DUK_USE_REFERENCE_COUNTING) /* Note: heap->refzero_free_running 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_ASSERT(heap->ms_prevent_count == 0); DUK_ASSERT(heap->ms_running == 0); heap->ms_prevent_count = 1; heap->ms_running = 1; /* * Free activation/catcher freelists on every mark-and-sweep for now. * This is an initial rough draft; ideally we'd keep count of the * freelist size and free only excess entries. */ DUK_D(DUK_DPRINT("freeing temporary freelists")); duk_heap_free_freelists(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. */ #if defined(DUK_USE_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) duk__clear_assert_refcounts(heap); #endif #if defined(DUK_USE_LITCACHE_SIZE) duk__wipe_litcache(heap); #endif duk__mark_roots_heap(heap); /* Mark main reachability roots. */ #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always handled to completion inline in DECREF. */ #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_ASSERTIONS) && defined(DUK_USE_REFERENCE_COUNTING) duk__check_assert_refcounts(heap); #endif #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* Always handled to completion inline in DECREF. */ #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). * * At present compaction is not allowed when mark-and-sweep runs * during error handling because it involves a duk_safe_call() * interfering with error state. */ if ((flags & DUK_MS_FLAG_EMERGENCY) && !(flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION)) { if (heap->lj.type != DUK_LJ_TYPE_UNKNOWN) { DUK_D(DUK_DPRINT("lj.type (%ld) not DUK_LJ_TYPE_UNKNOWN, skip object compaction", (long) heap->lj.type)); } else { DUK_D(DUK_DPRINT("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); } /* * Finish */ DUK_ASSERT(heap->ms_prevent_count == 1); DUK_ASSERT(heap->ms_running == 1); heap->ms_prevent_count = 0; heap->ms_running = 0; /* * Assertions after */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(heap->ms_prevent_count == 0); DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RECLIMIT_REACHED(heap)); DUK_ASSERT(heap->ms_recursion_depth == 0); duk__assert_heaphdr_flags(heap); duk__assert_validity(heap); #if defined(DUK_USE_REFERENCE_COUNTING) /* Note: heap->refzero_free_running may be true; a refcount * finalizer may trigger a mark-and-sweep. */ duk__assert_valid_refcounts(heap); #endif /* DUK_USE_REFERENCE_COUNTING */ #if defined(DUK_USE_LITCACHE_SIZE) duk__assert_litcache_nulls(heap); #endif /* DUK_USE_LITCACHE_SIZE */ #endif /* DUK_USE_ASSERTIONS */ /* * Reset trigger counter */ #if defined(DUK_USE_VOLUNTARY_GC) tmp = (count_keep_obj + count_keep_str) / 256; heap->ms_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->ms_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 /* * Stats dump */ #if defined(DUK_USE_DEBUG) duk__dump_stats(heap); #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 e.g. during string table and object property allocation * resizing using heap->pf_prevent_count. 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 or any DECREF checking * for finalize_list. * * As of Duktape 2.1 finalization happens outside mark-and-sweep * protection. Mark-and-sweep is allowed while the finalize_list * is being processed, but no rescue decisions are done while the * process is on-going. This avoids incorrect rescue decisions * if an object is considered reachable (and thus rescued) because * of a reference via finalize_list (which is considered a reachability * root). When finalize_list is being processed, reachable objects * with FINALIZED set will just keep their FINALIZED flag for later * mark-and-sweep processing. * * This could also be handled (a bit better) by having a more refined * notion of reachability for rescue/free decisions. * * XXX: avoid finalizer execution when doing emergency GC? */ #if defined(DUK_USE_FINALIZER_SUPPORT) /* Attempt to process finalize_list, pf_prevent_count check * is inside the target. */ duk_heap_process_finalize_list(heap); #endif /* DUK_USE_FINALIZER_SUPPORT */ }