예제 #1
0
DUK_LOCAL void duk__run_voluntary_gc(duk_heap *heap) {
	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DD(DUK_DDPRINT("mark-and-sweep in progress -> skip voluntary mark-and-sweep now"));
	} else {
		duk_small_uint_t flags;
		duk_bool_t rc;

		DUK_D(DUK_DPRINT("triggering voluntary mark-and-sweep"));
		flags = 0;
		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);
	}
}
예제 #2
0
DUK_INTERNAL void *duk_heap_mem_alloc(duk_heap *heap, duk_size_t size) {
	void *res;
	duk_bool_t rc;
	duk_small_int_t i;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT_DISABLE(size >= 0);

	/*
	 *  Voluntary periodic GC (if enabled)
	 */

	DUK__VOLUNTARY_PERIODIC_GC(heap);

	/*
	 *  First attempt
	 */

#if defined(DUK_USE_GC_TORTURE)
	/* simulate alloc failure on every alloc (except when mark-and-sweep is running) */
	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first alloc attempt fails"));
		res = NULL;
		DUK_UNREF(res);
		goto skip_attempt;
	}
#endif
	res = heap->alloc_func(heap->heap_udata, size);
	if (res || size == 0) {
		/* for zero size allocations NULL is allowed */
		return res;
	}
#if defined(DUK_USE_GC_TORTURE)
 skip_attempt:
#endif

	DUK_D(DUK_DPRINT("first alloc attempt failed, attempt to gc and retry"));

	/*
	 *  Avoid a GC if GC is already running.  This can happen at a late
	 *  stage in a GC when we try to e.g. resize the stringtable
	 *  or compact objects.
	 */

	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed, gc in progress (gc skipped), alloc size %ld", (long) size));
		return NULL;
	}

	/*
	 *  Retry with several GC attempts.  Initial attempts are made without
	 *  emergency mode; later attempts use emergency mode which minimizes
	 *  memory allocations forcibly.
	 */

	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
		duk_small_uint_t flags;

		flags = 0;
		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
			flags |= DUK_MS_FLAG_EMERGENCY;
		}

		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);

		res = heap->alloc_func(heap->heap_udata, size);
		if (res) {
			DUK_D(DUK_DPRINT("duk_heap_mem_alloc() succeeded after gc (pass %ld), alloc size %ld",
			                 (long) (i + 1), (long) size));
			return res;
		}
	}

	DUK_D(DUK_DPRINT("duk_heap_mem_alloc() failed even after gc, alloc size %ld", (long) size));
	return NULL;
}
예제 #3
0
DUK_INTERNAL void *duk_heap_mem_realloc_indirect(duk_heap *heap, duk_mem_getptr cb, void *ud, duk_size_t newsize) {
	void *res;
	duk_bool_t rc;
	duk_small_int_t i;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT_DISABLE(newsize >= 0);

	/*
	 *  Voluntary periodic GC (if enabled)
	 */

	DUK__VOLUNTARY_PERIODIC_GC(heap);

	/*
	 *  First attempt
	 */

#if defined(DUK_USE_GC_TORTURE)
	/* simulate alloc failure on every realloc (except when mark-and-sweep is running) */
	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first indirect realloc attempt fails"));
		res = NULL;
		DUK_UNREF(res);
		goto skip_attempt;
	}
#endif
	res = heap->realloc_func(heap->heap_udata, cb(heap, ud), newsize);
	if (res || newsize == 0) {
		/* for zero size allocations NULL is allowed */
		return res;
	}
#if defined(DUK_USE_GC_TORTURE)
 skip_attempt:
#endif

	DUK_D(DUK_DPRINT("first indirect realloc attempt failed, attempt to gc and retry"));

	/*
	 *  Avoid a GC if GC is already running.  See duk_heap_mem_alloc().
	 */

	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed, gc in progress (gc skipped), alloc size %ld", (long) newsize));
		return NULL;
	}

	/*
	 *  Retry with several GC attempts.  Initial attempts are made without
	 *  emergency mode; later attempts use emergency mode which minimizes
	 *  memory allocations forcibly.
	 */

	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
		duk_small_uint_t flags;

