Package garbage collection policy parameters into a single structure.

The garbage collection policy had been determined informally.  This change
defines the garbage collection behavior in a centralized location making it
clear how the system will behave when requesting a garbage collection in one
of the four common situations.  This change is required to allow us to change
the policy for an explicit garbage collection.

In addition, this change hoists out the thread priority toggle from the
main garbage collector routine into two simple subroutines making this
it much easier to read and spot behavior differences.

Bug: 3379352
Change-Id: If0d7d9ef337819598fb6e268f7a127f2bae23580
diff --git a/vm/alloc/Alloc.c b/vm/alloc/Alloc.c
index 9ea92b6..630743b 100644
--- a/vm/alloc/Alloc.c
+++ b/vm/alloc/Alloc.c
@@ -302,7 +302,7 @@
     }
     dvmLockHeap();
     dvmWaitForConcurrentGcToComplete();
-    dvmCollectGarbageInternal(clearSoftReferences, GC_EXPLICIT);
+    dvmCollectGarbageInternal(GC_EXPLICIT);
     dvmUnlockHeap();
 }
 
diff --git a/vm/alloc/Heap.c b/vm/alloc/Heap.c
index 456fe50..c314a31 100644
--- a/vm/alloc/Heap.c
+++ b/vm/alloc/Heap.c
@@ -27,7 +27,6 @@
 #include "alloc/MarkSweep.h"
 
 #include "utils/threads.h"      // need Android thread priorities
-#define kInvalidPriority        10000
 
 #include <cutils/sched_policy.h>
 
@@ -36,12 +35,42 @@
 #include <limits.h>
 #include <errno.h>
 
-static const char* GcReasonStr[] = {
-    [GC_FOR_MALLOC] = "GC_FOR_MALLOC",
-    [GC_CONCURRENT] = "GC_CONCURRENT",
-    [GC_EXPLICIT] = "GC_EXPLICIT"
+static const GcSpec kGcForMallocSpec = {
+    false,
+    true,
+    PRESERVE,
+    "GC_FOR_ALLOC"
 };
 
+const GcSpec *GC_FOR_MALLOC = &kGcForMallocSpec;
+
+static const GcSpec kGcConcurrentSpec  = {
+    true,
+    true,
+    PRESERVE,
+    "GC_CONCURRENT"
+};
+
+const GcSpec *GC_CONCURRENT = &kGcConcurrentSpec;
+
+static const GcSpec kGcExplicitSpec = {
+    false,
+    true,
+    PRESERVE,
+    "GC_EXPLICIT"
+};
+
+const GcSpec *GC_EXPLICIT = &kGcExplicitSpec;
+
+static const GcSpec kGcBeforeOomSpec = {
+    false,
+    false,
+    CLEAR,
+    "GC_BEFORE_OOM"
+};
+
+const GcSpec *GC_BEFORE_OOM = &kGcBeforeOomSpec;
+
 /*
  * Initialize the GC heap.
  *
@@ -227,9 +256,8 @@
     }
     /* This may adjust the soft limit as a side-effect.
      */
-    LOGD_HEAP("dvmMalloc initiating GC%s",
-            clearSoftReferences ? "(clear SoftReferences)" : "");
-    dvmCollectGarbageInternal(clearSoftReferences, GC_FOR_MALLOC);
+    const GcSpec *spec = clearSoftReferences ? GC_BEFORE_OOM : GC_FOR_MALLOC;
+    dvmCollectGarbageInternal(spec);
 }
 
 /* Try as hard as possible to allocate some memory.
@@ -513,6 +541,60 @@
 }
 
 /*
+ * Raises the scheduling priority of the current thread.  Returns the
+ * original priority if successful.  Otherwise, returns INT_MAX on
+ * failure.
+ */
+static int raiseThreadPriority(void)
+{
+    /* Get the priority (the "nice" value) of the current thread.  The
+     * getpriority() call can legitimately return -1, so we have to
+     * explicitly test errno.
+     */
+    errno = 0;
+    int oldThreadPriority = getpriority(PRIO_PROCESS, 0);
+    if (errno != 0) {
+        LOGI_HEAP("getpriority(self) failed: %s", strerror(errno));
+    } else if (oldThreadPriority > ANDROID_PRIORITY_NORMAL) {
+        /* Current value is numerically greater than "normal", which
+         * in backward UNIX terms means lower priority.
+         */
+        if (oldThreadPriority >= ANDROID_PRIORITY_BACKGROUND) {
+            set_sched_policy(dvmGetSysThreadId(), SP_FOREGROUND);
+        }
+        if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_NORMAL) != 0) {
+            LOGI_HEAP("Unable to elevate priority from %d to %d",
+                      oldThreadPriority, ANDROID_PRIORITY_NORMAL);
+        } else {
+            /*
+             * The priority has been elevated.  Return the old value
+             * so the caller can restore it later.
+             */
+            LOGD_HEAP("Elevating priority from %d to %d",
+                      oldThreadPriority, ANDROID_PRIORITY_NORMAL);
+            return oldThreadPriority;
+        }
+    }
+    return INT_MAX;
+}
+
+/*
+ * Sets the current thread scheduling priority.
+ */
+static void setThreadPriority(int newThreadPriority)
+{
+    if (setpriority(PRIO_PROCESS, 0, newThreadPriority) != 0) {
+        LOGW_HEAP("Unable to reset priority to %d: %s",
+                  newThreadPriority, strerror(errno));
+    } else {
+        LOGD_HEAP("Reset priority to %d", oldThreadPriority);
+    }
+    if (newThreadPriority >= ANDROID_PRIORITY_BACKGROUND) {
+        set_sched_policy(dvmGetSysThreadId(), SP_BACKGROUND);
+    }
+}
+
+/*
  * Initiate garbage collection.
  *
  * NOTES:
@@ -526,17 +608,16 @@
  * way to enforce this is to refuse to GC on an allocation made by the
  * JDWP thread -- we have to expand the heap or fail.
  */
