summaryrefslogtreecommitdiff
path: root/source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c')
-rwxr-xr-xsource4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c1214
1 files changed, 1214 insertions, 0 deletions
diff --git a/source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c b/source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c
new file mode 100755
index 0000000000..264da05721
--- /dev/null
+++ b/source4/lib/appweb/ejs-2.0/ejs/ejsGarbage.c
@@ -0,0 +1,1214 @@
+/*
+ * @file ejsGarbage.c
+ * @brief EJS Garbage collector.
+ * @overview This implements a generational mark and sweep collection scheme.
+ */
+/********************************* Copyright **********************************/
+/*
+ * @copy default
+ *
+ * Copyright (c) Mbedthis Software LLC, 2003-2006. All Rights Reserved.
+ *
+ * This software is distributed under commercial and open source licenses.
+ * You may use the GPL open source license described below or you may acquire
+ * a commercial license from Mbedthis Software. You agree to be fully bound
+ * by the terms of either license. Consult the LICENSE.TXT distributed with
+ * this software for full details.
+ *
+ * This software is open source; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See the GNU General Public License for more
+ * details at: http://www.mbedthis.com/downloads/gplLicense.html
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the
+ * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * This GPL license does NOT permit incorporating this software into
+ * proprietary programs. If you are unable to comply with the GPL, you must
+ * acquire a commercial license to use this software. Commercial licenses
+ * for this software and support services are available from Mbedthis
+ * Software at http://www.mbedthis.com
+ *
+ * @end
+ */
+/********************************** Includes **********************************/
+
+#include "ejs.h"
+
+#if BLD_FEATURE_EJS
+
+/****************************** Forward Declarations **************************/
+
+static void mark(Ejs *ep);
+static void markObjByVar(Ejs *ep, EjsVar *op);
+static void markObj(EjsObj *obj);
+static void markPerm(Ejs *ep, uint gen);
+static int sweep(Ejs *ep, uint gen);
+static EjsGCLink *ejsAlloc(EJS_LOC_DEC(ep, loc), int slabIndex);
+static void ejsGracefulDegrade(Ejs *ep);
+static void resetMarks(Ejs *ep, EjsSlab *slab);
+
+#if FUTURE
+static void ageGenerations(Ejs *ep);
+#endif
+
+#if BLD_DEBUG && (!BREW || BREW_SIMULATOR)
+uint breakAddr;
+#endif
+
+/************************************* Code ***********************************/
+
+void ejsGCInit(Ejs *ep, int objInc, int propInc, int varInc, int strInc)
+{
+ EjsSlab *slab;
+
+ if (ep->service && ep->service->globalClass) {
+ ep->service->globalClass->objectState->gcMarked = 1;
+ }
+
+ slab = &ep->slabs[EJS_SLAB_OBJ];
+ slab->allocIncrement = objInc;
+ slab->size = EJS_ALLOC_ALIGN(sizeof(EjsObj));
+
+ slab = &ep->slabs[EJS_SLAB_PROPERTY];
+ slab->allocIncrement = propInc;
+ slab->size = EJS_ALLOC_ALIGN(sizeof(EjsProperty));
+
+ slab = &ep->slabs[EJS_SLAB_VAR];
+ slab->allocIncrement = varInc;
+ slab->size = EJS_ALLOC_ALIGN(sizeof(EjsVar));
+
+ /*
+ * Initialize GC.
+ * Enable GC both idle and demand collections.
+ * Set no limits and garbage collect if the slabs are
+ * empty and we have used more than the THRESHOLD of ram.
+ */
+ ep->gc.debugLevel = 0;
+ ep->gc.enable = 1;
+ ep->gc.enableIdleCollect = 1;
+ ep->gc.enableDemandCollect = 1;
+ ep->gc.workQuota = EJS_GC_WORK_QUOTA;
+ ep->gc.maxMemory = 0;
+}
+
+
+/******************************************************************************/
+#if BLD_FEATURE_ALLOC_STATS
+
+void ejsPrintAllocReport(Ejs *ep, bool printLeakReport)
+{
+ EjsSlab *slab;
+ char *name;
+ int slabIndex, isObj;
+
+ for (slabIndex = 0; slabIndex < EJS_SLAB_MAX; slabIndex++) {
+ slab = &ep->slabs[slabIndex];
+ if (slabIndex == EJS_SLAB_VAR) {
+ name = "var";
+ } else if (slabIndex == EJS_SLAB_PROPERTY) {
+ name = "prop";
+ } else {
+ name = "obj";
+ }
+ mprLog(ep, 0, " ");
+ mprLog(ep, 0, " GC \"%s\" local slab", name);
+ mprLog(ep, 0, " Total blocks %14d",
+ slab->allocCount + slab->freeCount);
+ mprLog(ep, 0, " Block size %14d", slab->size);
+ mprLog(ep, 0, " Slab RAM allocated %14d",
+ (slab->allocCount + slab->freeCount) * slab->size);
+ mprLog(ep, 0, " Slab RAM in use %14d",
+ slab->allocCount * slab->size);
+ mprLog(ep, 0, " Blocks in use %14d", slab->allocCount);
+ mprLog(ep, 0, " Free blocks %14d", slab->freeCount);
+ mprLog(ep, 0, " Peak allocated %14d", slab->peakAllocated);
+ mprLog(ep, 0, " Peak free %14d", slab->peakFree);
+ mprLog(ep, 0, " Total allocations %14d", slab->totalAlloc);
+ mprLog(ep, 0, " Total blocks reclaimed %14d", slab->totalReclaimed);
+ mprLog(ep, 0, " Total sweeps %14d", slab->totalSweeps);
+ mprLog(ep, 0, " Allocation inc %14d", slab->allocIncrement);
+ }
+
+ mprLog(ep, 0, " ");
+ mprLog(ep, 0, " Total EJS memory in use %10d", ejsGetUsedMemory(ep));
+ mprLog(ep, 0, " Total EJS memory allocated %10d",
+ ejsGetAllocatedMemory(ep));
+
+ if (printLeakReport) {
+ mprLog(ep, 0, " ");
+ for (slabIndex = 0; slabIndex < EJS_SLAB_MAX; slabIndex++) {
+ int size;
+
+ slab = &ep->slabs[slabIndex];
+
+ isObj = 0;
+ mprLog(ep, 0, " ");
+ if (slabIndex == EJS_SLAB_VAR) {
+ name = "var";
+ size = sizeof(EjsVar);
+ } else if (slabIndex == EJS_SLAB_PROPERTY) {
+ name = "prop";
+ size = sizeof(EjsProperty);
+ } else {
+ name = "obj";
+ size = sizeof(EjsObj);
+ isObj++;
+ }
+#if BLD_FEATURE_ALLOC_LEAK_TRACK
+{
+ EjsGCLink *lp;
+ EjsObj *obj;
+ int count;
+
+ mprLog(ep, 0, "EJS Leak Report for \"%s\"", name);
+ count = 0;
+
+ for (lp = slab->allocList[0].next; lp; lp = lp->next) {
+ mprLog(ep, 0, " %-20s %10d", lp->allocatedBy, size);
+ if (isObj) {
+ obj = (EjsObj*) lp;
+ mprLog(ep, 0, " %-20s %10d %s %s",
+ lp->allocatedBy, size,
+ obj->permanent ? "permanent" : "",
+ obj->alive ? "alive" : ""
+ );
+ } else {
+ mprLog(ep, 0, " %-20s %10d", lp->allocatedBy,
+ size);
+ }
+ count++;
+ }
+ mprLog(ep, 0, " Total blocks %14d", count);
+}
+#endif
+ }
+ mprLog(ep, 0, " ");
+ }
+}
+
+#endif
+/******************************************************************************/
+/*
+ * Slab allocator
+ */
+
+static EjsGCLink *ejsAlloc(EJS_LOC_DEC(ep, loc), int slabIndex)
+{
+ EjsSlab *slab;
+ EjsGCLink *block;
+ EjsGC *gc;
+ uint allocatedMemory;
+ int i;
+
+ mprStackCheck(ep);
+
+ if (slabIndex < 0 || slabIndex >= EJS_SLAB_MAX) {
+ mprAssert(0);
+ return 0;
+ }
+
+ /*
+ * See if the slab has some free blocks
+ */
+ slab = &ep->slabs[slabIndex];
+ if ((block = slab->freeList.next) == 0) {
+
+ allocatedMemory = ejsGetAllocatedMemory(ep);
+ gc = &ep->gc;
+
+ /*
+ * No blocks available. If demand collection is enabled, try
+ * to garbage collect first. We collect if we have done a good
+ * work quota or we are over the max memory limit.
+ */
+ if (slabIndex != EJS_SLAB_VAR &&
+ ep->gc.enable && ep->gc.enableDemandCollect) {
+ if ((ep->gc.workDone > ep->gc.workQuota) ||
+ (gc->maxMemory > 0 && allocatedMemory > gc->maxMemory)) {
+
+#if DEBUG_USE_ONLY
+ if (ep->gc.debugLevel > 0) {
+ mprLog(ep, 0, "Need GC, EJS RAM %d, MPR RAM %d\n",
+ allocatedMemory, mprGetAllocatedMemory(ep));
+ if (ep->gc.debugLevel > 4) {
+ ejsPrintAllocReport(ep, 0);
+ }
+ }
+#endif
+ if (ejsCollectGarbage(ep, slabIndex) == 0) {
+ block = slab->freeList.next;
+ }
+ }
+ }
+
+ if (block == 0) {
+ if (gc->maxMemory > 0 && allocatedMemory > gc->maxMemory) {
+ /*
+ * We are above the max memory limit. We will fail this
+ * memory allocation, but allow subsequent allocations to
+ * permit error recovery. We gracefully degrade by setting
+ * slab chunk sizes to 1. This minimizes real memory
+ * consumption. This allows us to create
+ * an exception block to be created by upper layers.
+ */
+ if (! gc->degraded) {
+ ejsGracefulDegrade(ep);
+ return 0;
+ }
+ }
+
+ /*
+ * Still non available, so allocate more memory for a set of blocks
+ * OPT -- should bypass mprAlloc. Need mprMalloc.
+ */
+ block = mprAlloc(ep->slabAllocContext,
+ slab->size * slab->allocIncrement);
+ if (block == 0) {
+ /*
+ * Now we're in trouble. We should really never get here
+ * as the graceful degrade will have signaled a memory
+ * allocation failure.
+ */
+ mprAssert(block != 0);
+ return 0;
+ }
+
+ /*
+ * Chain all the blocks together onto the slab free list
+ */
+ for (i = slab->allocIncrement - 1; i >= 0; i--) {
+ block->next = slab->freeList.next;
+#if BLD_DEBUG
+ block->magic = EJS_MAGIC_FREE;
+#endif
+ slab->freeList.next = block;
+ block = (EjsGCLink*) ((char*) block + slab->size);
+ }
+
+ block = slab->freeList.next;
+
+#if BLD_FEATURE_ALLOC_STATS
+ slab->freeCount += slab->allocIncrement;
+ if (slab->freeCount > slab->peakFree) {
+ slab->peakFree = slab->freeCount;
+ }
+#endif
+ }
+ }
+
+ /*
+ * We use block to point to the user data in the block. We only
+ * store the magic number (if debug). No other data is stored in the
+ * user block.
+ */
+#if BLD_DEBUG
+ mprAssert(block->magic == EJS_MAGIC_FREE);
+#endif
+
+ /*
+ * Remove from the free list
+ */
+ slab->freeList.next = block->next;
+
+ /*
+ * Zero block
+ */
+ memset(block, 0, slab->size);
+
+#if BLD_DEBUG
+ block->magic = EJS_MAGIC;
+#endif
+
+#if BLD_FEATURE_ALLOC_STATS
+ slab->totalAlloc++;
+ if (++slab->allocCount > slab->peakAllocated) {
+ slab->peakAllocated = slab->allocCount;
+ }
+ slab->freeCount--;
+#endif
+
+#if BLD_DEBUG && (!BREW || BREW_SIMULATOR)
+ if ((uint) block == breakAddr) {
+ mprBreakpoint(MPR_LOC, "Watched Block");
+ }
+#endif
+ return block;
+}
+
+
+/******************************************************************************/
+
+EjsObj *ejsAllocObj(EJS_LOC_DEC(ep, loc))
+{
+ EjsObj *obj;
+ EjsSlab *slab;
+
+ obj = (EjsObj*) ejsAlloc(EJS_LOC_PASS(ep, loc), EJS_SLAB_OBJ);
+
+ /*
+ * Add to the allocated block list for the New generation.
+ */
+ if (obj) {
+ slab = &ep->slabs[EJS_SLAB_OBJ];
+ obj->gc.next = slab->allocList[EJS_GEN_NEW].next;
+
+#if BLD_FEATURE_ALLOC_LEAK_TRACK
+ obj->gc.allocatedBy = loc;
+#endif
+
+ obj->ejs = ep;
+ slab->allocList[EJS_GEN_NEW].next = (EjsGCLink*) obj;
+
+ ep->gc.workDone++;
+ }
+
+ return obj;
+}
+
+
+/******************************************************************************/
+
+EjsProperty *ejsAllocProperty(EJS_LOC_DEC(ep, loc))
+{
+ EjsProperty *prop;
+
+ prop = (EjsProperty*) ejsAlloc(EJS_LOC_PASS(ep, loc), EJS_SLAB_PROPERTY);
+ mprAssert(prop);
+
+ if (prop) {
+ prop->var.type = EJS_TYPE_NULL;
+ prop->var.isProperty = 1;
+#if BLD_FEATURE_ALLOC_LEAK_TRACK
+ prop->var.gc.allocatedBy = loc;
+#endif
+ }
+ return prop;
+}
+
+
+/******************************************************************************/
+
+EjsVar *ejsAllocVar(EJS_LOC_DEC(ep, loc))
+{
+ EjsVar *vp;
+
+ vp = (EjsVar*) ejsAlloc(EJS_LOC_PASS(ep, loc), EJS_SLAB_VAR);
+ mprAssert(vp);
+
+ if (vp) {
+#if BLD_FEATURE_ALLOC_LEAK_TRACK
+ EjsSlab *slab;
+ vp->gc.allocatedBy = loc;
+ slab = &ep->slabs[EJS_SLAB_VAR];
+ vp->gc.next = slab->allocList[EJS_GEN_NEW].next;
+ slab->allocList[EJS_GEN_NEW].next = (EjsGCLink*) vp;
+#endif
+#if BLD_DEBUG
+ vp->propertyName = 0;
+#endif
+ }
+ return vp;
+}
+
+
+/******************************************************************************/
+/*
+ * Return the block back to the relevant slab
+ */
+
+void ejsFree(Ejs *ep, void *ptr, int slabIndex)
+{
+ EjsSlab *slab;
+ EjsGCLink *block;
+
+ mprAssert(ep);
+ mprAssert(ptr);
+
+ if (slabIndex < 0 || slabIndex >= EJS_SLAB_MAX) {
+ mprAssert(slabIndex >= 0 && slabIndex < EJS_SLAB_MAX);
+ return;
+ }
+ slab = &ep->slabs[slabIndex];
+
+#if BLD_FEATURE_ALLOC_LEAK_TRACK
+ if (slabIndex == EJS_SLAB_VAR) {
+ EjsVar *vp, *np, *prev;
+
+ /*
+ * Remove the block rom the alloc list. WARNING: this is slow
+ * and should not be used in production code.
+ */
+ vp = (EjsVar*) ptr;
+ prev = 0;
+ for (np = (EjsVar*) slab->allocList[0].next; np;
+ np = (EjsVar*) np->gc.next) {
+ if (vp == np) {
+ if (prev) {
+ prev->gc.next = (EjsGCLink*) np->gc.next;
+ } else {
+ slab->allocList[0].next = (EjsGCLink*) np->gc.next;
+ }
+ break;
+ }
+ prev = np;
+ }
+ if (np == 0) {
+ mprAssert(0);
+ }
+ }
+#endif
+
+ /*
+ * Insert into the free list. Only use the next ptr
+ */
+ block = (EjsGCLink*) ptr;
+
+#if BLD_DEBUG
+#if !BREW || BREW_SIMULATOR
+ if ((uint) block == breakAddr) {
+ mprBreakpoint(MPR_LOC, "Watched Block");
+ }
+#endif
+ mprAssert(block->magic == EJS_MAGIC);
+ block->magic = EJS_MAGIC_FREE;
+#endif
+
+ block->next = slab->freeList.next;
+ slab->freeList.next = block;
+
+#if BLD_FEATURE_ALLOC_STATS
+ slab->allocCount--;
+ if (++slab->freeCount >= slab->peakFree) {
+ slab->peakFree = slab->freeCount;
+ }
+ slab->totalReclaimed++;
+ if (slabIndex != 2) {
+ slabIndex = slabIndex;
+ }
+#endif
+}
+
+/******************************************************************************/
+/*
+ * Mark an object as being in-use. Traverse all properties for referenced
+ * objects and base classes.
+ */
+
+static void markObjByVar(Ejs *ep, EjsVar *obj)
+{
+ EjsProperty *pp;
+ EjsVar *vp, *baseClass;
+
+ mprAssert(ep);
+ mprAssert(obj);
+
+ obj->objectState->gcMarked = 1;
+
+#if BLD_DEBUG
+ if (ep->gc.debugLevel >= 3) {
+ int indent = min(ep->gc.gcIndent * 2, 32);
+ mprLog(ep, 0, "%.*s %-24s %.*s 0x%08X",
+ indent, " ",
+ obj->propertyName,
+ 32 - indent, "................................ ",
+ (uint) obj->objectState);
+ ep->gc.gcIndent++;
+ }
+ ep->gc.objectsInUse++;
+#endif
+
+ /*
+ * Traverse all referenced objects
+ * OPT -- optimize by directly accessing the object links and not using
+ * ejsGetFirst/NextProperty. Then just examine objects
+ * OPT -- first property in global is global. Should optimize this.
+ */
+ pp = ejsGetFirstProperty(obj, EJS_ENUM_ALL);
+ while (pp) {
+ vp = ejsGetVarPtr(pp);
+ if (vp->type == EJS_TYPE_OBJECT) {
+ if (!vp->objectState->gcMarked) {
+#if FUTURE
+ /*
+ * OPT -- we can use the dirty bit on objects to avoid
+ * visiting permanent objects that are clean. If so, don't
+ * forget the else case below.
+ */
+ obj = vp->objectState;
+ if ((!obj->alive && !obj->permanent) || obj->dirty)
+#endif
+ markObjByVar(ep, vp);
+ }
+
+ } else {
+#if BLD_DEBUG
+ if (ep->gc.debugLevel >= 3) {
+ int indent = min(ep->gc.gcIndent * 2, 32);
+ mprLog(ep, 0, "%.*s %-24s %.*s %s",
+ indent, " ",
+ vp->propertyName,
+ 32 - indent, "................................ ",
+ ejsGetVarTypeAsString(vp));
+ }
+ ep->gc.propertiesInUse++;
+#endif
+ }
+ pp = ejsGetNextProperty(pp, EJS_ENUM_ALL);
+ }
+
+ /*
+ * Traverse the base class
+ */
+ baseClass = obj->objectState->baseClass;
+ if (baseClass) {
+ mprAssert(baseClass->type == EJS_TYPE_OBJECT);
+ mprAssert(baseClass->objectState);
+ if (baseClass->objectState) {
+ if (! baseClass->objectState->gcMarked) {
+ markObjByVar(ep, baseClass);
+ }
+ }
+ }
+#if BLD_DEBUG
+ if (ep->gc.debugLevel >= 3) {
+ ep->gc.gcIndent--;
+ }
+#endif
+}
+
+
+/******************************************************************************/
+/*
+ * Mark phase. Examine all variable frames and the return result.
+ */
+
+static void mark(Ejs *ep)
+{
+ EjsVar *vp;
+ int i;
+
+#if BLD_DEBUG
+ if (ep->gc.debugLevel >= 3) {
+ mprLog(ep, 0, " ");
+ mprLog(ep, 0, "GC: Marked Blocks:");
+ }
+#endif
+
+ if (ep->frames) {
+ for (i = 0; i < mprGetItemCount(ep->frames); i++) {
+
+ vp = (EjsVar*) mprGetItem(ep->frames, i);
+ mprAssert(vp->type == EJS_TYPE_OBJECT);
+
+ if (! vp->objectState->gcMarked) {
+ markObjByVar(ep, vp);
+ }
+ }
+ }
+
+ vp = ep->result;
+ if (vp && vp->type == EJS_TYPE_OBJECT && ! vp->objectState->gcMarked) {
+ markObjByVar(ep, vp);
+ }
+
+ vp = ep->currentObj;
+ if (vp && vp->type == EJS_TYPE_OBJECT && ! vp->objectState->gcMarked) {
+ markObjByVar(ep, vp);
+ }
+
+ vp = ejsGetVarPtr(ep->currentProperty);
+ if (vp && vp->type == EJS_TYPE_OBJECT && ! vp->objectState->gcMarked) {
+ markObjByVar(ep, vp);
+ }
+
+ /*
+ * OPT -- we could mark master as "mark permanent" somehow and
+ * then we would not need to walk the master objects.
+ */
+ if (ep->slabAllocContext == ep->service->master) {
+ if (ep->service->master->global) {
+ markObjByVar(ep, ep->service->master->global);
+ }
+ }
+
+#if BLD_DEBUG
+ if (ep->gc.debugLevel >= 3) {
+ mprLog(ep, 0, " ");
+ }
+#endif
+}
+
+
+/******************************************************************************/
+#if UNUSED
+
+static void resetMark(EjsVar *obj)
+{
+ EjsProperty *pp;
+ EjsVar *vp, *baseClass;
+
+ obj->objectState->gcMarked = 0;
+ obj->objectState->visited = 1;
+
+ pp = ejsGetFirstProperty(obj, EJS_ENUM_ALL);
+ while (pp) {
+ vp = ejsGetVarPtr(pp);
+ if (vp->type == EJS_TYPE_OBJECT && !vp->objectState->visited) {
+ resetMark(vp);
+ }
+ pp = ejsGetNextProperty(pp, EJS_ENUM_ALL);
+ }
+
+ baseClass = obj->objectState->baseClass;
+ if (baseClass) {
+ mprAssert(baseClass->type == EJS_TYPE_OBJECT);
+ mprAssert(baseClass->objectState);
+ if (baseClass->objectState) {
+ if (! baseClass->objectState->visited) {
+ resetMark(baseClass);
+ }
+ }
+ }
+ obj->objectState->visited = 0;
+}
+
+/******************************************************************************/
+/*
+ * Mark phase. Examine all variable frames and the return result.
+ */
+
+static void resetAllMarks(Ejs *ep)
+{
+ EjsVar *vp;
+ int i;
+
+ for (i = 0; i < mprGetItemCount(ep->frames); i++) {
+ vp = (EjsVar*) mprGetItem(ep->frames, i);
+ resetMark(vp);
+ }
+
+ if (ep->result && ep->result->type == EJS_TYPE_OBJECT &&
+ ! ep->result->objectState->gcMarked) {
+ resetMark(ep->result);
+ }
+}
+
+#endif
+/******************************************************************************/
+/*
+ * Sweep up the garbage
+ */
+
+static void resetMarks(Ejs *ep, EjsSlab *slab)
+{
+ EjsVar *vp;
+ EjsObj *obj;
+ int gen, i;
+
+ for (gen = EJS_GEN_NEW; gen < EJS_GEN_MAX; gen++) {
+ obj = (EjsObj*) slab->allocList[gen].next;
+ for (; obj; obj = (EjsObj*) obj->gc.next) {
+ obj->gcMarked = 0;
+ obj->visited = 0;
+ }
+ }
+
+ if (ep->frames) {
+ for (i = 0; i < mprGetItemCount(ep->frames); i++) {
+
+ vp = (EjsVar*) mprGetItem(ep->frames, i);
+ mprAssert(vp->type == EJS_TYPE_OBJECT);
+
+ vp->objectState->gcMarked = 0;
+ vp->objectState->visited = 0;
+ }
+ }
+
+ if (ep->result && ep->result->type == EJS_TYPE_OBJECT) {
+ ep->result->objectState->gcMarked = 0;
+ }
+}
+
+/******************************************************************************/
+/*
+ * Mark all permanent and non-alive objects
+ */
+
+static void markPerm(Ejs *ep, uint gen)
+{
+ EjsSlab *slab;
+ EjsObj *obj;
+
+ slab = &ep->slabs[EJS_SLAB_OBJ];
+
+ for (obj = (EjsObj*) slab->allocList[gen].next; obj; ) {
+
+ if (! obj->gcMarked) {
+ if (!obj->alive || obj->permanent) {
+ markObj(obj);
+ }
+ }
+ obj = (EjsObj*) obj->gc.next;
+
+ }
+}
+
+/******************************************************************************/
+
+static void markObj(EjsObj *obj)
+{
+ EjsProperty *pp;
+ EjsPropLink *lp, *head;
+ EjsObj *op;
+
+ mprAssert(obj);
+
+ obj->gcMarked = 1;
+
+ head = &obj->link;
+ for (lp = head->next; lp != head; lp = lp->next) {
+
+ pp = ejsGetPropertyFromLink(lp);
+
+ if (pp->var.type == EJS_TYPE_OBJECT) {
+ op = pp->var.objectState;
+ if (op != 0 && !op->gcMarked) {
+ markObj(op);
+ }
+ }
+ }
+}
+
+/******************************************************************************/
+/*
+ * Sweep up the garbage. Return the number of objects freed.
+ */
+
+static int sweep(Ejs *ep, uint gen)
+{
+ EjsSlab *slab;
+ EjsObj *obj, *next, *prev;
+ int count;
+
+ slab = &ep->slabs[EJS_SLAB_OBJ];
+
+ /*
+ * Examine allocated objects in the specified generation (only).
+ * NOTE: we only sweep object allocated to this interpreter and so
+ * we do not sweep any permanent objects in the default interpreter.
+ */
+ prev = 0;
+ count = 0;
+ for (obj = (EjsObj*) slab->allocList[gen].next; obj; obj = next) {
+
+ next = (EjsObj*) obj->gc.next;
+
+#if BLD_DEBUG && (!BREW || BREW_SIMULATOR)
+ if ((uint) obj == breakAddr) {
+ mprBreakpoint(MPR_LOC, "Watched Block");
+ }
+#endif
+
+ /*
+ * If object has not been marked inuse and is not a permanent
+ * object, then free it.
+ */
+ if (! obj->gcMarked && obj->alive && !obj->permanent) {
+
+#if BLD_DEBUG
+ if (ep->gc.debugLevel >= 2) {
+ if (obj->objName) {
+ mprLog(ep, 0, "GC: destroy %-18s %10d, %8X",
+ obj->objName, (uint) obj, (uint) obj);
+ } else {
+ mprLog(ep, 0, "GC: destroy UNKNOWN %x", (uint) obj);
+ }
+ }
+#endif
+ if (ejsDestroyObj(ep, obj) < 0) {
+ prev = obj;
+ obj->gcMarked = 0;
+ continue;
+ }
+
+ if (prev) {
+ prev->gc.next = (EjsGCLink*) next;
+ } else {
+ slab->allocList[gen].next = (EjsGCLink*) next;
+ }
+ count++;
+
+ } else {
+ prev = obj;
+ /* Reset for next time */
+ obj->gcMarked = 0;
+ }
+ }
+
+ if (gen == (EJS_GEN_OLD - 1)) {
+ slab->lastRecentBlock = prev;
+ }
+#if BLD_FEATURE_ALLOC_STATS
+ slab->totalSweeps++;
+#endif
+#if BLD_DEBUG
+ if (ep->gc.debugLevel > 0) {
+ mprLog(ep, 0, "GC: Sweep freed %d objects", count);
+ }
+#endif
+ return count;
+}
+
+/******************************************************************************/
+/*
+ * Sweep all variables
+ */
+
+void ejsSweepAll(Ejs *ep)
+{
+ EjsSlab *slab;
+ EjsObj *obj, *next, *prev;
+ int gen;
+
+ slab = &ep->slabs[EJS_SLAB_OBJ];
+
+ for (gen = EJS_GEN_NEW; gen < EJS_GEN_MAX; gen++) {
+ prev = 0;
+ for (obj = (EjsObj*) slab->allocList[gen].next; obj; obj = next) {
+ next = (EjsObj*) obj->gc.next;
+ ejsDestroyObj(ep, obj);
+ }
+ break;
+ }
+}
+
+/******************************************************************************/
+
+bool ejsObjIsCollectable(EjsVar *vp)
+{
+ if (vp == 0 || !ejsVarIsObject(vp)) {
+ return 0;
+ }
+ return (vp->objectState->alive && !vp->objectState->permanent);
+}
+
+/******************************************************************************/
+#if FUTURE
+
+static void ageGenerations(Ejs *ep)
+{
+ EjsSlab *slab;
+ EjsGCLink *oldList;
+ int gen;
+
+ slab = &ep->slabs[EJS_SLAB_OBJ];
+
+ /*
+ * Age all blocks. First append all (old - 1) blocks onto the old
+ * alloc list
+ */
+ oldList = &slab->allocList[EJS_GEN_OLD];
+
+ if (slab->lastRecentBlock) {
+ slab->lastRecentBlock->gc.next = oldList->next;
+ oldList->next = (EjsGCLink*) slab->lastRecentBlock;
+ }
+
+ /*
+ * Now simply copy all allocation lists up one generation
+ */
+ for (gen = EJS_GEN_OLD - 1; gen > 0; gen--) {
+ slab->allocList[gen] = slab->allocList[gen - 1];
+ }
+ slab->allocList[0].next = 0;
+}
+
+#endif
+/******************************************************************************/
+/*
+ * Collect the garbage. This is a mark and sweep over all possible objects.
+ * If an object is not referenced, it and all contained properties will be
+ * freed. If a slabIndex is provided, the collection halts when a block is
+ * available for allocation on that slab.
+ *
+ * Return 0 if memory is now available after collecting garbage. Otherwise,
+ * return MPR_ERR_MEMORY.
+ */
+
+int ejsCollectGarbage(Ejs *ep, int slabIndex)
+{
+ EjsGeneration gen;
+
+ if (ep->flags & EJS_FLAGS_DONT_GC) {
+ return -1;
+ }
+
+ /*
+ * Prevent destructors invoking the garbage collector
+ */
+ if (ep->gc.collecting) {
+ return 0;
+ }
+ ep->gc.collecting = 1;
+
+ resetMarks(ep, &ep->slabs[EJS_SLAB_OBJ]);
+
+ /*
+ * Examine each generation of objects starting with the most recent
+ * generation. Stop scanning when we have a free block to use.
+ */
+ for (gen = EJS_GEN_NEW; gen < EJS_GEN_MAX; gen++) {
+
+ if (slabIndex >= 0 && ep->slabs[slabIndex].freeList.next) {
+ break;
+ }
+
+ /*
+ * FUTURE OPT. Should mark objects in new generation and those
+ * with a dirty bit set in older generations. Don't need to mark
+ * entire heap. But how to keep list of dirty objects.
+ */
+ mark(ep);
+ markPerm(ep, gen);
+ sweep(ep, gen);
+
+ /* FUTURE - not using generations yet */
+ break;
+ }
+
+ /*
+ * FUTURE -- not using generations yet.
+ *
+ * ageGenerations(ep);
+ */
+
+ ep->gc.workDone = 0;
+ ep->gc.collecting = 0;
+
+ return (gen < EJS_GEN_MAX) ? 0 : MPR_ERR_MEMORY;
+}
+
+
+/******************************************************************************/
+/*
+ * Should be called when the app has been idle for a little while and when it
+ * is likely to be idle a bit longer. Call ejsIsTimeForGC to see if this is
+ * true. Return the count of objects collected .
+ */
+
+int ejsIncrementalCollectGarbage(Ejs *ep)
+{
+ int count;
+
+ if (ep->gc.collecting) {
+ return 0;
+ }
+
+ ep->gc.collecting = 1;
+
+ resetMarks(ep, &ep->slabs[EJS_SLAB_OBJ]);
+ mark(ep);
+
+ /* Not generational yet */
+ count = sweep(ep, EJS_GEN_NEW);
+
+ ep->gc.collecting = 0;
+ ep->gc.workDone = 0;
+
+ return count;
+}
+
+/******************************************************************************/
+#if BLD_DEBUG
+
+void ejsDumpObjects(Ejs *ep)
+{
+ int oldDebugLevel;
+
+ mprLog(ep, 0, "Dump of objects in use\n");
+
+ oldDebugLevel = ep->gc.debugLevel;
+
+ ep->gc.debugLevel = 3;
+ ep->gc.objectsInUse = 0;
+ ep->gc.propertiesInUse = 0;
+ ep->gc.collecting = 1;
+
+ resetMarks(ep, &ep->slabs[EJS_SLAB_OBJ]);
+ mark(ep);
+
+ ep->gc.collecting = 0;
+ ep->gc.debugLevel = oldDebugLevel;
+
+ mprLog(ep, 0, "%d objects and %d properties in use",
+ ep->gc.objectsInUse, ep->gc.propertiesInUse);
+ mprLog(ep, 0, "%d object bytes, %d property bytes and %d total",
+ (int) (ep->gc.objectsInUse * sizeof(EjsObj)),
+ (int) (ep->gc.propertiesInUse * sizeof(EjsProperty)),
+ (int) ((ep->gc.objectsInUse * sizeof(EjsObj) +
+ ep->gc.propertiesInUse * sizeof(EjsProperty))));
+}
+
+#endif
+/******************************************************************************/
+/*
+ * Return true if there is time to do a garbage collection and if we will
+ * benefit from it.
+ */
+
+int ejsIsTimeForGC(Ejs *ep, int timeTillNextEvent)
+{
+ EjsGC *gc;
+
+ if (timeTillNextEvent < EJS_MIN_TIME_FOR_GC) {
+ /*
+ * Not enough time to complete a collection
+ */
+ return 0;
+ }
+
+ gc = &ep->gc;
+
+ /*
+ * Return if we haven't done enough work to warrant a collection
+ * Trigger a little short of the work quota to try to run GC before
+ * a demand allocation requires it.
+ */
+ if (!gc->enable || !gc->enableIdleCollect ||
+ (gc->workDone < (gc->workQuota - EJS_GC_MIN_WORK_QUOTA))) {
+ return 0;
+ }
+
+#if UNUSED
+ mprLog(ep, 0, "Time for GC. Work done %d, time till next event %d",
+ gc->workDone, timeTillNextEvent);
+#endif
+ return 1;
+}
+
+/******************************************************************************/
+/*
+ * Return the amount of memory in use by EJS
+ */
+
+uint ejsGetUsedMemory(Ejs *ep)
+{
+#if BLD_FEATURE_ALLOC_STATS
+ EjsSlab *slab;
+ int i, totalMemory, slabMemory;
+
+ totalMemory = 0;
+ for (i = 0; i < EJS_SLAB_MAX; i++) {
+ slab = &ep->slabs[i];
+ slabMemory = slab->allocCount * slab->size;
+ totalMemory += slabMemory;
+ }
+ return totalMemory;
+#else
+ return 0;
+#endif
+}
+
+/******************************************************************************/
+/*
+ * Return the amount of memory allocated by EJS
+ */
+
+uint ejsGetAllocatedMemory(Ejs *ep)
+{
+#if BLD_FEATURE_ALLOC_STATS
+ EjsSlab *slab;
+ int i, totalMemory, slabMemory;
+
+ totalMemory = 0;
+ for (i = 0; i < EJS_SLAB_MAX; i++) {
+ slab = &ep->slabs[i];
+ slabMemory = (slab->allocCount + slab->freeCount) * slab->size;
+ totalMemory += slabMemory;
+ }
+ return totalMemory;
+#else
+ return 0;
+#endif
+}
+
+/******************************************************************************/
+/*
+ * On a memory allocation failure, go into graceful degrade mode. Set all
+ * slab allocation chunk increments to 1 so we can create an exception block
+ * to throw.
+ */
+
+static void ejsGracefulDegrade(Ejs *ep)
+{
+ EjsSlab *slab;
+ int i;
+
+ mprLog(ep, 1, "WARNING: Memory almost depleted. In graceful degrade mode");
+ for (i = 0; i < EJS_SLAB_MAX; i++) {
+ slab = &ep->slabs[i];
+ slab->allocIncrement = 8;
+ }
+ ep->gc.degraded = 1;
+}
+
+/******************************************************************************/
+
+int ejsSetGCDebugLevel(Ejs *ep, int debugLevel)
+{
+ int old;
+
+ old = ep->gc.debugLevel;
+ ep->gc.debugLevel = debugLevel;
+ return old;
+}
+
+/******************************************************************************/
+
+int ejsSetGCMaxMemory(Ejs *ep, uint maxMemory)
+{
+ int old;
+
+ old = ep->gc.maxMemory;
+ ep->gc.maxMemory = maxMemory;
+
+ return old;
+}
+
+/******************************************************************************/
+
+bool ejsBlockInUseInt(EjsVar *vp)
+{
+ if (vp) {
+#if BLD_DEBUG
+ if (vp->gc.magic != EJS_MAGIC) {
+ return 0;
+ }
+ if (vp->type == EJS_TYPE_OBJECT && vp->objectState &&
+ vp->objectState->gc.magic != EJS_MAGIC) {
+ return 0;
+ }
+#endif
+ return 1;
+ }
+ return 1;
+}
+
+/******************************************************************************/
+#else
+void ejsGarbageDummy() {}
+
+#endif /* BLD_FEATURE_EJS */
+
+/******************************************************************************/
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim:tw=78
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */