DUK_LOCAL void duk__queue_refzero(duk_heap *heap, duk_heaphdr *hdr) { /* tail insert: don't disturb head in case refzero is running */ if (heap->refzero_list != NULL) { duk_heaphdr *hdr_prev; hdr_prev = heap->refzero_list_tail; DUK_ASSERT(hdr_prev != NULL); DUK_ASSERT(DUK_HEAPHDR_GET_NEXT(heap, hdr_prev) == NULL); DUK_HEAPHDR_SET_NEXT(heap, hdr, NULL); DUK_HEAPHDR_SET_PREV(heap, hdr, hdr_prev); DUK_HEAPHDR_SET_NEXT(heap, hdr_prev, hdr); DUK_ASSERT_HEAPHDR_LINKS(heap, hdr); DUK_ASSERT_HEAPHDR_LINKS(heap, hdr_prev); heap->refzero_list_tail = hdr; } else { DUK_ASSERT(heap->refzero_list_tail == NULL); DUK_HEAPHDR_SET_NEXT(heap, hdr, NULL); DUK_HEAPHDR_SET_PREV(heap, hdr, NULL); DUK_ASSERT_HEAPHDR_LINKS(heap, hdr); heap->refzero_list = hdr; heap->refzero_list_tail = hdr; } }
DUK_INTERNAL void duk_heap_insert_into_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) { DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING); #ifdef DUK_USE_DOUBLE_LINKED_HEAP if (heap->heap_allocated) { DUK_ASSERT(DUK_HEAPHDR_GET_PREV(heap, heap->heap_allocated) == NULL); DUK_HEAPHDR_SET_PREV(heap, heap->heap_allocated, hdr); } DUK_HEAPHDR_SET_PREV(heap, hdr, NULL); #endif DUK_HEAPHDR_SET_NEXT(heap, hdr, heap->heap_allocated); heap->heap_allocated = hdr; }
DUK_LOCAL void duk__init_object_parts(duk_heap *heap, duk_hobject *obj, duk_uint_t hobject_flags) { #if defined(DUK_USE_EXPLICIT_NULL_INIT) DUK_HOBJECT_SET_PROPS(heap, obj, NULL); #endif /* XXX: macro? sets both heaphdr and object flags */ obj->hdr.h_flags = hobject_flags; DUK_HEAPHDR_SET_TYPE(&obj->hdr, DUK_HTYPE_OBJECT); /* also goes into flags */ #if defined(DUK_USE_HEAPPTR16) /* Zero encoded pointer is required to match NULL */ DUK_HEAPHDR_SET_NEXT(heap, &obj->hdr, NULL); #if defined(DUK_USE_DOUBLE_LINKED_HEAP) DUK_HEAPHDR_SET_PREV(heap, &obj->hdr, NULL); #endif #endif DUK_ASSERT_HEAPHDR_LINKS(heap, &obj->hdr); DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, &obj->hdr); /* * obj->props is intentionally left as NULL, and duk_hobject_props.c must deal * with this properly. This is intentional: empty objects consume a minimum * amount of memory. Further, an initial allocation might fail and cause * 'obj' to "leak" (require a mark-and-sweep) since it is not reachable yet. */ }
/* arbitrary remove only works with double linked heap, and is only required by * reference counting so far. */ DUK_INTERNAL void duk_heap_remove_any_from_heap_allocated(duk_heap *heap, duk_heaphdr *hdr) { DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(hdr) != DUK_HTYPE_STRING); if (DUK_HEAPHDR_GET_PREV(heap, hdr)) { DUK_HEAPHDR_SET_NEXT(heap, DUK_HEAPHDR_GET_PREV(heap, hdr), DUK_HEAPHDR_GET_NEXT(heap, hdr)); } else { heap->heap_allocated = DUK_HEAPHDR_GET_NEXT(heap, hdr); } if (DUK_HEAPHDR_GET_NEXT(heap, hdr)) { DUK_HEAPHDR_SET_PREV(heap, DUK_HEAPHDR_GET_NEXT(heap, hdr), DUK_HEAPHDR_GET_PREV(heap, hdr)); } else { ; } }
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_HEAPHDR_HAS_REACHABLE(curr)) { /* * Reachable object, keep */ DUK_DDD(DUK_DDDPRINT("sweep, reachable: %p", (void *) curr)); if (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) { 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_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 (!heap->heap_allocated) { heap->heap_allocated = curr; } if (prev) { DUK_HEAPHDR_SET_NEXT(heap, prev, 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) { 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; }
static void duk__refzero_free_pending(duk_hthread *thr) { duk_heaphdr *h1, *h2; duk_heap *heap; duk_int_t count = 0; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); /* * Detect recursive invocation */ if (DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)) { DUK_DDD(DUK_DDDPRINT("refzero free running, skip run")); return; } /* * Churn refzero_list until empty */ DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap); while (heap->refzero_list) { duk_hobject *obj; duk_bool_t rescued = 0; /* * Pick an object from the head (don't remove yet). */ h1 = heap->refzero_list; obj = (duk_hobject *) h1; DUK_DD(DUK_DDPRINT("refzero processing %p: %!O", (void *) h1, (duk_heaphdr *) h1)); DUK_ASSERT(DUK_HEAPHDR_GET_PREV(h1) == NULL); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h1) == DUK_HTYPE_OBJECT); /* currently, always the case */ /* * Finalizer check. * * Note: running a finalizer may have arbitrary side effects, e.g. * queue more objects on refzero_list (tail), or even trigger a * mark-and-sweep. * * Note: quick reject check should match vast majority of * objects and must be safe (not throw any errors, ever). */ /* XXX: If object has FINALIZED, it was finalized by mark-and-sweep on * its previous run. Any point in running finalizer again here? If * finalization semantics is changed so that finalizer is only run once, * checking for FINALIZED would happen here. */ /* A finalizer is looked up from the object and up its prototype chain * (which allows inherited finalizers). */ if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) { DUK_DDD(DUK_DDDPRINT("object has a finalizer, run it")); DUK_ASSERT(h1->h_refcount == 0); h1->h_refcount++; /* bump refcount to prevent refzero during finalizer processing */ duk_hobject_run_finalizer(thr, obj); /* must never longjmp */ h1->h_refcount--; /* remove artificial bump */ DUK_ASSERT_DISABLE(h1->h_refcount >= 0); /* refcount is unsigned, so always true */ if (h1->h_refcount != 0) { DUK_DDD(DUK_DDDPRINT("-> object refcount after finalization non-zero, object will be rescued")); rescued = 1; } else { DUK_DDD(DUK_DDDPRINT("-> object refcount still zero after finalization, object will be freed")); } } /* Refzero head is still the same. This is the case even if finalizer * inserted more refzero objects; they are inserted to the tail. */ DUK_ASSERT(h1 == heap->refzero_list); /* * Remove the object from the refzero list. This cannot be done * before a possible finalizer has been executed; the finalizer * may trigger a mark-and-sweep, and mark-and-sweep must be able * to traverse a complete refzero_list. */ h2 = DUK_HEAPHDR_GET_NEXT(h1); if (h2) { DUK_HEAPHDR_SET_PREV(h2, NULL); /* not strictly necessary */ heap->refzero_list = h2; } else { heap->refzero_list = NULL; heap->refzero_list_tail = NULL; } /* * Rescue or free. */ if (rescued) { /* yes -> move back to heap allocated */ DUK_DD(DUK_DDPRINT("object rescued during refcount finalization: %p", (void *) h1)); DUK_HEAPHDR_SET_PREV(h1, NULL); DUK_HEAPHDR_SET_NEXT(h1, heap->heap_allocated); heap->heap_allocated = h1; } else { /* no -> decref members, then free */ duk__refcount_finalize_hobject(thr, obj); duk_heap_free_heaphdr_raw(heap, h1); } count++; } DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap); DUK_DDD(DUK_DDDPRINT("refzero processed %ld objects", (long) count)); /* * Once the whole refzero cascade has been freed, check for * a voluntary mark-and-sweep. */ #if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_VOLUNTARY_GC) /* 'count' is more or less comparable to normal trigger counter update * which happens in memory block (re)allocation. */ heap->mark_and_sweep_trigger_counter -= count; if (heap->mark_and_sweep_trigger_counter <= 0) { duk_bool_t rc; duk_small_uint_t flags = 0; /* not emergency */ DUK_D(DUK_DPRINT("refcount triggering mark-and-sweep")); rc = duk_heap_mark_and_sweep(heap, flags); DUK_UNREF(rc); DUK_D(DUK_DPRINT("refcount triggered mark-and-sweep => rc %ld", (long) rc)); } #endif /* DUK_USE_MARK_AND_SWEEP && DUK_USE_VOLUNTARY_GC */ }
static 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; #ifdef 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 are never placed on the heap allocated list */ DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) != DUK_HTYPE_STRING); next = DUK_HEAPHDR_GET_NEXT(curr); if (DUK_HEAPHDR_HAS_REACHABLE(curr)) { /* * Reachable object, keep */ DUK_DDD(DUK_DDDPRINT("sweep, reachable: %p", (void *) curr)); if (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)); #ifdef DUK_USE_DOUBLE_LINKED_HEAP if (heap->finalize_list) { DUK_HEAPHDR_SET_PREV(heap->finalize_list, curr); } DUK_HEAPHDR_SET_PREV(curr, NULL); #endif DUK_HEAPHDR_SET_NEXT(curr, heap->finalize_list); heap->finalize_list = curr; #ifdef DUK_USE_DEBUG count_finalize++; #endif } else { /* * Object will be kept; queue object back to heap_allocated (to tail) */ if (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)); #ifdef DUK_USE_DEBUG count_rescue++; #endif } else { /* * Plain, boring reachable object. */ count_keep++; } if (!heap->heap_allocated) { heap->heap_allocated = curr; } if (prev) { DUK_HEAPHDR_SET_NEXT(prev, curr); } #ifdef DUK_USE_DOUBLE_LINKED_HEAP DUK_HEAPHDR_SET_PREV(curr, prev); #endif 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. */ #ifdef 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) { DUK_HEAPHDR_SET_NEXT(prev, NULL); } #ifdef DUK_USE_DEBUG DUK_D(DUK_DPRINT("mark-and-sweep sweep objects (non-string): %d freed, %d kept, %d rescued, %d queued for finalization", (int) count_free, (int) count_keep, (int) count_rescue, (int) count_finalize)); #endif *out_count_keep = count_keep; }