-void dvmCollectGarbageInternal(bool clearSoftRefs, GcReason reason)
+void dvmCollectGarbageInternal(const GcSpec* spec)
 {
     GcHeap *gcHeap = gDvm.gcHeap;
-    u4 rootSuspend, rootSuspendTime, rootStart, rootEnd;
-    u4 dirtySuspend, dirtyStart, dirtyEnd;
+    u4 rootSuspend, rootSuspendTime, rootStart = 0 , rootEnd = 0;
+    u4 dirtySuspend, dirtyStart = 0, dirtyEnd = 0;
     u4 totalTime;
     size_t numObjectsFreed, numBytesFreed;
     size_t currAllocated, currFootprint;
     size_t percentFree;
-    GcMode gcMode;
-    int oldThreadPriority = kInvalidPriority;
+    int oldThreadPriority = INT_MAX;
 
     /* The heap lock must be held.
      */
@@ -546,7 +627,6 @@
         return;
     }
 
-    gcMode = (reason == GC_FOR_MALLOC) ? GC_PARTIAL : GC_FULL;
     gcHeap->gcRunning = true;
 
     /*
@@ -565,34 +645,8 @@
      * If we are not marking concurrently raise the priority of the
      * thread performing the garbage collection.
      */
-    if (reason != GC_CONCURRENT) {
-        /* Get the priority (the "nice" value) of the current thread.  The
-         * getpriority() call can legitimately return -1, so we have to
-         * explicitly test errno.
-         */
-        errno = 0;
-        int priorityResult = getpriority(PRIO_PROCESS, 0);
-        if (errno != 0) {
-            LOGI_HEAP("getpriority(self) failed: %s\n", strerror(errno));
-        } else if (priorityResult > ANDROID_PRIORITY_NORMAL) {
-            /* Current value is numerically greater than "normal", which
-             * in backward UNIX terms means lower priority.
-             */
-
-            if (priorityResult >= ANDROID_PRIORITY_BACKGROUND) {
-                set_sched_policy(dvmGetSysThreadId(), SP_FOREGROUND);
-            }
-
-            if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_NORMAL) != 0) {
-                LOGI_HEAP("Unable to elevate priority from %d to %d\n",
-                          priorityResult, ANDROID_PRIORITY_NORMAL);
-            } else {
-                /* priority elevated; save value so we can restore it later */
-                LOGD_HEAP("Elevating priority from %d to %d\n",
-                          priorityResult, ANDROID_PRIORITY_NORMAL);
-                oldThreadPriority = priorityResult;
-            }
-        }
+    if (!spec->isConcurrent) {
+        oldThreadPriority = raiseThreadPriority();
     }
 
     /* Make sure that the HeapWorker thread hasn't become
@@ -618,7 +672,7 @@
 
     /* Set up the marking context.
      */
