mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1331 lines
41 KiB
1331 lines
41 KiB
/*
|
|
* 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_tval(duk_heap *heap, duk_tval *tv);
|
|
|
|
/*
|
|
* Misc
|
|
*/
|
|
|
|
/* Select a thread for mark-and-sweep use.
|
|
*
|
|
* XXX: This needs to change later.
|
|
*/
|
|
DUK_LOCAL duk_hthread *duk__get_temp_hthread(duk_heap *heap) {
|
|
if (heap->curr_thread) {
|
|
return heap->curr_thread;
|
|
}
|
|
return heap->heap_thread; /* may be NULL, too */
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/* 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);
|
|
|
|
/* 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) {
|
|
continue;
|
|
}
|
|
duk__mark_heaphdr(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));
|
|
|
|
/* XXX: rearrange bits to allow a switch case to be used here? */
|
|
/* XXX: add a fast path for objects (and arrays)? */
|
|
/* DUK_HOBJECT_IS_ARRAY(h): needs no special handling now as there are
|
|
* no extra fields in need of marking.
|
|
*/
|
|
if (DUK_HOBJECT_IS_COMPFUNC(h)) {
|
|
duk_hcompfunc *f = (duk_hcompfunc *) h;
|
|
duk_tval *tv, *tv_end;
|
|
duk_hobject **fn, **fn_end;
|
|
|
|
/* '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(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_NATFUNC(h)) {
|
|
duk_hnatfunc *f = (duk_hnatfunc *) h;
|
|
DUK_UNREF(f);
|
|
/* nothing to mark */
|
|
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
|
|
} else if (DUK_HOBJECT_IS_BUFOBJ(h)) {
|
|
duk_hbufobj *b = (duk_hbufobj *) h;
|
|
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_THREAD(h)) {
|
|
duk_hthread *t = (duk_hthread *) h;
|
|
duk_tval *tv;
|
|
|
|
tv = t->valstack;
|
|
while (tv < t->valstack_top) {
|
|
duk__mark_tval(heap, tv);
|
|
tv++;
|
|
}
|
|
|
|
for (i = 0; i < (duk_uint_fast32_t) t->callstack_top; i++) {
|
|
duk_activation *act = t->callstack + i;
|
|
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 (i = 0; i < (duk_uint_fast32_t) t->catchstack_top; i++) {
|
|
duk_catcher *cat = t->catchstack + i;
|
|
}
|
|
#endif
|
|
|
|
duk__mark_heaphdr(heap, (duk_heaphdr *) t->resumer);
|
|
|
|
/* XXX: duk_small_uint_t would be enough for this loop */
|
|
for (i = 0; i < DUK_NUM_BUILTINS; i++) {
|
|
duk__mark_heaphdr(heap, (duk_heaphdr *) t->builtins[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* recursion tracking happens here only */
|
|
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)));
|
|
if (!h) {
|
|
return;
|
|
}
|
|
#if defined(DUK_USE_ROM_OBJECTS)
|
|
if (DUK_HEAPHDR_HAS_READONLY(h)) {
|
|
DUK_DDD(DUK_DDDPRINT("readonly object %p, skip", (void *) h));
|
|
return;
|
|
}
|
|
#endif
|
|
if (DUK_HEAPHDR_HAS_REACHABLE(h)) {
|
|
DUK_DDD(DUK_DDDPRINT("already marked reachable, skip"));
|
|
return;
|
|
}
|
|
DUK_HEAPHDR_SET_REACHABLE(h);
|
|
|
|
if (heap->mark_and_sweep_recursion_depth >= DUK_USE_MARK_AND_SWEEP_RECLIMIT) {
|
|
/* log this with a normal debug level because this should be relatively rare */
|
|
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->mark_and_sweep_recursion_depth++;
|
|
|
|
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();
|
|
}
|
|
|
|
heap->mark_and_sweep_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) {
|
|
return;
|
|
}
|
|
if (DUK_TVAL_IS_HEAP_ALLOCATED(tv)) {
|
|
duk__mark_heaphdr(heap, DUK_TVAL_GET_HEAPHDR(tv));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 refzero_list objects.
|
|
*
|
|
* Objects on the refzero_list have no inbound references. They might have
|
|
* outbound references to objects that we might free, which would invalidate
|
|
* any references held by the refzero objects. A refzero object might also
|
|
* be rescued by refcount finalization. Refzero objects are treated as
|
|
* reachability roots to ensure they (or anything they point to) are not
|
|
* freed in mark-and-sweep.
|
|
*/
|
|
|
|
#if defined(DUK_USE_REFERENCE_COUNTING)
|
|
DUK_LOCAL void duk__mark_refzero_list(duk_heap *heap) {
|
|
duk_heaphdr *hdr;
|
|
|
|
DUK_DD(DUK_DDPRINT("duk__mark_refzero_list: %p", (void *) heap));
|
|
|
|
hdr = heap->refzero_list;
|
|
while (hdr) {
|
|
duk__mark_heaphdr(heap, hdr);
|
|
hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
|
|
}
|
|
}
|
|
#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 (refzero_list is thus excluded).
|
|
*
|
|
* 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_hthread *thr;
|
|
duk_heaphdr *hdr;
|
|
duk_size_t count_finalizable = 0;
|
|
|
|
DUK_DD(DUK_DDPRINT("duk__mark_finalizable: %p", (void *) heap));
|
|
|
|
thr = duk__get_temp_hthread(heap);
|
|
DUK_ASSERT(thr != NULL);
|
|
|
|
hdr = heap->heap_allocated;
|
|
while (hdr) {
|
|
/* A finalizer is looked up from the object and up its prototype chain
|
|
* (which allows inherited finalizers). A prototype loop must not cause
|
|
* an error to be thrown here; duk_hobject_hasprop_raw() will ignore a
|
|
* prototype loop silently and indicate that the property doesn't exist.
|
|
*/
|
|
|
|
if (!DUK_HEAPHDR_HAS_REACHABLE(hdr) &&
|
|
DUK_HEAPHDR_GET_TYPE(hdr) == DUK_HTYPE_OBJECT &&
|
|
!DUK_HEAPHDR_HAS_FINALIZED(hdr) &&
|
|
duk_hobject_hasprop_raw(thr, (duk_hobject *) hdr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
|
|
|
|
/* heaphdr:
|
|
* - is not reachable
|
|
* - is an object
|
|
* - is not a finalized object
|
|
* - 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) {
|
|
if (DUK_HEAPHDR_HAS_FINALIZABLE(hdr)) {
|
|
duk__mark_heaphdr(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) {
|
|
duk__mark_heaphdr(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. Note
|
|
* that temproots may reside either in heap allocated list or the
|
|
* refzero work list. 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
|
|
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 */
|
|
duk__mark_heaphdr(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);
|
|
}
|
|
|
|
/* must also check refzero_list */
|
|
#if defined(DUK_USE_REFERENCE_COUNTING)
|
|
hdr = heap->refzero_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 /* DUK_USE_REFERENCE_COUNTING */
|
|
|
|
#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_hthread *thr;
|
|
duk_heaphdr *hdr;
|
|
|
|
thr = duk__get_temp_hthread(heap);
|
|
DUK_ASSERT(thr != NULL);
|
|
|
|
DUK_DD(DUK_DDPRINT("duk__finalize_refcounts: heap=%p, hthread=%p",
|
|
(void *) heap, (void *) thr));
|
|
|
|
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));
|
|
duk_heaphdr_refcount_finalize(thr, hdr);
|
|
}
|
|
|
|
hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
|
|
}
|
|
}
|
|
#endif /* DUK_USE_REFERENCE_COUNTING */
|
|
|
|
/*
|
|
* Clear (reachable) flags of refzero work list.
|
|
*/
|
|
|
|
#if defined(DUK_USE_REFERENCE_COUNTING)
|
|
DUK_LOCAL void duk__clear_refzero_list_flags(duk_heap *heap) {
|
|
duk_heaphdr *hdr;
|
|
|
|
DUK_DD(DUK_DDPRINT("duk__clear_refzero_list_flags: %p", (void *) heap));
|
|
|
|
hdr = heap->refzero_list;
|
|
while (hdr) {
|
|
DUK_HEAPHDR_CLEAR_REACHABLE(hdr);
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
|
|
/* DUK_HEAPHDR_HAS_FINALIZED may or may not be set. */
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(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);
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(hdr));
|
|
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
|
|
|
|
#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).
|
|
*/
|
|
DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT((duk_heaphdr *) h) == 0);
|
|
#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_int_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_UNREF(flags);
|
|
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_LIKELY(DUK_HEAPHDR_HAS_REACHABLE(curr))) {
|
|
/*
|
|
* Reachable object, keep
|
|
*/
|
|
|
|
DUK_DDD(DUK_DDDPRINT("sweep, reachable: %p", (void *) curr));
|
|
|
|
if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZABLE(curr))) {
|
|
/*
|
|
* If object has been marked finalizable, move it to the
|
|
* "to be finalized" work list. It will be collected on
|
|
* the next mark-and-sweep if it is still unreachable
|
|
* after running the finalizer.
|
|
*/
|
|
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
|
|
DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
|
|
DUK_DDD(DUK_DDDPRINT("object has finalizer, move to finalization work list: %p", (void *) curr));
|
|
|
|
#if defined(DUK_USE_DOUBLE_LINKED_HEAP)
|
|
if (heap->finalize_list != NULL) {
|
|
DUK_HEAPHDR_SET_PREV(heap, heap->finalize_list, curr);
|
|
}
|
|
DUK_HEAPHDR_SET_PREV(heap, curr, NULL);
|
|
#endif
|
|
DUK_HEAPHDR_SET_NEXT(heap, curr, heap->finalize_list);
|
|
DUK_ASSERT_HEAPHDR_LINKS(heap, curr);
|
|
heap->finalize_list = curr;
|
|
#if defined(DUK_USE_DEBUG)
|
|
count_finalize++;
|
|
#endif
|
|
} else {
|
|
/*
|
|
* Object will be kept; queue object back to heap_allocated (to tail)
|
|
*/
|
|
|
|
if (DUK_UNLIKELY(DUK_HEAPHDR_HAS_FINALIZED(curr))) {
|
|
/*
|
|
* Object's finalizer was executed on last round, and
|
|
* object has been happily rescued.
|
|
*/
|
|
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
|
|
DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT);
|
|
DUK_DD(DUK_DDPRINT("object rescued during mark-and-sweep finalization: %p", (void *) curr));
|
|
#if defined(DUK_USE_DEBUG)
|
|
count_rescue++;
|
|
#endif
|
|
} else {
|
|
/*
|
|
* Plain, boring reachable object.
|
|
*/
|
|
DUK_DD(DUK_DDPRINT("keep object: %!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_ASSERT_HEAPHDR_LINKS(heap, prev);
|
|
DUK_ASSERT_HEAPHDR_LINKS(heap, curr);
|
|
prev = curr;
|
|
}
|
|
|
|
DUK_HEAPHDR_CLEAR_REACHABLE(curr);
|
|
DUK_HEAPHDR_CLEAR_FINALIZED(curr);
|
|
DUK_HEAPHDR_CLEAR_FINALIZABLE(curr);
|
|
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
|
|
|
|
curr = next;
|
|
} else {
|
|
/*
|
|
* Unreachable object, free
|
|
*/
|
|
|
|
DUK_DDD(DUK_DDDPRINT("sweep, not reachable: %p", (void *) curr));
|
|
|
|
#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 (DUK_HEAPHDR_HAS_FINALIZED(curr)) {
|
|
DUK_DDD(DUK_DDDPRINT("finalized object not rescued: %p", (void *) curr));
|
|
}
|
|
|
|
/* 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_ASSERT_HEAPHDR_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;
|
|
}
|
|
|
|
/*
|
|
* Run (object) finalizers in the "to be finalized" work list.
|
|
*/
|
|
|
|
#if defined(DUK_USE_FINALIZER_SUPPORT)
|
|
DUK_LOCAL void duk__run_object_finalizers(duk_heap *heap, duk_small_uint_t flags) {
|
|
duk_heaphdr *curr;
|
|
duk_heaphdr *next;
|
|
#if defined(DUK_USE_DEBUG)
|
|
duk_size_t count = 0;
|
|
#endif
|
|
duk_hthread *thr;
|
|
|
|
DUK_DD(DUK_DDPRINT("duk__run_object_finalizers: %p", (void *) heap));
|
|
|
|
thr = duk__get_temp_hthread(heap);
|
|
DUK_ASSERT(thr != NULL);
|
|
|
|
curr = heap->finalize_list;
|
|
while (curr) {
|
|
DUK_DDD(DUK_DDDPRINT("mark-and-sweep finalize: %p", (void *) curr));
|
|
|
|
DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); /* only objects have finalizers */
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); /* flags have been already cleared */
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr)); /* No finalizers for ROM objects */
|
|
|
|
/* Keep heap->finalize_list up-to-date during the list walk.
|
|
* This has no functional impact, but does matter e.g. for
|
|
* duk_push_heapptr() asserts when assertions are enabled.
|
|
*/
|
|
heap->finalize_list = curr;
|
|
|
|
if (DUK_LIKELY((flags & DUK_MS_FLAG_SKIP_FINALIZERS) == 0)) {
|
|
/* Run the finalizer, duk_hobject_run_finalizer() sets FINALIZED.
|
|
* Next mark-and-sweep will collect the object unless it has
|
|
* become reachable (i.e. rescued). FINALIZED prevents the
|
|
* finalizer from being executed again before that.
|
|
*/
|
|
duk_hobject_run_finalizer(thr, (duk_hobject *) curr); /* must never longjmp */
|
|
DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZED(curr));
|
|
|
|
/* XXX: could clear FINALIZED already here; now cleared in
|
|
* next mark-and-sweep.
|
|
*/
|
|
} else {
|
|
/* Used during heap destruction: don't actually run finalizers
|
|
* because we're heading into forced finalization. Instead,
|
|
* queue finalizable objects back to the heap_allocated list.
|
|
*/
|
|
DUK_D(DUK_DPRINT("skip finalizers flag set, queue object to heap_allocated without finalizing"));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr));
|
|
}
|
|
|
|
/* queue back to heap_allocated */
|
|
next = DUK_HEAPHDR_GET_NEXT(heap, curr);
|
|
DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr);
|
|
|
|
curr = next;
|
|
#if defined(DUK_USE_DEBUG)
|
|
count++;
|
|
#endif
|
|
}
|
|
|
|
/* finalize_list will always be processed completely */
|
|
heap->finalize_list = NULL;
|
|
|
|
#if defined(DUK_USE_DEBUG)
|
|
DUK_D(DUK_DPRINT("mark-and-sweep finalize objects: %ld finalizers called", (long) count));
|
|
#endif
|
|
}
|
|
#endif /* DUK_USE_FINALIZER_SUPPORT */
|
|
|
|
/*
|
|
* Object compaction.
|
|
*
|
|
* Compaction is assumed to never throw an error.
|
|
*/
|
|
|
|
DUK_LOCAL int duk__protected_compact_object(duk_context *ctx, void *udata) {
|
|
duk_hobject *obj;
|
|
/* XXX: for threads, compact value stack, call stack, catch stack? */
|
|
|
|
DUK_UNREF(udata);
|
|
obj = duk_known_hobject(ctx, -1);
|
|
duk_hobject_compact_props((duk_hthread *) ctx, 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((duk_context *) thr, obj);
|
|
/* XXX: disable error handlers for duration of compaction? */
|
|
duk_safe_call((duk_context *) 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_hthread *thr;
|
|
|
|
DUK_DD(DUK_DDPRINT("duk__compact_objects: %p", (void *) heap));
|
|
|
|
thr = duk__get_temp_hthread(heap);
|
|
DUK_ASSERT(thr != NULL);
|
|
|
|
#if defined(DUK_USE_DEBUG)
|
|
duk__compact_object_list(heap, thr, heap->heap_allocated, &count_check, &count_compact, &count_bytes_saved);
|
|
duk__compact_object_list(heap, thr, heap->finalize_list, &count_check, &count_compact, &count_bytes_saved);
|
|
#if defined(DUK_USE_REFERENCE_COUNTING)
|
|
duk__compact_object_list(heap, thr, heap->refzero_list, &count_check, &count_compact, &count_bytes_saved);
|
|
#endif
|
|
#else
|
|
duk__compact_object_list(heap, thr, heap->heap_allocated);
|
|
duk__compact_object_list(heap, thr, heap->finalize_list);
|
|
#if defined(DUK_USE_REFERENCE_COUNTING)
|
|
duk__compact_object_list(heap, thr, heap->refzero_list);
|
|
#endif
|
|
#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)
|
|
DUK_LOCAL void duk__assert_heaphdr_flags(duk_heap *heap) {
|
|
duk_heaphdr *hdr;
|
|
|
|
hdr = heap->heap_allocated;
|
|
while (hdr) {
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
|
|
/* may have FINALIZED */
|
|
hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
|
|
}
|
|
|
|
#if defined(DUK_USE_REFERENCE_COUNTING)
|
|
hdr = heap->refzero_list;
|
|
while (hdr) {
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(hdr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(hdr));
|
|
DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(hdr));
|
|
/* DUK_HEAPHDR_HAS_FINALIZED may be set if we're doing a
|
|
* refzero finalization and mark-and-sweep gets triggered
|
|
* during the finalizer.
|
|
*/
|
|
/* DUK_HEAPHDR_HAS_FINALIZED may or may not be set. */
|
|
hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
|
|
}
|
|
#endif /* DUK_USE_REFERENCE_COUNTING */
|
|
}
|
|
|
|
#if defined(DUK_USE_REFERENCE_COUNTING)
|
|
DUK_LOCAL void duk__assert_valid_refcounts(duk_heap *heap) {
|
|
duk_heaphdr *hdr = heap->heap_allocated;
|
|
while (hdr) {
|
|
if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0 &&
|
|
DUK_HEAPHDR_HAS_FINALIZED(hdr)) {
|
|
/* 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.
|
|
*/
|
|
} else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) == 0) {
|
|
/* An object may be in heap_allocated list with a zero
|
|
* refcount also if it is a temporary object created by
|
|
* a finalizer; because finalization now runs inside
|
|
* mark-and-sweep, such objects will not be queued to
|
|
* refzero_list and will thus appear here with refcount
|
|
* zero.
|
|
*/
|
|
#if 0 /* this case can no longer occur because refcount is unsigned */
|
|
} else if (DUK_HEAPHDR_GET_REFCOUNT(hdr) < 0) {
|
|
DUK_D(DUK_DPRINT("invalid refcount: %ld, %p -> %!O",
|
|
(hdr != NULL ? (long) DUK_HEAPHDR_GET_REFCOUNT(hdr) : (long) 0),
|
|
(void *) hdr, (duk_heaphdr *) hdr));
|
|
DUK_ASSERT(DUK_HEAPHDR_GET_REFCOUNT(hdr) > 0);
|
|
#endif
|
|
}
|
|
hdr = DUK_HEAPHDR_GET_NEXT(heap, hdr);
|
|
}
|
|
}
|
|
#endif /* DUK_USE_REFERENCE_COUNTING */
|
|
#endif /* DUK_USE_ASSERTIONS */
|
|
|
|
/*
|
|
* Finalizer torture. Do one fake finalizer call which causes side effects
|
|
* similar to one or more finalizers on actual objects.
|
|
*/
|
|
|
|
#if defined(DUK_USE_FINALIZER_SUPPORT)
|
|
#if defined(DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE)
|
|
DUK_LOCAL duk_ret_t duk__markandsweep_fake_finalizer(duk_context *ctx) {
|
|
DUK_D(DUK_DPRINT("fake mark-and-sweep torture finalizer executed"));
|
|
|
|
/* Require a lot of stack to force a value stack grow/shrink.
|
|
* Recursive mark-and-sweep is prevented by allocation macros
|
|
* so this won't trigger another mark-and-sweep.
|
|
*/
|
|
duk_require_stack(ctx, 100000);
|
|
|
|
/* XXX: do something to force a callstack grow/shrink, perhaps
|
|
* just a manual forced resize or a forced relocating realloc?
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
DUK_LOCAL void duk__markandsweep_torture_finalizer(duk_hthread *thr) {
|
|
duk_context *ctx;
|
|
duk_int_t rc;
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
ctx = (duk_context *) thr;
|
|
|
|
/* Avoid fake finalization when callstack limit has been reached.
|
|
* Otherwise a callstack limit error will be created, then refzero'ed.
|
|
*/
|
|
if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit ||
|
|
thr->callstack_size + 2 * DUK_CALLSTACK_GROW_STEP >= thr->callstack_max /*approximate*/) {
|
|
DUK_D(DUK_DPRINT("call recursion depth reached, avoid fake mark-and-sweep torture finalizer"));
|
|
return;
|
|
}
|
|
|
|
/* Run fake finalizer. Avoid creating unnecessary garbage. */
|
|
duk_push_c_function(ctx, duk__markandsweep_fake_finalizer, 0 /*nargs*/);
|
|
rc = duk_pcall(ctx, 0 /*nargs*/);
|
|
DUK_UNREF(rc); /* ignored */
|
|
duk_pop(ctx);
|
|
}
|
|
#endif /* DUK_USE_MARKANDSWEEP_FINALIZER_TORTURE */
|
|
#endif /* DUK_USE_FINALIZER_SUPPORT */
|
|
|
|
/*
|
|
* Main mark-and-sweep function.
|
|
*
|
|
* 'flags' represents the features requested by the caller. The current
|
|
* heap->mark_and_sweep_base_flags is ORed automatically into the flags;
|
|
* the base flags mask typically prevents certain mark-and-sweep operations
|
|
* to avoid trouble.
|
|
*/
|
|
|
|
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 */
|
|
}
|
|
|