#if defined(DUK_USE_ASSERTIONS)
		void *ptr_pre;  /* ptr before mark-and-sweep */
		void *ptr_post;
#endif

#if defined(DUK_USE_ASSERTIONS)
		ptr_pre = cb(heap, ud);
#endif
		flags = 0;
		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
			flags |= DUK_MS_FLAG_EMERGENCY;
		}

		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);
#if defined(DUK_USE_ASSERTIONS)
		ptr_post = cb(heap, ud);
		if (ptr_pre != ptr_post) {
			/* useful for debugging */
			DUK_DD(DUK_DDPRINT("note: base pointer changed by mark-and-sweep: %p -> %p",
			                   (void *) ptr_pre, (void *) ptr_post));
		}
#endif

		/* Note: key issue here is to re-lookup the base pointer on every attempt.
		 * The pointer being reallocated may change after every mark-and-sweep.
		 */

		res = heap->realloc_func(heap->heap_udata, cb(heap, ud), newsize);
		if (res || newsize == 0) {
			DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() succeeded after gc (pass %ld), alloc size %ld",
			                 (long) (i + 1), (long) newsize));
			return res;
		}
	}

	DUK_D(DUK_DPRINT("duk_heap_mem_realloc_indirect() failed even after gc, alloc size %ld", (long) newsize));
	return NULL;
}
예제 #4
0
void duk_heap_heaphdr_decref(duk_hthread *thr, duk_heaphdr *h) {
	duk_heap *heap;

#if 0
	DUK_DDD(DUK_DDDPRINT("heaphdr decref %p (%ld->%ld): %!O",
	                     (void *) h,
	                     (h != NULL ? (long) h->h_refcount : (long) 0),
	                     (h != NULL ? (long) (h->h_refcount - 1) : (long) 0),
	                     (duk_heaphdr *) h));
#endif

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);

	if (!h) {
		return;
	}
	DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
	DUK_ASSERT(h->h_refcount >= 1);

	if (--h->h_refcount != 0) {
		return;
	}

	heap = thr->heap;
	DUK_DDD(DUK_DDDPRINT("refzero %p: %!O", (void *) h, (duk_heaphdr *) h));

#ifdef DUK_USE_MARK_AND_SWEEP
	/*
	 *  If mark-and-sweep is running, don't process 'refzero' situations at all.
	 *  They may happen because mark-and-sweep needs to finalize refcounts for
	 *  each object it sweeps.  Otherwise the target objects of swept objects
	 *  would have incorrect refcounts.
	 *
	 *  Note: mark-and-sweep could use a separate decref handler to avoid coming
	 *  here at all.  However, mark-and-sweep may also call finalizers, which
	 *  can do arbitrary operations and would use this decref variant anyway.
	 */
	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h));
		return;
	}