-    if (!dvmHeapBeginMarkStep(gcMode)) {
+    if (!dvmHeapBeginMarkStep(spec->isPartial)) {
         LOGE_HEAP("dvmHeapBeginMarkStep failed; aborting\n");
         dvmAbort();
     }
@@ -635,7 +689,7 @@
     gcHeap->weakReferences = NULL;
     gcHeap->phantomReferences = NULL;
 
-    if (reason == GC_CONCURRENT) {
+    if (spec->isConcurrent) {
         /*
          * Resume threads while tracing from the roots.  We unlock the
          * heap to allow mutator threads to allocate from free space.
@@ -653,7 +707,7 @@
     LOGD_HEAP("Recursing...");
     dvmHeapScanMarkedObjects();
 
-    if (reason == GC_CONCURRENT) {
+    if (spec->isConcurrent) {
         /*
          * Re-acquire the heap lock and perform the final thread
          * suspension.
@@ -685,7 +739,8 @@
      * All strongly-reachable objects have now been marked.  Process
      * weakly-reachable objects discovered while tracing.
      */
-    dvmHeapProcessReferences(&gcHeap->softReferences, clearSoftRefs,
+    dvmHeapProcessReferences(&gcHeap->softReferences,
+                             spec->softReferencePolicy == CLEAR,
                              &gcHeap->weakReferences,
                              &gcHeap->phantomReferences);
 
@@ -717,16 +772,16 @@
         verifyRootsAndHeap();
     }
 
-    if (reason == GC_CONCURRENT) {
+    if (spec->isConcurrent) {
         dirtyEnd = dvmGetRelativeTimeMsec();
         dvmUnlockHeap();
         dvmResumeAllThreads(SUSPEND_FOR_GC);
     }
-    dvmHeapSweepUnmarkedObjects(gcMode, reason == GC_CONCURRENT,
+    dvmHeapSweepUnmarkedObjects(spec->isPartial, spec->isConcurrent,
                                 &numObjectsFreed, &numBytesFreed);
     LOGD_HEAP("Cleaning up...");
     dvmHeapFinishMarkStep();
-    if (reason == GC_CONCURRENT) {
+    if (spec->isConcurrent) {
         dvmLockHeap();
     }
 
@@ -763,7 +818,7 @@
     dvmUnlockMutex(&gDvm.heapWorkerListLock);
     dvmUnlockMutex(&gDvm.heapWorkerLock);
 
-    if (reason == GC_CONCURRENT) {
+    if (spec->isConcurrent) {
         /*
          * Wake-up any threads that blocked after a failed allocation
          * request.
@@ -771,30 +826,25 @@
         dvmBroadcastCond(&gDvm.gcHeapCond);
     }
 
-    if (reason != GC_CONCURRENT) {
+    if (!spec->isConcurrent) {
         dirtyEnd = dvmGetRelativeTimeMsec();
         dvmResumeAllThreads(SUSPEND_FOR_GC);
-        if (oldThreadPriority != kInvalidPriority) {
-            if (setpriority(PRIO_PROCESS, 0, oldThreadPriority) != 0) {
-                LOGW_HEAP("Unable to reset priority to %d: %s\n",
-                          oldThreadPriority, strerror(errno));
-            } else {
-                LOGD_HEAP("Reset priority to %d\n", oldThreadPriority);
-            }
-
-            if (oldThreadPriority >= ANDROID_PRIORITY_BACKGROUND) {
-                set_sched_policy(dvmGetSysThreadId(), SP_BACKGROUND);
-            }
+        /*
+         * Restore the original thread scheduling priority if it was
+         * changed at the start of the current garbage collection.
+         */
+        if (oldThreadPriority != INT_MAX) {
+            setThreadPriority(oldThreadPriority);
         }
     }
 
     percentFree = 100 - (size_t)(100.0f * (float)currAllocated / currFootprint);
-    if (reason != GC_CONCURRENT) {
+    if (!spec->isConcurrent) {
         u4 markSweepTime = dirtyEnd - rootStart;
         bool isSmall = numBytesFreed > 0 && numBytesFreed < 1024;
         totalTime = rootSuspendTime + markSweepTime;
         LOGD("%s freed %s%zdK, %d%% free %zdK/%zdK, paused %ums",
-             GcReasonStr[reason],
+             spec->reason,
              isSmall ? "<" : "",
              numBytesFreed ? MAX(numBytesFreed / 1024, 1) : 0,
              percentFree,
@@ -807,7 +857,7 @@
         bool isSmall = numBytesFreed > 0 && numBytesFreed < 1024;
         totalTime = rootSuspendTime + rootTime + dirtySuspendTime + dirtyTime;
         LOGD("%s freed %s%zdK, %d%% free %zdK/%zdK, paused %ums+%ums",
-             GcReasonStr[reason],
+             spec->reason,
              isSmall ? "<" : "",
              numBytesFreed ? MAX(numBytesFreed / 1024, 1) : 0,
              percentFree,
diff --git a/vm/alloc/Heap.h b/vm/alloc/Heap.h
index faa967a..01a50d2 100644
--- a/vm/alloc/Heap.h
+++ b/vm/alloc/Heap.h
@@ -19,6 +19,29 @@
 #ifndef _DALVIK_ALLOC_HEAP
 #define _DALVIK_ALLOC_HEAP
 
+typedef struct {
+  /* If true, only the application heap is threatened. */
+  bool isPartial;
+  /* If true, the trace is run concurrently with the mutator. */
+  bool isConcurrent;
+  /* Toggles for the soft reference clearing policy. */
+  enum { CLEAR, PRESERVE } softReferencePolicy;
+  /* A name for this garbage collection mode. */
+  const char *reason;
+} GcSpec;
+
+/* Not enough space for an "ordinary" Object to be allocated. */
+extern const GcSpec *GC_FOR_MALLOC;
+
+/* Automatic GC triggered by exceeding a heap occupancy threshold. */
+extern const GcSpec *GC_CONCURRENT;
+
+/* Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1. */
+extern const GcSpec *GC_EXPLICIT;
+
+/* Final attempt to reclaim memory before throwing an OOM. */
+extern const GcSpec *GC_BEFORE_OOM;
+
 /*
  * Initialize the GC heap.
  *
@@ -58,26 +81,10 @@
 size_t dvmObjectSizeInHeap(const Object *obj);
 #endif
 
-typedef enum {
-    /* GC all heaps. */
-    GC_FULL,
-    /* GC just the first heap. */
-    GC_PARTIAL
-} GcMode;
-
-typedef enum {
-    /* Not enough space for an "ordinary" Object to be allocated. */
-    GC_FOR_MALLOC,
-    /* Automatic GC triggered by exceeding a heap occupancy threshold. */
-    GC_CONCURRENT,
-    /* Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1. */
-    GC_EXPLICIT,
-} GcReason;
-
 /*
  * Run the garbage collector without doing any locking.
  */
-void dvmCollectGarbageInternal(bool clearSoftRefs, GcReason reason);
+void dvmCollectGarbageInternal(const GcSpec *spec);
 
 /*
  * Blocks the calling thread until the garbage collector is inactive.
diff --git a/vm/alloc/HeapSource.c b/vm/alloc/HeapSource.c
index bf51e70..e3cb422 100644
--- a/vm/alloc/HeapSource.c
+++ b/vm/alloc/HeapSource.c
@@ -404,7 +404,7 @@
         dvmWaitCond(&gHs->gcThreadCond, &gHs->gcThreadMutex);
         dvmLockHeap();
         dvmChangeStatus(NULL, THREAD_RUNNING);
-        dvmCollectGarbageInternal(false, GC_CONCURRENT);
+        dvmCollectGarbageInternal(GC_CONCURRENT);
         dvmChangeStatus(NULL, THREAD_VMWAIT);
         dvmUnlockHeap();
     }
@@ -1474,9 +1474,9 @@
     return hs->numHeaps;
 }
 
-void *dvmHeapSourceGetImmuneLimit(GcMode mode)
+void *dvmHeapSourceGetImmuneLimit(bool isPartial)
 {
-    if (mode == GC_PARTIAL) {
+    if (isPartial) {
         return hs2heap(gHs)->base;
     } else {
         return NULL;
diff --git a/vm/alloc/HeapSource.h b/vm/alloc/HeapSource.h
index 70bc128..22d8dab 100644
--- a/vm/alloc/HeapSource.h
+++ b/vm/alloc/HeapSource.h
@@ -197,7 +197,7 @@
  * heap.  Addresses at or above this pointer are threatened, addresses
  * below this pointer are immune.
  */
-void *dvmHeapSourceGetImmuneLimit(GcMode mode);
+void *dvmHeapSourceGetImmuneLimit(bool isPartial);
 
 /*
  * Returns the maximum size of the heap.  This value will be either
diff --git a/vm/alloc/MarkSweep.c b/vm/alloc/MarkSweep.c
index 37fc223..10394f1 100644
--- a/vm/alloc/MarkSweep.c
+++ b/vm/alloc/MarkSweep.c
@@ -100,7 +100,7 @@
     return *stack->top;
 }
 
-bool dvmHeapBeginMarkStep(GcMode mode)
+bool dvmHeapBeginMarkStep(bool isPartial)
 {
     GcMarkContext *ctx = &gDvm.gcHeap->markContext;
 
@@ -108,7 +108,7 @@
         return false;
     }
     ctx->finger = NULL;
-    ctx->immuneLimit = dvmHeapSourceGetImmuneLimit(mode);
+    ctx->immuneLimit = dvmHeapSourceGetImmuneLimit(isPartial);
     return true;
 }
 
@@ -1058,7 +1058,7 @@
  * Walk through the list of objects that haven't been marked and free
  * them.  Assumes the bitmaps have been swapped.
  */
-void dvmHeapSweepUnmarkedObjects(GcMode mode, bool isConcurrent,
+void dvmHeapSweepUnmarkedObjects(bool isPartial, bool isConcurrent,
                                  size_t *numObjects, size_t *numBytes)
 {
     HeapBitmap currMark[HEAP_SOURCE_MAX_HEAP_COUNT];
@@ -1069,7 +1069,7 @@
 
     numBitmaps = dvmHeapSourceGetNumHeaps();
     dvmHeapSourceGetObjectBitmaps(currLive, currMark, numBitmaps);
-    if (mode == GC_PARTIAL) {
+    if (isPartial) {
         numSweepBitmaps = 1;
         assert((uintptr_t)gDvm.gcHeap->markContext.immuneLimit == currLive[0].base);
     } else {
diff --git a/vm/alloc/MarkSweep.h b/vm/alloc/MarkSweep.h
index ece12e0..9b57f45 100644
--- a/vm/alloc/MarkSweep.h
+++ b/vm/alloc/MarkSweep.h
@@ -46,7 +46,7 @@
     const void *finger;   // only used while scanning/recursing.
 } GcMarkContext;
 
-bool dvmHeapBeginMarkStep(GcMode mode);
+bool dvmHeapBeginMarkStep(bool isPartial);
 void dvmHeapMarkRootSet(void);
 void dvmHeapReMarkRootSet(void);
 void dvmHeapScanMarkedObjects(void);
@@ -56,7 +56,7 @@
                               Object **phantomReferences);
 void dvmHeapFinishMarkStep(void);
 void dvmHeapSweepSystemWeaks(void);
-void dvmHeapSweepUnmarkedObjects(GcMode mode, bool isConcurrent,
+void dvmHeapSweepUnmarkedObjects(bool isPartial, bool isConcurrent,
                                  size_t *numObjects, size_t *numBytes);
 
 #endif  // _DALVIK_ALLOC_MARK_SWEEP