|
|
|
/*
|
|
|
|
* 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? */
|
|
|
|
|
16-bit fields and heap pointer compression work
Memory optimization work for very low memory devices (96 to 256kB system RAM).
Overall changes are:
- 16-bit fields for various internal structures to reduce their size
- Heap pointer compression to reduce pointer size to 16 bits
When DUK_OPT_LIGHTFUNC_BUILTINS and the new low memory options are enabled,
Duktape initial heap memory usage is about 23kB (compared to baseline of
about 45kB) on x86.
Unless low memory feature options are enabled, there should be no visible
changes to Duktape behavior.
More detailed changes:
- 16-bit changes for duk_heaphdr: pointer compression, refcount
- 16-bit changes for duk_hstring: hash, blen, and clen can all be 16 bits,
use 0xFFFF as string byte length limit (call sites ensure this limit is
never exceeded)
- 16-bit changes for duk_hbuffer, use 0xFFFF as buffer length limit
- 16-bit fields for hobject size (entry part, array part), drop hash part
since it's not usually needed for extremely low memory environments
- 16-bit changes for duk_hcompiledfunction
- Heap pointer packing for stringtable
- Heap pointer packing for 'strs' built-in strings list (saves around 600
to 700 bytes but may not be a good tradeoff because call site size will
increase)
Other changes:
- Heaphdr NULL init fix. The original macros were broken: the double/single
linked macro variants were the wrong way around. Now sets through macro
to work properly with compressed pointers.
- Rename duk_hbuffer CURR_DATA_PTR -> DATA_PTR to reduce macro length
(previous name was tediously long)
- Rename buffer "usable_size" to "alloc_size" throughout as they have been
the same for a while now (they used to differ when buffer had an extra NUL).
- Add memory optimization markers to Duktape.env (pointer compression and
individual 16-bit field options)
- Rename a few internal fields for clarity: duk_hobject 'p' to 'props',
heap->st to heap->strtable
- Add a safety check for buffer alloc size (should not be triggered but
prevents wrapping if call sites don't properly check for sizes)
- Other minor cleanups
10 years ago
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
16-bit fields and heap pointer compression work
Memory optimization work for very low memory devices (96 to 256kB system RAM).
Overall changes are:
- 16-bit fields for various internal structures to reduce their size
- Heap pointer compression to reduce pointer size to 16 bits
When DUK_OPT_LIGHTFUNC_BUILTINS and the new low memory options are enabled,
Duktape initial heap memory usage is about 23kB (compared to baseline of
about 45kB) on x86.
Unless low memory feature options are enabled, there should be no visible
changes to Duktape behavior.
More detailed changes:
- 16-bit changes for duk_heaphdr: pointer compression, refcount
- 16-bit changes for duk_hstring: hash, blen, and clen can all be 16 bits,
use 0xFFFF as string byte length limit (call sites ensure this limit is
never exceeded)
- 16-bit changes for duk_hbuffer, use 0xFFFF as buffer length limit
- 16-bit fields for hobject size (entry part, array part), drop hash part
since it's not usually needed for extremely low memory environments
- 16-bit changes for duk_hcompiledfunction
- Heap pointer packing for stringtable
- Heap pointer packing for 'strs' built-in strings list (saves around 600
to 700 bytes but may not be a good tradeoff because call site size will
increase)
Other changes:
- Heaphdr NULL init fix. The original macros were broken: the double/single
linked macro variants were the wrong way around. Now sets through macro
to work properly with compressed pointers.
- Rename duk_hbuffer CURR_DATA_PTR -> DATA_PTR to reduce macro length
(previous name was tediously long)
- Rename buffer "usable_size" to "alloc_size" throughout as they have been
the same for a while now (they used to differ when buffer had an extra NUL).
- Add memory optimization markers to Duktape.env (pointer compression and
individual 16-bit field options)
- Rename a few internal fields for clarity: duk_hobject 'p' to 'props',
heap->st to heap->strtable
- Add a safety check for buffer alloc size (should not be triggered but
prevents wrapping if call sites don't properly check for sizes)
- Other minor cleanups
10 years ago
|
|
|
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)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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"));
|
|
|
|
}
|
|
|
|
#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_DECENV(h)) {
|
|
|
|
duk_hdecenv *e = (duk_hdecenv *) h;
|
|
|
|
DUK_ASSERT_HDECENV_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_ASSERT_HOBJENV_VALID(e);
|
|
|
|
duk__mark_heaphdr(heap, (duk_heaphdr *) e->target);
|
|
|
|
} 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;
|
First round of lightfunc changes
A lot of changes to add preliminary lightfunc support:
* Add LIGHTFUNC tagged type to duk_tval.h and API.
* Internal changes for preliminary to support lightfuncs in call handling
and other operations (FIXMEs left in obvious places where support is
still missing after this commit)
* Preliminary Ecmascript and API testcases for lightfuncs
Detailed notes:
* Because magic is signed, reading it back involves sign extension which is
quite verbose to do in C. Use macros for reading the magic value and other
bit fields encoded in the flags.
* Function.prototype.bind(): the 'length' property of a bound function now
comes out wrong. We could simply look up the virtual 'length' property
even if h_target is NULL: no extra code and binding is relatively rare in
hot paths. Rewrite more cleanly in any case.
* The use flag DUK_USE_LIGHTFUNC_BUILTINS controls the forced lightfunc
conversion of built-ins. This results in non-compliant built-ins but
significant memory savings in very memory poor environments.
* Reject eval(), Thread.yield/resume as lightfuncs. These functions have
current assertions that they must be called as fully fledged functions.
* Lightfuncs are serialized like ordinary functions for JSON, JX, and JC
by this diff.
* Add 'magic' to activation for lightfuncs. It will be needed for lightweight
functions: we don't have the duk_tval related to the lightfunc, so we must
copy the magic value to the activation when a call is made.
* When lightfuncs are used as property lookup base values, continue property
lookup from the Function.prototype object. This is necessary to allow e.g.
``func.call()`` and ``func.apply()`` to be used.
* Call handling had to be reworked for lightfuncs, especially how bound
function chains are handled. This is a relatively large change but is
necessary to support lightweight functions properly in bound function
resolution.
The current solution is not ideal. The bytecode executor will first try an
ecma-to-ecma call setup which resolves the bound function chain first. If
the final, unbound function is not viable (a native function) the call setup
returns with an error code. The caller will then perform a normal call.
Although bound function resolution has already been done, the normal call
handling code will re-do it (and detect there is nothing to do).
This situation could be avoided by decoupling bound function handling and
effective this binding computation from the actual call setup. The caller
could then to do this prestep first, and only then decide whether to use an
ecma-to-ecma call or an ordinary heavyweight call.
Remove duk__find_nonbound_function as unused.
* Use indirect magic to allow LIGHTFUNCs for Date. Most of the built-in
functions not directly eligible as lightfuncs are the Date built-in methods,
whose magic values contain too much information to fit into the 8-bit magic
of a LIGHTFUNC value.
To work around this, add an array (duk__date_magics[]) containing the
actual control flags needed by the built-ins, and make the Date built-in
magic value an index into this table. With this change Date built-ins are
successfully converted to lightfuncs.
Testcase fixes:
- Whitespace fixes
- Print error for indirect eval error to make diagnosis easier
- Fix error string to match errmsg updated in this branch
11 years ago
|
|
|
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]);
|
|
|
|
}
|
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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++) {
|
16-bit fields and heap pointer compression work
Memory optimization work for very low memory devices (96 to 256kB system RAM).
Overall changes are:
- 16-bit fields for various internal structures to reduce their size
- Heap pointer compression to reduce pointer size to 16 bits
When DUK_OPT_LIGHTFUNC_BUILTINS and the new low memory options are enabled,
Duktape initial heap memory usage is about 23kB (compared to baseline of
about 45kB) on x86.
Unless low memory feature options are enabled, there should be no visible
changes to Duktape behavior.
More detailed changes:
- 16-bit changes for duk_heaphdr: pointer compression, refcount
- 16-bit changes for duk_hstring: hash, blen, and clen can all be 16 bits,
use 0xFFFF as string byte length limit (call sites ensure this limit is
never exceeded)
- 16-bit changes for duk_hbuffer, use 0xFFFF as buffer length limit
- 16-bit fields for hobject size (entry part, array part), drop hash part
since it's not usually needed for extremely low memory environments
- 16-bit changes for duk_hcompiledfunction
- Heap pointer packing for stringtable
- Heap pointer packing for 'strs' built-in strings list (saves around 600
to 700 bytes but may not be a good tradeoff because call site size will
increase)
Other changes:
- Heaphdr NULL init fix. The original macros were broken: the double/single
linked macro variants were the wrong way around. Now sets through macro
to work properly with compressed pointers.
- Rename duk_hbuffer CURR_DATA_PTR -> DATA_PTR to reduce macro length
(previous name was tediously long)
- Rename buffer "usable_size" to "alloc_size" throughout as they have been
the same for a while now (they used to differ when buffer had an extra NUL).
- Add memory optimization markers to Duktape.env (pointer compression and
individual 16-bit field options)
- Rename a few internal fields for clarity: duk_hobject 'p' to 'props',
heap->st to heap->strtable
- Add a safety check for buffer alloc size (should not be triggered but
prevents wrapping if call sites don't properly check for sizes)
- Other minor cleanups
10 years ago
|
|
|
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
|
Use DUK_HOBJECT_FLAG_HAVE_FINALIZER for checks
One bottleneck in refzero and mark-and-sweep handling is checking whether an
object has an own or inherited _Finalizer property. This check walked the
prototype chain and did a property lookup for every object. Because a
finalizer is usually not present, the prototype chain would almost always be
walked to completion.
Improve this behavior by:
* Adding a DUK_HOBJECT_FLAG_HAVE_FINALIZER flag. The flag is set when the
object has an own _Finalizer property with a callable value, and cleared
otherwise. The flag is *only* set by duk_set_finalizer(), so any other
means of changing the internal _Finalizer property will leave the flag out
of sync (which causes a finalizer run to be skipped).
* Adding duk_hobject_has_finalizer_fast() which checks for finalizer existence
by walking the prototype chain, but only checking the flag, not the property
table.
* Use the fast finalizer check in refzero and mark-and-sweep.
Out-of sync cases:
* If the flag is set but there is no actual finalizer, the object will go
through finalizer processing when garbage collecting. This is harmless:
the finalizer call will fail and the object will be garbage collected, but
with some potential delay (especially for mark-and-sweep).
* If the flag is cleared but there is an actual finalizer, the finalizer will
be ignored.
Related changes:
* When duk_dump_function() is called, zero DUK_HOBJECT_FLAG_HAVE_FINALIZER on
serialization, so it won't be set when the function is loaded back. If this
is not done, the loaded function will (harmlessly) go through finalizer
processing when garbage collected.
* Update debugger artificial properties to include "have_finalizer" flag.
Other changes:
* A few DUK_UNLIKELY() attributes for prototype sanity limits which are
almost never hit.
8 years ago
|
|
|
* (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_GET_TYPE(hdr) == DUK_HTYPE_OBJECT &&
|
|
|
|
!DUK_HEAPHDR_HAS_FINALIZED(hdr) &&
|
Use DUK_HOBJECT_FLAG_HAVE_FINALIZER for checks
One bottleneck in refzero and mark-and-sweep handling is checking whether an
object has an own or inherited _Finalizer property. This check walked the
prototype chain and did a property lookup for every object. Because a
finalizer is usually not present, the prototype chain would almost always be
walked to completion.
Improve this behavior by:
* Adding a DUK_HOBJECT_FLAG_HAVE_FINALIZER flag. The flag is set when the
object has an own _Finalizer property with a callable value, and cleared
otherwise. The flag is *only* set by duk_set_finalizer(), so any other
means of changing the internal _Finalizer property will leave the flag out
of sync (which causes a finalizer run to be skipped).
* Adding duk_hobject_has_finalizer_fast() which checks for finalizer existence
by walking the prototype chain, but only checking the flag, not the property
table.
* Use the fast finalizer check in refzero and mark-and-sweep.
Out-of sync cases:
* If the flag is set but there is no actual finalizer, the object will go
through finalizer processing when garbage collecting. This is harmless:
the finalizer call will fail and the object will be garbage collected, but
with some potential delay (especially for mark-and-sweep).
* If the flag is cleared but there is an actual finalizer, the finalizer will
be ignored.
Related changes:
* When duk_dump_function() is called, zero DUK_HOBJECT_FLAG_HAVE_FINALIZER on
serialization, so it won't be set when the function is loaded back. If this
is not done, the loaded function will (harmlessly) go through finalizer
processing when garbage collected.
* Update debugger artificial properties to include "have_finalizer" flag.
Other changes:
* A few DUK_UNLIKELY() attributes for prototype sanity limits which are
almost never hit.
8 years ago
|
|
|
duk_hobject_has_finalizer_fast(thr, (duk_hobject *) hdr)) {
|
|
|
|
/* 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]);
|
16-bit fields and heap pointer compression work
Memory optimization work for very low memory devices (96 to 256kB system RAM).
Overall changes are:
- 16-bit fields for various internal structures to reduce their size
- Heap pointer compression to reduce pointer size to 16 bits
When DUK_OPT_LIGHTFUNC_BUILTINS and the new low memory options are enabled,
Duktape initial heap memory usage is about 23kB (compared to baseline of
about 45kB) on x86.
Unless low memory feature options are enabled, there should be no visible
changes to Duktape behavior.
More detailed changes:
- 16-bit changes for duk_heaphdr: pointer compression, refcount
- 16-bit changes for duk_hstring: hash, blen, and clen can all be 16 bits,
use 0xFFFF as string byte length limit (call sites ensure this limit is
never exceeded)
- 16-bit changes for duk_hbuffer, use 0xFFFF as buffer length limit
- 16-bit fields for hobject size (entry part, array part), drop hash part
since it's not usually needed for extremely low memory environments
- 16-bit changes for duk_hcompiledfunction
- Heap pointer packing for stringtable
- Heap pointer packing for 'strs' built-in strings list (saves around 600
to 700 bytes but may not be a good tradeoff because call site size will
increase)
Other changes:
- Heaphdr NULL init fix. The original macros were broken: the double/single
linked macro variants were the wrong way around. Now sets through macro
to work properly with compressed pointers.
- Rename duk_hbuffer CURR_DATA_PTR -> DATA_PTR to reduce macro length
(previous name was tediously long)
- Rename buffer "usable_size" to "alloc_size" throughout as they have been
the same for a while now (they used to differ when buffer had an extra NUL).
- Add memory optimization markers to Duktape.env (pointer compression and
individual 16-bit field options)
- Rename a few internal fields for clarity: duk_hobject 'p' to 'props',
heap->st to heap->strtable
- Add a safety check for buffer alloc size (should not be triggered but
prevents wrapping if call sites don't properly check for sizes)
- Other minor cleanups
10 years ago
|
|
|
#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)
|
16-bit fields and heap pointer compression work
Memory optimization work for very low memory devices (96 to 256kB system RAM).
Overall changes are:
- 16-bit fields for various internal structures to reduce their size
- Heap pointer compression to reduce pointer size to 16 bits
When DUK_OPT_LIGHTFUNC_BUILTINS and the new low memory options are enabled,
Duktape initial heap memory usage is about 23kB (compared to baseline of
about 45kB) on x86.
Unless low memory feature options are enabled, there should be no visible
changes to Duktape behavior.
More detailed changes:
- 16-bit changes for duk_heaphdr: pointer compression, refcount
- 16-bit changes for duk_hstring: hash, blen, and clen can all be 16 bits,
use 0xFFFF as string byte length limit (call sites ensure this limit is
never exceeded)
- 16-bit changes for duk_hbuffer, use 0xFFFF as buffer length limit
- 16-bit fields for hobject size (entry part, array part), drop hash part
since it's not usually needed for extremely low memory environments
- 16-bit changes for duk_hcompiledfunction
- Heap pointer packing for stringtable
- Heap pointer packing for 'strs' built-in strings list (saves around 600
to 700 bytes but may not be a good tradeoff because call site size will
increase)
Other changes:
- Heaphdr NULL init fix. The original macros were broken: the double/single
linked macro variants were the wrong way around. Now sets through macro
to work properly with compressed pointers.
- Rename duk_hbuffer CURR_DATA_PTR -> DATA_PTR to reduce macro length
(previous name was tediously long)
- Rename buffer "usable_size" to "alloc_size" throughout as they have been
the same for a while now (they used to differ when buffer had an extra NUL).
- Add memory optimization markers to Duktape.env (pointer compression and
individual 16-bit field options)
- Rename a few internal fields for clarity: duk_hobject 'p' to 'props',
heap->st to heap->strtable
- Add a safety check for buffer alloc size (should not be triggered but
prevents wrapping if call sites don't properly check for sizes)
- Other minor cleanups
10 years ago
|
|
|
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)
|
16-bit fields and heap pointer compression work
Memory optimization work for very low memory devices (96 to 256kB system RAM).
Overall changes are:
- 16-bit fields for various internal structures to reduce their size
- Heap pointer compression to reduce pointer size to 16 bits
When DUK_OPT_LIGHTFUNC_BUILTINS and the new low memory options are enabled,
Duktape initial heap memory usage is about 23kB (compared to baseline of
about 45kB) on x86.
Unless low memory feature options are enabled, there should be no visible
changes to Duktape behavior.
More detailed changes:
- 16-bit changes for duk_heaphdr: pointer compression, refcount
- 16-bit changes for duk_hstring: hash, blen, and clen can all be 16 bits,
use 0xFFFF as string byte length limit (call sites ensure this limit is
never exceeded)
- 16-bit changes for duk_hbuffer, use 0xFFFF as buffer length limit
- 16-bit fields for hobject size (entry part, array part), drop hash part
since it's not usually needed for extremely low memory environments
- 16-bit changes for duk_hcompiledfunction
- Heap pointer packing for stringtable
- Heap pointer packing for 'strs' built-in strings list (saves around 600
to 700 bytes but may not be a good tradeoff because call site size will
increase)
Other changes:
- Heaphdr NULL init fix. The original macros were broken: the double/single
linked macro variants were the wrong way around. Now sets through macro
to work properly with compressed pointers.
- Rename duk_hbuffer CURR_DATA_PTR -> DATA_PTR to reduce macro length
(previous name was tediously long)
- Rename buffer "usable_size" to "alloc_size" throughout as they have been
the same for a while now (they used to differ when buffer had an extra NUL).
- Add memory optimization markers to Duktape.env (pointer compression and
individual 16-bit field options)
- Rename a few internal fields for clarity: duk_hobject 'p' to 'props',
heap->st to heap->strtable
- Add a safety check for buffer alloc size (should not be triggered but
prevents wrapping if call sites don't properly check for sizes)
- Other minor cleanups
10 years ago
|
|
|
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
|
|
|
|
|
|
|
|
if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
|
|
|
|
DUK_D(DUK_DPRINT("refuse to do a recursive mark-and-sweep"));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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: 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_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: 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 */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 */
|
|
|
|
}
|