#endif

	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		/*
		 *  Strings have no internal references but do have "weak"
		 *  references in the string cache.  Also note that strings
		 *  are not on the heap_allocated list like other heap
		 *  elements.
		 */

		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
		duk_heap_string_remove(heap, (duk_hstring *) h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	case DUK_HTYPE_OBJECT:
		/*
		 *  Objects have internal references.  Must finalize through
		 *  the "refzero" work list.
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk__queue_refzero(heap, h);
		duk__refzero_free_pending(thr);
		break;

	case DUK_HTYPE_BUFFER:
		/*
		 *  Buffers have no internal references.  However, a dynamic
		 *  buffer has a separate allocation for the buffer.  This is
		 *  freed by duk_heap_free_heaphdr_raw().
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	default:
		DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h)));
		DUK_UNREACHABLE();
	}
}
예제 #5
0
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 */
}
예제 #6
0
int duk_heap_mark_and_sweep(duk_heap *heap, int flags) {
	duk_size_t count_keep_obj;
	duk_size_t count_keep_str;
	duk_size_t tmp;

	/* FIXME: 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 (duk__get_temp_hthread(heap) == NULL) {
		DUK_D(DUK_DPRINT("temporary hack: gc skipped because we don't have a temp thread"));

		/* reset voluntary gc trigger count */
#ifdef DUK_USE_VOLUNTARY_GC
		heap->mark_and_sweep_trigger_counter = DUK_HEAP_MARK_AND_SWEEP_TRIGGER_SKIP;
#endif
		return 0;  /* OK */
	}

	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) starting, requested flags: 0x%08x, effective flags: 0x%08x",
	                 flags, flags | heap->mark_and_sweep_base_flags));

	flags |= heap->mark_and_sweep_base_flags;

	/*
	 *  Assertions before
	 */

#ifdef 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);
#ifdef 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).
	 */

	duk__mark_roots_heap(heap);               /* main reachability roots */
#ifdef DUK_USE_REFERENCE_COUNTING
	duk__mark_refzero_list(heap);             /* refzero_list treated as reachability roots */
#endif
	duk__mark_temproots_by_heap_scan(heap);   /* temproots */

	duk__mark_finalizable(heap);              /* mark finalizable as reachability roots */
	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 */

#ifdef DUK_USE_REFERENCE_COUNTING
	duk__finalize_refcounts(heap);
#endif
	duk__sweep_heap(heap, flags, &count_keep_obj);
	duk__sweep_stringtable(heap, &count_keep_str);
#ifdef DUK_USE_REFERENCE_COUNTING
	duk__clear_refzero_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.
	 *
	 *  Note: this may silently (and safely) fail if GC is caused by an
	 *  allocation call in stringtable resize_hash().  Resize_hash()
	 *  will prevent a recursive call to itself by setting the
	 *  DUK_MS_FLAG_NO_STRINGTABLE_RESIZE in heap->mark_and_sweep_base_flags.
	 */

	/* XXX: stringtable emergency compaction? */

#if defined(DUK_USE_MS_STRINGTABLE_RESIZE)
	if (!(flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE)) {
		DUK_DD(DUK_DDPRINT("resize stringtable: %p", (void *) heap));
		duk_heap_force_stringtable_resize(heap);
	} else {
		DUK_D(DUK_DPRINT("stringtable resize skipped because DUK_MS_FLAG_NO_STRINGTABLE_RESIZE is set"));
	}
#endif

	/*
	 *  Finalize objects in the finalization work list.  Finalized
	 *  objects are queued back to heap_allocated with FINALIZED set.
	 *
	 *  Since finalizers may cause arbitrary side effects, they are
	 *  prevented 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 (!(flags & DUK_MS_FLAG_NO_FINALIZERS)) {
		duk__run_object_finalizers(heap);
	} else {
		DUK_D(DUK_DPRINT("finalizer run skipped because DUK_MS_FLAG_NO_FINALIZERS is set"));
	}

	/*
	 *  Finish
	 */

	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);

	/*
	 *  Assertions after
	 */

#ifdef 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);
#ifdef 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
	 */

#ifdef DUK_USE_VOLUNTARY_GC
	tmp = (count_keep_obj + count_keep_str) / 256;
	heap->mark_and_sweep_trigger_counter =
	    (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: %d objects kept, %d strings kept, trigger reset to %d",
	                 (int) count_keep_obj, (int) count_keep_str, (int) heap->mark_and_sweep_trigger_counter));
#else
	DUK_D(DUK_DPRINT("garbage collect (mark-and-sweep) finished: %d objects kept, %d strings kept, no voluntary trigger",
	                 (int) count_keep_obj, (int) count_keep_str));
#endif
	return 0;  /* OK */
}
예제 #7
0
void *duk_heap_mem_realloc(duk_heap *heap, void *ptr, size_t newsize) {
	void *res;
	int rc;
	int i;

	DUK_ASSERT(heap != NULL);
	/* ptr may be NULL */
	DUK_ASSERT_DISABLE(newsize >= 0);

	/*
	 *  Voluntary periodic GC (if enabled)
	 */

	DUK__VOLUNTARY_PERIODIC_GC(heap);

	/*
	 *  First attempt
	 */

#ifdef DUK_USE_GC_TORTURE
	/* simulate alloc failure on every realloc (except when mark-and-sweep is running) */
	if (!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_DDD(DUK_DDDPRINT("gc torture enabled, pretend that first realloc attempt fails"));
		res = NULL;
		DUK_UNREF(res);
		goto skip_attempt;
	}
#endif
	res = heap->realloc_func(heap->alloc_udata, ptr, newsize);
	if (res || newsize == 0) {
		/* for zero size allocations NULL is allowed */
		return res;
	}
#ifdef DUK_USE_GC_TORTURE
 skip_attempt:
#endif

	DUK_D(DUK_DPRINT("first realloc attempt failed, attempt to gc and retry"));

	/*
	 *  Avoid a GC if GC is already running.  See duk_heap_mem_alloc().
	 */

	if (DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)) {
		DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed, gc in progress (gc skipped), alloc size %d", newsize));
		return NULL;
	}

	/*
	 *  Retry with several GC attempts.  Initial attempts are made without
	 *  emergency mode; later attempts use emergency mode which minimizes
	 *  memory allocations forcibly.
	 */

	for (i = 0; i < DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_LIMIT; i++) {
		int flags;

		flags = 0;
		if (i >= DUK_HEAP_ALLOC_FAIL_MARKANDSWEEP_EMERGENCY_LIMIT - 1) {
			flags |= DUK_MS_FLAG_EMERGENCY;
		}

		rc = duk_heap_mark_and_sweep(heap, flags);
		DUK_UNREF(rc);

		res = heap->realloc_func(heap->alloc_udata, ptr, newsize);
		if (res) {
			DUK_D(DUK_DPRINT("duk_heap_mem_realloc() succeeded after gc (pass %d), alloc size %d",
			                 i + 1, newsize));
			return res;
		}
	}

	DUK_D(DUK_DPRINT("duk_heap_mem_realloc() failed even after gc, alloc size %d", newsize));
	return NULL;
}
예제 #8
0
DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) {
	duk_hthread *thr;
	duk_heaphdr *curr;
	duk_uint_t round_no;
	duk_size_t count_all;
	duk_size_t count_finalized;
	duk_size_t curr_limit;

	DUK_ASSERT(heap != NULL);
	DUK_ASSERT(heap->heap_thread != NULL);

#if defined(DUK_USE_REFERENCE_COUNTING)
	DUK_ASSERT(heap->refzero_list == NULL);  /* refzero not running -> must be empty */
#endif
#if defined(DUK_USE_MARK_AND_SWEEP)
	DUK_ASSERT(heap->finalize_list == NULL);  /* mark-and-sweep not running -> must be empty */
#endif

	/* XXX: here again finalizer thread is the heap_thread which needs
	 * to be coordinated with finalizer thread fixes.
	 */
	thr = heap->heap_thread;
	DUK_ASSERT(thr != NULL);

	/* Prevent mark-and-sweep for the pending finalizers, also prevents
	 * refzero handling from moving objects away from the heap_allocated
	 * list.  (The flag meaning is slightly abused here.)
	 */
	DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap);

	curr_limit = 0;  /* suppress warning, not used */
	for (round_no = 0; ; round_no++) {
		curr = heap->heap_allocated;
		count_all = 0;
		count_finalized = 0;
		while (curr) {
			count_all++;
			if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) {
				/* Only objects in heap_allocated may have finalizers.  Check that
				 * the object itself has a _Finalizer property (own or inherited)
				 * so that we don't execute finalizers for e.g. Proxy objects.
				 */
				DUK_ASSERT(thr != NULL);
				DUK_ASSERT(curr != NULL);

				if (duk_hobject_hasprop_raw(thr, (duk_hobject *) curr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) {
					if (!DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) curr)) {
						DUK_ASSERT(DUK_HEAP_HAS_FINALIZER_NORESCUE(heap));  /* maps to finalizer 2nd argument */
						duk_hobject_run_finalizer(thr, (duk_hobject *) curr);
						count_finalized++;
					}
				}
			}
			curr = DUK_HEAPHDR_GET_NEXT(heap, curr);
		}

		/* Each round of finalizer execution may spawn new finalizable objects
		 * which is normal behavior for some applications.  Allow multiple
		 * rounds of finalization, but use a shrinking limit based on the
		 * first round to detect the case where a runaway finalizer creates
		 * an unbounded amount of new finalizable objects.  Finalizer rescue
		 * is not supported: the semantics are unclear because most of the
		 * objects being finalized here are already reachable.  The finalizer
		 * is given a boolean to indicate that rescue is not possible.
		 *
		 * See discussion in: https://github.com/svaarala/duktape/pull/473
		 */

		if (round_no == 0) {
			/* Cannot wrap: each object is at least 8 bytes so count is
			 * at most 1/8 of that.
			 */
			curr_limit = count_all * 2;
		} else {
			curr_limit = (curr_limit * 3) / 4;   /* Decrease by 25% every round */
		}
		DUK_D(DUK_DPRINT("finalizer round %ld complete, %ld objects, tried to execute %ld finalizers, current limit is %ld",
		                 (long) round_no, (long) count_all, (long) count_finalized, (long) curr_limit));

		if (count_finalized == 0) {
			DUK_D(DUK_DPRINT("no more finalizable objects, forced finalization finished"));
			break;
		}
		if (count_finalized >= curr_limit) {
			DUK_D(DUK_DPRINT("finalizer count above limit, potentially runaway finalizer; skip remaining finalizers"));
			break;
		}
	}

	DUK_ASSERT(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap));
	DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap);
}
예제 #9
0
DUK_INTERNAL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h) {
	duk_heap *heap;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(h != NULL);

	heap = thr->heap;
	DUK_DDD(DUK_DDDPRINT("refzero %p: %!O", (void *) h, (duk_heaphdr *) h));

	/*
	 *  If mark-and-sweep is running, don't process 'refzero' situations at all.
	 *  They may happen because mark-and-sweep needs to finalize refcounts for
	 *  each object it sweeps.  Otherwise the target objects of swept objects
	 *  would have incorrect refcounts.
	 *
	 *  This check must be enabled also when mark-and-sweep support has been
	 *  disabled: the flag is also used in heap destruction when running
	 *  finalizers for remaining objects, and the flag prevents objects from
	 *  being moved around in heap linked lists.
	 *
	 *  Note: mark-and-sweep could use a separate decref handler to avoid coming
	 *  here at all.  However, mark-and-sweep may also call finalizers, which
	 *  can do arbitrary operations and would use this decref variant anyway.
	 */
	if (DUK_UNLIKELY(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap))) {
		DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h));
		return;
	}

	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		/*
		 *  Strings have no internal references but do have "weak"
		 *  references in the string cache.  Also note that strings
		 *  are not on the heap_allocated list like other heap
		 *  elements.
		 */

		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
		duk_heap_string_remove(heap, (duk_hstring *) h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	case DUK_HTYPE_OBJECT:
		/*
		 *  Objects have internal references.  Must finalize through
		 *  the "refzero" work list.
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk__queue_refzero(heap, h);
		duk__refzero_free_pending(thr);
		break;

	case DUK_HTYPE_BUFFER:
		/*
		 *  Buffers have no internal references.  However, a dynamic
		 *  buffer has a separate allocation for the buffer.  This is
		 *  freed by duk_heap_free_heaphdr_raw().
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	default:
		DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h)));
		DUK_UNREACHABLE();
	}
}
예제 #10
0
DUK_INTERNAL void duk_heaphdr_refzero(duk_hthread *thr, duk_heaphdr *h) {
	duk_heap *heap;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(h != NULL);

	heap = thr->heap;
	DUK_DDD(DUK_DDDPRINT("refzero %p: %!O", (void *) h, (duk_heaphdr *) h));

	/*
	 *  Refzero handling is skipped entirely if (1) mark-and-sweep is
	 *  running or (2) execution is paused in the debugger.  The objects
	 *  are left in the heap, and will be freed by mark-and-sweep or
	 *  eventual heap destruction.
	 *
	 *  This is necessary during mark-and-sweep because refcounts are also
	 *  updated during the sweep phase (otherwise objects referenced by a
	 *  swept object would have incorrect refcounts) which then calls here.
	 *  This could be avoided by using separate decref macros in
	 *  mark-and-sweep; however, mark-and-sweep also calls finalizers which
	 *  would use the ordinary decref macros anyway and still call this
	 *  function.
	 *
	 *  This check must be enabled also when mark-and-sweep support has been
	 *  disabled: the flag is also used in heap destruction when running
	 *  finalizers for remaining objects, and the flag prevents objects from
	 *  being moved around in heap linked lists.
	 */

	/* XXX: ideally this would be just one flag (maybe a derived one) so
	 * that a single bit test is sufficient to check the condition.
	 */
#if defined(DUK_USE_DEBUGGER_SUPPORT)
	if (DUK_UNLIKELY(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap) || DUK_HEAP_IS_PAUSED(heap))) {
#else
	if (DUK_UNLIKELY(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap))) {
#endif
		DUK_DDD(DUK_DDDPRINT("refzero handling suppressed when mark-and-sweep running, object: %p", (void *) h));
		return;
	}

	switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
	case DUK_HTYPE_STRING:
		/*
		 *  Strings have no internal references but do have "weak"
		 *  references in the string cache.  Also note that strings
		 *  are not on the heap_allocated list like other heap
		 *  elements.
		 */

		duk_heap_strcache_string_remove(heap, (duk_hstring *) h);
		duk_heap_string_remove(heap, (duk_hstring *) h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	case DUK_HTYPE_OBJECT:
		/*
		 *  Objects have internal references.  Must finalize through
		 *  the "refzero" work list.
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk__queue_refzero(heap, h);
		duk__refzero_free_pending(thr);
		break;

	case DUK_HTYPE_BUFFER:
		/*
		 *  Buffers have no internal references.  However, a dynamic
		 *  buffer has a separate allocation for the buffer.  This is
		 *  freed by duk_heap_free_heaphdr_raw().
		 */

		duk_heap_remove_any_from_heap_allocated(heap, h);
		duk_heap_free_heaphdr_raw(heap, h);
		break;

	default:
		DUK_D(DUK_DPRINT("invalid heap type in decref: %ld", (long) DUK_HEAPHDR_GET_TYPE(h)));
		DUK_UNREACHABLE();
	}
}

#if !defined(DUK_USE_FAST_REFCOUNT_DEFAULT)
DUK_INTERNAL void duk_tval_incref(duk_tval *tv) {
	DUK_ASSERT(tv != NULL);

	if (DUK_TVAL_NEEDS_REFCOUNT_UPDATE(tv)) {
		duk_heaphdr *h = DUK_TVAL_GET_HEAPHDR(tv);
		DUK_ASSERT(h != NULL);
		DUK_ASSERT(DUK_HEAPHDR_HTYPE_VALID(h));
		DUK_ASSERT_DISABLE(h->h_refcount >= 0);
		DUK_HEAPHDR_PREINC_REFCOUNT(h);
	}
}